You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/arts/cache/README.md

11 KiB

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.

El patrón sigue el resto de artefactos:

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(...) en services.
  • El core puro vive en $libs/cache como createCacheRuntime().

Qué Resuelve

cache responde a preguntas que una cache simple no contesta:

  • 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 scope 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().

Uso Mínimo

import { createEngineCache, memoryCacheAdapter, CACHE_POLICY_INTERACTIVE } from '$svrs/cache';

const Cache = createEngineCache({
	adapter: memoryCacheAdapter(),
	defaultPolicy: CACHE_POLICY_INTERACTIVE
});

const project = await Cache.query({
	key: ['project', projectId],
	scope: 'public',
	tags: [{ type: 'project', id: projectId }],
	fetcher: () => ProjectRepo.findById(projectId)
});

La segunda lectura con la misma key/scope/policy servirá el valor cacheado si sigue válido.

App.cache

createActiveApp() expone App.cache cuando se declara el servicio:

const App = createActiveApp({
	services: {
		cache: defineActiveCache()
	}
});

const value = await App.cache.query({
	key: ['settings'],
	scope: 'public',
	fetcher: loadSettings
});

For real apps, configure policies, adapter or scope resolver:

const App = createActiveApp({
	services: {
		cache: defineActiveCache({
			scopeResolver: () => ({
				tenantId: App.session?.current?.data?.tenantId,
				actorId: App.session?.current?.user?.id,
				permissionHash: App.perm?.currentSnapshot.version,
				locale: App.langs.getLocale()
			})
		})
	}
});

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:

const App = createActiveApp({
	cache: {
		defaultMemoryAdapter: {
			suppressProductionWarning: true
		}
	}
});

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.

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.

Reacting to identity / session changes

ActiveCache is a passive runtime: it never subscribes to the bus on its own. Cross-module reactions live in orca presets declared at the App level. The standard preset clears the cache on session identity change and on session revoke:

import { createActiveApp, applyStandardOrca } from '$active-app';

const App = createActiveApp({
	services: {
		cache: defineActiveCache({}),
		session: defineActiveSession({ ... })
	}
});

applyStandardOrca(App);
// → on SESSION_EVENT_IDENTITY_CHANGED: App.cache.clear()
// → on SESSION_EVENT_REVOKED:          App.cache.clear()

Cherry-pick if the standard set is too aggressive:

import {
	applyCacheClearOnIdentityChange,
	applyCacheClearOnRevoke
} from '$active-app';

applyCacheClearOnIdentityChange(App);
// SESSION_EVENT_REVOKED is not handled — caches survive sign-out.

Cache itself listens to nothing. It exposes clear() / invalidate() and trusts the orchestration layer to call them. This keeps cache invalidation policy out of the cache art and on the App composition.

Keys

Las keys son arrays deterministas:

['posts', { page: 1, filters: { status: 'published' } }][('tenant', tenantId, 'projects')][
	'profile'
];

El normalizador:

  • Ordena keys de objetos.
  • Omite campos undefined en objetos.
  • Soporta Date, Map, Set, URLSearchParams y BigInt.
  • Rechaza funciones, símbolos, números no finitos y referencias circulares.

Scopes

Los scopes evitan fugas de datos:

scope: 'public'      // compartible
scope: 'tenant'      // requiere tenantId
scope: 'actor'       // requiere actorId; tenantId opcional
scope: 'permission'  // requiere actorId + permissionHash
scope: { mode: 'custom', values: { tenantId, reportId } }

Regla de calidad: datos privados nunca deberían cachearse como public.

Policies

Las políticas expresan intención:

import {
	CACHE_POLICY_INTERACTIVE,
	CACHE_POLICY_CATALOG,
	CACHE_POLICY_PRIVATE_SESSION
} from '$svrs/cache';

Incluidas:

  • interactive: UI normal, stale-while-revalidate.
  • catalog: datos estables, ventanas largas.
  • privateSession: datos privados, persistencia desactivada por defecto.
  • realtime: casi sin cache.
  • immutable: datos versionados o inmutables.

Puedes definir las tuyas:

import { createEngineCache } from '$svrs/cache';

const Cache = createEngineCache({
	policies: {
		dashboard: {
			freshFor: '15s',
			staleFor: '2m',
			staleIfErrorFor: '10m',
			gcAfter: '30m',
			mode: CACHE_READ_MODE_STALE_WHILE_REVALIDATE,
			persist: true
		}
	}
});

Modos

CACHE_READ_MODE_CACHE_FIRST;
CACHE_READ_MODE_STALE_WHILE_REVALIDATE;
CACHE_READ_MODE_MUST_REVALIDATE;
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 origen.
  • bypass-cache: ignora lectura, llama al origen y escribe resultado.
  • no-store: ignora lectura, llama al origen y no escribe.

Tags Y Epochs

Las entradas pueden declarar tags:

tags: [
	{ type: 'project', id: projectId },
	{ type: 'project', id: 'LIST' }
];

Invalidar un tag no escanea ni borra todas las keys. Incrementa un epoch:

await Cache.invalidate({
	tag: { type: 'project', id: projectId },
	scope: 'tenant'
});

Cada entry guarda los epochs con los que fue escrita. En la siguiente lectura, si el epoch actual no coincide, la entry queda invalidada.

También hay invalidación por key exacta y key prefix:

await Cache.invalidate({ key: ['project', projectId], scope: 'tenant' });
await Cache.invalidate({ keyPrefix: ['projects'], scope: 'tenant' });

ActiveCache

createActiveCache() expone la misma API que el engine y añade estado reactivo:

const Cache = createActiveCache();

const entry = Cache.entry({
	key: ['dashboard'],
	scope: 'public',
	fetcher: loadDashboard
});

await entry.load();

entry.data;
entry.status;
entry.error;
entry.loading;

ActiveCacheEntry no hace magia en efectos. Cargas explícitamente con load() o refresh(), y el estado cambia sin bucles reactivos.

entry.set(value) reutiliza por defecto la policy, tags, schemaVersion, persist y scope definidos en la entry, y permite sobrescribirlos por llamada.

Mutate

La mutación v1 es conservadora:

await Cache.mutate({
	commit: () => App.http.patch(`/projects/${projectId}`, { body: patch }),
	invalidate: [{ tag: { type: 'project', id: 'LIST' }, scope: 'tenant' }],
	update: [
		{
			key: ['project', projectId],
			scope: 'tenant',
			reducer: (project) => ({ ...project, ...patch })
		}
	]
});

Semántica:

  1. Ejecuta commit().
  2. Si falla, no toca la cache.
  3. Si funciona, invalida tags/prefix/keys.
  4. Aplica updates exactos.

Optimistic journal queda fuera de v1.

Explain

explain() existe desde v1 porque cache sin introspección se vuelve opaca:

const info = await Cache.explain(['project', projectId], {
	scope: 'tenant',
	schemaVersion: 'Project:v1'
});

Devuelve adapter, key efectiva, estado, decisión, razón, schema y epochs.

Adapters

Memory

const adapter = memoryCacheAdapter({
	maxEntries: 5000,
	maxSizeBytes: 64 * 1024 * 1024
});

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é disponible.

Storage

const adapter = storageCacheAdapter({
	storage: App.storage.adapter,
	namespace: 'cache'
});

Ú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.

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.

Eventos Y Logger

El engine emite eventos:

Cache.on(CACHE_EVENT_ALL, (event) => {
	console.log(event.type, event.reason);
});

arts/cache enruta esos eventos al EngineLogger inyectado:

  • Hits/misses/sets/invalidation en debug.
  • Errores de adapter, refresh y stale-if-error en warn.

Las categorías y mensajes viven en consts.ts, no como strings dispersos.

Seguridad

Defaults y recomendaciones:

  • 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.

Página De Prueba

La demo interactiva está en:

La documentacion actual de runtime vive en /active.

Muestra App.cache, entry reactiva, invalidación por tag, scope actor/tenant, eventos y explain().

Roadmap

v1 incluido:

  • createEngineCache()
  • createActiveCache()
  • memoryCacheAdapter()
  • storageCacheAdapter()
  • key normalization
  • scopes
  • policies
  • stale-while-revalidate
  • stale-if-error
  • singleflight
  • tag/prefix epochs
  • mutate conservador
  • explain
  • logger integration
  • App.cache
  • orca presets (applyCacheClearOnIdentityChange, applyCacheClearOnRevoke)

Siguiente:

  • tieredCacheAdapter()
  • adapter Redis por interfaz, sin dependencia dura
  • integración HTTP explícita
  • optimistic journal
  • browser Cache API para Request/Response

Powered by TurnKey Linux.