Open Source · Apache 2.0
The Right Architecture for Data-Intensive Apps

The Sync Layer
Your App Has Been Missing

Your app should never wait on a network call to write data.
Embed a real local database — SQLite, DuckDB, Derby, H2, or HyperSQL — and get full ACID transactions at native speed with zero connectivity dependency. SyncLite captures every committed transaction and replicates it automatically to your central store.

🚫 No custom CDC code. 🚫 No message brokers to operate. 🛡️ No data loss on network failure. This is how modern apps should handle data — and SyncLite makes it a one-day integration.

Native Database Speed
Reads and writes hit an embedded engine directly — no network roundtrip, no latency penalty. Your app is always fast.
🔒
Zero Data Loss, Offline-Ready
Every committed transaction is logged locally. SyncLite delivers it to the destination when connectivity allows — exactly once, automatically.
🧹
Cut Your Data Stack in Half
Batching, retries, schema evolution, exactly-once delivery — all handled. You write SQL. SyncLite replaces 4–5 point solutions with one platform.
Read the Docs → View on GitHub
Local-First Apps Edge-to-Cloud Sync Real-Time Pipelines Database ETL & Replication IoT & MQTT Ingestion Agent Memory

How It Works

Your App
SyncLite Logger
SQLite · DuckDB · Derby · H2 · HyperSQL
──▶
Binary Log Files
Staging Storage
Local · SFTP · S3 · MinIO · Kafka
──▶
Always-On Sink (Embedded/Standalone)
SyncLite Consolidator
──▶
Your Destination
PostgreSQL · MySQL · MS SQL Server
DuckDB · Apache Iceberg · MongoDB...

Sources produce compact binary logs → shipped to staging → Consolidator delivers in real time. Sub-second latency on local stages.

Built for Every Data Sync Problem

One platform, five problem domains. Pick the one you need today — the architecture handles the rest.

📱

Local-First & Offline Apps

Embed SQLite or DuckDB in your desktop, mobile, or edge app. SyncLite replicates every write to the cloud automatically — your app keeps working offline, data syncs when connectivity returns.

☁️

Edge-to-Cloud Sync

Deploy hundreds of edge devices. Each runs a local embedded DB. SyncLite consolidates all of them into a single cloud database in real time — without you writing a line of replication code.

Real-Time Streaming Pipelines

Use the SyncLiteStream API or Kafka Producer-compatible interface for high-throughput append-only event ingestion. Land events in any data warehouse or lake with exactly-once semantics.

🔄

Database ETL & Replication

SyncLite DBReader connects to PostgreSQL, MySQL, Oracle, SQL Server, and more. Replicate tables incrementally via watermarks, or capture changes at the binary log level for near-zero latency.

📡

IoT & MQTT Ingestion

SyncLite QReader subscribes to any MQTT v3.1 broker — Mosquitto, EMQX, AWS IoT Core, Azure IoT Hub. Parse CSV or JSON payloads and land sensor data in your analytics DB in minutes.

🤖

Agent Memory & GenAI

Give AI agents a durable, queryable local memory store backed by SQLite. All state changes are automatically replicated to a central database for observability, replayability, and multi-agent coordination.

Start Syncing in Minutes

Native libraries for Rust, Python, and C++ — plus the Java JDBC driver and a language-agnostic HTTP API. Pick your stack, SyncLite handles the rest.

// Pure-Rust SyncLite. Offline-first SQLite syncing to PostgreSQL.
use synclite::{SyncLiteOptions, DestinationOptions, DstType, DstSyncMode, DeviceType, Value};
use synclite::rusqlite::Connection;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Wire up logger + shipper + embedded consolidator in one call.
    synclite::initialize(
        DeviceType::Sqlite,
        "orders-device",                 // device name
        "orders.db",                       // local SQLite path
        Some(DestinationOptions {
            dst_type: DstType::Postgres,
            dst_connection_string: "postgresql://postgres:postgres@localhost:5432/syncdb".into(),
            dst_database: Some("syncdb".into()),
            dst_schema:   Some("syncschema".into()),
            dst_sync_mode: DstSyncMode::Replication,
        }),
        SyncLiteOptions::default(),
    )?;

    // From here on the app talks to a plain local SQLite database.
    let mut conn = Connection::open("orders.db")?;
    conn.execute("CREATE TABLE IF NOT EXISTS orders(id INTEGER, item TEXT, qty INTEGER)", &[])?;
    conn.execute("INSERT INTO orders VALUES(?, ?, ?)",
        &[Value::Int(1), Value::Text("widget".into()), Value::Int(100)])?;
    conn.commit()?;

    // Roll the active log segment, then block until the apply lands in PostgreSQL.
    // NOTE: await_sync is only used here to demonstrate SyncLite's background sync in action —
    // apps normally just keep writing; SyncLite ships changes asynchronously in the background.
    conn.flush()?;
    synclite::await_sync("orders.db", std::time::Duration::from_secs(30))?;
    conn.close()?;
    // ↑ logged + shipped + consolidated into PostgreSQL.
    Ok(())
}
// Standard JDBC — SyncLite captures every transaction transparently.
// Offline-first SQLite syncing to PostgreSQL.

Path dbPath = Path.of("orders.db");

// Wire up logger + shipper + embedded consolidator in one call.
SQLite.initialize(dbPath, "orders-device",
    DestinationOptions.builder()
        .dstType(DstType.POSTGRES)
        .connectionString("jdbc:postgresql://localhost:5432/syncdb")
        .database("syncdb")
        .schema("syncschema")
        .syncMode(DstSyncMode.REPLICATION)
        .build());

// From here on the app talks to a plain local SQLite database via JDBC.
try (Connection conn = DriverManager.getConnection("jdbc:synclite_sqlite:" + dbPath);
     Statement  s    = conn.createStatement()) {

    s.execute("CREATE TABLE IF NOT EXISTS orders(id INT, item TEXT, qty INT)");
    s.execute("INSERT INTO orders VALUES(1, 'widget', 100)");
    conn.commit();

    ResultSet rs = s.executeQuery("SELECT * FROM orders WHERE id = 1");
    while (rs.next()) /* [READ FROM LOCAL DB] */ {}

    // Roll the active log segment, then block until the apply lands in PostgreSQL.
    // NOTE: awaitSync is only used here to demonstrate SyncLite's background sync in action —
    // apps normally just keep writing; SyncLite ships changes asynchronously in the background.
    SQLite.flush(dbPath);
    SyncLite.awaitSync(dbPath, Duration.ofSeconds(30));

    try (Connection pg = DriverManager.getConnection(
            "jdbc:postgresql://localhost:5432/syncdb", "postgres", "postgres");
         PreparedStatement ps = pg.prepareStatement(
            "SELECT id, item, qty FROM syncschema.orders WHERE id = ?")) {
        ps.setInt(1, 1);
        try (ResultSet pgrs = ps.executeQuery()) {
            while (pgrs.next()) /* [READ FROM POSTGRESQL POST SYNC] */ {}
        }
    }
}
SQLite.closeDevice(dbPath);
// ↑ logged + shipped + consolidated into PostgreSQL.
// SyncLiteStore — typed CRUD without raw SQL, schema evolution built-in
Class.forName("io.synclite.logger.SQLiteStore");
SQLiteStore.initialize(dbPath, conf);

try (SyncLiteStore store = SQLiteStore.open(dbPath)) {

    store.createTable("players", new LinkedHashMap<>(Map.of(
        "id", "INTEGER PRIMARY KEY", "name", "TEXT", "score", "INTEGER"
    )));

    store.insert("players", Map.of("id", 1, "name", "Alice", "score", 100));
    store.update("players", Map.of("score", 250), Map.of("name", "Alice"));
    store.delete("players", Map.of("id", 1));

    List<Map<String,Object>> rows = store.selectAll("players");
}
SQLiteStore.closeDevice(dbPath);
// SyncLiteStream — fluent append-only event ingestion
Class.forName("io.synclite.logger.Streaming");
Streaming.initialize(dbPath, conf);

try (SyncLiteStream stream = SyncLiteStream.open(dbPath)) {

    stream.createTable("events", new LinkedHashMap<>(Map.of(
        "ts", "BIGINT", "event_type", "TEXT", "user_id", "TEXT"
    )));

    stream.insert("events", Map.of(
        "ts", System.currentTimeMillis(), "event_type", "SIGNUP", "user_id", "u1"
    ));

    // New columns added inline — schema evolves automatically
    stream.insertBatch("events", List.of(
        Map.of("ts", System.currentTimeMillis(), "event_type", "VIEW",     "user_id", "u2", "source", "web"),
        Map.of("ts", System.currentTimeMillis(), "event_type", "PURCHASE", "user_id", "u3", "source", "app")
    ));
}
# SyncLite DBReader — job configuration file (not application code)
# Table/topic mappings are configured via the web UI at http://localhost:8080/synclite-dbreader

synclite-device-dir = /opt/synclite/devices
synclite-logger-configuration-file = /opt/synclite/synclite.conf

src-type = POSTGRESQL
src-connection-string = jdbc:postgresql://pg.internal:5432/sales
src-user = reader
src-password = secret
src-connection-timeout-s = 30

src-dbreader-method = INCREMENTAL
src-dbreader-interval-s = 10
src-dbreader-batch-size = 100000

src-object-type = TABLE
src-default-unique-key-column-list = id
src-default-incremental-key-column-list = updated_at
src-infer-schema-changes = true

# DBReader handles batching, retries, checkpoints, and restarts.
# SyncLite QReader — job configuration file (not application code)
# Topic-to-table mappings are configured via the web UI at http://localhost:8080/synclite-qreader

synclite-device-dir = /opt/synclite/devices
synclite-logger-configuration-file = /opt/synclite/synclite.conf

mqtt-broker-url = tcp://mqtt.example.com:1883
mqtt-qos-level = 1
mqtt-clean-session = true
mqtt-broker-connection-timeout-s = 10
mqtt-broker-connection-retry-interval-s = 2

src-message-format = CSV
src-message-field-delimiter = ,

qreader-synclite-device-type = SQLITE_APPENDER
qreader-map-devices-to-single-synclite-device = true
qreader-default-synclite-device-name = iot_device
qreader-default-synclite-table-name = iot_events

# Works with Mosquitto, EMQX, AWS IoT Core, and Azure IoT Hub.
# Python via the synclite PyO3 wheel. Offline-first SQLite syncing to PostgreSQL.
# The Connection / Statement objects match the Rust and C++ samples one-for-one.
import synclite as sl

# Wire up logger + shipper + embedded consolidator in one call.
sl.initialize(
    device_type="SQLITE",
    device_name="orders-device",                 # device name
    db_path="orders.db",                          # local SQLite path
    destination=sl.DestinationOptions(
        dst_type="POSTGRES",
        dst_connection_string="postgresql://postgres:postgres@localhost:5432/syncdb",
        dst_database="syncdb",
        dst_schema="syncschema",
        dst_sync_mode="REPLICATION",
    ),
)

# From here on the app talks to a plain local SQLite database.
conn = sl.Connection.open("orders.db")
conn.execute("CREATE TABLE IF NOT EXISTS orders(id INTEGER, item TEXT, qty INTEGER)")
conn.execute("INSERT INTO orders VALUES(?, ?, ?)", [1, "widget", 100])
conn.commit()

# Roll the active log segment, then block until the apply lands in PostgreSQL.
# NOTE: await_sync is only used here to demonstrate SyncLite's background sync in action —
# apps normally just keep writing; SyncLite ships changes asynchronously in the background.
conn.flush()
sl.await_sync("orders.db", 30.0)
conn.close()
# ↑ logged + shipped + consolidated into PostgreSQL.
// Header-only RAII wrapper over the synclite C ABI (synclite.hpp, C++17).
// Offline-first SQLite syncing to PostgreSQL.
#include "synclite.hpp"

int main() {
    synclite::DestinationOptions dst;
    dst.dst_type              = SYNCLITE_DST_POSTGRES;
    dst.dst_connection_string = "postgresql://postgres:postgres@localhost:5432/syncdb";
    dst.dst_database          = "syncdb";
    dst.dst_schema            = "syncschema";
    dst.dst_sync_mode         = "REPLICATION";

    synclite::initialize(SYNCLITE_DEVICE_SQLITE, "orders-device", "orders.db", &dst);

    // From here on the app talks to a plain local SQLite database.
    synclite::Connection conn("orders.db");
    conn.execute("CREATE TABLE IF NOT EXISTS orders(id INTEGER, item TEXT, qty INTEGER)");
    conn.execute("INSERT INTO orders VALUES(?, ?, ?)",
                  { synclite::Value(1), synclite::Value("widget"), synclite::Value(100) });
    conn.commit();

    // Roll the active log segment, then block until the apply lands in PostgreSQL.
    // NOTE: await_sync is only used here to demonstrate SyncLite's background sync in action —
    // apps normally just keep writing; SyncLite ships changes asynchronously in the background.
    conn.flush();
    synclite::await_sync("orders.db", 30.0);
    // ↑ logged + shipped + consolidated into PostgreSQL.
    return 0;
}
# Any language — plain HTTP/JSON via SyncLite DB server
# Works with Python, Go, Rust, C#, C++, Ruby, Node.js…
import requests

BASE = "http://localhost:5555/synclite"

# 1. Initialize
requests.post(BASE, json={
    "db-type": "SQLITE", "db-name": "myapp",
    "sql": "initialize"
})

# 2. DDL
requests.post(BASE, json={"db-name": "myapp",
    "sql": "CREATE TABLE IF NOT EXISTS orders(id INT, item TEXT)"})

# 3. Batched insert
requests.post(BASE, json={"db-name": "myapp",
    "sql": "INSERT INTO orders VALUES(?, ?)",
    "arguments": [[1, "widget"], [2, "gadget"]]})

# 4. SELECT — local read from the embedded database
resp = requests.post(BASE, json={"db-name": "myapp",
    "sql": "SELECT id, item FROM orders"})
print(resp.json())
// Jedis-compatible API for Redis-style commands on SyncLiteKV
try (SyncRedis redis = new SyncRedis("jdbc:synclite_sqlite:" + dbPath, conf)) {

  redis.set("session:u1", "active");
  redis.hset("profile:u1", "tier", "gold");
  redis.incrBy("counter:events", 1);

  String state = redis.get("session:u1");
}
// Commands are persisted locally and replicated through the standard SyncLite pipeline.
// Kafka Producer-compatible API backed by SyncLiteStream
Properties props = new Properties();
props.put("bootstrap.servers", "synclite://local");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

try (Producer<String, String> p = new KafkaProducer<>(props)) {
  p.send(new ProducerRecord<>("events", "u1", "signup"));
  p.send(new ProducerRecord<>("events", "u2", "purchase"));
}
// Same producer workflow, with SyncLite handling local durability and downstream replication.

Everything You Need. Nothing You Don't.

SyncLite is designed to disappear into your stack — minimal config, maximum reliability.

🔁

Exactly-Once Semantics

Transactional log capture ensures every committed write is delivered exactly once to the destination — no duplicates, no gaps.

📶

Offline Resilient

Edge devices work fully offline. Log files accumulate locally and sync automatically when connectivity is restored.

🔀

Many-to-Many

Thousands of edge devices consolidating into a single destination. One source fanning out to multiple destinations simultaneously.

🧬

Schema Evolution

Add a column on the edge and it appears in the destination automatically. No manual migration scripts.

🔐

Log Encryption

Encrypt log files in transit with a public/private key pair. The destination only decrypts — the edge never holds the private key.

📊

Live Dashboard

Per-device replication lag, throughput metrics, and error tracking — all in the Consolidator web UI, updated in real time.

🔧

Bi-Directional Commands

Push commands from the cloud back to edge devices — trigger purges, reload config, run schema migrations remotely.

🚫

No Vendor Lock-In

Entirely open-source (Apache 2.0). Works with your existing stack. Swap staging or destination without touching application code.

Connect to Any System

SyncLite Consolidator delivers to wherever your data needs to live.

Relational

PostgreSQLMySQLMicrosoft SQL Server SQLiteDuckDB

Data Lakes & Analytics

Apache IcebergClickHouse

NoSQL

MongoDB

A Complete Platform, Modular by Design

Use only what you need. Every component is independently deployable.

ComponentWhat It DoesLanguage
SyncLite Logger The embeddable runtime component for your app: local DB, write-ahead log, shipper, and optional in-process consolidator. It wraps SQLite, DuckDB, Derby, H2, or HyperSQL and handles local durability plus downstream sync. Java · Rust · Python
SyncLite Consolidator Standalone central consolidation service. It accepts log segments from many embedded devices and applies them to destinations, keeping the data path separate from the embedded runtime. Java WAR
SyncLite DBReader Configurable ETL, replication, and migration jobs that move data from source databases into SyncLite devices and onward to destinations. Java WAR
SyncLite QReader MQTT and IoT connector that lands broker traffic into SyncLite devices for downstream replication and sync. Java WAR
SyncLite Client Interactive CLI for inspecting and querying SyncLite devices. Connect directly to an embedded device or through SyncLite DB over HTTP. CLI
SyncLite Job Monitor Unified operations dashboard for scheduling and monitoring Consolidator, DBReader, and QReader jobs from a single web UI. Java WAR
SyncLite DB Local-first, sync-enabled database server that exposes the runtime over HTTP/JSON. Use it when you want the runtime available from a language that does not yet embed the native library, or when multiple processes share one device. Any (HTTP)

Contact

Questions, feedback, or just want to say hi? Here's where to find us.

✉️
Support Email
Need help with setup, configuration, or production use?
support@synclite.io
🐙
GitHub Issues
Found a bug or have a feature request? Open an issue on GitHub.
Open an Issue →
📖
Documentation
Full platform docs, quickstarts, config reference, and API guides.
Read the Docs →
🤝
Contribute
PRs welcome. Read the contributing guide before opening a pull request.
Contributing Guide →
ℹ️
About SyncLite
Mission, patent info, author background, and the story behind the platform.
Read About Page →

Ship Your First Sync in Under 5 Minutes

One command deploys Tomcat, downloads OpenJDK 25, and starts all SyncLite apps. No cloud account. No signup. Fully local.

Read the Docs → ★ Star on GitHub View Patent Details
git clone --recurse-submodules git@github.com:syncliteio/SyncLite.git
cd SyncLite/bin && ./deploy.sh && ./start.sh