Ir al contenido

Persistencia

La persistencia permite que Presage recuerde los atributos del usuario y las senales de comportamiento entre recargas de pagina y sesiones del navegador.

El driver recomendado para aplicaciones SaaS en el navegador:

import { createAdaptiveClient, createLocalStorageDriver } from '@presage-kit/core'
const client = createAdaptiveClient({
persistence: {
driver: createLocalStorageDriver('my-app'),
},
// ...rules
})

Los datos se almacenan en localStorage bajo claves con namespace:

  • my-app:traits — Atributos del usuario (rol, plan, etc.)
  • my-app:signals — Senales de comportamiento

El prefijo evita colisiones con otras bibliotecas o multiples instancias de Presage en el mismo dominio.

Un driver en memoria para entornos SSR y testing:

import { createAdaptiveClient, createMemoryDriver } from '@presage-kit/core'
const client = createAdaptiveClient({
persistence: {
driver: createMemoryDriver(),
},
// ...rules
})

Los datos viven unicamente en memoria y se pierden cuando el proceso termina. Esto es util para:

  • Renderizado del lado del servidorlocalStorage no esta disponible en el servidor
  • Pruebas unitarias — Sin efectos secundarios entre ejecuciones de tests
  • Storybook — Historias aisladas que no contaminan el almacenamiento del navegador
ClaveDatosCuando se actualiza
traitsObjeto UserTraitsEn identify(), updateTraits() y cualquier cambio de estado
signalsObjeto BehavioralSignalsDespues del recalculo de senales (100ms despues de track())

Implemente la interfaz StorageDriver para almacenar datos en cualquier lugar:

interface StorageDriver {
get<T>(key: string): T | null
set<T>(key: string, value: T): void
remove(key: string): void
clear(): void
}
import type { StorageDriver } from '@presage-kit/core'
function createSessionStorageDriver(prefix: string): StorageDriver {
function prefixed(key: string): string {
return `${prefix}:${key}`
}
return {
get<T>(key: string): T | null {
const raw = sessionStorage.getItem(prefixed(key))
if (raw === null) return null
try {
return JSON.parse(raw) as T
} catch {
return null
}
},
set<T>(key: string, value: T): void {
sessionStorage.setItem(prefixed(key), JSON.stringify(value))
},
remove(key: string): void {
sessionStorage.removeItem(prefixed(key))
},
clear(): void {
const toRemove: string[] = []
for (let i = 0; i < sessionStorage.length; i++) {
const key = sessionStorage.key(i)
if (key?.startsWith(`${prefix}:`)) {
toRemove.push(key)
}
}
for (const key of toRemove) {
sessionStorage.removeItem(key)
}
},
}
}
import type { StorageDriver } from '@presage-kit/core'
function createCookieDriver(prefix: string): StorageDriver {
return {
get<T>(key: string): T | null {
const name = `${prefix}_${key}`
const match = document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`))
if (!match) return null
try {
return JSON.parse(decodeURIComponent(match[1])) as T
} catch {
return null
}
},
set<T>(key: string, value: T): void {
const name = `${prefix}_${key}`
const encoded = encodeURIComponent(JSON.stringify(value))
document.cookie = `${name}=${encoded}; path=/; max-age=31536000; SameSite=Lax`
},
remove(key: string): void {
const name = `${prefix}_${key}`
document.cookie = `${name}=; path=/; max-age=0`
},
clear(): void {
// Clear all cookies with the prefix
const cookies = document.cookie.split('; ')
for (const cookie of cookies) {
const name = cookie.split('=')[0]
if (name.startsWith(`${prefix}_`)) {
document.cookie = `${name}=; path=/; max-age=0`
}
}
},
}
}

Un problema comun con el estado persistido es el destello de contenido no personalizado (FOUC): la interfaz se renderiza con valores por defecto y luego parpadea al estado personalizado una vez que los datos se cargan.

Presage evita esto porque el metodo StorageDriver.get() es sincrono. Cuando se llama a createAdaptiveClient():

  1. El driver lee los atributos y senales en cache de forma sincrona
  2. La madurez se calcula a partir de las senales en cache
  3. El UserContext inicial esta listo antes del primer renderizado
  4. Los componentes reciben la variante correcta desde la primera pintura
// This is synchronous — no loading state needed
const client = createAdaptiveClient({
persistence: {
driver: createLocalStorageDriver('my-app'),
},
rules: [...],
})
// client.getContext() already has cached data
console.log(client.getContext().traits) // { userId: 'user-123', role: 'admin', ... }

Este diseno es intencional. Tanto localStorage como sessionStorage son APIs sincronas, lo que las hace ideales para anti-FOUC. Si construye un driver asincrono personalizado (por ejemplo, IndexedDB), debera manejar el estado de carga usted mismo.

Si no configura un driver de persistencia, Presage sigue funcionando, pero el contexto del usuario se reinicia en cada recarga de pagina. Esto puede ser util para:

  • Prototipos y demos
  • Aplicaciones renderizadas en el servidor donde el contexto proviene del servidor
  • Escenarios donde siempre llama a identify() desde su sistema de autenticacion al montar el componente
// No persistence — context resets on reload
const client = createAdaptiveClient({
rules: [...],
})