2 puntos por GN⁺ 2024-06-27 | 1 comentarios | Compartir por WhatsApp
  • Triplit es una base de datos open source que sincroniza datos en tiempo real entre el servidor y el navegador, y se presenta como una base de datos full-stack que se usa integrándola en la app como paquete de Typescript
  • Maneja tanto el almacenamiento en servidor como la sincronización de consultas del cliente, y soporta actualizaciones incrementales, resolución de conflictos a nivel de propiedad, caché local, modo offline y reconexión automática
  • Soporta almacenamientos conectables como SQLite, IndexedDB, LevelDB y Memory, y ofrece almacenamiento persistente del lado del servidor junto con un panel de administración
  • Se pueden usar APIs de consultas y cambios en React y vanilla Javascript, y se ofrece como un monorepo compuesto por bindings para React y Svelte, CLI, Console, Server y más
  • También ofrece actualizaciones optimistas para interacciones rápidas, rollback y reintentos de actualizaciones fallidas, permisos de lectura y escritura forzados desde el servidor, y funciones colaborativas basadas en CRDT

Qué ofrece Triplit

  • Triplit es una base de datos open source que sincroniza datos en tiempo real entre el servidor y el navegador
  • Ofrece un almacén de datos sincronizado que puede agregarse a una app como paquete de Typescript
  • Guarda los datos en el servidor y sincroniza inteligentemente las consultas del cliente
  • A este enfoque, Triplit lo llama base de datos full-stack
    • También se ofrece un video de presentación de la comunidad Local First: presentation

Funciones principales

  • Sincronización en tiempo real

    • Soporta actualizaciones incrementales
    • Ofrece resolución de conflictos a nivel de propiedad
  • Usabilidad local-first

    • Ofrece caché local basada en una base de datos completa del lado del cliente
    • Hace que todas las interacciones se sientan rápidas con actualizaciones optimistas
    • Soporta modo offline, reconexión automática y garantía de consistencia
  • Almacenamiento y operación en servidor

    • Ofrece almacenamiento persistente del lado del servidor
    • Incluye un panel de administración
    • Soporta proveedores de almacenamiento conectables como SQLite, IndexedDB, LevelDB y Memory
  • Modelo de datos y API

    • Soporta consultas relacionales para modelos de datos complejos
    • Proporciona seguridad de datos y autocompletado de Typescript mediante esquemas
    • Ofrece una API simple para consultas y cambios en vanilla Javascript y React
  • Colaboración y seguridad

    • Fuerza permisos tanto de lectura como de escritura desde el servidor
    • Ofrece funciones de colaboración y multijugador basadas en CRDTs
    • Usa parches delta para reducir el tráfico de red y apuntar a baja latencia
    • Gestiona rollback y reintentos para actualizaciones fallidas

Estructura del monorepo

  • TriplitDB: DB diseñada para ejecutarse en entornos JS como navegador, Node, Deno y React Native, que ofrece consultas rápidas y con actualizaciones en vivo manteniendo consistencia con múltiples escritores en red
  • Client: biblioteca de navegador para interactuar con TriplitDB local y remoto
  • CLI: herramienta de línea de comandos para scaffolding de proyectos, ejecución del entorno de desarrollo full-stack, migraciones de servidor y más
  • React: binding de React para @triplit/client
  • Svelte: binding de Svelte para @triplit/client
  • Console: app para ver y modificar datos de proyectos Triplit y administrar esquemas
  • Server: servidor Node que sincroniza datos entre clientes de Triplit
  • Server-core: biblioteca independiente del protocolo para construir servidores Triplit
  • Docs: documentación de Triplit hecha con Nextra
  • Types: tipos compartidos entre proyectos Triplit
  • UI: componentes UI compartidos basados en shadcn

Flujo de inicio rápido

  • Un proyecto nuevo comienza con npm create triplit-app@latest my-app
  • En un proyecto existente, instala @triplit/cli como dependencia de desarrollo y luego ejecuta npm run triplit init
  • Define el esquema en my-app/triplit/schema.ts
    • El ejemplo define los campos id, text y completed en la colección todos
    • completed se configura como un campo Boolean con valor por defecto false
  • Inicia el servidor de sincronización para desarrollo con npm run triplit dev
  • El servidor de desarrollo imprime las variables de entorno necesarias para que la app se sincronice con el servidor
    • En el ejemplo con Vite: VITE_TRIPLIT_SERVER_URL=http://localhost:6543
    • VITE_TRIPLIT_TOKEN=copied-in-from-triplit-dev

Ejemplo de uso con React y verificación de sincronización

  • El ejemplo en React usa TriplitClient y useQuery
  • El cliente se crea recibiendo el esquema, la URL del servidor y el token
  • Se suscribe al resultado de la consulta todos con useQuery(client.query('todos'))
  • Al cambiar el checkbox, invierte el valor de completed con client.update
  • Después de iniciar la app, al abrir otra pestaña del navegador se puede confirmar que los datos se sincronizan en tiempo real

Documentación y canales de contacto

1 comentarios

 
GN⁺ 2024-06-27
Opiniones de Hacker News
  • Probé Triplit en el proyecto https://github.com/thanhnguyen2187/cryptaa y funcionó como esperaba.
    El modelo de datos encaja bien con una idea más distribuida/P2P que con una única base de datos central como fuente de verdad, pero el self-hosting y el lenguaje de consultas dejan que desear.
    La documentación no dejaba claro cómo generar tokens de autenticación del servidor, así que creé uno con el comando dev de la CLI; que el token quede en logs en texto plano en un servicio del sistema no es bueno para la seguridad, aunque creo que presupone un problema mayor de permisos de acceso.
    El DSL de consultas personalizado no tiene la expresividad de SQL, como UNIQUE o COUNT, así que tuve que hacer algunas agregaciones manualmente.
    Hace poco vi Evolu https://www.evolu.dev/docs y parece tener un alcance y funciones similares; Triplit tiene .subscribe() y Evolu no, Evolu usa SQL tipado basado en Kysely, por lo que las consultas resultan más familiares y avanzadas, y en el navegador Evolu usa SQLite sobre OPFS, mientras que Triplit parece usar IndexedDB.
    Publicación que hice en Reddit: https://www.reddit.com/r/sveltejs/comments/1dndpj8/cryptaa_a...

    • Estamos reorganizando la documentación de self-hosting para que la configuración quede más clara, y los puntos que señalaste ayudan.
      En cuanto a consultas, todavía no hay agregaciones, pero están en el roadmap; creemos que aprovechar el motor de consultas incremental abre posibilidades muy interesantes.
      Por ejemplo, en un dashboard de datos que se actualiza cada hora, con sistemas tradicionales (Postgres, MongoDB, etc.) habría que volver a ejecutar la consulta desde cero cada vez, pero si se procesan solo los datos nuevos, con un enfoque más cercano a Materialize, se puede mantener actualizado de forma mucho más eficiente.
      Todavía no probé Evolu directamente, pero puede que alguien lo haya comparado en Discord: https://triplit.dev/discord
    • Gracias por mencionar Evolu; tanto Triplit como Evolu se ven interesantes, y me gustaría ver una comparación entre ambos.
    • Evolu también admite subscribe, ya sea con useQuery o de forma separada.
  • Cuando se usan DB con estos excelentes protocolos de sincronización offline, me pregunto cómo manejan la evolución del esquema cuando no se pueden actualizar al mismo tiempo distintas versiones de clientes.
    Tengo el contexto de haber sufrido este problema antes en una app móvil de salud.

    • Lo mejor es crear solo tablas nuevas y no hacer cambios que rompan la compatibilidad en las tablas existentes.
      Si hace falta, terminas haciendo escrituras dobles en ambas versiones.
      Es parecido a hacer una migración live sin downtime de un cambio incompatible en una DB SQL, pero como el momento de transición depende del cliente, tienes que mantener esa lógica durante más tiempo.
      También es importante tener una tabla que coordine la versión más reciente, y si hay un cambio incompatible, hacer que los clientes atrasados le pidan al usuario que actualice.
      En función de la versión mínima soportada en todos los clientes, también puedes decidir cuánto tiempo mantener las lecturas/escrituras dobles.
    • En resumen, la forma más sencilla de garantizar compatibilidad es mantener la compatibilidad hacia atrás del esquema.
      Triplit muestra una advertencia si generas un cambio que no es compatible hacia atrás, como se ve en la documentación: https://www.triplit.dev/docs/schemas/updating#pushing-the-sc...
      Dicho eso, con el tiempo puede surgir naturalmente una definición de esquema desordenada, con muchos nombres confusos.
      Todavía no lanzamos una solución para corregirlo, pero estamos trabajando en varias cosas para hacerlo menos doloroso; para el contexto de distintos enfoques, el documento de Cambria es excelente: https://www.inkandswitch.com/cambria/
    • Creo que algunos clientes, por ejemplo el servidor, deberían poder sincronizar incluso con esquemas muy antiguos.
      Un usuario podría haber dejado el teléfono guardado en un cajón durante 2 años, así que basta con que cada cliente se migre a sí mismo lo antes posible.
    • Tener una forma integrada de definir y soportar migraciones históricas sería una killer feature.
      Evitaría que todos tengan que reinventar su propia gestión de migraciones.
  • No termino de entender para qué apps está bien que el cliente pueda escribir directamente en la DB, ni cómo se sostiene eso sin lógica de backend.
    Tengo la misma duda sobre Supabase y Firestore, así que siento que algo se me escapa.

    • La mayoría de las cosas que se construyen en el mundo real casi no tienen lógica de negocio y son básicamente CRUD.
      En entornos empresariales obviamente ocurre lo contrario, y me frustra ver discusiones que ignoran eso.
      Sobre todo en Twitter tecnológico, cuando veo a gente defender cierto stack o forma de trabajar, muchas veces es demasiado evidente que solo han hecho CRUD y nunca sistemas de negocio, por lo que no entienden por qué desarrolladores con experiencia no están de acuerdo.
    • Una vez hice una app colaborativa con Firebase, y funciona más o menos si limitas fuertemente lo que cada persona puede hacer con sus propios comentarios o tarjetas y solo les das permisos para acciones específicas.
      No creo que encaje bien con cosas que tienen mucha lógica de backend.
    • Ambos tienen control de acceso aplicado en el backend, así que no es que no haya nada de lógica de backend.
      En Supabase, por ejemplo, existe una función llamada seguridad a nivel de fila.
      El cliente puede enviar solicitudes a Supabase, pero Supabase ejecuta consultas adicionales en el backend para decidir si la solicitud entrante está permitida.
      Como ejemplo simple, se puede permitir leer, escribir y actualizar una fila solo cuando el valor de la columna UserID coincide con el usuario autenticado que hizo la solicitud.
  • Veníamos guardando las configuraciones de usuario en Triplit, y esas configuraciones debían poder ser administradas por un administrador
    Los usuarios debían sentir que la app siempre corría localmente, y la calidad de internet a menudo no era buena, pero como usaban varios dispositivos y los administradores debían ver y gestionar la configuración de otros usuarios, hacía falta sincronización
    En general, Triplit fue excelente tanto en la experiencia de desarrollo frontend como en el soporte; cuando encontrábamos issues o solicitudes de funciones, el equipo las resolvía muy rápido
    Cuando haya una respuesta sobre el despliegue de alta disponibilidad, también planeamos mover datos más importantes en lugar de Postgres

  • Me da curiosidad por qué eligieron la licencia AGPL

    • Queríamos que Triplit fuera fácil de self-hostear con la licencia AGPL, y a la vez asegurarnos de que quienes lo modifiquen devuelvan esos cambios a la comunidad
    • Quisiera evitar que, por usar esta DB, mi producto también tenga que ser AGPL
  • Creo haber visto una presentación de YouTube https://www.youtube.com/playlist?list=PLTbD2QA-VMnXFsLbuPGz1... en el servidor de Discord de Local First https://localfirstweb.dev/, así que me da gusto verlo en Show HN
    Como no uso TypeScript, quizá no sea el público objetivo principal, y a diferencia de la web, uso local-first sobre todo en apps móviles con conectividad inestable, con Flutter y un backend en Rust
    Otras soluciones local-first como ElectricSQL y PowerSync sincronizan directamente las DB del cliente y del servidor, por lo que son más independientes del cliente/servidor
    Las soluciones basadas en CRDT también pueden usarse en cliente y servidor mediante FFI; por ejemplo, automerge está en Rust, así que del lado de Flutter puede usarse con FFI vía flutter_rust_bridge, en la web con WASM y en el backend con Rust
    Triplit parece más una sincronización cliente-servidor clásica, donde el servidor es la fuente de verdad, que una resolución sin conflictos entre distintos clientes
    Me da curiosidad por qué eligieron una solución a nivel de lenguaje en lugar de un enfoque de capa de DB más independiente del cliente y del servidor, y parece difícil que en el futuro soporten lenguajes y frameworks que no estén basados en JS
    Además, parece que quieren competir con Supabase, pero Supabase también está experimentando con sincronización a nivel de DB en Postgres y CRDT, así que quizá podría alcanzarlos https://news.ycombinator.com/item?id=33931971

    • Hemos pensado mucho en Flutter y otros soportes nativos, y Flutter en particular aparece con frecuencia
      Sin embargo, decidimos empezar concentrándonos en TypeScript puro: creemos que el mercado es lo bastante grande, confiamos en el futuro de las PWA y pensamos que enfocarnos ahí nos permite crear la mejor experiencia
      Algún día probablemente hagamos algo más independiente de la plataforma, pero no está claro cuándo
      Los equipos de ElectricSQL y Supabase son excelentes y muy reflexivos, y parece que seguirán creciendo en el ámbito de SQL; esa es la diferencia más fundamental de enfoque
      En Triplit creemos que evitar SQL permite ofrecer a los desarrolladores la mejor experiencia, y hay espacio suficiente para que ambas filosofías coexistan
  • Si es LWW, me pregunto si la cantidad de información del cliente crece linealmente con el número de operaciones
    Es decir, si mientras el usuario modifica más la DB el log de operaciones sigue creciendo, o si usan checkpoints, y cómo escala en términos de espacio cuando un usuario hace millones de operaciones por día

    • Triplit guarda el historial de modificaciones de un atributo específico, pero las consultas se mantienen rápidas gracias al índice del valor más reciente
      Dicho eso, el registro LWW en sí no exige guardar historial; es solo la implementación actual para que incluso los clientes que estuvieron offline mucho tiempo puedan sincronizarse de forma eficiente
      No diría que ya hayamos llegado del todo a un millón de operaciones al día, pero que el servidor tenga autoridad es una ventaja
      En el futuro, el servidor de Triplit podría rastrear la marca de tiempo de la última sincronización de cada cliente y podar el historial progresivamente, de forma similar a como Postgres procesa con VACUUM las tuplas muertas
  • Sería bueno tener bindings de Rust para poder usarlo en Tauri
    Si se combinan el crecimiento de Tauri, el soporte para dispositivos móviles que viene pronto y la reciente popularidad de SQLite, podría cerrar la brecha para apps offline-first y convertirse en la opción predeterminada para muchos equipos de desarrollo

    • Estoy intentando agregar bindings de Rust a ElectricSQL, una solución de sincronización parecida
      ElectricSQL funciona en la capa de DB, así que es independiente del lenguaje, y como usa Rust en el servidor, los bindings de Rust podrían funcionar tanto en cliente como en servidor
      Si quieres desarrollarlo en conjunto, avísame
    • Me parece que Tauri usa un renderer web nativo
      Si es así, Triplit debería funcionar directamente
  • He estado usando Triplit durante un tiempo en una app de React Native y funciona muy bien
    Lo recomiendo mucho; fue la única DB local-first que cumplía todos los requisitos que necesitaba
    Tiene un lenguaje de consultas adecuado y sensato (no SQL), excelente soporte de TypeScript, soporte offline, soporte para React Native, y también me gusta que sea open source y self-hosteable

  • Me pregunto si no es posible usarlo junto con una DB PostgreSQL existente

    • Por ahora no, pero tenemos una herramienta interna que hace sincronización bidireccional usando el protocolo de replicación de Postgres y WAL2JSON
      Todavía no está lista para publicarse, pero queremos que la gente pueda probarla pronto
    • Te convendría mirar ElectricSQL, que funciona con un Postgres existente
      Yo también me estoy inclinando por usar eso