Targ Apps Docs

Persistence

Persistencia de Targ-Apps-Finance — Chroma-Fs (IndexedDB), snapshot síncrono en memoria y fallback a localStorage.

Targ-Apps-Finance corre enteramente en el cliente (Chroma + WASM, sin backend). Los datos sobreviven al recargar porque src/main.js hidrata el snapshot antes del primer render, y cada mutación se escribe de forma asíncrona a Chroma-Fs.

Backend activo: Chroma-Fs

chroma.json declara "plugins": ["chroma-fs"]. La configuración vive en chroma-fs.json y debe coincidir con CHROMA_FS_CONFIG en logic/persist.js:

{
  "storage": {
    "backend": "indexeddb",
    "indexedDb": {
      "dbName": "targ-apps-finance",
      "storeName": "files"
    }
  }
}

El snapshot canónico es el archivo virtual finance/snapshot.json dentro de esa base IndexedDB.

CampoValor
Pluginchroma-fs (createStorage / openChromaStorage)
Backend navegadorindexeddb (CHROMA_FS_CONFIG / chroma-fs.json)
Backend Chroma Desktopfilesystem + postMessage (CHROMA_DESKTOP_FS_CONFIG en runtime)
DB / store (solo navegador)targ-apps-finance / files
Pathfinance/snapshot.json (SNAPSHOT_PATH)

Web vs Chroma Desktop (Tauri)

chroma-fs.json declara backend: "indexeddb" para el navegador. En Chroma Desktop la app corre en un <iframe> sin globals __TAURI__; IndexedDB ahí es poco fiable (origen 127.0.0.1, partición cross-site). openChromaStorage() detecta el iframe y sobrescribe en runtime a filesystem + puente postMessage hacia el shell Tauri (archivos bajo el app-data del host).

EntornoBackend activogetPersistBackend()Dónde mirar los datos
chroma dev (navegador)IndexedDBchroma-fsDevTools → Application → IndexedDB → targ-apps-finance
Chroma Desktop (develop o zip)Filesystem bridgechroma-fs+filesystem<app-data>/apps/<id>/data/finance/snapshot.json en disco

resolveOpenStorageConfig() elige el backend; getPersistStorageMode() devuelve indexeddb o filesystem. Activá trazas con globalThis.__TAF_PERSIST_DEBUG__ = true.

Verificación manual en desktop

  1. cd Targ-Apps-Finance && npm run dev (con dev.desktop: true en chroma.json).
  2. Chroma Desktop abre la app en develop mode.
  3. Creá una cuenta y cerrá/reabrí el iframe o Chroma Desktop.
  4. La cuenta debe seguir presente.

Consola del iframe (DevTools → inspeccionar el frame de la app):

getPersistBackend()            // "chroma-fs+filesystem"
getPersistStorageMode()        // "filesystem"
__tafPersist.resolveOpenStorageConfig()
await __chromaT2t.runAll()

Si getPersistBackend() devuelve "memory" o "localStorage", Chroma-Fs no está activo — revisá la consola por [TAF] Chroma-Fs open failed.

Hidratación (antes del render)

// src/main.js
import { hydratePersist, openChromaStorage } from "../logic/persist.js";

let chromaStorage = null;
try {
    chromaStorage = await openChromaStorage();
} catch (err) {
    console.warn("[TAF] Chroma-Fs preload failed:", err?.message ?? err);
}

const { backend } = await hydratePersist(chromaStorage ? { storage: chromaStorage } : {});
const { App } = await import("./App.js");
render(App, document.getElementById("app"));

App se importa después de hidratar para que seedIfEmpty en financeStore vea el snapshot real y no siembre categorías sobre un cache vacío que luego se sobrescribe.

Orden de resolución en hydratePersist():

  1. Chroma-Fs — openChromaStorage() con resolveOpenStorageConfig() (IndexedDB en navegador; filesystem bridge en iframe de Chroma Desktop). main.js precarga el storage y lo pasa a hydratePersist({ storage }).
  2. Si no hay snapshot, migra claves legacy taf:* de localStorage.
  3. Si Chroma-Fs no está disponible (tests, SSR, IDB bloqueado) → localStorage taf:*.
  4. Si tampoco hay localStorage → memoria (la sesión no sobrevive al reload).

Errores al leer un snapshot corrupto no desactivan IndexedDB: se conserva el storage y se escribe un snapshot nuevo en el próximo setPersisted. flushPersist() propaga errores de escritura (ya no se tragan en silencio).

Un pagehide dispara flushPersist() para no perder writes pendientes.

logic/persist.js — cache síncrono + flush async

Los stores y repositorios siguen siendo síncronos. persist.js mantiene un snapshot en memoria; getPersisted / setPersisted leen y escriben ese cache y encolan un write a Chroma-Fs.

import {
  hydratePersist,
  flushPersist,
  getPersisted,
  setPersisted,
  getPersistBackend,
  getPersistStorageMode,
  resolveOpenStorageConfig,
  SNAPSHOT_PATH,
} from "../logic/persist.js";

await hydratePersist();                 // una vez, en main.js
setPersisted("accounts", [...]);        // sync + save async
await flushPersist();                   // tests / export

Forma del snapshot:

{
  schemaVersion: 1,
  transactions: [],
  categories: [],
  accounts: [],
  budgets: [],
  settings: { defaultCurrency, locale, weekStartsOn, theme } | null,
  onboardingComplete: false
}

onboardingComplete se pone en true al crear la primera cuenta (y se deriva de accounts.length > 0 si el flag no venía en un snapshot viejo). Una vez true, no se apaga aunque se borren todas las cuentas.

storage.js y repositorio

logic/persistence/storage.js es un adaptador fino sobre persist (getItem/setItem/removeItem). repository.js no cambia: CRUD por colección (transactions, categories, accounts, budgets).

Export/import (TAF-018) serializa el snapshot completo, incluido onboardingComplete y settings. La lógica pura vive en logic/export.js y logic/import.js; la UI usa logic/persistence/exportImport.js.

Tests

Ver Testing para la suite completa y el plugin chroma-t2t.

test/logic/persist.test.js (mock createMemoryStorage(), fake-indexeddb, node --test) cubre:

  • chroma-fs.json ≡ CHROMA_FS_CONFIG
  • round-trip de cuentas, settings y flag de onboarding
  • el flag sobrevive a accounts: []
  • fallback a memoria si IndexedDB no está disponible
  • snapshot corrupto no bloquea escrituras posteriores
  • round-trip real por openChromaStorage + IndexedDB simulado
  • resolveOpenStorageConfig() (navegador vs iframe Chroma Desktop)
  • modo chroma-fs+filesystem al hidratar en iframe
  • persistencia con globals Tauri en ventana top-level (sigue en IndexedDB)
  • flushPersist rechaza cuando el backend falla

test/logic/integration.test.js incluye un ciclo E2E onboarding → cuenta → reload con restricciones tipo Tauri (IndexedDB + origen custom simulado).

Relación con el store

FinanceProvider y SettingsProvider no hablan con IndexedDB. Leen el cache ya hidratado. Tras un import de backup, refreshFromStorage() vuelve a leer los repositorios.

On this page