@dokebi/cache
v0.4.1
Published
Named cache backends for Dokebi: in-process LRU or a shared Redis driver.
Maintainers
Readme
@dokebi/cache
An optional per-app JSON cache plugin. Each backend picks a driver: local (default, process memory) or redis (shared; needs @dokebi/cache-redis). core ^0.4.0, Node.js 24.0.0 or later, version 0.4.0 (unpublished). Named backends are configured under options.dokebi-cache.backends in the single YAML. AI agents start with AI.md.
schemaVersion: 1
options:
dokebi-cache:
backends:
products:
driver: local # may be omitted
ttlMs: 30000
maxEntries: 1000
maxBytes: 16777216
maxEntryBytes: 1048576
maxKeyBytes: 1024
shared:
driver: redis
connection: main # a name from dokebi-redis.connections
namespace: my-service
ttlMs: 60000
maxEntryBytes: 65536Registration and use
- The factory input is
{ app, options?, entry? }(entryis the module of the entry filesrc/infra/cache/extension.ts, see "Extension" below).createPlugin({ app: app.view })readsapp.config['dokebi-cache']; givenoptionsare used instead. Invalid options areINVALID_PLUGIN_OPTIONS(cause: the coreINVALID_OPTIONS, paths indetails.issues). The options are onedefineOptionsdeclaration insrc/options.ts, and the build generatesoptions.schema.jsonfrom it (D-0063). - After
app.register/start, getapp.get(plugin).backend('products'). - A consumer plugin puts
import { dependency } from '@dokebi/cache'inmetadata.dependenciesand usescontext.getDependency(dependency). An already registered dokebi-cache instance is reused. backend.get(key)returns unknown, or undefined when missing;set(key, value, { ttlMs? })returnsPromise<void>;delete(key)returns whether a live value was removed. null is a normal value. TTL is the required default or a per-call override, 1–2147483647 ms. 0 or unlimited is not allowed.backend.getOrSet(key, load, { ttlMs? })(D-0106) returns the cached value, or stores and returnsload()'s result. Concurrent calls of a process for the same key share one get, one load and one set (single flight, no stampede on the source); each caller gets its own copy, a failure reaches every waiter and is not cached.maxLoads(every backend, default 10000) bounds the keys loading at once; past it a call loads on its own.
Drivers
| Key (local) | Type | Default | Description |
|---|---|---|---|
| ttlMs | integer ms | — (required) | Default TTL |
| maxEntries | integer | 1000 | Entry limit |
| maxBytes | integer | 16777216 | Total key and value bytes |
| maxEntryBytes | integer | 1048576 | One encoded value |
| maxKeyBytes | integer | 1024 | Key UTF-8 bytes |
| maxLoads | integer | 10000 | Keys whose getOrSet load is shared at once (also redis and app-driver backends) |
driver: local: follows "Local behavior" below. Not shared across apps, backends, threads or processes.driver: redis: fieldsconnection,namespace,ttlMs,maxEntryBytes(required),maxKeyBytes(1024),maxDepth(64),maxNodes(10000),maxPending(4096, 1–100000; old namemaxPendingOperations, renamed in 0.4.0 by D-0071 and still accepted with a warning),maxPendingBytes(64 MiB of writes in flight, at leastmaxEntryBytes),maxLoads(10000). SizemaxPendingas at least the requests one worker serves at once times the cache operations each has in flight (AI.md "SizingmaxPending", D-0106: 128 refused 3,872 of 4,000 reads at once); past a limit a request answers a retryable 503CACHE_BUSY. Behavior is described in @dokebi/cache-redis.- With at least one redis backend,
dokebi-redis ^0.4.0becomes a runtime dependency. When not registered, it is created from the@dokebi/redispackage. With local only there is no dependency and the Redis package is never loaded. - The driver package is imported in setup. When it is not installed, start fails with
CacheErrorDRIVER_NOT_INSTALLEDand thefixsaysnpm install @dokebi/cache-redis. A connection name missing from dokebi-redis isUNKNOWN_CONNECTION. - The driver contract is
CacheDriver { view: CacheBackend; close(): Promise<void> }. Consumers get onlyview; only the plugin's cleanup callsclose.
Extension (D-0064)
dokebi addcreates the user-owned entry filesrc/infra/cache/extension.ts(an emptyCacheExtensionsubclass). Left empty, behavior is the default and costs nothing (hooks you did not override are never called).- Hooks (
protected override, three):dependencies(),createDriver(input),wrapBackend(input). Constructor parameters or shadowing a non-hook member fail withINVALID_EXTENSION(fix,data.reason) when the plugin is created. - Driver registry: the built-in
localandredisstay, and app drivers are added withexport const drivers = { <name>: (input) => driver }in the entry file or withdefineCachePlugin({ drivers }). YAML usesdriver: <name>andsettings. Details in AI.md §9 andexamples/29-extension.
Install (CLI)
- Add it with
npx dokebi add @dokebi/cache.@dokebi/cache-redisis not a plugin; add it withnpm install. planInstallandcheckof the install module (dist/install.js) returnrequires: ['dokebi-redis']when a redis backend exists. So without dokebi-redis, add stops withMISSING_REQUIRED_PLUGIN, and removing a dokebi-redis in use is refused.checkreports an error diagnosticDRIVER_PACKAGE_MISSINGper backend (pathbackends.<name>.driver) when the project's package.json lacks@dokebi/cache-redis.- The manifest is schemaVersion 2 and declares
dokebi-redisas an optional dependency.@dokebi/redisand@dokebi/cache-redisare optional peerDependencies.
Local behavior
A JSON snapshot is made on write and independent data is returned on read. null, boolean, string, finite number, array and plain object are supported, and integers only within the safe range. undefined, bigint, functions, Date, Buffer, classes, getters, proxies, sparse arrays and cycles are rejected. -0 comes back as 0, as JSON does.
JsonCodec of @dokebi/cache/codec checks the JSON UTF-8 size including the J1: prefix. The defaults are depth 64 and 10000 nodes. maxEntryBytes is per value; maxBytes is the total of key and encoded value bytes and does not mean a JS heap limit. A failed replacement keeps the existing value. A corrupt value is CORRUPT_VALUE, never hidden as a miss.
LRU limits the entry count and bytes. Expiry is checked on the monotonic clock at read and delete. There are no full scans and no per-entry timers. Expired entries not read yet can still count toward capacity.
Shutdown and logs
- After consumer cleanup the provider closes the drivers. Afterwards
backend(name)isCLOSED. close and the internal Map are not public. - Logs: debug
CACHE_BACKEND_READYwhen a backend is ready, error when a driver close fails (the driver's error code orCACHE_CLOSE_FAILED). Fields arebackendandcodeonly; never keys, values, namespaces or addresses. - A key is a non-empty, well-formed UTF-16 string with a limited UTF-8 length.
Main CacheError.code values: INVALID_OPTIONS, INVALID_KEY, UNKNOWN_BACKEND, UNKNOWN_CONNECTION, CLOSED, INVALID_VALUE, VALUE_TOO_LARGE, CORRUPT_VALUE, DRIVER_NOT_INSTALLED, DRIVER_INVALID, (redis) PROVIDER_FAILED, OUTCOME_UNKNOWN (both with the Redis error as cause), QUEUE_FULL. Through dokebi-web QUEUE_FULL answers 503 CACHE_BUSY and CLOSED 503 CACHE_UNAVAILABLE (Retry-After: 1); a transient Redis cause answers @dokebi/redis's 503 (D-0102). CacheError is a DokebiError (plugin: 'dokebi-cache', fix), and other packages classify it by code. Extension: UNKNOWN_DRIVER, DRIVER_CONFLICT, INVALID_ENTRY, INVALID_EXTENSION. Original values and keys never appear in error strings. Runnable examples: example 14 (local), example 15 (redis), and for extensions examples/29-extension.
