In-memory TTL cache for compat (compat:<id>) provider configs
with Redis pub/sub invalidation for multi-process coherence.
Why
Without a cache every callProvider() / streamText() invocation for a
compat slot reads api_keys from SQLite (hot path for AI generation, can
be called hundreds of times per pipeline run). This module caches the
decrypted { apiKey, baseUrl, model, displayName } blob with a short TTL
and invalidates it on write.
Multi-process coherence
When REDIS_URL is set, every invalidate() / invalidateAll() call
also publishes to sentri:compat-config:invalidate. Other server
instances listening on the channel drop their cached entry for the same
slot id, so a save on instance A is visible to instance B within one
Redis round-trip. The published message includes an _origin field so
the publisher skips its own echo (same pattern as routes/sse.js).
Single-process mode
When Redis is not available the module degrades to a purely local cache
with TTL-based staleness. This is safe for single-instance deployments;
the TTL (default 60 s, overridable via COMPAT_CONFIG_CACHE_TTL_MS)
bounds the staleness window.
Exports
get— Read-through accessor(slotId, loader) => value|null.invalidate— Drop a single slot's cached entry + publish.invalidateAll— Drop every entry + publish (used by admin resets).__test— Internals exposed for tests (stats, clear, TTL override).
- Source:
Members
(inner, constant) ALL
Sentinel payload for "invalidate every cached slot".
- Source:
(inner, constant) CHANNEL
Pub/sub channel name. Prefix matches the sentri: convention used by sse.js.
- Source:
(inner, constant) _cache
Cache entries: slotId → { value, expiresAt }.
value === null is a legitimate cached result (slot deleted) — we use
expiresAt rather than presence-of-key to test staleness.
- Source:
(inner, constant) _instanceId
Unique id for this process — used to skip self-echo from Redis.
- Source:
(inner, constant) _stats
Stats for observability / tests.
- Source:
(inner) _ttlMs
TTL in milliseconds. Exposed via __test.setTtl() so tests can shrink
the TTL without waiting 60 s in real time. Read from env at module load
so production deployments can tune staleness without a code change.
- Source:
Methods
(static) get(slotId, loader) → {any}
Read-through accessor. Returns the cached value for slotId if fresh,
otherwise calls loader(slotId) to refresh the entry. loader is
supplied by the caller (rather than imported here) to avoid a circular
dependency on apiKeyRepo.
Parameters:
| Name | Type | Description |
|---|---|---|
slotId |
string | Canonical provider id (e.g. |
loader |
function |
|
- Source:
Returns:
The cached or freshly-loaded value.
- Type
- any
(static) invalidate(slotId)
Drop the cached entry for a single slot and broadcast the invalidation to
other instances. Must be called by every write-path (setCompatSlot,
deleteCompatSlot) so the cache never serves stale credentials.
Parameters:
| Name | Type | Description |
|---|---|---|
slotId |
string | Canonical provider id. |
- Source:
(static) invalidateAll()
Drop every cached entry and broadcast. Used by admin resets and tests.
- Source: