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.
Sources produce compact binary logs → shipped to staging → Consolidator delivers in real time. Sub-second latency on local stages.
One platform, five problem domains. Pick the one you need today — the architecture handles the rest.
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.
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.
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.
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.
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.
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.
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::Consolidation, }), 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.CONSOLIDATION) .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 — ETL/replication configuration (not application code) job-name = orders_incremental source-type = POSTGRESQL source.host = pg.internal source.port = 5432 source.database = sales source.user = reader table.1.source = public.orders table.1.target = orders table.1.mode = INCREMENTAL table.1.watermark-column = updated_at # DBReader handles batching, retries, checkpoints, and restarts.
# SyncLite QReader — MQTT ingestion configuration (not application code) job-name = iot_temperature_ingest broker = tcp://mqtt.example.com:1883 client-id = synclite-qreader-01 subscription.1 = devices/+/telemetry payload-format = JSON target-table = iot_events field.device_id = $.deviceId field.temperature = $.temp field.event_time = $.ts # Works with Mosquitto, EMQX, AWS IoT Core, and Azure IoT Hub.
# Python via the synclite PyO3 wheel — built from the in-tree Rust workspace. # See synclite-logger-rust/python/ for install via `maturin develop --release`. # No pip install, no Rust toolchain, no maturin. import synclite as sl # Minimal SQLite-device config shipping to PostgreSQL. with open("synclite_logger.conf", "w") as f: f.write( "device-name=orders-device\n" "db-engine=SQLITE\n" "device-type=SQLITE\n" "db-path=orders.db\n" "local-data-stage-directory=synclite-stage\n" "dst-type=POSTGRES\n" "dst-connection-string=postgresql://postgres:postgres@localhost:5432/syncdb\n" "dst-database=syncdb\n" "dst-schema=syncschema\n" "dst-sync-mode=CONSOLIDATION\n" ) with sl.Runtime.open_config("synclite_logger.conf") as rt: rt.log_sql("CREATE TABLE IF NOT EXISTS orders(id INTEGER, item TEXT, qty INTEGER)") rt.log_sql("INSERT INTO orders VALUES(1, 'widget', 100)") rt.commit() rt.flush_log() # ↑ logged locally; shipped + consolidated into PostgreSQL in the background. # Richer Connection / Statement / await_sync / DB-API surface is coming via synclite-logger-python.
// 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 = SYNCLITE_SYNC_CONSOLIDATION; 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"]]})
// 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.
SyncLite is designed to disappear into your stack — minimal config, maximum reliability.
Transactional log capture ensures every committed write is delivered exactly once to the destination — no duplicates, no gaps.
Edge devices work fully offline. Log files accumulate locally and sync automatically when connectivity is restored.
Thousands of edge devices consolidating into a single destination. One source fanning out to multiple destinations simultaneously.
Add a column on the edge and it appears in the destination automatically. No manual migration scripts.
Encrypt log files in transit with a public/private key pair. The destination only decrypts — the edge never holds the private key.
Per-device replication lag, throughput metrics, and error tracking — all in the Consolidator web UI, updated in real time.
Push commands from the cloud back to edge devices — trigger purges, reload config, run schema migrations remotely.
Entirely open-source (Apache 2.0). Works with your existing stack. Swap staging or destination without touching application code.
SyncLite Consolidator delivers to wherever your data needs to live.
Relational
Data Lakes & Analytics
NoSQL
Use only what you need. Every component is independently deployable.
| Component | What It Does | Language |
|---|---|---|
| SyncLite Logger | Embeddable JDBC driver. Wraps SQLite, DuckDB, Derby, H2, HyperSQL. Captures every SQL transaction into binary log files and ships them to staging storage. | Java · Python |
| SyncLite DB | Standalone HTTP/JSON database server. Language-agnostic access to all SyncLite device types over plain HTTP. Full transaction support, pagination, HMAC auth. | Any (HTTP) |
| SyncLite Consolidator | Always-on central sink. Reads log files from staging, replicates transactionally to one or more destinations. Web UI for job config, live metrics, and SQL analytics. | Java WAR |
| SyncLite DBReader | Database ETL and replication. Reads from PostgreSQL, MySQL, Oracle, SQL Server, and more. Supports full load, watermark-based incremental, and log-based CDC. | Java WAR |
| SyncLite QReader | IoT MQTT connector. Subscribes to any MQTT v3.1 broker, parses CSV/JSON payloads, feeds the SyncLite pipeline. Works with Mosquitto, EMQX, AWS IoT Core, Azure IoT Hub. | Java WAR |
| SyncLite Client | Interactive CLI for SyncLite devices. Connect directly (embedded) or via HTTP to a SyncLite DB server. Execute SQL, inspect data, test pipelines. | CLI |
| SyncLite Job Monitor | Unified operations dashboard. Start, stop, schedule, and monitor all Consolidator, DBReader, and QReader jobs from a single web UI. Supports cron scheduling and alerting. | Java WAR |
| SyncLite Validator | End-to-end integration testing. Generates synthetic workloads, runs them through the full pipeline, and does a row-by-row comparison of source and destination data. | Java WAR |
Questions, feedback, or just want to say hi? Here's where to find us.