@warlock.js/cache 4.0.171 → 4.1.1
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/README.md +85 -0
- package/cjs/index.cjs +4088 -0
- package/cjs/index.cjs.map +1 -0
- package/esm/cache-manager.d.mts +314 -0
- package/esm/cache-manager.d.mts.map +1 -0
- package/esm/cache-manager.mjs +486 -0
- package/esm/cache-manager.mjs.map +1 -0
- package/esm/cached/auto-key.d.mts +25 -0
- package/esm/cached/auto-key.d.mts.map +1 -0
- package/esm/cached/auto-key.mjs +55 -0
- package/esm/cached/auto-key.mjs.map +1 -0
- package/esm/cached/cached.d.mts +54 -0
- package/esm/cached/cached.d.mts.map +1 -0
- package/esm/cached/cached.mjs +25 -0
- package/esm/cached/cached.mjs.map +1 -0
- package/esm/cached/index.d.mts +3 -0
- package/esm/cached/index.mjs +5 -0
- package/esm/cached/normalize-args.d.mts +51 -0
- package/esm/cached/normalize-args.d.mts.map +1 -0
- package/esm/cached/normalize-args.mjs +26 -0
- package/esm/cached/normalize-args.mjs.map +1 -0
- package/esm/drivers/base-cache-driver.d.mts +322 -0
- package/esm/drivers/base-cache-driver.d.mts.map +1 -0
- package/esm/drivers/base-cache-driver.mjs +522 -0
- package/esm/drivers/base-cache-driver.mjs.map +1 -0
- package/esm/drivers/file-cache-driver.d.mts +68 -0
- package/esm/drivers/file-cache-driver.d.mts.map +1 -0
- package/esm/drivers/file-cache-driver.mjs +174 -0
- package/esm/drivers/file-cache-driver.mjs.map +1 -0
- package/esm/drivers/index.d.mts +9 -0
- package/esm/drivers/index.mjs +11 -0
- package/esm/drivers/lru-memory-cache-driver.d.mts +136 -0
- package/esm/drivers/lru-memory-cache-driver.d.mts.map +1 -0
- package/esm/drivers/lru-memory-cache-driver.mjs +317 -0
- package/esm/drivers/lru-memory-cache-driver.mjs.map +1 -0
- package/esm/drivers/memory-cache-driver.d.mts +112 -0
- package/esm/drivers/memory-cache-driver.d.mts.map +1 -0
- package/esm/drivers/memory-cache-driver.mjs +241 -0
- package/esm/drivers/memory-cache-driver.mjs.map +1 -0
- package/esm/drivers/memory-extended-cache-driver.d.mts +17 -0
- package/esm/drivers/memory-extended-cache-driver.d.mts.map +1 -0
- package/esm/drivers/memory-extended-cache-driver.mjs +34 -0
- package/esm/drivers/memory-extended-cache-driver.mjs.map +1 -0
- package/esm/drivers/mock-cache-driver.d.mts +137 -0
- package/esm/drivers/mock-cache-driver.d.mts.map +1 -0
- package/esm/drivers/mock-cache-driver.mjs +226 -0
- package/esm/drivers/mock-cache-driver.mjs.map +1 -0
- package/esm/drivers/null-cache-driver.d.mts +69 -0
- package/esm/drivers/null-cache-driver.d.mts.map +1 -0
- package/esm/drivers/null-cache-driver.mjs +92 -0
- package/esm/drivers/null-cache-driver.mjs.map +1 -0
- package/esm/drivers/pg-cache-driver.d.mts +148 -0
- package/esm/drivers/pg-cache-driver.d.mts.map +1 -0
- package/esm/drivers/pg-cache-driver.mjs +437 -0
- package/esm/drivers/pg-cache-driver.mjs.map +1 -0
- package/esm/drivers/redis-cache-driver.d.mts +86 -0
- package/esm/drivers/redis-cache-driver.d.mts.map +1 -0
- package/esm/drivers/redis-cache-driver.mjs +312 -0
- package/esm/drivers/redis-cache-driver.mjs.map +1 -0
- package/esm/index.d.mts +21 -0
- package/esm/index.mjs +24 -0
- package/esm/list/index.d.mts +1 -0
- package/esm/list/memory-cache-list.d.mts +77 -0
- package/esm/list/memory-cache-list.d.mts.map +1 -0
- package/esm/list/memory-cache-list.mjs +119 -0
- package/esm/list/memory-cache-list.mjs.map +1 -0
- package/esm/metrics.d.mts +118 -0
- package/esm/metrics.d.mts.map +1 -0
- package/esm/metrics.mjs +197 -0
- package/esm/metrics.mjs.map +1 -0
- package/esm/scoped-cache.d.mts +205 -0
- package/esm/scoped-cache.d.mts.map +1 -0
- package/esm/scoped-cache.mjs +274 -0
- package/esm/scoped-cache.mjs.map +1 -0
- package/esm/tagged-cache.d.mts +89 -0
- package/esm/tagged-cache.d.mts.map +1 -0
- package/esm/tagged-cache.mjs +147 -0
- package/esm/tagged-cache.mjs.map +1 -0
- package/esm/tagged-scoped-cache.d.mts +111 -0
- package/esm/tagged-scoped-cache.d.mts.map +1 -0
- package/esm/tagged-scoped-cache.mjs +142 -0
- package/esm/tagged-scoped-cache.mjs.map +1 -0
- package/esm/types.d.mts +1067 -0
- package/esm/types.d.mts.map +1 -0
- package/esm/types.mjs +62 -0
- package/esm/types.mjs.map +1 -0
- package/esm/utils.d.mts +161 -0
- package/esm/utils.d.mts.map +1 -0
- package/esm/utils.mjs +222 -0
- package/esm/utils.mjs.map +1 -0
- package/llms-full.txt +2071 -0
- package/llms.txt +28 -0
- package/package.json +53 -39
- package/skills/apply-cache-patterns/SKILL.md +97 -0
- package/skills/cache-basics/SKILL.md +121 -0
- package/skills/configure-pg-cache/SKILL.md +115 -0
- package/skills/configure-set-options/SKILL.md +96 -0
- package/skills/handle-cache-errors/SKILL.md +91 -0
- package/skills/observe-cache/SKILL.md +103 -0
- package/skills/overview/SKILL.md +69 -0
- package/skills/pick-cache-driver/SKILL.md +115 -0
- package/skills/test-cache-code/SKILL.md +219 -0
- package/skills/use-cache-atomic/SKILL.md +67 -0
- package/skills/use-cache-bulk/SKILL.md +57 -0
- package/skills/use-cache-list/SKILL.md +85 -0
- package/skills/use-cache-lock/SKILL.md +104 -0
- package/skills/use-cache-namespace/SKILL.md +88 -0
- package/skills/use-cache-similarity/SKILL.md +94 -0
- package/skills/use-cache-tags/SKILL.md +85 -0
- package/skills/use-cache-update-merge/SKILL.md +84 -0
- package/skills/use-cache-utils/SKILL.md +89 -0
- package/skills/use-cached-hof/SKILL.md +102 -0
- package/skills/use-swr/SKILL.md +104 -0
- package/cjs/cache-manager.d.ts +0 -163
- package/cjs/cache-manager.d.ts.map +0 -1
- package/cjs/cache-manager.js +0 -322
- package/cjs/cache-manager.js.map +0 -1
- package/cjs/drivers/base-cache-driver.d.ts +0 -152
- package/cjs/drivers/base-cache-driver.d.ts.map +0 -1
- package/cjs/drivers/base-cache-driver.js +0 -321
- package/cjs/drivers/base-cache-driver.js.map +0 -1
- package/cjs/drivers/file-cache-driver.d.ts +0 -45
- package/cjs/drivers/file-cache-driver.d.ts.map +0 -1
- package/cjs/drivers/file-cache-driver.js +0 -133
- package/cjs/drivers/file-cache-driver.js.map +0 -1
- package/cjs/drivers/index.d.ts +0 -8
- package/cjs/drivers/index.d.ts.map +0 -1
- package/cjs/drivers/lru-memory-cache-driver.d.ts +0 -98
- package/cjs/drivers/lru-memory-cache-driver.d.ts.map +0 -1
- package/cjs/drivers/lru-memory-cache-driver.js +0 -252
- package/cjs/drivers/lru-memory-cache-driver.js.map +0 -1
- package/cjs/drivers/memory-cache-driver.d.ts +0 -82
- package/cjs/drivers/memory-cache-driver.d.ts.map +0 -1
- package/cjs/drivers/memory-cache-driver.js +0 -218
- package/cjs/drivers/memory-cache-driver.js.map +0 -1
- package/cjs/drivers/memory-extended-cache-driver.d.ts +0 -13
- package/cjs/drivers/memory-extended-cache-driver.d.ts.map +0 -1
- package/cjs/drivers/memory-extended-cache-driver.js +0 -25
- package/cjs/drivers/memory-extended-cache-driver.js.map +0 -1
- package/cjs/drivers/null-cache-driver.d.ts +0 -58
- package/cjs/drivers/null-cache-driver.d.ts.map +0 -1
- package/cjs/drivers/null-cache-driver.js +0 -84
- package/cjs/drivers/null-cache-driver.js.map +0 -1
- package/cjs/drivers/redis-cache-driver.d.ts +0 -57
- package/cjs/drivers/redis-cache-driver.d.ts.map +0 -1
- package/cjs/drivers/redis-cache-driver.js +0 -263
- package/cjs/drivers/redis-cache-driver.js.map +0 -1
- package/cjs/index.d.ts +0 -6
- package/cjs/index.d.ts.map +0 -1
- package/cjs/index.js +0 -1
- package/cjs/index.js.map +0 -1
- package/cjs/tagged-cache.d.ts +0 -77
- package/cjs/tagged-cache.d.ts.map +0 -1
- package/cjs/tagged-cache.js +0 -160
- package/cjs/tagged-cache.js.map +0 -1
- package/cjs/types.d.ts +0 -391
- package/cjs/types.d.ts.map +0 -1
- package/cjs/types.js +0 -36
- package/cjs/types.js.map +0 -1
- package/cjs/utils.d.ts +0 -50
- package/cjs/utils.d.ts.map +0 -1
- package/cjs/utils.js +0 -55
- package/cjs/utils.js.map +0 -1
- package/esm/cache-manager.d.ts +0 -163
- package/esm/cache-manager.d.ts.map +0 -1
- package/esm/cache-manager.js +0 -322
- package/esm/cache-manager.js.map +0 -1
- package/esm/drivers/base-cache-driver.d.ts +0 -152
- package/esm/drivers/base-cache-driver.d.ts.map +0 -1
- package/esm/drivers/base-cache-driver.js +0 -321
- package/esm/drivers/base-cache-driver.js.map +0 -1
- package/esm/drivers/file-cache-driver.d.ts +0 -45
- package/esm/drivers/file-cache-driver.d.ts.map +0 -1
- package/esm/drivers/file-cache-driver.js +0 -133
- package/esm/drivers/file-cache-driver.js.map +0 -1
- package/esm/drivers/index.d.ts +0 -8
- package/esm/drivers/index.d.ts.map +0 -1
- package/esm/drivers/lru-memory-cache-driver.d.ts +0 -98
- package/esm/drivers/lru-memory-cache-driver.d.ts.map +0 -1
- package/esm/drivers/lru-memory-cache-driver.js +0 -252
- package/esm/drivers/lru-memory-cache-driver.js.map +0 -1
- package/esm/drivers/memory-cache-driver.d.ts +0 -82
- package/esm/drivers/memory-cache-driver.d.ts.map +0 -1
- package/esm/drivers/memory-cache-driver.js +0 -218
- package/esm/drivers/memory-cache-driver.js.map +0 -1
- package/esm/drivers/memory-extended-cache-driver.d.ts +0 -13
- package/esm/drivers/memory-extended-cache-driver.d.ts.map +0 -1
- package/esm/drivers/memory-extended-cache-driver.js +0 -25
- package/esm/drivers/memory-extended-cache-driver.js.map +0 -1
- package/esm/drivers/null-cache-driver.d.ts +0 -58
- package/esm/drivers/null-cache-driver.d.ts.map +0 -1
- package/esm/drivers/null-cache-driver.js +0 -84
- package/esm/drivers/null-cache-driver.js.map +0 -1
- package/esm/drivers/redis-cache-driver.d.ts +0 -57
- package/esm/drivers/redis-cache-driver.d.ts.map +0 -1
- package/esm/drivers/redis-cache-driver.js +0 -263
- package/esm/drivers/redis-cache-driver.js.map +0 -1
- package/esm/index.d.ts +0 -6
- package/esm/index.d.ts.map +0 -1
- package/esm/index.js +0 -1
- package/esm/index.js.map +0 -1
- package/esm/tagged-cache.d.ts +0 -77
- package/esm/tagged-cache.d.ts.map +0 -1
- package/esm/tagged-cache.js +0 -160
- package/esm/tagged-cache.js.map +0 -1
- package/esm/types.d.ts +0 -391
- package/esm/types.d.ts.map +0 -1
- package/esm/types.js +0 -36
- package/esm/types.js.map +0 -1
- package/esm/utils.d.ts +0 -50
- package/esm/utils.d.ts.map +0 -1
- package/esm/utils.js +0 -55
- package/esm/utils.js.map +0 -1
package/llms.txt
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Warlock Cache
|
|
2
|
+
|
|
3
|
+
> Package: `@warlock.js/cache`
|
|
4
|
+
|
|
5
|
+
> A Robust Cache Manager for Nodejs
|
|
6
|
+
|
|
7
|
+
## Skills
|
|
8
|
+
|
|
9
|
+
- [apply-cache-patterns](@warlock.js/cache/apply-cache-patterns/SKILL.md): 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`.
|
|
10
|
+
- [cache-basics](@warlock.js/cache/cache-basics/SKILL.md): Start with @warlock.js/cache — the cache singleton, primary ops (set / get / pull / remove / many / forever / increment / remember), TTL shapes, init flow. Triggers: `cache`, `cache.setCacheConfigurations`, `cache.init`, `cache.set`, `cache.get`, `cache.remove`, `cache.remember`, `cache.flush`; "start with warlock cache", "wire up cache at startup", "which cache skill do I need"; typical import `import { cache } from "@warlock.js/cache"`. Skip: driver choice — `@warlock.js/cache/pick-cache-driver/SKILL.md`; set options — `@warlock.js/cache/configure-set-options/SKILL.md`; competing libs `lru-cache`, `node-cache`, `keyv`; native `Map`.
|
|
11
|
+
- [configure-pg-cache](@warlock.js/cache/configure-pg-cache/SKILL.md): 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`.
|
|
12
|
+
- [configure-set-options](@warlock.js/cache/configure-set-options/SKILL.md): 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`.
|
|
13
|
+
- [handle-cache-errors](@warlock.js/cache/handle-cache-errors/SKILL.md): 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.
|
|
14
|
+
- [observe-cache](@warlock.js/cache/observe-cache/SKILL.md): 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`.
|
|
15
|
+
- [overview](@warlock.js/cache/overview/SKILL.md): Front-door orientation for `@warlock.js/cache` — multi-driver caching (memory / memoryExtended / lru / file / redis / pg / null / mock) with a single `cache` API: get/set/has/pull/remember, TTL shapes, tag-based invalidation, key namespaces, distributed locks, stale-while-revalidate, atomic update/merge, cache lists, vector similarity, metrics + events, and the `cached()` HOF. TRIGGER when: code imports from `@warlock.js/cache` (`cache`, `cached`, `setCacheConfigurations`, a `*CacheDriver`); user asks "what does @warlock.js/cache do", "which cache driver", "cache TTL / tags / invalidation", "distributed lock", "stale-while-revalidate", "semantic / vector cache", "compare with node-cache / keyv / cache-manager"; package.json adds `@warlock.js/cache`. Skip: specific task already known — load the matching task skill directly (`cache-basics`, `pick-cache-driver`, `configure-set-options`, `use-cache-tags`, `use-cache-namespace`, `use-cache-list`, `use-cache-lock`, `use-swr`, `use-cached-hof`, `use-cache-similarity`, `use-cache-update-merge`, `use-cache-atomic`, `use-cache-bulk`, `use-cache-utils`, `apply-cache-patterns`, `observe-cache`, `handle-cache-errors`, `configure-pg-cache`, `test-cache-code`).
|
|
16
|
+
- [pick-cache-driver](@warlock.js/cache/pick-cache-driver/SKILL.md): 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`.
|
|
17
|
+
- [test-cache-code](@warlock.js/cache/test-cache-code/SKILL.md): 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`.
|
|
18
|
+
- [use-cache-atomic](@warlock.js/cache/use-cache-atomic/SKILL.md): 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`.
|
|
19
|
+
- [use-cache-bulk](@warlock.js/cache/use-cache-bulk/SKILL.md): 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`.
|
|
20
|
+
- [use-cache-list](@warlock.js/cache/use-cache-list/SKILL.md): 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`.
|
|
21
|
+
- [use-cache-lock](@warlock.js/cache/use-cache-lock/SKILL.md): 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`.
|
|
22
|
+
- [use-cache-namespace](@warlock.js/cache/use-cache-namespace/SKILL.md): 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.
|
|
23
|
+
- [use-cache-similarity](@warlock.js/cache/use-cache-similarity/SKILL.md): 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`.
|
|
24
|
+
- [use-cache-tags](@warlock.js/cache/use-cache-tags/SKILL.md): 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`.
|
|
25
|
+
- [use-cache-update-merge](@warlock.js/cache/use-cache-update-merge/SKILL.md): 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`.
|
|
26
|
+
- [use-cache-utils](@warlock.js/cache/use-cache-utils/SKILL.md): 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`.
|
|
27
|
+
- [use-cached-hof](@warlock.js/cache/use-cached-hof/SKILL.md): 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`.
|
|
28
|
+
- [use-swr](@warlock.js/cache/use-swr/SKILL.md): 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).
|
package/package.json
CHANGED
|
@@ -1,40 +1,54 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
2
|
+
"name": "@warlock.js/cache",
|
|
3
|
+
"description": "A Robust Cache Manager for Nodejs",
|
|
4
|
+
"keywords": [
|
|
5
|
+
"nodejs",
|
|
6
|
+
"cache",
|
|
7
|
+
"redis",
|
|
8
|
+
"lru",
|
|
9
|
+
"memory",
|
|
10
|
+
"caching",
|
|
11
|
+
"warlockjs",
|
|
12
|
+
"warlock.js",
|
|
13
|
+
"cache-manager",
|
|
14
|
+
"cache-manager-nodejs",
|
|
15
|
+
"cache-manager-node",
|
|
16
|
+
"cache-manager-nodejs-redis",
|
|
17
|
+
"cache-manager-node-redis",
|
|
18
|
+
"cache-manager-nodejs-memory",
|
|
19
|
+
"cache-manager-node-memory",
|
|
20
|
+
"cache-manager-nodejs-memcached"
|
|
21
|
+
],
|
|
22
|
+
"author": "hassanzohdy",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/warlockjs/cache"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@warlock.js/fs": "*",
|
|
30
|
+
"@mongez/reinforcements": "^3.2.0",
|
|
31
|
+
"@warlock.js/logger": "*",
|
|
32
|
+
"ms": "^2.1.3"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"redis": "^4.6.10 || ^5.0.0",
|
|
36
|
+
"pg": "^8.11.0"
|
|
37
|
+
},
|
|
38
|
+
"version": "4.1.1",
|
|
39
|
+
"main": "./cjs/index.cjs",
|
|
40
|
+
"module": "./esm/index.mjs",
|
|
41
|
+
"types": "./esm/index.d.mts",
|
|
42
|
+
"exports": {
|
|
43
|
+
".": {
|
|
44
|
+
"import": {
|
|
45
|
+
"types": "./esm/index.d.mts",
|
|
46
|
+
"default": "./esm/index.mjs"
|
|
47
|
+
},
|
|
48
|
+
"require": {
|
|
49
|
+
"types": "./esm/index.d.mts",
|
|
50
|
+
"default": "./cjs/index.cjs"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: apply-cache-patterns
|
|
3
|
+
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`.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Real-world caching patterns
|
|
7
|
+
|
|
8
|
+
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).
|
|
9
|
+
|
|
10
|
+
## Memoize an expensive function — `remember`
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
const user = await cache.remember(`user:${id}`, "1h", async () => {
|
|
14
|
+
return db.users.find(id); // runs only on cache miss
|
|
15
|
+
});
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- The callback runs once per miss.
|
|
19
|
+
- Concurrent callers for the same key share the in-flight promise (stampede protection) — within one Node process.
|
|
20
|
+
- `null` is the universal miss sentinel. `remember` short-circuits on a **truthy** cached value (`if (cachedValue) return cachedValue;`), so a stored `null` reads back as a miss and the callback **re-runs on every call** — you get no caching at all, plus a wasted write each time. To actually cache a "not found," store a truthy sentinel instead (see negative caching below).
|
|
21
|
+
|
|
22
|
+
## Cross-process stampede protection — distributed lock via `onConflict`
|
|
23
|
+
|
|
24
|
+
`remember`'s lock is per-process. For cross-node safety, acquire a short-lived distributed lock before doing expensive work:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
const lockKey = `lock:build-report:${reportId}`;
|
|
28
|
+
const acquired = await cache.set(lockKey, process.pid, {
|
|
29
|
+
onConflict: "create",
|
|
30
|
+
ttl: "2m",
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (!acquired.wasSet) {
|
|
34
|
+
// another node is already building — wait or skip
|
|
35
|
+
return cache.get(`report:${reportId}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const report = await buildExpensiveReport(reportId);
|
|
40
|
+
await cache.set(`report:${reportId}`, report, "1h");
|
|
41
|
+
return report;
|
|
42
|
+
} finally {
|
|
43
|
+
await cache.remove(lockKey);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
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).
|
|
48
|
+
|
|
49
|
+
## Negative caching
|
|
50
|
+
|
|
51
|
+
Cache "not found" results with a shorter TTL to avoid hammering the origin:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
const user = await cache.remember(`user:${id}`, "5m", async () => {
|
|
55
|
+
const found = await db.users.find(id);
|
|
56
|
+
return found ?? { __miss: true };
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (user?.__miss) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Don't return raw `null` inside `remember` to "cache the miss" — it won't. `remember`'s truthy guard treats a stored `null` as a miss, so the callback re-runs every time and the origin still gets hammered. The truthy `{ __miss: true }` sentinel is what actually skips the next call.
|
|
65
|
+
|
|
66
|
+
## Per-tenant caching
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
// In cache config:
|
|
70
|
+
options: {
|
|
71
|
+
redis: {
|
|
72
|
+
url: "...",
|
|
73
|
+
globalPrefix: () => `tenant-${currentContext.tenantId}`,
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// At the call site — no tenancy awareness needed:
|
|
78
|
+
await cache.set("user:1", user, "1h");
|
|
79
|
+
// Actual key: "tenant-42.user.1"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Clear a tenant out:
|
|
83
|
+
```ts
|
|
84
|
+
await cache.removeNamespace(""); // when globalPrefix is set, flush scopes to it
|
|
85
|
+
// or
|
|
86
|
+
await cache.tags([`tenant-${tenantId}`]).invalidate();
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## See also
|
|
90
|
+
|
|
91
|
+
- [`@warlock.js/cache/use-cache-atomic/SKILL.md`](@warlock.js/cache/use-cache-atomic/SKILL.md) — `increment` / `decrement` counters
|
|
92
|
+
- [`@warlock.js/cache/use-cache-bulk/SKILL.md`](@warlock.js/cache/use-cache-bulk/SKILL.md) — `many` / `setMany`
|
|
93
|
+
- [`@warlock.js/cache/use-cache-utils/SKILL.md`](@warlock.js/cache/use-cache-utils/SKILL.md) — `CACHE_FOR` constants and TTL/key helpers
|
|
94
|
+
- [`@warlock.js/cache/use-cache-tags/SKILL.md`](@warlock.js/cache/use-cache-tags/SKILL.md) — tag-based invalidation
|
|
95
|
+
- [`@warlock.js/cache/use-cache-namespace/SKILL.md`](@warlock.js/cache/use-cache-namespace/SKILL.md) — scoped handles and `removeNamespace`
|
|
96
|
+
- [`@warlock.js/cache/use-swr/SKILL.md`](@warlock.js/cache/use-swr/SKILL.md) — stale-while-revalidate for slow upstreams
|
|
97
|
+
- [`@warlock.js/cache/use-cached-hof/SKILL.md`](@warlock.js/cache/use-cached-hof/SKILL.md) — `cached()` HOF for declarative memoization
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cache-basics
|
|
3
|
+
description: 'Start with @warlock.js/cache — the cache singleton, primary ops (set / get / pull / remove / many / forever / increment / remember), TTL shapes, init flow. Triggers: `cache`, `cache.setCacheConfigurations`, `cache.init`, `cache.set`, `cache.get`, `cache.remove`, `cache.remember`, `cache.flush`; "start with warlock cache", "wire up cache at startup", "which cache skill do I need"; typical import `import { cache } from "@warlock.js/cache"`. Skip: driver choice — `@warlock.js/cache/pick-cache-driver/SKILL.md`; set options — `@warlock.js/cache/configure-set-options/SKILL.md`; competing libs `lru-cache`, `node-cache`, `keyv`; native `Map`.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Cache basics
|
|
7
|
+
|
|
8
|
+
Unified cache manager with 8 built-in drivers (memory / memoryExtended / LRU / file / null / redis / pg / mock), tag-based invalidation, list sub-API, atomic `update`/`merge`, similarity retrieval, scoped namespace handles, and a rich `set` options object. The same surface across drivers — switch via config, not call sites.
|
|
9
|
+
|
|
10
|
+
> This skill is the cache **map** — read it first, then load the specific skill for the task.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
yarn add @warlock.js/cache
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Foundations
|
|
19
|
+
|
|
20
|
+
The 10 things that are true in every cache use:
|
|
21
|
+
|
|
22
|
+
1. **Public API is the `cache` singleton** (`import { cache } from "@warlock.js/cache"`). No `new CacheManager()` for consumers.
|
|
23
|
+
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" })`.
|
|
24
|
+
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 })`.
|
|
25
|
+
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).
|
|
26
|
+
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).
|
|
27
|
+
6. **The value you read is a deep clone.** `structuredClone` protects the cache from accidental mutation of returned objects.
|
|
28
|
+
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).
|
|
29
|
+
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.
|
|
30
|
+
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).
|
|
31
|
+
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).
|
|
32
|
+
|
|
33
|
+
## Minimal startup
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import {
|
|
37
|
+
cache,
|
|
38
|
+
MemoryCacheDriver,
|
|
39
|
+
RedisCacheDriver,
|
|
40
|
+
type CacheConfigurations,
|
|
41
|
+
} from "@warlock.js/cache";
|
|
42
|
+
|
|
43
|
+
const config: CacheConfigurations = {
|
|
44
|
+
default: "redis",
|
|
45
|
+
logging: false,
|
|
46
|
+
drivers: {
|
|
47
|
+
memory: MemoryCacheDriver,
|
|
48
|
+
redis: RedisCacheDriver,
|
|
49
|
+
},
|
|
50
|
+
options: {
|
|
51
|
+
memory: { ttl: "1h" },
|
|
52
|
+
redis: { url: "redis://localhost:6379", ttl: "7d" },
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
cache.setCacheConfigurations(config);
|
|
57
|
+
await cache.init();
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Primary ops
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
// Set + get
|
|
64
|
+
await cache.set("user.1", user, "1h");
|
|
65
|
+
const cached = await cache.get<User>("user.1"); // User | null
|
|
66
|
+
|
|
67
|
+
// Presence + read-and-delete
|
|
68
|
+
const exists = await cache.has("user.1"); // boolean
|
|
69
|
+
const taken = await cache.pull<User>("user.1"); // returns then removes
|
|
70
|
+
|
|
71
|
+
// Remove + flush
|
|
72
|
+
await cache.remove("user.1");
|
|
73
|
+
await cache.flush(); // wipe everything (current driver)
|
|
74
|
+
|
|
75
|
+
// Many at once — array positionally aligned with the keys (null for misses)
|
|
76
|
+
const [u1, u2, u3] = await cache.many(["user.1", "user.2", "user.3"]);
|
|
77
|
+
// → (User | null)[]
|
|
78
|
+
|
|
79
|
+
// No-TTL writes
|
|
80
|
+
await cache.forever("config.version", "1.2.3");
|
|
81
|
+
|
|
82
|
+
// Counters
|
|
83
|
+
await cache.increment("post.42.views"); // +1, returns new value
|
|
84
|
+
await cache.increment("post.42.views", 10); // +10
|
|
85
|
+
await cache.decrement("inventory.sku-x"); // -1
|
|
86
|
+
|
|
87
|
+
// Memoize an expensive function
|
|
88
|
+
const user = await cache.remember("user.1", "1h", async () => db.users.find(1));
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Pick a skill
|
|
92
|
+
|
|
93
|
+
| If the task is about… | Load |
|
|
94
|
+
| --- | --- |
|
|
95
|
+
| 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) |
|
|
96
|
+
| 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) |
|
|
97
|
+
| 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) |
|
|
98
|
+
| Scoped handles via `cache.namespace(prefix, options?)` | [`@warlock.js/cache/use-cache-namespace/SKILL.md`](@warlock.js/cache/use-cache-namespace/SKILL.md) |
|
|
99
|
+
| Tag-based invalidation — `cache.tags([...]).invalidate()` | [`@warlock.js/cache/use-cache-tags/SKILL.md`](@warlock.js/cache/use-cache-tags/SKILL.md) |
|
|
100
|
+
| Stale-while-revalidate — `cache.swr(...)` | [`@warlock.js/cache/use-swr/SKILL.md`](@warlock.js/cache/use-swr/SKILL.md) |
|
|
101
|
+
| 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) |
|
|
102
|
+
| 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) |
|
|
103
|
+
| 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) |
|
|
104
|
+
| 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) |
|
|
105
|
+
| Similarity retrieval — `set({ vector })` + `cache.similar(...)` | [`@warlock.js/cache/use-cache-similarity/SKILL.md`](@warlock.js/cache/use-cache-similarity/SKILL.md) |
|
|
106
|
+
| Postgres driver setup (KV-only or with pgvector) | [`@warlock.js/cache/configure-pg-cache/SKILL.md`](@warlock.js/cache/configure-pg-cache/SKILL.md) |
|
|
107
|
+
| `cache.metrics()` aggregate snapshot + event bus for per-event reactions | [`@warlock.js/cache/observe-cache/SKILL.md`](@warlock.js/cache/observe-cache/SKILL.md) |
|
|
108
|
+
| Error classes (`CacheConfigurationError`, `CacheUnsupportedError`, etc.) | [`@warlock.js/cache/handle-cache-errors/SKILL.md`](@warlock.js/cache/handle-cache-errors/SKILL.md) |
|
|
109
|
+
| Tests that touch cache code paths — `MockCacheDriver`, `MemoryCacheDriver` | [`@warlock.js/cache/test-cache-code/SKILL.md`](@warlock.js/cache/test-cache-code/SKILL.md) |
|
|
110
|
+
|
|
111
|
+
## Things NOT to do
|
|
112
|
+
|
|
113
|
+
- Don't call `new RedisCacheDriver()` directly in app code — register it in the configuration and let the manager load it.
|
|
114
|
+
- Don't store un-serializable values (functions, symbols, class instances with methods) on the `redis` or `file` drivers — they JSON-roundtrip.
|
|
115
|
+
- Don't rely on `remember()` for cross-process stampede protection. It only serializes within one Node process.
|
|
116
|
+
- Don't mix `ttl` and `expiresAt` in the same `set` call — it throws `CacheConfigurationError`.
|
|
117
|
+
- Don't call `update()` / `merge()` on the file driver — it throws.
|
|
118
|
+
- Don't assume `setNX` is available on every driver — prefer `onConflict: "create"` which works everywhere.
|
|
119
|
+
- Don't run `cache.similar()` against memory drivers in production with large datasets — O(N) brute force.
|
|
120
|
+
- Don't auto-migrate the `pg` table from app code. The driver exposes `driver.schema()` returning DDL.
|
|
121
|
+
- Don't switch embedders without re-embedding the entire vector index.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: configure-pg-cache
|
|
3
|
+
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`.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# `pg` cache driver — Postgres setup
|
|
7
|
+
|
|
8
|
+
Persistent cache backed by your existing Postgres pool. Two modes: **KV-only** (default) or **pgvector** (opt in via `options.pg.vector`). Same driver, same API — flip a config flag.
|
|
9
|
+
|
|
10
|
+
## Always-true facts
|
|
11
|
+
|
|
12
|
+
1. **Caller owns the connection.** Pass an already-built `pg.Pool` (or `Client`) via `options.pg.client`. The driver never closes it on `cache.disconnect()` — your pool stays usable everywhere else.
|
|
13
|
+
2. **`pg` is an optional peer dep.** Lazy-loaded; install only if you use this driver.
|
|
14
|
+
3. **No auto-migration.** Driver exposes `driver.schema()` returning a DDL string — caller runs it via their own migration tool.
|
|
15
|
+
4. **Table name is regex-validated** (`[A-Za-z_][A-Za-z0-9_]*`) before DDL interpolation. No SQL injection via misconfiguration.
|
|
16
|
+
5. **TTL is lazy on read.** `SELECT ... WHERE expires_at IS NULL OR expires_at > now()`. Expired rows aren't auto-deleted unless you GC them yourself.
|
|
17
|
+
6. **`onConflict` is race-safe at the SQL layer:**
|
|
18
|
+
- `create` → `INSERT ... ON CONFLICT DO UPDATE WHERE expires_at < now() RETURNING value` (reclaims expired rows; blocks live ones).
|
|
19
|
+
- `update` → `UPDATE ... WHERE expires_at IS NULL OR expires_at > now() RETURNING value`.
|
|
20
|
+
- `upsert` → unconditional `INSERT ... ON CONFLICT DO UPDATE`.
|
|
21
|
+
7. **`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.
|
|
22
|
+
8. **pgvector requires `CREATE EXTENSION vector;` once on the database.** Lazy probe on first vector op throws `CacheConfigurationError` if missing; result is cached.
|
|
23
|
+
9. **Vectors are passed as text literals** (`'[1,2,3]'::vector`). No binary protocol dependency — works against any pg client.
|
|
24
|
+
|
|
25
|
+
## Configuration
|
|
26
|
+
|
|
27
|
+
### KV-only
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { Pool } from "pg";
|
|
31
|
+
import { cache, PgCacheDriver } from "@warlock.js/cache";
|
|
32
|
+
|
|
33
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
34
|
+
|
|
35
|
+
cache.setCacheConfigurations({
|
|
36
|
+
default: "pg",
|
|
37
|
+
drivers: { pg: PgCacheDriver },
|
|
38
|
+
options: {
|
|
39
|
+
pg: {
|
|
40
|
+
client: pool,
|
|
41
|
+
table: "warlock_cache", // optional, default
|
|
42
|
+
ttl: "1h", // optional default
|
|
43
|
+
globalPrefix: "prod-app",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
await cache.init();
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
When the pool isn't available at config time (lazy bootstrap, per-tenant pools, test swapping), drop `client` from the static block and inject it at use-time: `cache.use("pg", { client: pool })`. Runtime options merge over static per-key, runtime wins. Re-calling with new options throws — register a second driver name for a second config.
|
|
51
|
+
|
|
52
|
+
### pgvector mode
|
|
53
|
+
|
|
54
|
+
Same driver — add the `vector` block:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
options: {
|
|
58
|
+
pg: {
|
|
59
|
+
client: pool,
|
|
60
|
+
vector: {
|
|
61
|
+
dimensions: 1536, // must match your embedder
|
|
62
|
+
index: "hnsw", // or "ivfflat"; default "hnsw"
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## One-time schema setup
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
await pool.query(driver.schema());
|
|
72
|
+
// CREATE TABLE IF NOT EXISTS warlock_cache (
|
|
73
|
+
// key TEXT PRIMARY KEY,
|
|
74
|
+
// value JSONB NOT NULL,
|
|
75
|
+
// expires_at TIMESTAMPTZ,
|
|
76
|
+
// stale_at TIMESTAMPTZ,
|
|
77
|
+
// tags TEXT[] NOT NULL DEFAULT '{}'::TEXT[],
|
|
78
|
+
// embedding VECTOR(1536) -- only when vector config is set
|
|
79
|
+
// );
|
|
80
|
+
// CREATE INDEX IF NOT EXISTS idx_warlock_cache_expires_at ...
|
|
81
|
+
// CREATE INDEX IF NOT EXISTS idx_warlock_cache_tags ... USING GIN (tags);
|
|
82
|
+
// CREATE INDEX IF NOT EXISTS idx_warlock_cache_embedding ... USING hnsw (embedding vector_cosine_ops);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Pipe this through whichever migration tool you use (Knex, Prisma, plain SQL files, Atlas).
|
|
86
|
+
|
|
87
|
+
## Index strategies (pgvector)
|
|
88
|
+
|
|
89
|
+
- `hnsw` (default) — faster query, slower build, larger on disk. The right default.
|
|
90
|
+
- `ivfflat` — faster build, slightly slower query. Useful for bulk ingest then static reads.
|
|
91
|
+
|
|
92
|
+
Switching strategies requires rebuilding the index.
|
|
93
|
+
|
|
94
|
+
## Errors you'll surface
|
|
95
|
+
|
|
96
|
+
- `CacheConfigurationError: requires a 'client' option` — forgot to pass the pool.
|
|
97
|
+
- `CacheConfigurationError: invalid table name` — non-`[A-Za-z_][A-Za-z0-9_]*` characters in `options.pg.table`.
|
|
98
|
+
- `CacheConfigurationError: pgvector extension not installed` — run `CREATE EXTENSION vector;` once on the DB, or remove the `vector` block.
|
|
99
|
+
- `CacheConfigurationError: vector dimension mismatch` — input vector length ≠ configured dimensions. Embedder probably changed.
|
|
100
|
+
- `CacheUnsupportedError: similarity retrieval requires the 'vector' config block` — KV-only mode; add `options.pg.vector`.
|
|
101
|
+
|
|
102
|
+
See [`@warlock.js/cache/handle-cache-errors/SKILL.md`](@warlock.js/cache/handle-cache-errors/SKILL.md) for the full error class hierarchy.
|
|
103
|
+
|
|
104
|
+
## Things NOT to do
|
|
105
|
+
|
|
106
|
+
- Don't auto-run `driver.schema()` from app code — it's a one-time migration. Run it through your migration pipeline.
|
|
107
|
+
- Don't share the driver's pool with the connection-eager `pg.Client` form for long-running apps — use `pg.Pool`.
|
|
108
|
+
- Don't expect the driver to close your pool. `cache.disconnect()` deliberately leaves it open. Close the pool yourself when shutting down.
|
|
109
|
+
- Don't switch embedders without re-embedding the index. Vectors aren't portable across models.
|
|
110
|
+
- Don't put the `pg` driver behind a connection-string the cache itself manages — pass the pool you already built for the rest of the app.
|
|
111
|
+
|
|
112
|
+
## Related
|
|
113
|
+
|
|
114
|
+
- [`@warlock.js/cache/use-cache-similarity/SKILL.md`](@warlock.js/cache/use-cache-similarity/SKILL.md) — the `similar()` API across all drivers
|
|
115
|
+
- [`@warlock.js/cache/pick-cache-driver/SKILL.md`](@warlock.js/cache/pick-cache-driver/SKILL.md) — comparing pg with memory / redis / file
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: configure-set-options
|
|
3
|
+
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`.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# The `set` options object
|
|
7
|
+
|
|
8
|
+
`cache.set(key, value, ttlOrOptions?)` — the 3rd argument accepts three shapes.
|
|
9
|
+
|
|
10
|
+
## The three shapes
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
// 1. Number — seconds
|
|
14
|
+
await cache.set("name", "Jane", 600);
|
|
15
|
+
|
|
16
|
+
// 2. String — human-readable duration, parsed via `ms`
|
|
17
|
+
await cache.set("name", "Jane", "10m"); // "1s", "30m", "1h", "7d", "2 weeks"
|
|
18
|
+
|
|
19
|
+
// 3. Options object
|
|
20
|
+
await cache.set("user:1", user, {
|
|
21
|
+
ttl: "1h",
|
|
22
|
+
expiresAt: new Date("2026-12-31"),
|
|
23
|
+
tags: ["users"],
|
|
24
|
+
onConflict: "create",
|
|
25
|
+
driver: "redis",
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Option keys
|
|
30
|
+
|
|
31
|
+
| Key | Type | Notes |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| `ttl` | `number \| string` | Relative expiry. Mutually exclusive with `expiresAt`. |
|
|
34
|
+
| `expiresAt` | `number \| Date` | Absolute deadline (epoch ms or Date). Must be in the future. Mutually exclusive with `ttl`. |
|
|
35
|
+
| `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). |
|
|
36
|
+
| `onConflict` | `"create" \| "update" \| "upsert"` | See below. Default `"upsert"`. |
|
|
37
|
+
| `driver` | `string` | Per-call driver override by registered name. |
|
|
38
|
+
| `vector` | `number[]` | Embedding indexed alongside the entry for [`cache.similar()`](@warlock.js/cache/use-cache-similarity/SKILL.md). Drivers without similarity support throw `CacheUnsupportedError`. |
|
|
39
|
+
|
|
40
|
+
## `onConflict` policies
|
|
41
|
+
|
|
42
|
+
Self-documenting enum; Redis maps these to `NX` / `XX` natively, others emulate.
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
// create — set only if key is missing
|
|
46
|
+
const result = await cache.set("lock:jobs:import", workerId, {
|
|
47
|
+
onConflict: "create",
|
|
48
|
+
ttl: "5m",
|
|
49
|
+
});
|
|
50
|
+
// result: { wasSet: true, existing: null } on acquire
|
|
51
|
+
// { wasSet: false, existing: <prior workerId> } on conflict — someone else holds the lock
|
|
52
|
+
|
|
53
|
+
if (!result.wasSet) {
|
|
54
|
+
// another worker is already running; abort.
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// update — set only if key exists (don't resurrect expired sessions)
|
|
58
|
+
await cache.set("session:abc", session, { onConflict: "update" });
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Conditional writes (`"create"` / `"update"`) return a `CacheSetResult`; unconditional `"upsert"` returns the value or driver instance as before.
|
|
62
|
+
|
|
63
|
+
## Mutually-exclusive validations
|
|
64
|
+
|
|
65
|
+
Both of these throw `CacheConfigurationError`:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
await cache.set("k", v, { ttl: "1h", expiresAt: Date.now() + 1000 }); // both set
|
|
69
|
+
await cache.set("k", v, { expiresAt: Date.now() - 1000 }); // past deadline
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Inline tags vs `cache.tags([...]).set(...)`
|
|
73
|
+
|
|
74
|
+
Both work; inline is terser when you're writing one value under known tags:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
// Inline
|
|
78
|
+
await cache.set("user:1", user, { tags: ["users", "tenant-42"] });
|
|
79
|
+
|
|
80
|
+
// Fluent (useful when you already have a tagged instance)
|
|
81
|
+
const users = cache.tags(["users"]);
|
|
82
|
+
await users.set("user:1", user);
|
|
83
|
+
await users.invalidate(); // drops every key tagged "users"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Inline tag semantics are **additive**, never replace. A subsequent `set("user:1", ...)` with no `tags` leaves previous associations intact (the tag index still points to the key), and a `set(..., { tags: [...] })` only appends the key to those tags' index entries — it never removes the key from tags it was bound to earlier. To drop stale bindings, invalidate the old tag explicitly via `cache.tags([...]).invalidate()`.
|
|
87
|
+
|
|
88
|
+
## Back-compat note
|
|
89
|
+
|
|
90
|
+
Every call site using the old positional-TTL shape keeps working:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
await cache.set("k", v); // no TTL — driver default
|
|
94
|
+
await cache.set("k", v, 3600); // seconds
|
|
95
|
+
await cache.set("k", v, undefined); // same as no TTL
|
|
96
|
+
```
|