Skip to content
@synclib-io

synclib.io

offline sync and data streams on any platform

synclib

Offline-first bidirectional database sync. Sequence numbers for speed, Merkle trees for correctness.

Clients keep a local SQLite database that syncs with a Postgres-backed server over Phoenix channels. Writes happen locally and sync when connected — no loading spinners, no network dependency for reads.

How it works

Client (SQLite) Server (Postgres)
┌─────────────┐ WebSocket / Phoenix ┌──────────────────┐
│ local writes│── push ─────────────► │ apply + broadcast│
│ │◄─ pull ────────────── │ seqnum triggers │
│ merkle tree │◄─ verify ──────────► │ row hashes │
└─────────────┘ └──────────────────┘

Layer 1 — Sequence numbers: Every write gets a monotonically increasing seqnum. Clients track the last-seen seqnum per table and pull only newer rows. Fast incremental sync.

Layer 2 — Merkle trees: Periodic SHA256-based integrity checks compare hash roots between client and server, drill into differing blocks, and repair only the rows that drifted.

Synclib supports two Merkle approaches depending on your needs:

  • Server-authoritative hashes (default): The server computes row_hash via Postgres triggers (pg_synclib_hash) and clients store the server-provided value. Merkle comparison checks that client and server have the same data — if hashes differ, the data drifted and gets repaired. This is the simpler approach and is preferred for most sync use cases where the goal is consistency between devices.

  • Client-verified hashes: Clients use synclib_hash directly to compute hashes locally from their own data, then compare against the server's hashes. This verifies correctness — that the data wasn't corrupted in transit or storage. Use this when your application needs to guarantee data integrity beyond just sameness (e.g., financial records, medical data, audit trails).

Both approaches use the same synclib_hash library and produce identical hashes, so they can be mixed. Most applications should start with server-authoritative hashes and add client-side verification only where correctness guarantees are needed.

Repositories

Server

RepoDescription
synclib-serverElixir/Phoenix sync server with seqnum triggers, Merkle verification, and scoped broadcasting

Client libraries

RepoDescription
synclib-client-jsTypeScript sync client — channels, push/pull, Merkle verification
synclib_client_flutterDart/Flutter sync client — same capabilities, native mobile + desktop

Database wrappers

RepoDescription
synclib-jsTypeScript/JavaScript SQLite wrapper with automatic change tracking
synclib_flutterDart/Flutter SQLite wrapper with automatic change tracking

Hashing

RepoDescription
synclib-hashCross-platform Merkle tree hashing (WASM + native). Used by pg-synclib-hash for server-side computation and available to clients for local correctness verification
pg-synclib-hashPostgreSQL extension — computes row hashes at write time via triggers (server-authoritative)

Core

RepoDescription
synclibcC library for SQLite change tracking — compiled to native and WASM, used by all client wrappers

Testing

RepoDescription
synclib-test-harnessEnd-to-end test harness for validating sync across clients and server

Firestore compatibility

RepoDescription
synclib-firestore-flutterDart/Flutter Firestore-compatible API for local SQLite via synclib
synclib-firestore-jsTypeScript Firestore-compatible API for local SQLite via synclib

Quick start

TypeScript

import{SyncClient,ChannelRole}from'synclib-sync';constclient=newSyncClient({
db,serverUrl: 'wss://api.example.com/socket',clientId: 'device-abc',channels: [{topic: 'sync:user:user-123',role: ChannelRole.PUSH,tables: [{name: 'todos'}],}],});awaitclient.initialize();awaitclient.connect(token);

Dart / Flutter

import'package:synclib_sync/synclib_sync.dart';
final client =SyncClient(SyncClientConfig(
database: db,
serverUrl:'wss://api.example.com/socket',
clientId:'device-abc',
channels: [
SyncChannel(
topic:'sync:user:user-123',
role:ChannelRole.push,
tables: [SyncTable('todos')],
),
],
));
await client.initialize();
await client.connect(token: token);

Server (Elixir)

mix deps.get && mix ecto.setup && mix phx.server

Implement the 6 behaviours for your use case — channel joins, snapshot queries, change handling, broadcast routing, connection hooks, and schema management.

Key features

  • Offline-first — local SQLite with automatic change tracking. Works without a connection, syncs when online.
  • Bidirectional — push, pull, or bidirectional per channel. Per-table direction overrides.
  • Schema migrations — server-driven. Clients receive SQLite DDL over the sync channel and apply automatically.
  • Multi-platform — TypeScript for web/Node.js, Dart/Flutter for iOS/Android/desktop, Elixir for the server.
  • Phoenix channels — persistent WebSocket connections with scoped broadcasting (user, group, world).
  • JWT auth — HS256 and RS256 token verification on every connection.
  • Extensible — custom message types, conflict resolvers, and server-side behaviours.

Architecture

synclibc (C)
├── synclib_flutter (Dart FFI)
│ └── synclib_client_flutter (sync client)
│ └── synclib-firestore-flutter (Firestore API)
├── synclib-js (TypeScript, WASM)
│ └── synclib-client-js (sync client)
│ └── synclib-firestore-js (Firestore API)
└── synclib-hash (WASM + native)
└── pg-synclib-hash (Postgres extension)
synclib-server (Elixir/Phoenix)
├── seqnum triggers
├── merkle verification
├── scoped broadcasting
└── schema management

License

All repositories are MIT licensed.


synclib.io

Popular repositories Loading

  1. synclib-io.github.io synclib-io.github.ioPublic

    HTML

  2. synclib_flutter synclib_flutterPublic

    Dart

  3. synclib_client_flutter synclib_client_flutterPublic

    Dart

  4. synclib-js synclib-jsPublic

    TypeScript

  5. synclib-client-js synclib-client-jsPublic

    TypeScript

  6. synclib-hash synclib-hashPublic

    C

Repositories

Showing 10 of 11 repositories

Top languages

Loading…

Most used topics

Loading…