Technique
Comment les données sont stockées
L'architecture habituelle serait un serveur avec une base de données et un compte par utilisateur. Je voulais que les données restent chez l'entreprise, dans un seul fichier sur son ordinateur. La base devait donc s'exécuter au même endroit que les données : dans le navigateur.
1. Un fichier plutôt qu'un serveur
L'application se compose uniquement de fichiers HTML, CSS et JavaScript statiques, servis par GitHub Pages. Aucun serveur ne reçoit les enregistrements. SQLite est compilé en module WebAssembly (@sqlite.org/sqlite-wasm), inclus localement dans l'application, avec empreinte et version attribuées lors de la compilation comme pour les autres fichiers. Rien n'est récupéré depuis un CDN tiers pendant l'exécution ; la base peut donc fonctionner hors ligne.
Un détail essentiel : le fichier sur le disque est une véritable base SQLite, pas un format d'exportation maison. Il s'ouvre dans tout outil SQLite, se sauvegarde, se copie et se transfère sur un autre ordinateur.
2. Trois couches
La base ne s'exécute jamais sur le fil principal du navigateur, mais dans son propre Web Worker. L'interface reste ainsi réactive pendant un import volumineux. Les deux communiquent uniquement par messages.
Interface · fil principal
Astro, TypeScript, Bootstrap
- Formulaires, calendrier, listes et exports PDF/CSV
- État et bus d'événements
- Accès à la base uniquement par messages
- Conserve l'accès au fichier et y écrit les données
⇄postMessage
ID, délai maximal,
transfert de grands tampons
Base de données · Web Worker
SQLite 3.54 via WebAssembly
- Schéma avec migrations :
user_version de 1 à 11
- WAL, clés étrangères et cache de pages de 20 Mo
- Requêtes, listes paginées et import du BVL
- Export de la base entière sous forme d'octets
⇄octets
export · import
Stockage · chez l'utilisateur
Un fichier .sqlite
- File System Access API ; référence du fichier conservée dans IndexedDB pour la réouverture au démarrage
- Mode OPFS si le navigateur le permet
- Solutions de repli : fichier JSON, localStorage et mémoire
3. L'enregistrement, étape par étape
- Une saisie, par exemple un nouveau traitement, est envoyée au worker par message. Chaque message porte un identifiant et un délai maximal ; la réponse revient de façon asynchrone.
- Le worker écrit dans sa base SQLite, située en mémoire ou, si le navigateur l'autorise, dans l'Origin Private File System (OPFS).
- Il exporte ensuite toute la base sous forme d'octets (
sqlite3_js_db_export) et transmet le tampon au fil principal sans copie, grâce au mécanisme Transferable.
- Le fil principal écrit ces octets dans le fichier .sqlite de l'utilisateur via son accès au fichier :
createWritable, write, puis close. Le fichier sur disque contient ainsi une base complète et cohérente.
- Les changements rapprochés sont regroupés. Si une écriture est déjà en cours, l'application mémorise simplement qu'une autre sera nécessaire ensuite. Une série de modifications provoque donc une écriture, pas une écriture par frappe.
4. Reconnaître le contenu du fichier
À l'ouverture, l'application lit les octets et vérifie la signature initiale : SQLite format 3. Le contenu détermine le format, pas l'extension. Un fichier nommé .json peut contenir une base binaire. Les anciennes versions utilisaient JSON, et ce mélange a un jour fait paraître « vide » une base intacte. Depuis, la signature fait foi.
S'il s'agit de SQLite, le worker en fait la base active via sqlite3_deserialize. En mode OPFS, OpfsDb.importDb l'importe dans le fichier persistant. S'il s'agit de JSON, les données sont importées comme un instantané. Les deux chemins aboutissent à la même base et exécutent les migrations.
5. La préparation des données du BVL
La base officielle des autorisations du BVL, l'Office fédéral allemand de la protection des consommateurs et de la sécurité alimentaire, est accessible par une API publique. Elle n'est toutefois pas adaptée à une utilisation directe dans le navigateur : nombreux points d'accès, nombreuses requêtes, aucun usage hors ligne. Les données sont donc préparées en amont par une chaîne dédiée dans le dépôt public pflanzenschutz-db :
- Tous les deux jours, GitHub Actions récupère les données de l'ensemble des points d'accès du BVL, les valide et les enrichit, notamment avec les indicateurs d'usage en agriculture biologique, les noms des substances actives et les mentions de danger.
- Le résultat est un fichier SQLite, compressé en Brotli et en ZIP, publié sur GitHub Pages avec un manifeste indiquant la taille et l'empreinte SHA-256 de chaque fichier.
- L'application charge le manifeste, choisit le plus petit format que le navigateur peut décompresser, affiche la progression et transmet les octets au worker.
- Le worker ouvre le téléchargement comme une base temporaire en mémoire, puis copie ses tables dans la base de l'utilisateur en une seule transaction, dans l'ordre des dépendances pour préserver les clés étrangères.
- Tout le catalogue se trouve alors dans le fichier .sqlite de l'utilisateur et reste consultable hors ligne. Un journal interne à la base indique la version importée.
| État au 25 octobre 2026 | Valeur |
| Produits du catalogue | 2,165 |
| Usages autorisés, par produit, culture et organisme nuisible | 30,836 |
| Substances actives | 482 |
| Téléchargement, Brotli / ZIP | 6,8 Mo / 12,5 Mo |
| Après décompression, SQLite | 113 Mo |
6. Hors ligne et comme application
À l'installation, un service worker met en cache tous les fichiers de compilation identifiés par leur empreinte, module WebAssembly compris. L'application démarre ensuite sans réseau. Chaque compilation inscrit une nouvelle version du cache dans le service worker ; sinon les utilisateurs de l'application installée conserveraient l'ancien code, car le navigateur ne réinstalle pas un service worker identique octet pour octet.
Installée sur ordinateur, Digitale PSM s'enregistre via la File Handling API pour les extensions .sqlite, .db et .json. Un double-clic dans l'explorateur ouvre directement la base dans l'application. La référence du dernier fichier est conservée dans IndexedDB ; au démarrage suivant, l'application redemande seulement l'autorisation et le rouvre.
7. Performances et robustesse
- Pas de copie des grands tampons. Les exports de la base et l'import du BVL passent d'un fil d'exécution à l'autre par transfert, sans clonage structuré. À 100 Mo, cela peut faire la différence entre une interface fluide et une interface figée.
- Des délais adaptés à chaque opération. Les appels ordinaires disposent de 30 secondes ; l'import du BVL, de huit minutes au maximum, car les navigateurs ralentissent les minuteries des onglets en arrière-plan. Si le worker plante, tous les appels en attente échouent immédiatement au lieu de rester suspendus.
- Un chargement par pages. L'historique, les produits et les points GPS sont chargés par pages à l'aide d'un curseur. L'interface ne dessine que les lignes visibles.
- Une cause de perte de données éliminée. En mode SQLite, le worker gère directement l'historique, qui n'est plus joint à l'instantané de l'état. Sans cela, l'enregistrement d'un réglage aurait pu réécrire une liste périmée et supprimer toutes les entrées ajoutées depuis l'ouverture du fichier.
8. Les limites actuelles
- Priorité à l'ordinateur. L'accès aux fichiers par la File System Access API n'existe que dans les navigateurs Chromium sur ordinateur, comme Chrome et Edge. Il manque sur téléphone, aussi bien dans Safari sur iPhone que dans Chrome sur Android. Cette version ne peut donc pas y ouvrir de fichier, et une nouvelle base disparaît au rechargement de la page. Une version mobile nécessite un autre stockage, comme OPFS ou IndexedDB, et n'a pas encore été développée.
- OPFS exige une isolation. Le stockage persistant dans le worker via OPFS nécessite l'isolation entre origines, avec les en-têtes COOP/COEP que GitHub Pages ne permet pas de définir. En production, la base tourne donc dans la mémoire du worker ; la copie de référence reste le fichier sur disque.
- Le fichier entier est réécrit. Chaque sauvegarde réécrit toute la base. Cela reste léger pour les seuls enregistrements de l'entreprise, mais le fichier dépasse 100 Mo après import du catalogue BVL et le temps d'écriture devient sensible. Ce choix privilégie la simplicité ; l'étape suivante serait de n'écrire que les pages modifiées.
9. Fiche technique
| Application | digitale-psm.de — gratuite, sans publicité ni compte |
| Langue | Allemand ; l'application suit la réglementation allemande sur les autorisations et la base du BVL |
| Début du projet | Avril 2025 ; version actuelle 2.0 |
| Licence et code | MIT, code source sur GitHub |
| Technologies | Astro 5, TypeScript, Bootstrap 5 inclus localement, SQLite 3.54 via WebAssembly, Web Worker, service worker et manifeste d'application web |
| Stockage | Fichier SQLite détenu par l'utilisateur via la File System Access API ; OPFS si disponible ; solutions de repli avec fichier JSON et localStorage |
| Données d'autorisation | API du BVL, préparation dans pflanzenschutz-db par GitHub Actions tous les deux jours, distribution en Brotli/ZIP avec manifeste et SHA-256 |
| Hébergement | Fichiers statiques sur GitHub Pages ; compilation et déploiement par GitHub Actions |
| Projet associé | st.digitale-psm.de : tableau de statistiques de la base du BVL, avec requêtes SQL directement dans le navigateur |