Your app should never wait on a network call to write data.
Embed a real local database — SQLite, DuckDB, Derby, H2, or HyperSQL — with full ACID transactions at native speed, and SyncLite replicates every committed transaction to your central store automatically.
Your AI agent should never lose what it learns.
Each agent persists its own local intelligence in an embedded database and syncs it into a shared central layer — a two-way loop where agents learn faster and act smarter together.
🚫 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, Node.js, and C++ — plus the Java JDBC driver and a language-agnostic HTTP API. Pick your stack, SyncLite handles the rest.
pip install synclite
npm install synclite
cargo add synclite-rs
io.synclite:synclite:1.1.0
// Node.js via the synclite N-API package. Offline-first SQLite syncing to PostgreSQL. // The connection API mirrors the Rust and Python samples one-for-one. const { initialize, SqliteConnection, awaitSync } = require('synclite'); // Wire up logger + shipper + embedded consolidator in one call. initialize({ device_type: 'SQLITE', device_name: 'orders-device', // device name db_path: 'orders.db', // local SQLite path destination: { 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. const conn = SqliteConnection.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: 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. conn.flush(); awaitSync('orders.db', 30); conn.close(); // ↑ logged + shipped + consolidated into PostgreSQL.
// 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.
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.
Shape replication per destination: filter and rename tables & columns, map data types (best-effort or exact), remap values, install replication triggers, choose write modes, and tune batch sizes — all with detailed throughput and statistics reporting.
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 | 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) |
Questions, feedback, or just want to say hi? Here's where to find us.