HN présente : Triplit - une base de données de synchronisation open source qui s’exécute côté serveur et client
(github.com/aspen-cloud)- Triplit est une base de données open source qui synchronise les données en temps réel entre le serveur et le navigateur, et se présente comme une base de données full-stack utilisable dans une application sous forme de package Typescript
- Elle gère à la fois le stockage côté serveur et la synchronisation des requêtes client, avec prise en charge des mises à jour incrémentales, de la résolution de conflits au niveau des propriétés, du cache local, du mode hors ligne et de la reconnexion automatique
- Elle prend en charge des stockages enfichables comme SQLite, IndexedDB, LevelDB et Memory, et fournit un stockage persistant côté serveur ainsi qu’un tableau de bord d’administration
- Les API de requête et de modification peuvent être utilisées avec React et vanilla Javascript, et l’ensemble est proposé sous forme de monorepo comprenant des bindings React et Svelte, une CLI, une Console, un Server, etc.
- Elle fournit aussi des mises à jour optimistes pour des interactions rapides, le rollback et la relance des mises à jour échouées, des autorisations de lecture et d’écriture imposées par le serveur, ainsi que des fonctionnalités collaboratives basées sur les CRDT
Ce que propose Triplit
- Triplit est une base de données open source qui synchronise les données en temps réel entre le serveur et le navigateur
- Elle fournit un magasin de données synchronisé qui peut être ajouté à une application comme package Typescript
- Elle stocke les données sur le serveur et synchronise intelligemment les requêtes du client
- Triplit appelle cette approche une base de données full-stack
- Une vidéo de présentation issue de la communauté Local First est également disponible : presentation
Fonctionnalités principales
-
Synchronisation en temps réel
- Prise en charge des mises à jour incrémentales
- Résolution de conflits au niveau des propriétés
-
Expérience local-first
- Fournit un cache local reposant sur une base de données entièrement côté client
- Les mises à jour optimistes rendent toutes les interactions plus réactives
- Prise en charge du mode hors ligne, de la reconnexion automatique et des garanties de cohérence
-
Stockage et exploitation côté serveur
- Fournit un stockage persistant côté serveur
- Inclut un tableau de bord d’administration
- Prend en charge des fournisseurs de stockage enfichables comme SQLite, IndexedDB, LevelDB et Memory
-
Modèle de données et API
- Prend en charge les requêtes relationnelles pour des modèles de données complexes
- Le schéma apporte la sûreté des données et l’autocomplétion Typescript
- Fournit une API simple pour les requêtes et modifications en vanilla Javascript et React
-
Collaboration et sécurité
- Le serveur impose des autorisations pour la lecture comme pour l’écriture
- Fournit des fonctions de collaboration et de multijoueur basées sur les CRDTs
- Utilise des delta patches pour réduire le trafic réseau et viser une faible latence
- Gère le rollback et la relance en cas de mise à jour échouée
Composition du monorepo
- TriplitDB : base de données conçue pour s’exécuter dans des environnements JS comme le navigateur, Node, Deno ou React Native, fournissant des requêtes rapides, live et mises à jour tout en maintenant la cohérence entre plusieurs auteurs sur le réseau
- Client : bibliothèque navigateur pour interagir avec TriplitDB en local et à distance
- CLI : outil en ligne de commande pour le scaffolding de projet, l’exécution d’un environnement de développement full-stack, les migrations serveur, etc.
- React : bindings React pour
@triplit/client - Svelte : bindings Svelte pour
@triplit/client - Console : application pour consulter et modifier les données d’un projet Triplit et gérer le schéma
- Server : serveur Node qui synchronise les données entre les clients Triplit
- Server-core : bibliothèque indépendante du protocole pour créer un serveur Triplit
- Docs : documentation Triplit réalisée avec Nextra
- Types : types partagés par les projets Triplit
- UI : composants UI partagés basés sur shadcn
Flux de démarrage rapide
- Un nouveau projet commence avec
npm create triplit-app@latest my-app - Dans un projet existant, il faut installer
@triplit/clicomme dépendance de développement puis exécuternpm run triplit init - Définir le schéma dans
my-app/triplit/schema.ts- L’exemple définit les champs
id,textetcompleteddans la collectiontodos completedest défini comme un champ booléen dont la valeur par défaut estfalse
- L’exemple définit les champs
- Lancer le serveur de synchronisation de développement avec
npm run triplit dev - Le serveur de développement affiche les variables d’environnement nécessaires à la synchronisation de l’application avec le serveur
- Dans l’exemple Vite :
VITE_TRIPLIT_SERVER_URL=http://localhost:6543 VITE_TRIPLIT_TOKEN=copied-in-from-triplit-dev
- Dans l’exemple Vite :
Exemple d’utilisation avec React et vérification de la synchronisation
- L’exemple React utilise
TriplitClientetuseQuery - Le client est créé à partir du schéma, de l’URL du serveur et du token
useQuery(client.query('todos'))permet de s’abonner aux résultats de la requêtetodos- Quand l’état de la case à cocher change,
client.updateinverse la valeur decompleted - Après avoir lancé l’application, il suffit d’ouvrir un autre onglet du navigateur pour vérifier que les données se synchronisent en temps réel
Documentation et canaux de contact
- Guide de démarrage complet : getting started guide
- Tutoriel plus détaillé : building a real-time todo app with Triplit, Vite, and React
- Questions, aide au démarrage et aperçu des nouvelles fonctionnalités sur Discord
- Les annonces les plus récentes sont disponibles sur Twitter/X
1 commentaires
Avis sur Hacker News
J’ai utilisé Triplit dans le projet https://github.com/thanhnguyen2187/cryptaa et il a fonctionné comme prévu.
Le modèle de données correspond bien à une conception plus distribuée/P2P qu’à une base centrale unique comme source de vérité, mais le self-hosting et le langage de requête laissent à désirer.
La documentation n’indiquait pas clairement comment générer le jeton d’authentification serveur, donc j’ai créé un jeton avec la commande
devde la CLI ; le fait que le jeton se retrouve en clair dans les logs d’un service système n’est pas idéal côté sécurité, même si je considère que cela suppose déjà un problème d’accès plus large.Le DSL de requête personnalisé manque de l’expressivité de SQL, comme
UNIQUEouCOUNT, ce qui oblige à faire certaines agrégations soi-même.J’ai récemment regardé Evolu https://www.evolu.dev/docs : son périmètre et ses fonctionnalités semblent similaires. Triplit a
.subscribe()alors qu’Evolu ne l’a pas ; Evolu utilise du SQL typé basé sur Kysely, donc les requêtes sont plus familières et plus avancées ; dans le navigateur, Evolu utilise SQLite sur OPFS, tandis que Triplit semble utiliser IndexedDB.Mon post sur Reddit : https://www.reddit.com/r/sveltejs/comments/1dndpj8/cryptaa_a...
Côté requêtes, il n’y a pas encore d’agrégations, mais c’est dans la roadmap ; avec le moteur de requêtes incrémental, il y a selon moi des pistes très intéressantes.
Par exemple, pour un tableau de bord de données mis à jour toutes les heures, les systèmes existants (Postgres, MongoDB, etc.) doivent relancer la requête depuis le début à chaque fois, alors qu’en ne traitant que les nouvelles données, dans une approche proche de Materialize, on peut maintenir les mises à jour en continu de manière bien plus efficace.
Je n’ai pas encore essayé Evolu moi-même, mais il y a peut-être des personnes sur Discord qui ont fait la comparaison : https://triplit.dev/discord
useQueryou une approche séparée.Quand on utilise une base de données avec un aussi bon protocole de synchronisation offline, je me demande comment gérer l’évolution du schéma quand on ne peut pas mettre à niveau simultanément différentes versions des clients.
J’ai déjà souffert de ce problème dans le contexte d’une app mobile de santé.
Si nécessaire, on finit par faire une double écriture vers les deux versions.
C’est similaire à une migration live sans interruption d’un changement incompatible dans une base SQL, sauf que le moment de bascule dépend du client, donc il faut conserver cette logique plus longtemps.
Une table qui coordonne la version la plus récente est aussi importante, et en cas de changement cassant, les clients en retard doivent demander à l’utilisateur de mettre à niveau.
On peut aussi déterminer combien de temps maintenir les doubles lectures/écritures en fonction de la version minimale prise en charge sur l’ensemble des clients.
Triplit affiche un avertissement si l’on crée un changement non rétrocompatible, comme indiqué dans la documentation : https://www.triplit.dev/docs/schemas/updating#pushing-the-sc...
Cela dit, avec le temps, on peut naturellement se retrouver avec une définition de schéma désordonnée, pleine de noms confus.
Nous n’avons pas encore publié de solution pour corriger cela, mais nous travaillons sur plusieurs choses pour rendre le processus moins pénible ; pour le contexte autour des différentes approches, le document Cambria est excellent : https://www.inkandswitch.com/cambria/
Un utilisateur a peut-être laissé son téléphone dans un tiroir pendant deux ans ; chaque client n’a qu’à se migrer lui-même dès que possible.
Cela éviterait à tout le monde de réinventer sa propre gestion des migrations.
J’ai du mal à comprendre dans quels types d’apps il est acceptable que le client puisse écrire directement dans la DB, et comment cela peut tenir sans logique backend.
Je me pose la même question avec Supabase et Firestore, donc j’ai l’impression de passer à côté de quelque chose.
En environnement d’entreprise, c’est évidemment l’inverse, et les discussions qui ignorent cela sont frustrantes.
En particulier sur la tech Twitter, quand je vois des gens défendre une stack ou une manière de travailler, il est souvent évident qu’ils n’ont jamais construit de systèmes métier et seulement du CRUD ; ils ne comprennent donc pas pourquoi des développeurs expérimentés ne sont pas d’accord.
Je ne pense pas que cela convienne aux systèmes avec beaucoup de logique backend.
Dans Supabase, par exemple, il existe une fonctionnalité appelée sécurité au niveau des lignes.
Le client peut envoyer des requêtes à Supabase, mais Supabase exécute des requêtes supplémentaires côté backend pour déterminer si la requête entrante est autorisée.
Exemple simple : on peut faire en sorte qu’une ligne ne puisse être lue, écrite ou mise à jour que lorsque la valeur de la colonne
UserIDcorrespond à l’utilisateur authentifié à l’origine de la requête.Nous stockions les paramètres utilisateur dans Triplit, et ces paramètres devaient pouvoir être gérés par un administrateur
L’utilisateur devait avoir l’impression que l’app tournait toujours en local, et la qualité de la connexion Internet était souvent mauvaise, mais comme il fallait pouvoir passer d’un appareil à l’autre et que l’administrateur devait voir et gérer les paramètres d’autres utilisateurs, une synchronisation était nécessaire
Dans l’ensemble, Triplit a été excellent, tant pour l’expérience développeur frontend que pour le support ; lorsqu’on signale une issue ou une demande de fonctionnalité, l’équipe la traite très rapidement
Quand il y aura une réponse sur le déploiement haute disponibilité, nous prévoyons de migrer aussi des données plus critiques depuis Postgres
Je me demande pourquoi avoir choisi la licence AGPL
Je crois avoir vu la présentation YouTube https://www.youtube.com/playlist?list=PLTbD2QA-VMnXFsLbuPGz1... sur le serveur Discord Local First https://localfirstweb.dev/ ; ça fait plaisir de le voir dans Show HN
Comme je n’utilise pas TypeScript, je ne suis peut-être pas dans la cible principale, et contrairement au Web, j’utilise surtout le local-first pour des apps mobiles où la connectivité est instable, avec Flutter et un backend Rust
D’autres solutions local-first comme ElectricSQL et PowerSync synchronisent directement les DB client et serveur, elles sont donc plus indépendantes côté client/serveur
Les solutions basées sur des CRDT peuvent aussi être utilisées côté client et serveur via FFI ; par exemple, automerge étant en Rust, on peut l’utiliser côté Flutter via FFI avec
flutter_rust_bridge, en Web via WASM, et côté backend en RustTriplit semble plutôt relever d’une synchronisation client-serveur plus classique, avec le serveur comme source de vérité, plutôt que d’une résolution sans conflit entre différents clients
Je me demande pourquoi avoir choisi une solution au niveau du langage plutôt qu’une approche au niveau de la DB, plus indépendante du client et du serveur ; il me semble aussi difficile de prendre en charge à l’avenir des langages et frameworks non basés sur JS
Par ailleurs, Triplit semble vouloir concurrencer Supabase, mais Supabase expérimente aussi la synchronisation au niveau DB de Postgres et les CRDT, donc ils pourraient rattraper leur retard https://news.ycombinator.com/item?id=33931971
Cela dit, nous avons choisi de commencer en nous concentrant sur le pur TypeScript : le marché est assez vaste, nous croyons à l’avenir des PWA, et nous pensons qu’il faut nous concentrer là-dessus pour offrir la meilleure expérience
Un jour, nous construirons sans doute quelque chose de plus indépendant des plateformes, mais le calendrier n’est pas clair
Les équipes d’ElectricSQL et de Supabase sont toutes deux excellentes et réfléchies, et elles devraient continuer à progresser dans l’univers SQL ; c’est la différence la plus fondamentale entre nos approches
Triplit estime pouvoir offrir la meilleure expérience développeur en évitant SQL, et il y a largement de la place pour que les deux philosophies coexistent
Si c’est du LWW, je me demande si la quantité d’informations côté client augmente linéairement avec le nombre d’opérations
Autrement dit, plus l’utilisateur modifie la DB, plus le journal d’opérations grossit-il indéfiniment, ou y a-t-il des checkpoints ? Je me demande comment cela passe à l’échelle en espace quand un utilisateur effectue des millions d’opérations par jour
Cela dit, le registre LWW lui-même n’impose pas de conserver l’historique ; c’est seulement l’implémentation actuelle, destinée à permettre à des clients restés longtemps hors ligne de se resynchroniser efficacement
Nous ne pouvons pas encore dire que nous avons pleinement atteint le million d’opérations par jour, mais le fait que le serveur fasse autorité présente des avantages
À l’avenir, le serveur Triplit pourra suivre le timestamp de dernière synchronisation de chaque client et élaguer progressivement l’historique, un peu comme Postgres traite les tuples morts avec
VACUUMCe serait bien d’avoir des bindings Rust pour pouvoir l’utiliser avec Tauri
Avec la croissance de Tauri, la prise en charge prochaine des appareils mobiles et la popularité récente de SQLite, cela pourrait combler un manque pour les apps offline-first et devenir le choix par défaut de nombreuses équipes de développement
ElectricSQL fonctionne au niveau de la DB, donc il est indépendant du langage, et comme le serveur utilise Rust, des bindings Rust pourraient fonctionner à la fois côté client et côté serveur
Si tu veux contribuer au développement, fais-moi signe
Si c’est le cas, Triplit devrait fonctionner directement
J’utilise Triplit depuis un moment dans une app React Native, et ça marche très bien
Je le recommande vivement ; c’est la seule DB local-first qui répondait à tous mes critères
Il y a un langage de requête correct et sain (pas SQL), un excellent support TypeScript, le mode hors ligne, la prise en charge de React Native, et j’apprécie aussi que ce soit open source et auto-hébergeable
Je me demande s’il est impossible de l’utiliser avec une DB PostgreSQL existante
Il n’est pas encore prêt à être rendu public, mais nous voulons permettre aux gens de l’essayer bientôt
Je penche moi aussi vers cette solution