2 pontos por GN⁺ 2024-06-27 | 1 comentários | Compartilhar no WhatsApp
  • Triplit é um banco de dados open source que sincroniza dados em tempo real entre o servidor e o navegador, e se apresenta como um banco de dados full-stack usado no app como um pacote Typescript
  • Ele lida tanto com o armazenamento no servidor quanto com a sincronização de consultas no cliente, oferecendo atualizações incrementais, resolução de conflitos por atributo, cache local, modo offline e reconexão automática
  • Suporta armazenamentos plugáveis como SQLite, IndexedDB, LevelDB e Memory, além de fornecer armazenamento persistente no lado do servidor e um dashboard de administração
  • APIs de consulta e mutação podem ser usadas em React e Javascript vanilla, e ele é oferecido como um monorepo composto por React·Svelte bindings, CLI, Console e Server
  • Também oferece atualizações otimistas para interações rápidas, rollback e nova tentativa de atualizações com falha, permissões de leitura e escrita impostas pelo servidor e recursos de colaboração baseados em CRDT

O que o Triplit oferece

  • Triplit é um banco de dados open source que sincroniza dados em tempo real entre o servidor e o navegador
  • Fornece um armazenamento de dados sincronizado que pode ser adicionado ao app como um pacote Typescript
  • Armazena dados no servidor e sincroniza de forma inteligente as consultas do cliente
  • O Triplit chama esse modelo de banco de dados full-stack
    • Também há um vídeo de apresentação da comunidade Local First: presentation

Principais recursos

  • Sincronização em tempo real

    • Suporta atualizações incrementais
    • Oferece resolução de conflitos por atributo
  • Usabilidade local-first

    • Fornece cache local com base em um banco de dados totalmente no lado do cliente
    • Faz com que todas as interações pareçam rápidas com atualizações otimistas
    • Suporta modo offline, reconexão automática e garantia de consistência
  • Armazenamento e operação no servidor

    • Fornece armazenamento persistente no lado do servidor
    • Inclui um dashboard de administração
    • Suporta provedores de armazenamento plugáveis como SQLite, IndexedDB, LevelDB e Memory
  • Modelo de dados e API

    • Suporta consultas relacionais para modelos de dados complexos
    • Fornece segurança de dados e autocompletar do Typescript por meio de schema
    • Oferece uma API simples para consultas e mutações em Javascript vanilla e React
  • Colaboração e segurança

    • Impõe permissões no servidor tanto para leitura quanto para escrita
    • Fornece recursos de colaboração e multiplayer baseados em CRDTs
    • Usa delta patches para reduzir o tráfego de rede e buscar baixa latência
    • Gerencia rollback e nova tentativa para atualizações com falha

Estrutura do monorepo

  • TriplitDB: banco de dados projetado para rodar em ambientes JS como navegador, Node, Deno e React Native, oferecendo consultas rápidas com atualização ao vivo enquanto mantém consistência entre múltiplos autores na rede
  • Client: biblioteca para navegador que interage com TriplitDB local e remoto
  • CLI: ferramenta de linha de comando para scaffolding de projetos, execução do ambiente de desenvolvimento full-stack, migrações de servidor etc.
  • React: React binding para @triplit/client
  • Svelte: Svelte binding para @triplit/client
  • Console: app para visualizar e alterar dados de projetos Triplit e gerenciar schema
  • Server: servidor Node que sincroniza dados entre clientes Triplit
  • Server-core: biblioteca independente de protocolo para criar servidores Triplit
  • Docs: documentação do Triplit feita com Nextra
  • Types: tipos compartilhados pelos projetos Triplit
  • UI: componentes de UI compartilhados baseados em shadcn

Fluxo de início rápido

  • Um novo projeto começa com npm create triplit-app@latest my-app
  • Em um projeto existente, instale @triplit/cli como dependência de desenvolvimento e depois execute npm run triplit init
  • Defina o schema em my-app/triplit/schema.ts
    • O exemplo define os campos id, text e completed na coleção todos
    • completed é configurado como um campo Boolean com valor padrão false
  • Inicie o servidor de sincronização de desenvolvimento com npm run triplit dev
  • O servidor de desenvolvimento exibe as variáveis de ambiente necessárias para o app sincronizar com o servidor
    • No exemplo com Vite: VITE_TRIPLIT_SERVER_URL=http://localhost:6543
    • VITE_TRIPLIT_TOKEN=copied-in-from-triplit-dev

Exemplo de uso com React e verificação da sincronização

  • O exemplo com React usa TriplitClient e useQuery
  • O cliente é criado com schema, URL do servidor e token
  • Use useQuery(client.query('todos')) para assinar os resultados da consulta todos
  • Ao mudar o checkbox, use client.update para alternar o valor de completed
  • Depois de iniciar o app, ao abrir outra aba do navegador, é possível verificar que os dados são sincronizados em tempo real

Documentação e canais de contato

1 comentários

 
GN⁺ 2024-06-27
Opiniões no Hacker News
  • Usei o Triplit no projeto https://github.com/thanhnguyen2187/cryptaa e ele funcionou como esperado
    O modelo de dados combina bem com uma concepção mais distribuída/P2P, em vez de ter um único banco de dados central como fonte da verdade, mas self-hosting e a linguagem de consulta deixam a desejar
    Como a documentação não deixava claro como gerar tokens de autenticação do servidor, criei um token com o comando dev da CLI; o fato de o token ficar em logs em texto puro no serviço do sistema não é bom do ponto de vista de segurança, embora eu veja isso como algo que pressupõe um problema maior de permissões de acesso
    A DSL de consulta personalizada não tem a expressividade de coisas como UNIQUE e COUNT em SQL, então é preciso fazer algumas agregações manualmente
    Vi recentemente o Evolu https://www.evolu.dev/docs, e o escopo e os recursos parecem semelhantes; o Triplit tem .subscribe() e o Evolu não, enquanto o Evolu usa SQL tipado baseado em Kysely, então as consultas são mais familiares e avançadas; no navegador, o Evolu usa SQLite sobre OPFS, enquanto o Triplit parece usar IndexedDB
    Post que publiquei no Reddit: https://www.reddit.com/r/sveltejs/comments/1dndpj8/cryptaa_a...

    • A documentação de self-hosting está sendo reorganizada para deixar a configuração mais clara, e os pontos que você levantou ajudam
      No lado das consultas, ainda não há agregações, mas isso está no roadmap; acho que, aproveitando o mecanismo de consultas incrementais, é possível seguir por caminhos bem interessantes
      Por exemplo, em um dashboard de dados atualizado a cada hora, nos sistemas existentes (Postgres, MongoDB etc.) seria preciso reexecutar a consulta do zero toda vez, mas, com uma abordagem mais próxima do Materialize, processando apenas os novos dados, ele poderia continuar sendo atualizado de forma muito mais eficiente
      Ainda não usei o Evolu diretamente, mas pode haver alguém no Discord que já tenha feito uma comparação: https://triplit.dev/discord
    • Obrigado por apresentar o Evolu; Triplit e Evolu parecem interessantes, e eu gostaria de ver uma comparação entre os dois
    • O Evolu também oferece suporte a subscribe, via useQuery ou por um modo separado
  • Ao usar um banco de dados com um protocolo de sincronização offline tão bom, fico curioso sobre como lidar com a evolução do schema quando não é possível atualizar versões diferentes dos clientes ao mesmo tempo
    Tenho o contexto de já ter sofrido com isso em um app móvel de saúde no passado

    • O melhor é criar apenas novas tabelas e não fazer mudanças que quebrem compatibilidade nas tabelas existentes
      Se necessário, acaba-se fazendo escrita dupla nas duas versões ao mesmo tempo
      É parecido com fazer uma migração ao vivo, sem downtime, de uma mudança incompatível em um banco SQL, mas o momento da transição depende do cliente, então é preciso manter essa lógica por mais tempo
      Uma tabela que coordene a versão mais recente também é importante e, se houver uma mudança incompatível, clientes desatualizados devem pedir ao usuário para atualizar
      Também dá para decidir por quanto tempo manter leitura/escrita dupla vinculando isso à versão mínima suportada em todos os clientes
    • Em resumo, manter compatibilidade retroativa no schema é a forma mais fácil de garantir compatibilidade
      O Triplit exibe um aviso quando você faz uma alteração que não é compatível retroativamente, como dá para ver na documentação: https://www.triplit.dev/docs/schemas/updating#pushing-the-sc...
      Dito isso, com o tempo pode surgir naturalmente uma definição de schema bagunçada, cheia de nomes confusos
      Ainda não lançamos uma solução para corrigir isso, mas estamos trabalhando em algumas coisas para tornar o processo menos doloroso; como pano de fundo para várias abordagens, o documento do Cambria é excelente: https://www.inkandswitch.com/cambria/
    • Acho que alguns clientes, como o servidor, precisam conseguir sincronizar até com schemas muito antigos
      O usuário pode ter deixado o celular guardado em uma gaveta por 2 anos, então cada cliente deve migrar a si próprio o mais rápido possível
    • Ter uma forma integrada de definir e oferecer suporte a migrações antigas parece que seria um recurso matador
      Isso evitaria que todo mundo tivesse que reinventar sua própria forma de gerenciar migrações
  • Não entendo bem em que tipo de app faz sentido o cliente poder escrever diretamente no banco de dados, nem como isso se sustenta sem lógica de backend
    Tenho a mesma dúvida sobre Supabase e Firestore, então parece que estou deixando passar alguma coisa

    • A maioria das coisas criadas no mundo real quase não tem lógica de negócio e é basicamente CRUD
      No ambiente corporativo, é claro que é o contrário, e é frustrante ver discussões que ignoram isso
      Especialmente no Twitter de tecnologia, quando vejo alguém defendendo uma stack ou uma forma de trabalhar, muitas vezes fica óbvio demais que a pessoa só fez CRUD e nunca criou sistemas de negócio; por isso ela não entende por que desenvolvedores experientes não concordam
    • Já criei um app colaborativo com Firebase e, se você limitar fortemente o que cada pessoa pode fazer com seus próprios comentários ou cartões e conceder apenas permissões para ações específicas, funciona razoavelmente
      Não acho que combine bem com algo que tenha muita lógica de backend
    • Ambos têm controle de acesso imposto no backend, então não é que não exista nenhuma lógica de backend
      No Supabase, por exemplo, há um recurso chamado segurança em nível de linha
      O cliente pode enviar uma requisição ao Supabase, mas o Supabase executa consultas adicionais no backend para decidir se a requisição recebida é permitida
      Um exemplo simples: permitir ler, escrever e atualizar uma linha somente quando o valor da coluna UserID for igual ao usuário autenticado que fez a requisição
  • Temos armazenado as configurações dos usuários no Triplit, e essas configurações precisavam poder ser gerenciadas por administradores
    O usuário deve sentir que o app está sempre rodando localmente, e a qualidade da internet também costuma ser ruim; porém, como ele usa vários dispositivos e administradores precisam ver e gerenciar as configurações de outros usuários, era necessária sincronização
    No geral, o Triplit tem sido excelente tanto na experiência de desenvolvimento frontend quanto no suporte; quando encontramos issues ou pedidos de funcionalidades, a equipe resolve tudo muito rapidamente
    Quando houver uma resposta para deploy de alta disponibilidade, pretendemos mover também dados mais críticos para ele, em vez do Postgres

  • Fiquei curioso sobre por que escolheram a licença AGPL

    • Queríamos permitir que o Triplit fosse facilmente auto-hospedado com a licença AGPL e, ao mesmo tempo, garantir que quem o modificasse contribuísse essas mudanças de volta para a comunidade
    • Se o DB usado obrigar o produto também a ser AGPL, eu preferiria evitar
  • Acho que vi a apresentação no YouTube https://www.youtube.com/playlist?list=PLTbD2QA-VMnXFsLbuPGz1... no servidor Discord Local First https://localfirstweb.dev/, então fiquei feliz em ver isso no Show HN
    Como não uso TypeScript, talvez eu não seja o público-alvo principal; diferentemente da web, uso local-first sobretudo em apps móveis com conexões instáveis, com Flutter e backend em Rust
    Outras soluções local-first, como ElectricSQL e PowerSync, sincronizam diretamente os DBs do cliente e do servidor, portanto são mais independentes de cliente/servidor
    Soluções baseadas em CRDT também podem ser usadas no cliente e no servidor via FFI; por exemplo, o automerge é em Rust, então no Flutter dá para usá-lo via FFI com flutter_rust_bridge, na web via WASM e no backend em Rust
    O Triplit parece mais uma sincronização cliente-servidor mais clássica, tendo o servidor como fonte da verdade, em vez de uma resolução sem conflitos entre diferentes clientes
    Fico curioso sobre por que escolheram uma solução em nível de linguagem, em vez de uma abordagem de camada de DB mais independente de cliente e servidor, e parece difícil dar suporte no futuro a linguagens e frameworks que não sejam baseados em JS
    Além disso, parece que vocês querem competir com o Supabase, mas o Supabase também está experimentando sincronização em nível de DB do Postgres e CRDTs, então talvez consiga alcançar vocês https://news.ycombinator.com/item?id=33931971

    • Temos pensado bastante em suporte nativo a Flutter e outras plataformas, e Flutter em especial é mencionado com frequência
      Ainda assim, decidimos começar focando em TypeScript puro; acreditamos que o mercado é grande o suficiente, confiamos no futuro das PWAs e achamos que precisamos nos concentrar nisso para criar a melhor experiência
      Em algum momento provavelmente criaremos algo mais independente de plataforma, mas o momento ainda não está claro
      As equipes do ElectricSQL e do Supabase são excelentes e cuidadosas, e parecem que continuarão crescendo no espaço de SQL; essa é a diferença mais fundamental de abordagem
      O Triplit acredita que, ao evitar SQL, pode oferecer a melhor experiência aos desenvolvedores, e há bastante espaço para as duas filosofias coexistirem
  • Se for LWW, fico curioso se a quantidade de informações no cliente cresce linearmente com o número de operações
    Ou seja, quanto mais o usuário modifica o DB, o log de operações continua crescendo, ou há checkpoints? E como isso escala em termos de espaço quando um usuário faz milhões de operações por dia?

    • O Triplit armazena o histórico de alterações de determinados atributos, mas as consultas continuam rápidas graças ao índice do valor mais recente
      No entanto, o registro LWW em si não exige armazenar histórico; essa é apenas a implementação atual para permitir que clientes que ficaram muito tempo offline também sincronizem com eficiência
      Ainda não dá para dizer que chegamos plenamente a um milhão de operações por dia, mas há a vantagem de o servidor ter autoridade
      No futuro, o servidor Triplit poderá rastrear o timestamp da última sincronização de cada cliente e podar o histórico gradualmente, de modo semelhante a como o Postgres trata tuplas mortas com VACUUM
  • Seria bom ter bindings Rust para usar no Tauri
    Com o crescimento do Tauri, o suporte a dispositivos móveis que está chegando e a popularidade recente do SQLite, isso poderia preencher a lacuna dos apps offline-first e se tornar a escolha padrão para muitas equipes de desenvolvimento

    • Estou tentando adicionar bindings Rust ao ElectricSQL, uma solução de sincronização parecida
      O ElectricSQL opera na camada de DB, por isso é independente de linguagem; como ele usa Rust no servidor, os bindings Rust poderiam funcionar tanto no cliente quanto no servidor
      Se quiser desenvolver isso junto, seria ótimo me avisar
    • Acho que o Tauri usa o renderizador web nativo
      Se for o caso, o Triplit deve funcionar imediatamente
  • Tenho usado o Triplit há algum tempo em um app React Native e ele funciona muito bem
    Recomendo muito; foi o único DB local-first que atendia a todos os meus requisitos
    Tem uma linguagem de consulta adequada e sensata (não SQL), ótimo suporte a TypeScript, suporte offline, suporte a React Native, e também é bom que seja open source e possa ser auto-hospedado

  • Fico curioso se não é possível usá-lo junto com um DB PostgreSQL existente

    • No momento não, mas temos uma ferramenta interna que faz sincronização bidirecional usando o protocolo de replicação do Postgres e WAL2JSON
      Ainda não está pronta para ser divulgada, mas queremos permitir que as pessoas testem em breve
    • Vale a pena dar uma olhada no ElectricSQL, que funciona com Postgres existente
      Eu também estou inclinado a usar essa opção