@ -1,36 +1,36 @@
# cache
`arts/cache` es la capa de cache de datos del framework. No es un `Map` con TTL:
es un motor de coherencia para decidir si un dato se puede servir, si está fresco,
si debe revalidarse, bajo qué scope de seguridad vive y qué invalidaciones lo
afectan .
`arts/cache` is the framework's data cache layer. It is not a `Map` with a TTL:
it is a coherence engine that decides whether a datum can be served, whether it
is fresh, whether it must be revalidated, under which security scope it lives and
which invalidations affect it .
El patrón sigue el resto de artefacto s:
The pattern follows the rest of the artifact s:
```ts
import { createEngineCache } from '$svrs/cache';
import { createActiveCache } from '$cache';
```
- `createEngineCache()` vive en `$svrs/cache` y es la API imperativa para server, servicios, repositorios, workers y tests.
- `createActiveCache()` vive en `$cache` y añade estado reactivo para Svelte.
- `App.cache` existe cuando la app declara `cache: defineActiveCache(...)` e n
- `createEngineCache()` lives in `$svrs/cache` and is the imperative API for server, services, repositories, workers and tests.
- `createActiveCache()` lives in `$cache` and adds reactive state for Svelte.
- `App.cache` exists when the app declares `cache: defineActiveCache(...)` i n
`services` .
- El core puro vive en `$libs/cache` como `createCacheRuntime()` .
- The pure core lives in `$libs/cache` as `createCacheRuntime()` .
## Qué Resuelve
## What It Solves
`cache` responde a preguntas que una cache simple no contesta :
`cache` answers questions a simple cache does not :
- Si el dato existe, si está `fresh` , `stale` , `expired` o invalidado .
- Si puede servirse stale mientras se refresca en background.
- Si puede servirse stale cuando el origen falla .
- Si pertenece a scop e `public` , `tenant` , `actor` , `permission` o `custom` .
- Si un cambio de tag o key prefix invalidó la entrada .
- Si hay otra petición idéntica en vuelo y debe deduplicarse .
- Por qué tomó una decisión, ví a `explain()` .
- Whether the datum exists, and whether it is `fresh` , `stale` , `expired` or invalidate d.
- Whether it can be served stale while refreshing in the background.
- Whether it can be served stale when the origin fails .
- Whether it belongs to th e `public` , `tenant` , `actor` , `permission` or `custom` scope .
- Whether a tag or key-prefix change invalidated the entry .
- Whether there is another identical request in flight and it must be deduplicated .
- Why it made a decision, vi a `explain()` .
## Uso Mínimo
## Minimal Use
```ts
import { createEngineCache, memoryCacheAdapter, CACHE_POLICY_INTERACTIVE } from '$svrs/cache';
@ -48,11 +48,11 @@ const project = await Cache.query({
});
```
La segunda lectura con la misma key/scope/policy servirá el valor cacheado si sigue válido .
A second read with the same key/scope/policy will serve the cached value if it is still valid .
## App.cache
`createActiveApp()` expone `App.cache` cuando se declara el servicio :
`createActiveApp()` exposes `App.cache` when the service is declared :
```ts
const App = createActiveApp({
@ -85,8 +85,8 @@ const App = createActiveApp({
});
```
Si una página demo o test se prerenderiza en modo producción y usa ese adapter memory de forma
intencional, configúralo de forma explícita :
If a demo or test page is prerendered in production mode and uses that memory
adapter intentionally, configure it explicitly :
```ts
const App = createActiveApp({
@ -98,11 +98,13 @@ const App = createActiveApp({
});
```
No uses esa opción para esconder una cache de producción accidental. En una app real, pasa
`cache.adapter` con el backend que quieras usar o deja el warning activo hasta decidir la estrategia.
Do not use that option to hide an accidental production cache. In a real app,
pass `cache.adapter` with the backend you want, or leave the warning active until
you decide the strategy.
Si usas `scope: 'actor'` o `scope: 'permission'` , el resolver debe aportar los valores
necesarios. Si faltan, el motor falla en vez de mezclar datos de usuarios.
If you use `scope: 'actor'` or `scope: 'permission'` , the resolver must provide
the necessary values. If they are missing, the engine fails instead of mixing
users' data.
### Reacting to identity / session changes
@ -141,7 +143,7 @@ policy out of the cache art and on the App composition.
## Keys
Las keys son arrays determinista s:
Keys are deterministic array s:
```ts
['posts', { page: 1, filters: { status: 'published' } }][('tenant', tenantId, 'projects')][
@ -149,30 +151,30 @@ Las keys son arrays deterministas:
];
```
El normalizado r:
The normalize r:
- Ordena keys de objeto s.
- Omite campo s `undefined` en objeto s.
- Soporta `Date` , `Map` , `Set` , `URLSearchParams` y `BigInt` .
- Rechaza funciones, símbolos, números no finitos y referencias circular es.
- Sorts object key s.
- Omits `undefined` fi elds i n objec ts.
- Supports `Date` , `Map` , `Set` , `URLSearchParams` and `BigInt` .
- Rejects functions, symbols, non-finite numbers and circular referenc es.
## Scopes
Los scopes evitan fugas de dato s:
Scopes prevent data leak s:
```ts
scope: 'public' // comparti ble
scope: 'tenant' // requie re tenantId
scope: 'actor' // requiere actorId; tenantId opc ional
scope: 'permission' // requie re actorId + permissionHash
scope: 'public' // sharea ble
scope: 'tenant' // requires tenantId
scope: 'actor' // requires actorId; tenantId opt ional
scope: 'permission' // requires actorId + permissionHash
scope: { mode: 'custom', values: { tenantId, reportId } }
```
Regla de calidad: datos privados nunca deberían cachearse como `public` .
Quality rule: private data should never be cached as `public` .
## Policies
Las políticas expresan intención :
Policies express intent :
```ts
import {
@ -182,15 +184,15 @@ import {
} from '$svrs/cache';
```
Incluidas :
Included :
- `interactive` : UI normal, `stale-while-revalidate` .
- `catalog` : datos estables, ventanas larga s.
- `privateSession` : datos privados, persistencia desactivada por defecto .
- `realtime` : casi sin cache.
- `immutable` : datos versionados o inmutables .
- `interactive` : normal UI , `stale-while-revalidate` .
- `catalog` : stable data, long window s.
- `privateSession` : private data, persistence off by default .
- `realtime` : almost no cache.
- `immutable` : versioned or immutable data .
Puedes definir las tuyas :
You can define your own :
```ts
import { createEngineCache } from '$svrs/cache';
@ -219,15 +221,15 @@ CACHE_READ_MODE_BYPASS_CACHE;
CACHE_READ_MODE_NO_STORE;
```
- `cache-first` : sirve caché mientras esté fresh.
- `stale-while-revalidate` : sirve stale dentro de ventana y refresca en background.
- `must-revalidate` : si no está fresh, bloquea y va al orige n.
- `bypass-cache` : ignora lectura, llama al origen y escribe resultado .
- `no-store` : ignora lectura, llama al origen y no escrib e.
- `cache-first` : serves cache while it is fresh.
- `stale-while-revalidate` : serves stale within the window and refreshes in the background.
- `must-revalidate` : if not fresh, blocks and goes to the origi n.
- `bypass-cache` : ignores the read, calls the origin and writes the result .
- `no-store` : ignores the read, calls the origin and does not writ e.
## Tags Y Epochs
## Tags and Epochs
Las entradas pueden declarar tags:
Entries can declare tags:
```ts
tags: [
@ -236,7 +238,7 @@ tags: [
];
```
Invalidar un tag no escanea ni borra todas las keys. Incrementa u n epoch:
Invalidating a tag neither scans nor deletes all keys. It bumps a n epoch:
```ts
await Cache.invalidate({
@ -245,10 +247,10 @@ await Cache.invalidate({
});
```
Cada entry guarda los epochs con los que fue escrita. En la siguiente lectura,
si el epoch actual no coincide, la entry queda invalidada .
Each entry stores the epochs it was written with. On the next read, if the
current epoch does not match, the entry is invalidated .
También hay invalidación por key exacta y key prefix:
There is also invalidation by exact key and key prefix:
```ts
await Cache.invalidate({ key: ['project', projectId], scope: 'tenant' });
@ -257,7 +259,7 @@ await Cache.invalidate({ keyPrefix: ['projects'], scope: 'tenant' });
## ActiveCache
`createActiveCache()` expone la misma API que el engine y añade estado reactivo :
`createActiveCache()` exposes the same API as the engine and adds reactive state :
```ts
const Cache = createActiveCache();
@ -276,30 +278,31 @@ entry.error;
entry.loading;
```
`ActiveCacheEntry` no hace magia en efectos. Cargas explícitamente con `load()` o
`refresh()` , y el estado cambia sin bucles reactivo s.
`ActiveCacheEntry` does no magic in effects. You load explicitly with `load()` or
`refresh()` , and the state changes without reactive loop s.
`entry.set(value)` reutiliza por defecto la `policy` , `tags` , `schemaVersion` ,
`persist` y `scope` definidos en la entry, y permite sobrescribirlos por llamada .
`entry.set(value)` reuses by default the `policy` , `tags` , `schemaVersion` ,
`persist` and `scope` defined on the entry, and lets you override them per call .
### Superficie del contrato `ActiveEngine`
### `ActiveEngine` contract surface
Además de reflejar el engine (`get` / `set` / `invalidate` / `mutate` / `explain` /
`clear` / `entry` ), `ActiveCache` implementa el contrato reactivo compartido:
Besides mirroring the engine (`get` / `set` / `invalidate` / `mutate` /
`explain` / `clear` / `entry` ), `ActiveCache` implements the shared reactive
contract:
- `Cache.loading` — `true` mientras haya cargas en vuelo (getter reactivo ).
- `Cache.lastError` — último error normalizado, o `null` .
- `Cache.disposed` — `true` tras `dispose()` .
- `Cache.clearError()` — limpia `lastError` .
- `Cache.snapshot()` — instantánea `{ lastEvent, eventCount, loading, lastError, disposed }` .
- `Cache.onChange(listener)` — invoca `listener(snapshot())` en cada cambio de estado
(carga, error, evento de cache, dispose); devuelve el desuscriptor. Es la base para
envolver `ActiveCache` en una vista reactiva .
- `Cache.dispose()` — idempotente; libera las entries propias y sus timers.
- `Cache.loading` — `true` while there are loads in flight (reactive getter ).
- `Cache.lastError` — the last normalized error, or `null` .
- `Cache.disposed` — `true` af te r `dispose()` .
- `Cache.clearError()` — clears `lastError` .
- `Cache.snapshot()` — snapshot `{ lastEvent, eventCount, loading, lastError, disposed }` .
- `Cache.onChange(listener)` — invokes `listener(snapshot())` on every state change
(load, error, cache event, dispose); returns the unsubscriber. It is the basis for
wrapping `ActiveCache` in a reactive view .
- `Cache.dispose()` — idempotent; releases its own entries and their timers.
## Mutate
La mutación v1 es conservadora :
The v1 mutation is conservative :
```ts
await Cache.mutate({
@ -315,18 +318,18 @@ await Cache.mutate({
});
```
Semántica :
Semantics :
1. Ejecuta `commit()` .
2. Si falla, no toca la cache.
3. Si funciona, invalida tags/prefix/keys.
4. Aplica updates exacto s.
1. Runs `commit()` .
2. If it fails, it does not touch the cache.
3. If it succeeds, it invalidates tags/prefix/keys.
4. Applies the exact update s.
Optimistic journal queda fuera de v1.
An optimistic journal is out of scope for v1.
## Explain
`explain()` existe desde v1 porque cache sin introspección se vuelve opaca :
`explain()` exists since v1 because a cache without introspection becomes opaque :
```ts
const info = await Cache.explain(['project', projectId], {
@ -335,7 +338,7 @@ const info = await Cache.explain(['project', projectId], {
});
```
Devuelve adapter, key efectiva, estado, decisión, razón, schema y epochs.
It returns the adapter, effective key, state, decision, reason, schema and epochs.
## Adapters
@ -348,11 +351,11 @@ const adapter = memoryCacheAdapter({
});
```
Soporta TTL, LRU aproximado, epochs e introspección.
Emite el warning constante `CACHE_MEMORY_ADAPTER_PRODUCTION_WARNING` si se crea
en runtime de producción. Es correcto para tests, demos, L1 por proceso y
desarrollo local; para producción multi-instancia usa un adapter intencional
o una composición tiered cuando esté disponi ble.
Supports TTL, approximate LRU, epochs and introspection. It emits the constant
warning `CACHE_MEMORY_ADAPTER_PRODUCTION_WARNING` if created in a production
runtime. It is correct for tests, demos, per-process L1 and local development;
for multi-instance production use an intentional adapter or a tiered composition
when availa ble.
### Storage
@ -363,17 +366,17 @@ const adapter = storageCacheAdapter({
});
```
Útil para persistencia cliente. Respeta `persist: false` , por lo que una policy
privada puede impedir escritura persistente.
El adapter mantiene un índice interno para que `Cache.clear()` pueda borrar tanto
entries como epochs persistidos; esto es importante en logout/wipe.
Useful for client persistence. It respects `persist: false` , so a private policy
can prevent persistent writing. The adapter keeps an internal index so that
`Cache.clear()` can delete both entries and persisted epochs; this matters on
logout/wipe.
Redis, tiered cache, browser Cache API y adapters edge quedan para fases posteriores.
El contrato está preparado para que Redis se integre sin dependencia dura .
Redis, tiered cache, the browser Cache API and edge adapters are left for later
phases. The contract is ready for Redis to integrate without a hard dependency .
## Eventos Y Logger
## Events and Logger
El engine emite evento s:
The engine emits event s:
```ts
Cache.on(CACHE_EVENT_ALL, (event) => {
@ -381,36 +384,38 @@ Cache.on(CACHE_EVENT_ALL, (event) => {
});
```
`arts/cache` enruta esos eventos al `EngineLogger` inyectado :
`arts/cache` routes those events to the injected `EngineLogger` :
- Hits/misses/sets/invalidation en `debug` .
- Errores de adapter, refresh y stale-if-error en `warn` .
- Hits/misses/sets/invalidation at `debug` .
- Adapter, refresh and stale-if-error errors at `warn` .
Las categorías y mensajes viven en `consts.ts` , no como strings disperso s.
Categories and messages live in `consts.ts` , not as scattered string s.
## Seguridad
## Security
Defaults y recomendacione s:
Defaults and recommendation s:
- No uses `public` para datos con `Authorization` , sesión o permisos.
- Usa `tenant` , `actor` o `permission` para datos privados.
- `privateSession` no persiste por defecto.
- Cambios de permisos deben invalidar scope `permission` o cambiar `permissionHash` .
- Logout o cambio de identidad debería llamar a `Cache.clear()` / invalidar scopes privados;
use `applyStandardOrca(App)` (or the individual presets) so this happens automatically
when the session art emits `SESSION_EVENT_*` .
- El cliente cachea para UX, no para seguridad. Las decisiones autoritativas viven en servidor.
- Do not use `public` for data with `Authorization` , session or permissions.
- Use `tenant` , `actor` or `permission` for private data.
- `privateSession` does not persist by default.
- Permission changes must invalidate the `permission` scope or change
`permissionHash` .
- Logout or identity change should call `Cache.clear()` / invalidate private
scopes; use `applyStandardOrca(App)` (or the individual presets) so this
happens automatically when the session art emits `SESSION_EVENT_*` .
- The client caches for UX, not for security. Authoritative decisions live on the
server.
## Página De Prueba
## Test Page
`cache` se ejercita dentro de la demo integrada del ecosistema, en
`/active/get-started/ecosystem` (la documentación de runtime vive en `/active` ).
Muestra `App.cache` , entry reactiva, invalidación por tag, scope actor/tenant ,
eventos y `explain()` .
`cache` is exercised inside the integrated ecosystem demo, at
`/active/get-started/ecosystem` (the runtime documentation lives at `/active` ).
It shows `App.cache` , a reactive entry, tag invalidation, actor/tenant scope ,
events and `explain()` .
## Roadmap
v1 incluido :
Included in v1 :
- `createEngineCache()`
- `createActiveCache()`
@ -423,16 +428,16 @@ v1 incluido:
- stale-if-error
- singleflight
- tag/prefix epochs
- mutate conservador
- conservative mutate
- explain
- logger integration
- `App.cache`
- orca presets (`applyCacheClearOnIdentityChange`, `applyCacheClearOnRevoke` )
Siguiente :
Next :
- `tieredCacheAdapter()`
- adapter Redis por interfaz, sin dependencia dura
- integración HTTP explícita
- Redis adapter by interface, without a hard dependency
- explicit HTTP integration
- optimistic journal
- browser Cache API para `Request/Response`
- browser Cache API for `Request/Response`