2 points par GN⁺ 2024-06-27 | 1 commentaires | Partager sur WhatsApp
  • 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/cli comme dépendance de développement puis exécuter npm run triplit init
  • Définir le schéma dans my-app/triplit/schema.ts
    • L’exemple définit les champs id, text et completed dans la collection todos
    • completed est défini comme un champ booléen dont la valeur par défaut est false
  • 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

Exemple d’utilisation avec React et vérification de la synchronisation

  • L’exemple React utilise TriplitClient et useQuery
  • 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ête todos
  • Quand l’état de la case à cocher change, client.update inverse la valeur de completed
  • 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

1 commentaires

 
GN⁺ 2024-06-27
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 dev de 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 UNIQUE ou COUNT, 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...

    • La documentation sur le self-hosting est en cours de réorganisation pour clarifier la configuration, et les points signalés sont utiles.
      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
    • Merci d’avoir mentionné Evolu ; Triplit et Evolu ont tous deux l’air intéressants, et j’aimerais voir une comparaison entre les deux.
    • Evolu prend aussi en charge subscribe, via useQuery ou 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é.

    • Il vaut mieux se contenter de créer de nouvelles tables et éviter les changements cassant la compatibilité sur les tables existantes.
      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.
    • En bref, garder le schéma rétrocompatible est le moyen le plus simple de garantir la compatibilité.
      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/
    • Je pense que certains clients, par exemple les serveurs, devraient pouvoir se synchroniser même avec des schémas très anciens.
      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.
    • Un mécanisme intégré pour définir et prendre en charge les anciennes migrations serait une killer feature.
      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.

    • La plupart des choses construites dans le monde réel ont très peu de logique métier et se rapprochent simplement du CRUD.
      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.
    • J’ai déjà construit une app collaborative avec Firebase : si l’on limite fortement ce que chacun peut faire sur ses propres commentaires ou cartes, et qu’on n’accorde que des permissions pour certaines actions, ça fonctionne à peu près.
      Je ne pense pas que cela convienne aux systèmes avec beaucoup de logique backend.
    • Les deux ont un contrôle d’accès appliqué côté backend, donc ce n’est pas comme s’il n’y avait aucune 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 UserID correspond à 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

    • Nous voulions permettre d’auto-héberger facilement Triplit sous licence AGPL, tout en garantissant que les personnes qui le modifient reversent leurs changements à la communauté
    • Si le fait d’utiliser cette DB oblige à mettre aussi le produit sous AGPL, je préférerais l’éviter
  • 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 Rust
    Triplit 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

    • Nous avons beaucoup réfléchi à la prise en charge de Flutter et d’autres environnements natifs, et Flutter revient particulièrement souvent
      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

    • Triplit conserve l’historique des modifications d’un attribut donné, mais les requêtes restent rapides grâce à l’index des valeurs les plus récentes
      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 VACUUM
  • Ce 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

    • Je cherche à ajouter des bindings Rust à ElectricSQL, une solution de synchronisation similaire
      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
    • Il me semble que Tauri utilise le moteur de rendu Web natif
      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

    • Pas pour l’instant, mais nous avons un outil interne qui fait de la synchronisation bidirectionnelle avec le protocole de réplication de Postgres et WAL2JSON
      Il n’est pas encore prêt à être rendu public, mais nous voulons permettre aux gens de l’essayer bientôt
    • Tu devrais jeter un œil à ElectricSQL, qui fonctionne avec un Postgres existant
      Je penche moi aussi vers cette solution