Back to flin
flin

La persistance dans le navigateur

Comment nous avons fait fonctionner FlinDB dans le navigateur -- du SSR avec rechargement à chaud, à la liaison de données bidirectionnelle, la soumission de formulaires par actions serveur, et le bug de persistance qui a failli tout casser.

Juste A. Gnimavo (Thales) & Claude | March 26, 2026 5 min flin
EN/ FR/ ES
flinflindbbrowserpersistencefrontend

Construire un moteur de base de données est une chose. Le faire fonctionner quand les utilisateurs interagissent avec à travers un navigateur web en est une tout autre. Le navigateur est un environnement hostile pour les opérations de base de données. Il n'y a pas de système de fichiers (directement). Il n'y a pas de stockage persistant qui survit à un rafraîchissement de page (sans effort). Il y a une frontière fondamentale entre le JavaScript côté client et la logique côté serveur qui doit être franchie avec soin.

Les sessions 201 et 203 sont celles où FlinDB a rencontré le navigateur.

L'architecture : SSR avec actions serveur

Le modèle de rendu web de FLIN est le rendu côté serveur (SSR) avec rechargement de module à chaud (HMR). Ce n'est pas un framework d'application mono-page. Ce n'est pas du WASM exécuté dans le navigateur. Le runtime FLIN s'exécute sur le serveur, rend le HTML et l'envoie au navigateur.

Le navigateur reçoit du HTML entièrement rendu plus un petit runtime JavaScript ($flin proxy) qui gère les mises à jour d'état côté client.

Session 201 : rendre le navigateur interactif

Fix 1 : liaison de données bidirectionnelle

L'attribut bind={} n'était pas traité par le rendu. La correction génère les attributs HTML appropriés :

flin<input bind={newTodo} />

Rend maintenant :

html<input value="" oninput="newTodo = this.value; _flinUpdate()" data-flin-bind="newTodo" />

Fix 2 : soumission de formulaire via actions serveur

Au lieu d'appeler une fonction JS, le formulaire envoie une requête POST à un endpoint /_action :

javascriptfunction _flinSubmit(actionName) {
    const stateData = {};
    for (const key in _state) {
        stateData[key] = $flin[key];
    }
    fetch('/_action', { method: 'POST', body: formData })
    .then(response => { if (response.ok) window.location.reload(); });
}

Session 203 : le bug de persistance

La session 201 a corrigé l'interactivité. Mais quand la page se rafraîchissait, les todos disparaissaient. Le fichier WAL contenait 0 octet.

Cause racine 1 : le bytecode écrase l'état injecté

Le serveur dev injecte les variables d'état avant l'exécution du bytecode. Mais le code source FLIN déclare newTodo = "" au niveau supérieur, écrasant la valeur injectée.

La correction : les globales protégées.

rustpub fn set_global_protected(&mut self, name: String, value: Value) {
    self.globals.insert(name.clone(), value);
    self.protected_globals.insert(name);
}

Les globales protégées ne peuvent pas être écrasées par le bytecode.

Cause racine 2 : Value::Text non géré par Trim

L'injection d'état crée Value::Text("Buy milk"). Mais OpCode::Trim ne gérait que Value::Object(id). Pour Value::Text, il retournait une chaîne vide.

Cause racine 3 : échecs silencieux du validateur

Le validateur échouait silencieusement -- pas d'erreur remontée, mais l'opération de sauvegarde était abandonnée discrètement.

Vérification

Avant la correction : ``bash $ cat embedded/todo-app/.flindb/wal.log # (empty - 0 bytes) ``

Après la correction : ``bash $ cat embedded/todo-app/.flindb/wal.log {"type":"Save","timestamp":1768567212273,"entity_type":"Todo","entity_id":1,...} ``

177 octets. Des données qui persistent.

La leçon

Les sessions 201 et 203 nous ont appris quelque chose de fondamental sur l'ingénierie de base de données : une base de données qui passe tous ses tests peut quand même échouer en production.

FlinDB avait 2 248 tests passant. Mais aucun de ces tests ne simulait le cycle complet : navigateur -> soumission de formulaire -> action serveur -> injection d'état -> exécution VM -> sauvegarde FlinDB -> écriture WAL -> destruction VM -> nouvelle VM -> rejeu WAL -> rendu de page -> navigateur.

Les bugs n'étaient pas dans la couche base de données de FlinDB. Ils étaient dans les points d'intégration.

Ce qui l'a fait fonctionner

Premièrement, le modèle de persistance basé sur le WAL. Chaque mutation est écrite dans un fichier journal. Le WAL est le pont entre les durées de vie des VM.

Deuxièmement, le rendu côté serveur. Les opérations de base de données s'exécutent sur le serveur où FlinDB a un accès direct au système de fichiers.

Troisièmement, le proxy $flin. Le runtime JavaScript côté client maintient un objet proxy qui reflète l'état côté serveur.

Le résultat est une base de données qui semble instantanée du point de vue de l'utilisateur -- taper un todo, appuyer sur Entrée, le voir apparaître -- tout en s'exécutant entièrement sur le serveur avec des garanties ACID complètes, la persistance WAL et la récupération après crash. Le navigateur est une fine couche de présentation. La base de données est réelle.


Ceci est la partie 15 de la série « How We Built FlinDB », documentant comment nous avons construit un moteur de base de données embarqué complet pour le langage de programmation FLIN.

Navigation de la série : - [068] FlinDB Hardening for Production - [069] FlinDB vs SQLite: Why We Built Our Own - [070] Persistence in the Browser (vous êtes ici)

Ceci conclut l'arc FlinDB. De la configuration zéro embarquée à la persistance navigateur, de la conception centrée sur les entités à l'event sourcing EAVT, des index hash à la recherche sémantique -- FlinDB est un moteur de base de données complet construit pour un langage qui croit que les données devraient juste fonctionner.

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles

Thales & Claude thales

Treize agents, quarante-trois minutes : la première session Workflow de Claude Fable 5, et ce qu'un script d'orchestration déterministe change aux builds multi-agents

Un prompt, treize agents, quarante-trois minutes : la première session de production avec Claude Fable 5 et l'outil Workflow de Claude Code a livré un site web de production complet de sept pages plus un endpoint backend de capture de leads, en un seul commit. Le carnet de bord : le script d'orchestration déterministe, le patron d'injection de contrat entre les phases, l'économie par agent du fan-out parallèle, et le suspense de la limite de session que le journal de reprise a transformé en non-événement.

23 min Jun 12, 2026
claude-fable-5claude-codeworkflow-toolmulti-agent +10
Thales & Claude casp

La porte a détecté sa propre dérive : une journée dans CASP avec Claude Fable 5

Nous avons confié au modèle Claude le plus autonome à ce jour les clés de CASP — le CLI open source qui garde les agents de code IA honnêtes face à git — avec l'autorité de rejeter notre propre roadmap. Il a rejeté cinq choses, trouvé deux vrais bugs dans le validateur en le dogfoodant, les a corrigés sous une porte à deux auditeurs, et a laissé casp check entièrement vert sur son propre dépôt pour la première fois. CASP 0.3.0 en est le résultat.

16 min Jun 10, 2026
caspzerosuiteworkflowai-cto +9
Thales & Claude zerosuite

La transplantation du CASP : comment la discipline des six fichiers est passée de Conductor à un ERP transport anti-fraude, ce que la compétence /next ajoute quand l'opérateur tape juste « next », et pourquoi le coût d'une dérive du CASP grimpe quand le projet, c'est l'argent des autres

La discipline du CASP qui a piloté trente-cinq sessions de Conductor est agnostique au produit. Le carnet de bord de sa transplantation sur KASSIA, un ERP transport anti-fraude pour un exploitant de flotte en Côte d'Ivoire : ce qui a migré, ce qui n'a pas migré (le validateur sur mesure — et ce que son absence coûte), ce que la compétence /next ajoute quand l'opérateur tape un seul mot, et là où le CASP s'arrête — le bug de déploiement qu'il ne pouvait pas voir parce qu'il enregistre l'intention, pas la réalité de l'infrastructure.

23 min Jun 8, 2026
kassiaerp-kassia-transport-logistiquezerosuiteCASP +15