2 Punkte von GN⁺ 2024-06-27 | 1 Kommentare | Auf WhatsApp teilen
  • Triplit ist eine Open-Source-Datenbank, die Daten in Echtzeit zwischen Server und Browser synchronisiert, und versteht sich als Full-Stack-Datenbank, die als Typescript-Paket in Apps eingebunden wird
  • Sie übernimmt sowohl die Serverspeicherung als auch die Synchronisierung von Client-Abfragen und unterstützt inkrementelle Updates, Konfliktauflösung auf Attributebene, lokales Caching, Offline-Modus und automatische Wiederverbindung
  • Unterstützt pluggbare Speicher wie SQLite, IndexedDB, LevelDB und Memory und bietet serverseitige persistente Speicherung sowie ein Verwaltungs-Dashboard
  • Die Query- und Änderungs-APIs können in React und vanilla Javascript verwendet werden; bereitgestellt wird sie als Monorepo mit React-/Svelte-Bindings, CLI, Console, Server und mehr
  • Für schnelle Interaktionen bietet sie optimistische Updates, Rollback und Wiederholungen fehlgeschlagener Updates, serverseitig erzwungene Lese- und Schreibberechtigungen sowie kollaborative Funktionen auf Basis von CRDTs

Was Triplit bietet

  • Triplit ist eine Open-Source-Datenbank, die Daten in Echtzeit synchronisiert zwischen Server und Browser
  • Sie stellt einen synchronisierten Datenspeicher bereit, der als Typescript-Paket zu einer App hinzugefügt werden kann
  • Sie speichert Daten auf dem Server und synchronisiert Client-Abfragen intelligent
  • Diese Form bezeichnet Triplit als Full-Stack-Datenbank
    • Es gibt auch ein Video eines Vortrags aus der Local-First-Community: presentation

Wichtige Funktionen

  • Echtzeit-Synchronisierung

    • Unterstützt inkrementelle Updates
    • Bietet Konfliktauflösung auf Attributebene
  • Local-First-Nutzbarkeit

    • Bietet lokales Caching auf Basis einer vollständigen clientseitigen Datenbank
    • Sorgt mit optimistischen Updates dafür, dass sich alle Interaktionen schnell anfühlen
    • Unterstützt Offline-Modus, automatische Wiederverbindung und Konsistenzgarantien
  • Serverspeicherung und Betrieb

    • Bietet serverseitige persistente Speicherung
    • Enthält ein Verwaltungs-Dashboard
    • Unterstützt pluggbare Speicheranbieter wie SQLite, IndexedDB, LevelDB und Memory
  • Datenmodell und API

    • Unterstützt relationale Abfragen für komplexe Datenmodelle
    • Bietet über Schemata Datensicherheit und Typescript-Autovervollständigung
    • Stellt einfache APIs für Abfragen und Änderungen in vanilla Javascript und React bereit
  • Zusammenarbeit und Sicherheit

    • Erzwingt serverseitig Berechtigungen sowohl für Lesen als auch Schreiben
    • Bietet kollaborative und Multiplayer-Funktionen auf Basis von CRDTs
    • Verwendet Delta-Patches, um den Netzwerkverkehr zu reduzieren und auf geringe Latenz auszurichten
    • Verwaltet Rollback und Wiederholungen für fehlgeschlagene Updates

Aufbau des Monorepos

  • TriplitDB: Eine für JS-Umgebungen wie Browser, Node, Deno und React Native konzipierte DB, die schnelle, Live-aktualisierte Abfragen bereitstellt und dabei Konsistenz bei mehreren Schreibenden über das Netzwerk hinweg aufrechterhält
  • Client: Browser-Bibliothek zur Interaktion mit lokalem und entferntem TriplitDB
  • CLI: Kommandozeilen-Tool für Projekt-Scaffolding, das Starten einer Full-Stack-Entwicklungsumgebung, Server-Migrationen und mehr
  • React: React-Binding für @triplit/client
  • Svelte: Svelte-Binding für @triplit/client
  • Console: App zum Anzeigen und Ändern von Daten in Triplit-Projekten sowie zur Verwaltung von Schemata
  • Server: Node-Server zur Synchronisierung von Daten zwischen Triplit-Clients
  • Server-core: Protokollunabhängige Bibliothek zum Erstellen von Triplit-Servern
  • Docs: Mit Nextra erstellte Triplit-Dokumentation
  • Types: Von Triplit-Projekten gemeinsam genutzte Typen
  • UI: Gemeinsame UI-Komponenten auf Basis von shadcn

Schneller Einstieg

  • Ein neues Projekt startet mit npm create triplit-app@latest my-app
  • In ein bestehendes Projekt wird zunächst @triplit/cli als Entwicklungsabhängigkeit installiert, danach wird npm run triplit init ausgeführt
  • In my-app/triplit/schema.ts wird das Schema definiert
    • Das Beispiel definiert in der Collection todos die Felder id, text und completed
    • completed ist als Boolean-Feld mit dem Standardwert false gesetzt
  • Mit npm run triplit dev wird der Synchronisationsserver für die Entwicklung gestartet
  • Der Entwicklungsserver gibt die Umgebungsvariablen aus, die die App für die Synchronisierung mit dem Server benötigt
    • Im Vite-Beispiel: VITE_TRIPLIT_SERVER_URL=http://localhost:6543
    • VITE_TRIPLIT_TOKEN=copied-in-from-triplit-dev

React-Beispiel und Überprüfung der Synchronisierung

  • Das React-Beispiel verwendet TriplitClient und useQuery
  • Der Client wird mit Schema, Server-URL und Token erstellt
  • Mit useQuery(client.query('todos')) wird das Abfrageergebnis für todos abonniert
  • Beim Ändern eines Kontrollkästchens wird mit client.update der Wert von completed umgeschaltet
  • Nach dem Start der App kann durch Öffnen eines weiteren Browser-Tabs bestätigt werden, dass die Daten in Echtzeit synchronisiert werden

Dokumentation und Kontaktkanäle

1 Kommentare

 
GN⁺ 2024-06-27
Meinungen auf Hacker News
  • Ich habe Triplit in einem Projekt https://github.com/thanhnguyen2187/cryptaa ausprobiert, und es funktionierte wie erwartet.
    Das Datenmodell passt gut zu einer eher verteilten/P2P-nahen Idee, statt eine einzelne zentrale DB als Source of Truth zu haben, aber Self-Hosting und Abfragesprache lassen zu wünschen übrig.
    In der Dokumentation war nicht klar beschrieben, wie man Server-Auth-Token erzeugt, daher habe ich mit dem CLI-Befehl dev ein Token erstellt; dass das Token in einem Systemdienst im Klartext in Logs landet, ist aus Sicherheitssicht nicht gut, setzt aber wohl ein größeres Problem mit Zugriffsrechten voraus.
    Dem eigenen Query-DSL fehlt im Vergleich zu SQL Ausdrucksstärke wie UNIQUE oder COUNT, sodass ich einige Aggregationen selbst erledigen muss.
    Kürzlich habe ich mir Evolu https://www.evolu.dev/docs angesehen; Umfang und Funktionen wirken ähnlich. Triplit hat .subscribe(), Evolu nicht; Evolu nutzt typisiertes SQL auf Basis von Kysely, wodurch Abfragen vertrauter und fortgeschrittener sind. Im Browser verwendet Evolu SQLite auf OPFS, während Triplit offenbar IndexedDB nutzt.
    Mein Beitrag auf Reddit: https://www.reddit.com/r/sveltejs/comments/1dndpj8/cryptaa_a...

    • Die Self-Hosting-Dokumentation wird gerade überarbeitet, damit die Konfiguration klarer wird; die angesprochenen Punkte helfen dabei.
      Bei den Abfragen gibt es noch keine Aggregationen, sie stehen aber auf der Roadmap. Mit der inkrementellen Query-Engine könnten sich hier spannende Möglichkeiten ergeben.
      Ein Daten-Dashboard, das sich etwa stündlich aktualisiert, müsste in bestehenden Systemen (Postgres, MongoDB usw.) jede Abfrage jedes Mal von Grund auf neu ausführen; wenn man in einer eher Materialize-ähnlichen Weise nur neue Daten verarbeitet, lässt es sich viel effizienter kontinuierlich aktualisieren.
      Evolu habe ich selbst noch nicht ausprobiert, aber vielleicht gibt es auf Discord jemanden, der einen Vergleich gemacht hat: https://triplit.dev/discord
    • Danke für den Hinweis auf Evolu; Triplit und Evolu sehen beide interessant aus, und ich würde gern einen Vergleich der beiden sehen.
    • Evolu unterstützt subscribe ebenfalls, über useQuery oder auf eine entkoppelte Weise.
  • Bei DBs mit solch großartigen Offline-Sync-Protokollen frage ich mich, wie Schema-Evolution gehandhabt wird, wenn man unterschiedliche Client-Versionen nicht gleichzeitig upgraden kann.
    Ich habe früher bei einer mobilen Health-App in genau diesem Kontext mit diesem Problem zu kämpfen gehabt.

    • Am besten legt man nur neue Tabellen an und nimmt an bestehenden Tabellen keine Breaking Changes vor.
      Falls nötig, schreibt man per Dual Write gleichzeitig in beide Versionen.
      Das ähnelt einer unterbrechungsfreien Live-Migration von Breaking Changes in einer SQL-DB, nur dass der Zeitpunkt des Umstiegs vom Kunden abhängt und man die Logik daher länger beibehalten muss.
      Wichtig ist auch eine Tabelle, die die aktuelle Version koordiniert; bei Breaking Changes sollten zurückgebliebene Clients den Nutzer zum Upgrade auffordern.
      In Verbindung mit der von allen Clients unterstützten Mindestversion kann man auch festlegen, wie lange Dual Reads/Writes beibehalten werden.
    • Kurz gesagt: Die Abwärtskompatibilität des Schemas zu erhalten, ist der einfachste Weg, Kompatibilität sicherzustellen.
      Triplit zeigt eine Warnung an, wenn man eine nicht abwärtskompatible Änderung erstellt; das ist in der Dokumentation zu sehen: https://www.triplit.dev/docs/schemas/updating#pushing-the-sc...
      Mit der Zeit kann dadurch allerdings ganz natürlich eine unordentliche Schema-Definition mit vielen verwirrenden Namen entstehen.
      Eine Lösung dafür haben wir noch nicht veröffentlicht, aber wir arbeiten an ein paar Dingen, die es weniger schmerzhaft machen sollen; als Hintergrund zu verschiedenen Ansätzen ist das Cambria-Paper hervorragend: https://www.inkandswitch.com/cambria/
    • Einige Clients, zum Beispiel Server, sollten meiner Ansicht nach auch mit sehr alten Schemas synchronisieren können.
      Ein Nutzer könnte sein Handy zwei Jahre lang in der Schublade gelassen haben; jeder Client sollte sich dann einfach so schnell wie möglich selbst migrieren.
    • Eine eingebaute Möglichkeit, frühere Migrationen zu definieren und zu unterstützen, wäre vermutlich ein Killer-Feature.
      Dann müsste nicht jeder seine eigene Migrationsverwaltung neu erfinden.
  • Ich verstehe nicht so recht, für welche Apps es in Ordnung ist, dass Clients direkt in die DB schreiben können, und wie das ohne Backend-Logik tragfähig sein soll.
    Bei Supabase und Firestore habe ich dieselbe Frage, also übersehe ich vermutlich etwas.

    • Die meisten Dinge, die in der realen Welt gebaut werden, haben kaum Business-Logik und sind im Grunde nur CRUD.
      In Unternehmensumgebungen ist es natürlich umgekehrt, und Diskussionen, die das ignorieren, sind frustrierend.
      Besonders wenn man auf Tech Twitter Beiträge sieht, die einen bestimmten Stack oder eine bestimmte Arbeitsweise verteidigen, ist oft sehr offensichtlich, dass die Leute nie Business-Systeme gebaut haben, sondern nur CRUD; dann verstehen sie nicht, warum erfahrene Entwickler nicht zustimmen.
    • Ich habe einmal mit Firebase eine Kollaborations-App gebaut; wenn man sehr strikt einschränkt, was jede Person mit ihren eigenen Kommentaren oder Karten tun darf, und nur bestimmte Aktionen erlaubt, funktioniert das einigermaßen.
      Für Dinge mit viel Backend-Logik scheint es mir nicht gut geeignet zu sein.
    • Beide haben Zugriffskontrolle, die im Backend erzwungen wird, es ist also nicht so, dass es gar keine Backend-Logik gäbe.
      In Supabase gibt es zum Beispiel eine Funktion namens Row Level Security.
      Der Client kann zwar Requests an Supabase senden, aber Supabase führt im Backend zusätzliche Abfragen aus, um zu entscheiden, ob der eingehende Request erlaubt ist.
      Als einfaches Beispiel kann man festlegen, dass eine Zeile nur gelesen, geschrieben und aktualisiert werden darf, wenn der Wert der Spalte UserID dem authentifizierten Nutzer entspricht, der den Request gestellt hat.
  • Wir haben Benutzereinstellungen in Triplit gespeichert, und diese Einstellungen mussten von Administratoren verwaltet werden können.
    Die Nutzer sollten das Gefühl haben, dass die App immer lokal läuft, und die Internetqualität ist häufig nicht besonders gut. Gleichzeitig wird die App aber auf mehreren Geräten genutzt, und Administratoren müssen die Einstellungen anderer Nutzer einsehen und verwalten können – daher war Synchronisierung nötig.
    Insgesamt waren sowohl die Frontend-Developer-Experience als auch der Support bei Triplit hervorragend. Wenn wir ein Issue oder einen Feature Request gefunden haben, hat das Team sehr schnell reagiert.
    Sobald es eine Antwort auf High-Availability-Deployments gibt, planen wir, auch wichtigere Daten von Postgres dorthin zu verschieben.

  • Ich frage mich, warum die AGPL-Lizenz gewählt wurde.

    • Wir wollten mit der AGPL-Lizenz ermöglichen, Triplit einfach selbst zu hosten, und zugleich sicherstellen, dass Personen, die Änderungen vornehmen, diese Änderungen wieder an die Community zurückgeben.
    • Wenn unsere Nutzung der DB bedeuten würde, dass auch unser Produkt AGPL sein muss, würden wir das vermeiden wollen.
  • Ich glaube, ich habe die YouTube-Präsentation https://www.youtube.com/playlist?list=PLTbD2QA-VMnXFsLbuPGz1... auf dem Local First Discord Server https://localfirstweb.dev/ gesehen, daher freue ich mich, das bei Show HN zu sehen.
    Da ich kein TypeScript verwende, gehöre ich vielleicht nicht zur primären Zielgruppe. Ich nutze Local First vor allem in Mobile-Apps mit unzuverlässiger Verbindung, anders als im Web, und verwende Flutter sowie ein Rust-Backend.
    Andere Local-First-Lösungen wie ElectricSQL und PowerSync synchronisieren Client- und Server-DB direkt und sind dadurch unabhängiger von Client/Server.
    CRDT-basierte Lösungen lassen sich ebenfalls per FFI auf Client und Server nutzen. Zum Beispiel ist automerge in Rust geschrieben, sodass man es auf Flutter-Seite per FFI über flutter_rust_bridge, im Web per WASM und im Backend mit Rust verwenden kann.
    Triplit wirkt eher wie eine klassischere Client-Server-Synchronisierung, bei der der Server die Source of Truth ist, statt konfliktfreie Auflösung zwischen unterschiedlichen Clients in den Vordergrund zu stellen.
    Ich frage mich, warum ihr euch für eine Lösung auf Sprachebene entschieden habt statt für einen stärker client- und serverunabhängigen Ansatz auf DB-Schicht. Es wirkt so, als dürfte es künftig schwierig werden, andere Sprachen und Frameworks außerhalb des JS-Ökosystems zu unterstützen.
    Außerdem scheint ihr mit Supabase konkurrieren zu wollen; Supabase experimentiert aber ebenfalls mit Synchronisierung auf DB-Ebene in Postgres und mit CRDTs, sodass sie möglicherweise aufholen könnten: https://news.ycombinator.com/item?id=33931971

    • Über Flutter und andere native Unterstützung haben wir viel nachgedacht, besonders Flutter kommt häufig zur Sprache.
      Für den Anfang haben wir uns jedoch entschieden, uns auf reines TypeScript zu konzentrieren. Wir glauben, dass der Markt groß genug ist, wir an die Zukunft von PWAs glauben und dass wir nur durch diesen Fokus die beste Experience schaffen können.
      Irgendwann werden wir vermutlich etwas Plattformunabhängigeres bauen, aber der Zeitpunkt ist noch unklar.
      Die Teams von ElectricSQL und Supabase sind beide hervorragend und durchdacht und werden im SQL-Bereich wohl weiter wachsen; genau das ist der grundlegendste Unterschied im Ansatz.
      Triplit geht davon aus, dass man Entwicklern die beste Experience bieten kann, indem man SQL vermeidet, und es gibt genügend Raum, damit beide Philosophien koexistieren können.
  • Bei LWW frage ich mich, ob die Informationsmenge auf dem Client linear mit der Anzahl der Operationen wächst.
    Anders gesagt: Wächst das Operation Log immer weiter, je mehr ein Nutzer die DB verändert, oder gibt es Checkpoints? Wie skaliert das beim Speicherplatz, wenn ein Nutzer pro Tag Millionen von Operationen ausführt?

    • Triplit speichert die Änderungshistorie bestimmter Attribute, aber dank eines Index auf die neuesten Werte bleiben Queries schnell.
      Ein LWW-Register selbst erfordert allerdings nicht, dass Historie gespeichert wird; das ist lediglich die aktuelle Implementierung, um auch Clients effizient synchronisieren zu können, die lange offline waren.
      Wir können noch nicht wirklich behaupten, bereits bei einer Million Operationen pro Tag angekommen zu sein, aber es hat Vorteile, dass der Server autoritativ ist.
      Künftig könnte der Triplit-Server den letzten Synchronisierungs-Timestamp jedes Clients verfolgen und die Historie schrittweise beschneiden, ähnlich wie Postgres tote Tupel per VACUUM bereinigt.
  • Rust-Bindings, damit man es in Tauri verwenden kann, wären schön.
    Zusammen mit dem Wachstum von Tauri, der bald kommenden Unterstützung für mobile Geräte und der jüngsten Popularität von SQLite könnte das die Lücke für Offline-First-Apps schließen und für viele Entwicklerteams zur Standardwahl werden.

    • Ich plane, Rust-Bindings für ElectricSQL hinzuzufügen, eine ähnliche Synchronisierungslösung.
      ElectricSQL arbeitet auf DB-Schicht und ist dadurch sprachunabhängig; außerdem wird serverseitig Rust verwendet, sodass Rust-Bindings sowohl auf Client- als auch auf Serverseite funktionieren könnten.
      Wenn du mitentwickeln möchtest, sag gern Bescheid.
    • Tauri verwendet doch vermutlich einen nativen Web-Renderer.
      Wenn das stimmt, sollte Triplit direkt funktionieren.
  • Ich nutze Triplit seit einiger Zeit in einer React-Native-App, und es funktioniert sehr gut.
    Klare Empfehlung; es war die einzige Local-First-DB, die alle meine Anforderungen erfüllt hat.
    Eine passende und vernünftige Query Language (nicht SQL), hervorragende TypeScript-Unterstützung, Offline-Support, React-Native-Unterstützung – und dass es Open Source ist und selbst gehostet werden kann, ist ebenfalls gut.

  • Ich frage mich, ob man es nicht zusammen mit einer bestehenden PostgreSQL-DB verwenden kann.

    • Derzeit nicht, aber wir haben ein internes Tool, das mit dem Replikationsprotokoll von Postgres und WAL2JSON eine bidirektionale Synchronisierung durchführt.
      Es ist noch nicht ganz bereit für eine Veröffentlichung, aber wir wollen es bald für andere testbar machen.
    • Schau dir am besten ElectricSQL an, das mit bestehendem Postgres funktioniert.
      Ich selbst tendiere ebenfalls dazu, das zu verwenden.