- Les applications frontend complexes partent d’un cache de réponses d’API, puis finissent par assumer des index manuels et l’invalidation du cache, au point de ressembler à de petites bases de données réimplémentées projet par projet
- Dans les frameworks déclaratifs comme React, pour éviter d’appeler l’API à chaque rendu, on place un cache dans l’état local ou dans une couche Redux, et cette couche finit progressivement par jouer le rôle de stockage centralisé
- Les stockages indexés par ID et les structures de consultation par date accélèrent les recherches, mais le maintien de la cohérence entre plusieurs structures comme
CACHEetENTRIES_BY_DATEalourdit les tests et les revues de code - Les modifications optimistes mettent à jour l’UI immédiatement avant la réponse du serveur, ce qui améliore la vitesse perçue, mais elles entraînent des coûts de cohérence : duplication de la logique client/serveur, suivi des changements en cours, rollback en cas d’erreur et réconciliation après redémarrage de l’app
- SQLSync cherche à fournir, dans la stack frontend, un cache durable, des index, des contraintes, des modifications optimistes et des requêtes réactives via une base locale SQLite et une synchronisation proche de Git Rebase
Comment un cache frontend grossit jusqu’à devenir une base de données
- La gestion des données côté frontend peut commencer par un simple cache qui stocke les réponses d’API dans des variables locales
- Les frameworks déclaratifs comme React rerender l’arbre plusieurs fois pendant les interactions utilisateur
- Pour éviter d’envoyer une requête API à chaque rendu, on peut stocker le résultat d’une requête ou une erreur dans l’état du composant avec
useStateetuseEffect - L’exemple est simplifié par souci de clarté ; en pratique, utiliser une bibliothèque API éprouvée est aussi une option
- Le cache peut être déplacé vers un niveau plus haut de l’arbre UI, ou en dehors de l’UI
- Redux est une bibliothèque de gestion d’état pour React qui unifie l’état et coordonne des modifications atomiques au fil du temps
- L’écosystème Redux s’est étendu avec des outils et des patterns pour gérer la mise en cache des données d’API
- Ce type d’usage vise à centraliser la logique de cache, coordonner les mises à jour et partager les résultats mis en cache entre composants
- À mesure que la couche de cache grossit, elle se rapproche d’un système de stockage centralisé qui manipule efficacement les données en fonction du moteur de rendu et des actions utilisateur
Index manuels et charge de cohérence
- Même côté frontend, on peut stocker les données reçues du serveur dans un objet indexé par ID afin de les consulter et modifier rapidement
- Dans les applications utilisant des API REST, il est fréquent de lire les données par lots puis d’enrichir davantage les objets nécessaires
- Stocker les objets par ID facilite la fusion des résultats d’API dans le cache
- Cette structure est optimisée pour créer, lire, modifier et supprimer par ID
- Les filtrages qui nécessitent de parcourir plusieurs éléments poussent à créer des index séparés pour éviter de scanner toutes les entrées
- Créer une structure
ENTRIES_BY_DATEà partir des parties année/mois/jour decreatedAtpermet de retrouver rapidement les entrées d’une date donnée - En contrepartie, il faut maintenir en permanence la cohérence entre
CACHEetENTRIES_BY_DATE - Les recherches sur une plage de dates nécessitent plusieurs accès ; un tableau trié par date, avec une logique de requête et de mise à jour plus complexe, pourrait être une meilleure structure
- Créer une structure
- Plus les index se multiplient, plus chacun exige sa propre logique de création, de mise à jour et de requête
- La vérification de la justesse devient une charge pour les tests et les revues de code
- Oublier une suppression ou une mise à jour dans un index peut créer des bugs difficiles à trouver
- On peut finir par consacrer plus de temps à gérer cette complexité d’infrastructure qu’à développer de nouvelles fonctionnalités applicatives
- Les index de vraies bases de données sont bien plus complexes que le simple fait de stocker les données sous une autre forme
- Ils incluent des aspects comme la collecte de statistiques, la gestion des versions des données, le contrôle des transactions, les verrous et les interactions avec l’optimiseur de requêtes
Les problèmes de cohérence ajoutés par les modifications optimistes
- Une modification optimiste simule localement l’effet d’une opération avant la réponse du serveur
- L’UI peut réagir immédiatement, comme s’il n’y avait pas de latence réseau
- Si le serveur prend une décision différente de celle attendue ou si une erreur survient, l’UI peut devoir annuler le changement et demander à l’utilisateur de corriger le problème
- C’est un outil puissant lorsque le client peut bien prédire le résultat serveur, que les erreurs peuvent être gérées côté client et que les logiques restent étroitement synchronisées
- Les mises à jour optimistes suivent généralement quatre étapes
- L’UI déclenche une opération d’écriture
- En supposant que le serveur acceptera, la modification est appliquée au cache local et l’UI est rerendue immédiatement
- L’opération de modification est envoyée au serveur de façon asynchrone
- La réponse du serveur est fusionnée dans le cache local, écrase l’ancienne modification optimiste et, si nécessaire, l’UI est rerendue
- Maintenir la cohérence avec le serveur introduit plusieurs contraintes
- Il faut dupliquer de la logique côté client et côté serveur pour prédire le résultat
- Il faut suivre chaque modification en cours pour gérer les erreurs asynchrones ou les divergences serveur
- Pour une meilleure expérience utilisateur, il peut être nécessaire de rendre durable la partie optimiste du cache afin de réconcilier les changements après un redémarrage de l’app
- Ce processus augmente le temps de développement et le coût de vérification de la justesse, et la gestion des données peut passer avant la création de valeur utilisateur ou de fonctionnalités différenciantes
La complexité de l’invalidation récursive du cache
- Dans les applications riches en données, la même information apparaît à plusieurs endroits du cache
- L’exemple de cache stocke ensemble
projects,tasksetusers - Après l’achèvement d’une tâche, plusieurs parties peuvent être affectées : progression du projet, tâches assignées à l’utilisateur, nouvelles informations de tâche
- L’exemple de cache stocke ensemble
- Pour réaligner le cache avec le serveur après l’achèvement d’une tâche, plusieurs allers-retours peuvent être nécessaires
- Signaler au serveur que la tâche est terminée
- Rafraîchir le projet puisque sa progression a changé
- Vérifier si de nouvelles tâches ont été assignées
- S’il y a une nouvelle assignation, récupérer cette tâche
- Une API plus complexe peut réduire le nombre d’allers-retours, mais il reste un couplage entre l’API ou la logique client et le modèle de données sous-jacent
- GraphQL est une approche de ce problème, mais pas une solution complète
- Une structure où l’UI doit savoir quelles parties du cache sont concernées par chaque modification devient fragile à mesure qu’elle grandit
- Les relations entre données et les agrégats peuvent affecter plusieurs parties du cache local
- Quand l’équipe d’ingénierie grandit, le problème peut franchir les frontières entre équipes et ressembler à une grande variable globale mutable dans un grand projet logiciel
- Combiné aux modifications optimistes, cela pousse le client à répliquer davantage de logique backend pour prédire les changements serveur
- Dans l’exemple, il peut essayer de retirer task 1 de user 1 et de recalculer la progression à partir du nombre total de tâches
- Plus on cherche à prédire localement les modifications imbriquées, plus le client duplique la stack backend
La stack de base de données frontend proposée par SQLSync
- SQLSync est une stack de base de données optimisée pour le frontend, construite au-dessus de SQLite
- Son moteur de synchronisation s’appuie sur des idées issues de Git et des systèmes distribués
- Elle est conçue pour s’intégrer naturellement aux frameworks frontend populaires comme React, Vue et Next.js
- Son objectif est de traiter les problèmes difficiles de gestion des données afin que les développeurs puissent se concentrer sur les fonctionnalités propres à leur application
- L’exemple d’application Todo implémente toute la couche de données avec 60 lignes de Rust et quelques requêtes SQL dispersées dans les composants
- SQLSync fournit un cache durable, les index, contraintes, triggers et optimisations de requêtes de SQLite, les modifications optimistes, l’invalidation intelligente du cache et les requêtes réactives
- Les données locales sont stockées dans une ou plusieurs bases SQLite
- Les index sont faciles à créer et se synchronisent automatiquement avec les données
- Comme côté backend, la base de données peut utiliser automatiquement les index pour accélérer les requêtes
- SQL peut exprimer des requêtes de données complexes, et des fonctionnalités comme les triggers, les foreign keys, les constraints ou la full-text search peuvent aussi être utilisées
- Les modifications optimistes sont traitées par un reducer
- La structure est similaire aux concepts fondamentaux de Redux
- Le reducer peut être écrit dans n’importe quel langage compilable en WebAssembly
- SQLSync exécute les modifications de façon optimiste côté client, et les exécute côté serveur dans un ordre globalement cohérent
- Le client se synchronise ensuite avec le serveur via une opération similaire à Git Rebase
- Cette architecture a l’avantage de supprimer le besoin d’invalidation récursive du cache
- Toute la logique de modification des données est écrite dans un reducer facile à partager entre client et serveur
- Toutes les transformations de données survenues pendant une modification deviennent automatiquement visibles
- Comme la synchronisation fonctionne comme Git Rebase, même si le serveur effectue des changements différents de ceux du client, le client a la garantie d’aboutir au même résultat cohérent
Travaux connexes
- « Building data-centric apps with a reactive relational database » de Riffle traite de l’idée de stocker tout l’état applicatif, y compris l’état de l’UI, dans une seule base de données réactive
- Les requêtes réactives offrent un modèle mental clair et s’accordent bien avec les systèmes déclaratifs comme React
- Les problèmes de développement d’apps clientes y sont abordés avec des idées issues de la communauté des bases de données
- Le texte couvre les avantages de modéliser l’état avec un modèle relationnel et de vrais index
- Stepan d’Instant.db a écrit deux articles sur les bases de données dans le navigateur
- Database in the Browser, a Spec
- A Graph-Based Firebase
- Ces deux articles abordent des problèmes similaires en se concentrant davantage sur la relation entre les stacks frontend et backend, et expliquent ce qui a motivé la création d’Instant.db comme successeur de Firebase fondé sur un graphe
- CR-SQLite, de Matt Wonlaw, est une extension SQLite
- Elle utilise des types de données répliquées sans conflit (CRDT) et un journal d’événements à ordre causal pour fusionner les données de manière cohérente
- Elle permet à des applications peer-to-peer de stocker des données dans SQLite et de collaborer sans coordinateur central
- C’est aussi un exemple d’exécution de SQLite dans le navigateur
- Matt Wonlaw explore également des idées connexes
- incremental computation
- l’amélioration de l’ergonomie de SQL via typed-sql
1 commentaires
Avis de Hacker News
Je connais bien ce projet, et son créateur est un ami ; je vais essayer de le faire venir ici pour répondre aux questions.
C’est un architecte de bases de données expérimenté. Avec SQLsync, il a voulu permettre aux développeurs frontend d’interroger et de mettre à jour une base de données distante comme si elle se trouvait entièrement dans le navigateur. En pratique, c’est presque le cas : grâce à WASM, on peut envoyer toute une base SQLite dans le navigateur. Le cœur du système est un algorithme réactif malin mais simple, qui synchronise plusieurs clients.
Si l’on considère qu’une grande partie du travail de développement consiste à synchroniser des données, alors React et les API REST peuvent aussi être vus comme des procédures de synchronisation, et cette approche ouvre de nouvelles possibilités. Plutôt que de créer encore une étrange base de données sur mesure sous forme d’arbre d’objets récupérés depuis une API et mis en cache, il suffit de s’appuyer sur la puissance d’une base relationnelle pour mettre à jour et interroger localement.
Cela dit, dans les entreprises web traditionnelles, l’adoption est difficile à cause des équipes backend/frontend spécialisées. Cela revient à retirer la base de données, le backend, le transport et la couche d’authentification pour les remplacer par un système en un seul bloc ; or la plupart des architectes système viennent du backend et connaissent mal ce problème. Comme cela touche en profondeur les deux côtés, ça s’intègre mal aux systèmes existants et finit par convenir surtout aux nouveaux développements. Le backend n’étant pas non plus un service AWS ou Azure, ni particulièrement compatible avec Lambda, la plupart des profils d’architectes que je rencontre n’y toucheront pas.
Cette approche existe déjà en partie avec une technologie plus ancienne, CouchDB+PouchDB. Pour certains usages, ça fonctionne assez bien, mais le système de requêtes n’est pas idéal, et la manière de gérer l’authentification et le périmètre des données est déroutante pour la plupart des gens. Le cas le plus simple est celui où les données appartiennent entièrement à un seul utilisateur et où l’on utilise tel quel un modèle de base de données par utilisateur. En partitionnant fortement les données avec des CRDT, on réduit aussi beaucoup les problèmes de conflits.
Mais il y a un problème de scalabilité. Quand 10 000 à 100 000 utilisateurs sont connectés, CouchDB demande énormément de CPU ; la technologie est ancienne, même si elle reste maintenue. Du point de vue de la conception système, dès que l’on commence à partager des données entre utilisateurs, la complexité augmente brutalement : on ne la résout pas vraiment, on ne fait que la déplacer, ce qui rend l’approche moins adaptée.
Cette approche semble viser le même objectif, mais elle risque fort de rencontrer des problèmes de scalabilité similaires. J’ai hâte de voir comment elle va évoluer ; cela ressemble à un premier pas.
Je me souviens qu’à une époque Chrome avait essayé d’intégrer littéralement une base de données SQL dans le navigateur, mais ça n’a pas vraiment pris, et localStorage est devenu dominant. Je ne cherche pas à minimiser l’utilité de ce projet ; en général, on choisit ce que le navigateur fournit. J’ai de grandes attentes quant aux possibilités qu’apporte WASM, et à ce qu’il pourra amener dans le navigateur à mesure qu’il mûrit ou gagne en fonctionnalités.
Dans une ancienne entreprise, nous utilisions un logiciel de gestion de projet avec un mécanisme de check-out/check-in pour les modifications. Quand on faisait un check-out d’un projet, on téléchargeait une copie à modifier localement ; au check-in, on la renvoyait au serveur. Pendant le check-out, le projet était verrouillé. À l’ère des applications avec mises à jour en direct, tout le monde trouvait cette approche dépassée.
Mais après dix ans à construire des webapps SPA, cette méthode de synchronisation des données me semble maintenant avoir été en avance sur son temps.
Au bout du compte, cela revient à savoir si l’on peut implémenter une procédure qui résout de manière cohérente les incohérences entre plusieurs mises à jour simultanées. Parfois c’est possible, parfois non ; cela dépend davantage des règles métier que de la capacité technique.
Si les règles métier ne permettent pas d’implémenter un mécanisme de résolution, alors il faut un verrouillage pour n’autoriser qu’une seule mise à jour à la fois, même si l’on possède la capacité technique de prendre en charge les mises à jour concurrentes.
Mais il est difficile de convaincre les gens que c’est vraiment ce qu’ils veulent. On tombe facilement dans la grande illusion selon laquelle tout doit toujours être disponible, alors qu’en réalité, c’est souvent une seule personne qui modifie quelque chose à la fois ; et si deux personnes ou plus doivent travailler dessus, elles doivent de toute façon se parler ou se coordonner.
Même dans un développement entièrement distribué comme avec Git, on ne peut pas résoudre les conflits automatiquement par magie. Pour choisir la bonne modification, il faut toujours communiquer avec les autres et comprendre le contexte.
Certaines choses ont besoin de solutions éprouvées.
Je me souviens que, quand mon ancienne entreprise est passée de RCS à CVS, l’un de mes collègues était agacé que CVS ne prenne pas en charge le check-out verrouillé.
https://en.wikipedia.org/wiki/Concurrent_Versions_System
Je pense qu’une stratégie de verrouillage à propriétaire unique peut aussi être simulée avec SQLSync. Cela dit, selon l’application, ce ne sera peut-être pas nécessaire. Si l’objectif est de travailler hors ligne puis de fusionner quand c’est prêt, SQLSync fournit ce pattern par défaut. Si l’objectif est qu’un seul client puisse effectuer des changements, alors il faut un pattern de verrouillage centralisé, et il est probablement possible de le coordonner via SQLSync.
Ici se mêlent le principe selon lequel « ce qui est mesuré est géré » et l’erreur des coûts irrécupérables.
Le vrai problème des bases de données, c’est la complexité. Chaque fonctionnalité prise isolément est généralement sûre, mais dès que stabilité, cache et indexation s’entrecroisent, la complexité explose, et il est en général absurde d’implémenter une base de données spécialisée pour son domaine.
Mais au moment où une entreprise réalise qu’elle a déjà investi dans l’implémentation de ces trois fonctionnalités et y a consacré beaucoup de ressources, il devient politiquement difficile de recommander de les retirer, et le coût réel pour éliminer cette dette technique d’un seul coup est également élevé.
À mon avis, le vrai problème, c’est la syntaxe SQL. Si l’expérience d’utilisation d’une base relationnelle standard était aussi agréable qu’une syntaxe familière de style C, plutôt que de l’anglais cassé, l’incitation à utiliser une DB plutôt qu’à en construire une soi-même serait plus forte. Les bases NoSQL ont été un bon pas dans cette direction, mais elles se sont généralement trop focalisées sur le big data au détriment de l’utilité au quotidien. Des choses comme Redis se sont imposées et sont très bien.
Faciliter l’exécution de SQL est une approche raisonnable, mais dans les bonnes bases de données, par exemple Postgres que j’aime beaucoup, SQL est le langage par défaut, et il est difficile d’obtenir de l’efficacité sans l’utiliser. On a vraiment besoin d’une base de données du genre PostgresPostSQL, qui répliquerait parfaitement Postgres tout en ayant un parseur par défaut prenant en charge un langage à la bonne syntaxe.
En programmation généraliste, des dizaines de langages sont utilisés et continuent d’évoluer. Même JavaScript, pourtant difficile à changer parce qu’il est exécuté par les navigateurs et qu’on ne contrôle pas celui de l’utilisateur, évolue grâce aux transpileurs et à WebAssembly.
Mais pour les bases de données, il n’y a en pratique qu’un seul SQL. Il existe des alternatives, mais aucune ne s’approche de SQL en termes d’usage. Peut-être que SQL n’est finalement pas si mauvais.
La raison tient peut-être au fait que le modèle relationnel est vraiment excellent. Les tentatives de s’en écarter ont de fortes chances de ne fonctionner que dans des niches. Le style déclaratif est lui aussi très bon, et il est difficile d’obtenir un grand succès en s’en éloignant. Si, au bout du compte, on ne crée qu’un SQL avec une syntaxe différente, ce ne sera pas, pour la plupart des gens, une amélioration suffisante pour changer de méthode.
Les applications écrites pour cette API pourraient implémenter une DB SQL. Il suffirait de parser SQL et d’implémenter un planificateur de requêtes qui émette des plans adaptés à cette API.
Je suis l’auteur. J’ai à peine parcouru la plupart des questions, et je continuerai à vérifier régulièrement si j’en ai manqué. Je me demande aussi si quelqu’un a déjà créé une meilleure façon de suivre les discussions HN.
Les échanges jusqu’ici me réjouissent beaucoup. Le premier article se concentrait moins sur le fonctionnement concret de SQLSync que sur les motivations de l’ingénierie frontend qui m’ont amené à le créer. Je parlerai de son fonctionnement dans le prochain article.
Il ne faut pas donner aux utilisateurs un modèle mental qui peut briser la réalité de manière grave, ou invisible.
Je crains que la synchronisation de bases de données à la place du modèle client-serveur ne soit l’un de ces cas. Le mécanisme de synchronisation peut simplement s’effondrer, ou reposer sur des hypothèses profondes qui ne sont pas satisfaites.
Si l’on a besoin d’une UI rapide, créer un ensemble de primitives CRDT à utiliser me semble plus sûr, et le reste restera probablement de la soumission de formulaires.
La synchronisation d’état entre client et serveur est un problème maudit.
En acceptant de sacrifier un peu l’expérience utilisateur et en revenant à quelque chose de plus proche du modèle PHP / rendu côté serveur, on peut éviter entièrement ce problème. Les SPA sont bien, mais l’envoi de formulaires multipart fonctionne toujours. Avec très peu de JavaScript, on peut lisser la plupart des aspérités restantes.
Dans nos produits web récents, l’état côté client se limite aux claims d’authentification de l’IdP tiers, à l’ID de session first-party dans les paramètres de requête, et au document courant. Honnêtement, je ne sais même pas où est stocké le premier. C’est le problème de Microsoft, pas le nôtre. Tout le reste de l’état est sur le serveur.
Nous traitons le client comme un terminal idiot qui ne fait qu’émettre des entrées toute la journée. Nous n’utilisons ni cookies first-party ni stockage local. Cette approche a grandement amélioré l’expérience de développement ciblant iOS/Safari.
Je voudrais donc demander quelle expérience vous cherchez réellement à offrir, et pourquoi elle justifie de séparer l’état client de l’état serveur.
Référence : https://news.ycombinator.com/item?id=37584049
Le modèle offline/local-first basé sur SQLite semble très en vogue en ce moment. C’est le troisième article que je lis à ce sujet cette semaine, et ça a l’air prometteur.
Mais comment cela se compare-t-il à ElectricSQLhttps://electric-sql.com/ et PowerSynchttps://powersync.com/ ?
ElectricSQL et PowerSync s’attaquent tous deux à un problème très difficile : la réplication partielle. L’idée est de créer une solution générale où une base de données centrale traditionnelle synchronise dans les deux sens uniquement ce dont le client a besoin, tout en prenant en charge les modifications optimistes et les questions de cohérence/résolution de conflits qui en découlent.
L’inconvénient, c’est la complexité de mise en œuvre. Il faut suivre précisément quel sous-ensemble de la base de données complète possède chaque client, afin de pouvoir pousser les changements uniquement vers ce sous-ensemble. Il faut aussi un nouveau DSL pour spécifier quel sous-ensemble de l’état de la base télécharger, qu’il faut en plus apprendre et optimiser. Cela dit, c’est réjouissant de les voir s’attaquer à un problème très difficile, et lorsque SQLSync sera prêt à prendre en charge la réplication partielle, les bonnes pratiques auront déjà été établies.
À l’inverse, SQLSync ne prend actuellement en charge que la synchronisation de toute la base. Tous les clients voient une vue cohérente de l’ensemble de la base de données. On peut immédiatement se demander si c’est une bonne idée, et cela ne convient pas à certaines apps. Mais si l’on pense à une app de finances personnelles, les objectifs clés sont la synchronisation entre appareils, la sauvegarde cloud, le fonctionnement hors ligne, etc. : stocker toute la base sur chaque appareil peut au contraire être le comportement souhaité. Un modèle de données orienté document comme Airtable peut aussi servir d’exemple. Si chaque Airtable est une base de données séparée, le client peut gérer quelles tables l’intéressent.
En se concentrant sur la synchronisation de toute la base, le moteur de synchronisation devient beaucoup plus simple que les solutions qui prennent en charge la réplication partielle. L’un des avantages est que le backend est très léger. La démo actuelle (https://sqlsync-todo.pages.dev) s’exécute entièrement dans des Cloudflare Durable Objects en consommant très peu de stockage et de temps CPU.
SQLSync a encore beaucoup de travail à faire pour rendre ces cas d’usage possibles, et reste proche d’un prototype, mais les premiers tests ont été très positifs.
Dans de grosses apps multitenant, quand les jeux de données individuels sont relativement petits, je me suis souvent demandé : « et si on envoyait simplement la base de données au client ? ». Ça ressemblait suffisamment à un pattern d’architecture maudit, très hors normes, pour que je n’aille pas plus loin. J’aimerais bien découvrir que j’avais tort.
Il reste beaucoup à faire pour le prouver correctement, mais je suis assez enthousiaste à l’idée de continuer à pousser dans cette direction et de voir où cela mène.
Ça ressemble à l’un de ces problèmes qui disparaissent complètement dès qu’on abandonne les SPA.
Avec des solutions de la famille Hotwire ou htmx, les requêtes redeviennent simplement des requêtes serveur, et le problème consistant à les rendre rapides est beaucoup mieux compris.
Je l’ai utilisé avec OCaml + Web Components, et c’était une expérience productivité 10/10. Il suffit d’un outil de build qui compile plus vite qu’un battement de cil, et il n’y a pas besoin de câbler des mappings JSON entre frontend et backend, donc c’est vraiment productif.
Personnellement, je préfère InertiaJs https://inertiajs.com. C’est une sorte de système de routeur frontend qui synchronise l’état avec le serveur « à l’ancienne ».
C’est particulièrement vrai pour un produit qui doit fonctionner dans des régions où Internet est instable.
Je suis en train d’écrire un article très similaire sur les « bases de données full-stack ». Il traite du schéma où beaucoup d’apps recréent, dans le code client frontend, la logique du backend et de la base de données. La solution que nous recommandons consiste à choisir une base de données pouvant s’exécuter à la fois côté serveur et côté client, puis à synchroniser les deux
La raison pour laquelle nous n’utilisons pas SQLite dans notre produit, c’est que, franchement, SQL n’est pas l’outil adapté pour interroger des données applicatives. Il ne s’emboîte pas facilement avec les structures de données voulues dans le code client, et presque toutes les bases de données SQL n’offrent aucun moyen de s’abonner aux changements d’une requête sans interroger celle-ci en boucle
Si l’idée d’avoir une base de données complète côté client vous plaît et que vous voulez une intégration profonde avec TypeScript/JavaScript, jetez un œil à ce que nous construisons : https://github.com/aspen-cloud/triplit
https://github.com/cpursley/walex
Je l’utilise de façon très simple. Quand les données d’une table sous-jacente changent, la requête est automatiquement réexécutée. Ce n’est sans doute pas aussi efficace que de mettre à jour les résultats de façon incrémentale, mais les requêtes SQLite étant généralement très rapides, je ne pense pas que ce soit un gros problème