Website · Documentation ·
Your app writes to a local embedded database. SyncLite makes every write land in PostgreSQL transactionally — automatically, durably, offline-tolerant.
Drop one library into your app and get a fully-featured embedded database (SQLite, DuckDB, Derby, H2, or HyperSQL) whose every transaction is durably logged and continuously consolidated to your central database or data lake.
Offline by default. Transactionally consistent. No CDC. No Kafka. No replication agent to wire up.
Your hot path never touches the network. Your app keeps working offline and catches up when connectivity returns.
flowchart TB
classDef app fill:#eef6ff,stroke:#2b6cb0,stroke-width:1px,color:#1a365d
classDef bad fill:#fff5f5,stroke:#c53030,stroke-width:1.5px,color:#742a2a
classDef edb fill:#fffaf0,stroke:#c98a00,stroke-width:1px,color:#5c3a00
classDef rt fill:#fff8e6,stroke:#c98a00,stroke-width:1.5px,color:#5c3a00
classDef dst fill:#f0fff4,stroke:#2f855a,stroke-width:1px,color:#22543d
subgraph Before["TRADITIONAL — app bound to a remote DB"]
direction LR
A1["Your App / Agent"]:::app
PG1[("PostgreSQL<br/>(network-bound)")]:::bad
A1 -- "every read/write<br/>over the network" --> PG1
end
subgraph After["SYNCLITE — local-first + background sync"]
direction LR
A2["Your App / Agent"]:::app
EDB[("Embedded DB<br/>SQLite / DuckDB<br/>local, always available")]:::edb
RT["SyncLite Runtime<br/>log + shipper + sync"]:::rt
PG2[("PostgreSQL")]:::dst
A2 -- "in-process read/write<br/>(no network in the hot path)" --> EDB
EDB --> RT
RT -- "async · durable · offline-tolerant" --> PG2
end
Before ==>|"drop in one library"| After
| Traditional (app → remote DB) | SyncLite (local-first) | |
|---|---|---|
| Read/write latency | network round-trip every op | in-process, memory-speed |
| Works offline | ✗ fails without connectivity | ✓ fully offline, syncs on reconnect |
| Delivery guarantee | app must build retries/dedup | ✓ durable log, exactly-once |
| Moving parts | app + DB + custom CDC/queue | ✓ one embedded library |
Each agent gets fast, private, persistent local memory in a lightweight embedded database. That knowledge flows upward into a shared intelligence layer thousands of agents can draw on — and collective knowledge flows back down so every agent learns faster and adapts sooner.
Write locally. Learn collectively. Sync automatically.
flowchart TB
classDef hub fill:#f0fff4,stroke:#2f855a,stroke-width:1.5px,color:#22543d
classDef agent fill:#eef6ff,stroke:#2b6cb0,stroke-width:1px,color:#1a365d
classDef mem fill:#fffaf0,stroke:#c98a00,stroke-width:1px,color:#5c3a00
Hub[("CENTRAL INTELLIGENCE<br/>shared, always-current<br/>collective memory")]:::hub
subgraph Fleet["Agent fleet — local-first memory, no network on the hot path"]
direction LR
subgraph A["Agent A"]
direction TB
AA["Agent A"]:::agent
MA[("Local Memory<br/>embedded DB")]:::mem
AA -- "in-process<br/>read/write" --> MA
end
subgraph B["Agent B"]
direction TB
AB["Agent B"]:::agent
MB[("Local Memory<br/>embedded DB")]:::mem
AB -- "in-process<br/>read/write" --> MB
end
subgraph C["Agent C"]
direction TB
AC["Agent C"]:::agent
MC[("Local Memory<br/>embedded DB")]:::mem
AC -- "in-process<br/>read/write" --> MC
end
end
MA <== "learn ⇅ contribute" ==> Hub
MB <== "learn ⇅ contribute" ==> Hub
MC <== "learn ⇅ contribute" ==> Hub
Local memory stays fast, private, and resilient. Central intelligence stays current — without forcing any agent onto a slow, fragile network dependency in its hot path. Knowledge flows up (each agent contributes) and back down (every agent learns).
Pick your language and add one dependency. No server to install. No service to configure.
pip install synclite # Python — self-contained wheel, batteries included
npm install synclite # Node.js — self-contained N-API package, batteries included
cargo add synclite-rs # Rust — cdylib also embeddable from C/C++, Go, Node, Ruby, C#
<!-- Java / Maven -->
<dependency>
<groupId>io.synclite</groupId>
<artifactId>synclite</artifactId>
<version>1.1.0</version>
</dependency>
// Java / Gradle
implementation 'io.synclite:synclite:1.1.0'
The Python wheel and Java jar are self-contained — they bundle the native runtime, DuckDB, and the PostgreSQL driver. pip install is genuinely all you need on Windows, Linux, and macOS.
import synclite as sl
DB_PATH = "sampledevice.db"
POSTGRES = "postgresql://postgres:postgres@localhost:5432/syncdb"
DB = "syncdb"
SCHEMA = "syncschema"
# One call wires up the local logger, the segment shipper,
# and the embedded consolidator that drains into PostgreSQL.
sl.initialize(
device_type="SQLITE",
device_name="sampledevice",
db_path=DB_PATH,
destination=sl.DestinationOptions(
dst_type="POSTGRES",
dst_connection_string=POSTGRES,
dst_database=DB,
dst_schema=SCHEMA,
dst_sync_mode="REPLICATION",
),
)
conn = sl.Connection.open(DB_PATH)
conn.execute("CREATE TABLE IF NOT EXISTS orders(id INTEGER PRIMARY KEY, item TEXT, qty INTEGER)")
conn.execute("INSERT INTO orders(id, item, qty) VALUES(?, ?, ?)", [1, "widget", 100])
conn.execute("INSERT INTO orders(id, item, qty) VALUES(?, ?, ?)", [2, "gadget", 50])
conn.commit()
conn.flush() # ensure pending writes are shipped before process exits
sl.await_sync(DB_PATH, 30.0) # optional: block until PostgreSQL confirms apply
conn.close()
Every INSERT / UPDATE / DELETE — and every ALTER TABLE — is durably logged and applied to PostgreSQL in the background, whether your app is online or not. Full sample: synclite-code-samples/python/synclite_rusqlite_postgres.py.
const { initialize, SqliteConnection, awaitSync } = require('synclite');
// One call wires up the local logger, the segment shipper,
// and the embedded consolidator that drains into PostgreSQL.
initialize({
device_type: 'SQLITE',
device_name: 'sampledevice',
db_path: 'sampledevice.db',
destination: {
dst_type: 'POSTGRES',
dst_connection_string: 'postgresql://postgres:postgres@localhost:5432/syncdb',
dst_database: 'syncdb',
dst_schema: 'syncschema',
dst_sync_mode: 'REPLICATION',
},
});
const conn = SqliteConnection.open('sampledevice.db');
conn.execute('CREATE TABLE IF NOT EXISTS orders(id INTEGER PRIMARY KEY, item TEXT, qty INTEGER)');
conn.execute('INSERT INTO orders(id, item, qty) VALUES(?, ?, ?)', [1, 'widget', 100]);
conn.execute('UPDATE orders SET qty = ? WHERE id = ?', [150, 1]);
conn.commit();
conn.flush();
awaitSync('sampledevice.db', 30);
conn.close();
The npm package contains the same embedded Rust runtime as the Python wheel: local SQLite/DuckDB, change capture, shipper, and consolidator. Full sample: synclite-code-samples/nodejs/synclite_sqlite_postgres.js.
<!-- Maven -->
<dependency>
<groupId>io.synclite</groupId>
<artifactId>synclite</artifactId>
<version>1.1.0</version>
</dependency>
import io.synclite.*;
import java.nio.file.Path;
import java.sql.*;
import java.time.Duration;
private static final Path DB_PATH = Path.of("sampledevice.db");
private static final String DEVICE_NAME = "sampledevice";
private static final String POSTGRES_URL =
"jdbc:postgresql://localhost:5432/syncdb?user=postgres&password=postgres";
private static final String POSTGRES_DB = "syncdb";
private static final String POSTGRES_SCHEMA = "syncschema";
// One call wires up the local logger, the segment shipper,
// and the embedded consolidator that drains into PostgreSQL.
SQLite.initialize(DB_PATH, DEVICE_NAME,
DestinationOptions.builder()
.dstType(DstType.POSTGRES)
.connectionString(POSTGRES_URL)
.database(POSTGRES_DB).schema(POSTGRES_SCHEMA)
.syncMode(DstSyncMode.REPLICATION)
.build());
try (Connection conn = DriverManager.getConnection(
"jdbc:synclite_sqlite:" + DB_PATH);
Statement s = conn.createStatement()) {
s.execute("CREATE TABLE IF NOT EXISTS orders(id INTEGER PRIMARY KEY, item TEXT, qty INTEGER)");
s.execute("INSERT INTO orders VALUES(1, 'widget', 100)");
s.execute("INSERT INTO orders VALUES(2, 'gadget', 50)");
}
SyncLite.awaitSync(DB_PATH, Duration.ofSeconds(30)); // optional: block until PostgreSQL confirms apply
SQLite.closeDevice(DB_PATH);
The in-process consolidator is bundled inside the jar — no external service needed. Full JDBC surface: arbitrary SELECT, JOIN, multi-statement transactions, stored procedures. Full sample: synclite-code-samples/java/SyncliteSqlitePostgresApp.java.
Sample failing? Check
<dbPath>.synclite/<dbName>.trace(logger errors) and<userHome>/synclite/job1/workDir/synclite_<deviceName>_<uuid>/synclite_device.trace(consolidator errors).
cargo add synclite-rscargo add synclite-rs
use synclite::rusqlite::Connection;
use synclite::{DestinationOptions, DeviceType, DstSyncMode, DstType, Result,
SyncLiteOptions, Value};
const DB_PATH: &str = "sampledevice.db";
const DEVICE_NAME: &str = "sampledevice";
const POSTGRES: &str = "postgresql://postgres:postgres@localhost:5432/syncdb";
const DB: &str = "syncdb";
const SCHEMA: &str = "syncschema";
// One call wires up the local logger, the segment shipper,
// and the embedded consolidator that drains into PostgreSQL.
synclite::initialize(
DeviceType::SQLITE,
DEVICE_NAME,
DB_PATH,
Some(DestinationOptions {
dst_type: DstType::Postgres,
dst_connection_string: POSTGRES.into(),
dst_database: Some(DB.into()),
dst_schema: Some(SCHEMA.into()),
dst_sync_mode: DstSyncMode::Replication,
}),
SyncLiteOptions::default(),
)?;
let mut conn = Connection::open(DB_PATH)?;
conn.execute("CREATE TABLE IF NOT EXISTS orders(id INTEGER PRIMARY KEY, item TEXT, qty INTEGER)", &[])?;
conn.execute("INSERT INTO orders(id, item, qty) VALUES(?, ?, ?)",
&[Value::Int(1), Value::Text("widget".into()), Value::Int(100)])?;
conn.execute("INSERT INTO orders(id, item, qty) VALUES(?, ?, ?)",
&[Value::Int(2), Value::Text("gadget".into()), Value::Int(50)])?;
conn.flush()?; // ensure pending writes are shipped before process exits
synclite::await_sync(DB_PATH, std::time::Duration::from_secs(30))?; // optional: block until PG confirms
conn.close()?;
The Rust crate is also a cdylib — embeddable from Python, Node.js, C/C++, Go, Ruby, C# via a single native library. Full sample: synclite-code-samples/rust/synclite_rusqlite_postgres.rs.
SELECT, JOIN, multi-statement transactions, ad-hoc DDL.insert/update/delete/selectAll — no SQL, schema evolves automatically), or a fluent Stream append-only API for high-throughput event ingestion.cdylib ABI: call it from C/C++, Go, Node.js, Ruby, or C#.Any number of apps embed their own runtime, in any language, all shipping to the same staging store and applying to the same destinations:
flowchart LR
classDef app fill:#eef6ff,stroke:#2b6cb0,stroke-width:1px,color:#1a365d
classDef rt fill:#fff8e6,stroke:#c98a00,stroke-width:1.5px,color:#5c3a00
classDef dst fill:#f0fff4,stroke:#2f855a,stroke-width:1px,color:#22543d
subgraph Fleet["Many apps · many devices — laptops · servers · edge boxes · containers · phones · IoT"]
direction TB
subgraph App1["App / Device 1 — Java"]
API1["SQL · Store · Stream"]:::app
RT1["SyncLite Runtime<br/>DB → Log → Shipper → Consolidator"]:::rt
API1 --> RT1
end
subgraph App2["App / Device 2 — Python"]
API2["SQL · Store · Stream"]:::app
RT2["SyncLite Runtime<br/>DB → Log → Shipper → Consolidator"]:::rt
API2 --> RT2
end
subgraph App3["App / Device 3 — Rust"]
API3["SQL · Store · Stream"]:::app
RT3["SyncLite Runtime<br/>DB → Log → Shipper → Consolidator"]:::rt
API3 --> RT3
end
subgraph AppN["App / Device N — Node · C/C++ · Go · Ruby · C# ..."]
APIN["SQL · Store · Stream"]:::app
RTN["SyncLite Runtime<br/>DB → Log → Shipper → Consolidator"]:::rt
APIN --> RTN
end
end
Stage[("Shared Stage<br/>FS · S3 · MinIO · SFTP")]
RT1 -- async --> Stage
RT2 -- async --> Stage
RT3 -- async --> Stage
RTN -- async --> Stage
Stage -- apply --> Dst
Dst["Current embedded-runtime target<br/>PostgreSQL<br/><br/>Standalone Consolidator<br/>PostgreSQL · MySQL · MSSQL · MongoDB<br/>Iceberg · DuckDB · S3"]:::dst
Inside each runtime: SQL (JDBC for Java, rusqlite for Rust, native bindings for Python / Node / C/C++ / Go / Ruby / C#) plus the Store CRUD and Stream APIs all sit on top of the same embedded DB, WAL logger, shipper, and in-process consolidator. The current embedded runtime path targets PostgreSQL; the standalone Consolidator is the broader multi-destination path.
A "device" is a logical embedded DB that the runtime owns end-to-end (storage + log + sync). Pick the API that fits your code:
SQLite, DuckDB, Derby, H2, HyperSQL). Arbitrary CREATE/ALTER/SELECT/INSERT/UPDATE/DELETE. Use this when you want a real embedded SQL DB that also happens to sync.SyncLiteStore typed CRUD (SQLITE_STORE, DUCKDB_STORE, …). insert/update/delete/selectAll against plain maps; schema evolves automatically. Highest consolidation throughput — no SQL-log parsing on the apply path.SyncLiteStream fluent insert/insertBatch over the append-only STREAMING device. For high-throughput event capture where UPDATE/DELETE are not needed.Which should I pick? For a new app,
SQLITE_STOREis usually the fastest and simplest starting point. Reach for SQL devices when you need raw SQL, JOINs, or multi-statement transactions.
SyncLite ships as two things:
| Component | Description | README |
|---|---|---|
SyncLite Runtime (Java) (synclite-<version>.jar) | One jar = JDBC / Store / Stream APIs + logger + shipper + (optional) in-process consolidator (via bundled synclite_jni native). Call initialize(dbPath, deviceName, destinationOptions) for the single-jar topology, or initialize(dbPath, conf) for logger-only mode paired with the standalone Consolidator WAR. | → |
| SyncLite Runtime (Rust) | Same runtime in Rust (logger + in-process consolidator) as a single cdylib. Consumable from Rust, Python, Node.js, C/C++, Go, Ruby, C# — anywhere you can load a native library. | → |
Deploy these only when you want a managed platform. They are standard webapps that consume the same runtime under the hood.
| Component | Description | README |
|---|---|---|
| SyncLite DB | Wraps the runtime as a tiny local-first HTTP/JSON service. Use it when you want the runtime accessible from a language that doesn't embed the native lib, or when multiple processes share one device. | → |
| SyncLite Client | Interactive CLI for inspecting and querying SyncLite devices. | → |
| SyncLite Consolidator | Standalone consolidation service for the central topology — accepts log segments from many devices and applies them to destinations. | → |
| SyncLite DBReader | Configurable database ETL / replication / migration jobs (source DB → SyncLite devices → destinations). | → |
| SyncLite QReader | MQTT / IoT connector that lands broker traffic into SyncLite devices. | → |
| SyncLite Job Monitor | Unified job management and scheduling UI for DBReader / QReader / Consolidator jobs. | → |
| SyncLite Validator | End-to-end integration test harness for SyncLite pipelines. | → |
| Sample Web App | JSP/Servlet demo that embeds SyncLite (Java) in logger-only mode and pairs with the standalone Consolidator WAR for sync. | → |
flowchart LR
classDef src fill:#eef6ff,stroke:#2b6cb0,color:#1a365d
classDef tool fill:#fef5ff,stroke:#805ad5,stroke-width:1.5px,color:#44337a
classDef dev fill:#fff8e6,stroke:#c98a00,color:#5c3a00
classDef ops fill:#edf2f7,stroke:#4a5568,color:#1a202c
classDef dst fill:#f0fff4,stroke:#2f855a,color:#22543d
SrcDB["Source DBs<br/>Oracle · MySQL · SQL Server<br/>Postgres · DB2 · MongoDB"]:::src
Brokers["IoT / MQTT brokers<br/>Mosquitto · HiveMQ · ..."]:::src
Apps["Your Apps<br/>(running embedded runtime)"]:::dev
DBReader["SyncLite DBReader<br/><sub>scheduled DB → device replication / ETL</sub>"]:::tool
QReader["SyncLite QReader<br/><sub>broker → device ingest</sub>"]:::tool
Devices[("SyncLite Stage")]:::ops
Consolidator["SyncLite Consolidator<br/><sub>standalone service<br/>applies log segments to destinations</sub>"]:::tool
JobMon["Job Monitor UI<br/><sub>schedule · monitor · alert<br/>DBReader · QReader · Consolidator jobs</sub>"]:::ops
Client["SyncLite Client<br/><sub>CLI — inspect / query devices</sub>"]:::ops
Dst["Destinations<br/>Postgres · MySQL · MSSQL · MongoDB<br/>Iceberg · DuckDB · S3"]:::dst
SrcDB --> DBReader --> Devices
Brokers --> QReader --> Devices
Apps --> Devices
Devices --> Consolidator --> Dst
JobMon -. orchestrates .-> DBReader
JobMon -. orchestrates .-> QReader
JobMon -. orchestrates .-> Consolidator
Client -. inspects .-> Devices
Solid lines are data flow. Dashed lines are the control plane. Nothing in this diagram is required by the runtime — reach for these only when you want a managed platform on top of the embedded runtime.
Use this when you want the central Consolidator + DBReader + QReader + Job Monitor running as services. No build required — grab the prebuilt platform zip:
Download the latest
synclite-platform-<version>.zipfrom GitHub Releases, unzip it, and open a terminal in the extracted folder.
cd bin/
./deploy.sh # or deploy.bat on Windows (downloads Tomcat + JDK, deploys all WARs)
./start.sh # or start.bat on Windows (starts Tomcat + every SyncLite app)
| URL | App |
|---|---|
| http://localhost:8080/synclite-consolidator | Configure and monitor consolidation jobs |
| http://localhost:8080/synclite-sample-app | Create devices, run SQL workloads, see live sync |
| http://localhost:8080/synclite-dbreader | Set up database ETL/replication pipelines |
| http://localhost:8080/synclite-qreader | Set up IoT MQTT connector pipelines |
| http://localhost:8080/synclite-jobmonitor | Manage and schedule all SyncLite jobs |
| http://localhost:8080/manager | Tomcat manager (user: synclite / pwd: synclite) |
Sample Web App → Consolidator (browser-driven, no external source DB)
DBReader → Consolidator (database → database replication/ETL)
Docker (all-in-one)
cd bin/
./docker-deploy.sh # Builds synclite-platform image (+ optional SFTP/MinIO + PostgreSQL/MySQL)
./docker-start.sh # Starts synclite-platform container and optional helpers
./docker-stop.sh # Stops synclite-platform container and optional helpers
⚠️ Docker helper scripts use default credentials. Change usernames, passwords, and enable TLS before any production use.
Full walkthrough: GETTING_STARTED.md - Try the tools together.
flowchart TB
classDef app fill:#eef6ff,stroke:#2b6cb0,stroke-width:1px,color:#1a365d
classDef bad fill:#fff5f5,stroke:#c53030,stroke-width:1.5px,color:#742a2a
classDef edb fill:#fffaf0,stroke:#c98a00,stroke-width:1px,color:#5c3a00
classDef rt fill:#fff8e6,stroke:#c98a00,stroke-width:1.5px,color:#5c3a00
classDef dst fill:#f0fff4,stroke:#2f855a,stroke-width:1px,color:#22543d
subgraph Before["TRADITIONAL — app bound to a remote DB"]
direction LR
A1["Your App / Agent"]:::app
PG1[("PostgreSQL<br/>(network-bound)")]:::bad
A1 -- "every read/write<br/>over the network" --> PG1
end
subgraph After["SYNCLITE — local-first + background sync"]
direction LR
A2["Your App / Agent"]:::app
EDB[("Embedded DB<br/>SQLite / DuckDB<br/>local, always available")]:::edb
RT["SyncLite Runtime<br/>log + shipper + sync"]:::rt
PG2[("PostgreSQL")]:::dst
A2 -- "in-process read/write<br/>(no network in the hot path)" --> EDB
EDB --> RT
RT -- "async · durable · offline-tolerant" --> PG2
end
Before ==>|"drop in one library"| After
Why the SyncLite path wins: your app reads and writes a local embedded DB at in-process speed and keeps working offline, while the SyncLite Runtime durably logs every write and syncs it to your destination in the background. The network moves off the critical path — so you get lower latency, offline resilience, and exactly-once delivery without wiring up a separate CDC tool, message bus, or replication agent.
| Traditional (app → remote DB) | SyncLite (local-first) | |
|---|---|---|
| Read/write latency | network round-trip every op | in-process, memory-speed |
| Works offline | ✗ fails without connectivity | ✓ fully offline, syncs on reconnect |
| Delivery guarantee | app must build retries/dedup | ✓ durable log, exactly-once |
| Moving parts | app + DB + custom CDC/queue | ✓ one embedded library |
Architecture support. SyncLite is 64-bit only —
x86_64andaarch64on Windows / Linux / macOS. 32-bit hosts are not supported because SyncLite Runtime (Rust) depends on the DuckDB engine, which requires a 64-bit host.
Prerequisites (Java-only build): Java 25, Apache Maven 3.8.6+
Additional prerequisites (build all loggers including Rust):
link.exe). Install from https://visualstudio.microsoft.com/visual-cpp-build-tools/ and select the "Desktop development with C++" workload.build-essential + cmake — e.g. sudo apt install build-essential pkg-config cmake.xcode-select --install.cargo-zigbuild and the Zig compiler on PATHmaturin — for the host-platform Python wheel:
python -m pip install maturin
# Windows: pip install delvewheel | Linux: pip install auditwheel | macOS: pip install delocate
Skip with -DskipPythonWheel=true (also skipped automatically by -DskipNonJavaLoggers=true).lib/nodejs/:
node --version
npm --version
Skip with -DskipNodePackage=true (also skipped automatically by -DskipNonJavaLoggers=true).git clone --recurse-submodules https://github.com/syncliteio/SyncLite.git SyncLite
cd SyncLite
| # | Flavor | What it produces |
|---|---|---|
| 1 | Full platform (default) | target/synclite-platform-<rev>.zip — Tomcat scripts + WARs + tools + samples + multi-arch native runtime |
| 2 | Full platform, Java-only | Same as #1 but no lib/native/ (no Rust toolchain required) |
| 3 | Runtime (recommended for app developers) | target/synclite-runtime-<rev>.zip — slim zip with lib/java/ (synclite jar) + multi-arch lib/native/ (Rust cdylibs) + lib/python/ wheels + lib/nodejs/ npm package + cross-language sample-apps/{cpp,java,nodejs,python,rust} |
# 1. Full platform (default)
mvn -Drevision=1.1.0 clean install
# 2. Full platform, Java-only (no Rust toolchain required)
mvn -Drevision=1.1.0 -DskipNonJavaLoggers=true clean install
# 3. Runtime only — slim embeddable zip
mvn -Drevision=1.1.0 -DruntimeOnly=true clean install
# Fastest build on a host without zig
mvn -Drevision=1.1.0 -DruntimeOnly=true -DskipRustCrossCompile=true -DskipTests clean install
For just the Java jar or just the Rust cdylibs, build individual subprojects directly:
cd synclite-logger-java && mvn installorcd synclite-logger-rust && cargo build --workspace --release.
The cross-compile toolchain (for Linux x86_64 and aarch64 cdylibs):
cargo install cargo-zigbuild
rustup target add x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu
# zig must be on PATH — download from https://ziglang.org/download/
The
bin/deploy.sh/bin/deploy.batscripts download Apache Tomcat 9.0.117 and OpenJDK 25 automatically. No manual installation needed for a quick start.
-DruntimeOnly=true)synclite-runtime-1.1.0/
+-- lib/
| +-- java/
| | +-- synclite-<version>.jar # Add to your app classpath
| | +-- synclite.conf # Default logger configuration
| +-- native/ # Multi-arch native cdylibs (Rust runtime)
| | +-- include/ # C / C++ ABI headers (synclite.h, synclite.hpp)
| | +-- synclite_<version>.dll # Windows host build
| | +-- synclite_<version>.dll.lib # Windows import library
| | +-- synclite_<version>.lib # Windows static library
| | +-- libsynclite_<version>_linux_x86_64.so # cross-compiled
| | +-- libsynclite_<version>_linux_aarch64.so # cross-compiled
| | +-- libsynclite_<version>.dylib # only if built on macOS
| | +-- synclite.conf
| +-- python/
| | +-- synclite-<version>-cp38-abi3-win_amd64.whl
| | +-- synclite-<version>-cp38-abi3-manylinux_2_28_x86_64.whl
| | +-- synclite-<version>-cp38-abi3-manylinux_2_28_aarch64.whl
| +-- nodejs/
| | +-- synclite-1.1.0.tgz
| | +-- synclite-native-win32-x64-msvc-1.1.0.tgz
| | +-- synclite-native-linux-arm64-gnu-1.1.0.tgz
| | +-- synclite-native-linux-x64-gnu-1.1.0.tgz
| +-- rust/
| +-- synclite-source/ # Self-contained Cargo workspace
+-- sample-apps/ # One sample per language
| +-- cpp/ # CMake-based, links lib/native
| +-- java/ # javac + lib/java/synclite-<version>.jar
| +-- python/ # pip install lib/python/*.whl
| +-- rust/ # cargo run, path-deps into lib/rust
| +-- nodejs/ # npm install lib/nodejs/synclite-1.1.0.tgz
+-- LICENSE
+-- synclite_platform_version.txt
synclite-platform-1.1.0/
+-- bin/
| +-- deploy.sh / deploy.bat # One-command setup: downloads Tomcat + JDK, deploys WARs
| +-- start.sh / start.bat # Start Tomcat + all SyncLite apps
| +-- stop.sh / stop.bat # Graceful shutdown
| +-- docker-deploy.sh / docker-start.sh / docker-stop.sh
| +-- stage/sftp/ stage/minio/ # Docker scripts for staging servers
| +-- dst/postgresql/ dst/mysql/ # Docker scripts for destination DBs
+-- lib/ # Same as runtime-only zip above
+-- tools/
| +-- synclite-client/ synclite-db/ synclite-dbreader/
| +-- synclite-qreader/ synclite-jobmonitor/ synclite-validator/
| +-- synclite-sample-app/
+-- sample-apps/ # cpp / java / python / rust / nodejs
Add synclite-<version>.jar to your project, then:
import io.synclite.*;
import java.nio.file.Path;
import java.sql.*;
Path dbDir = Path.of(System.getProperty("user.home"), "synclite", "db");
Path dbPath = dbDir.resolve("myapp.db");
Path conf = dbDir.resolve("synclite.conf");
Class.forName("io.synclite.SQLite");
SQLite.initialize(dbPath, conf);
try (Connection c = DriverManager.getConnection("jdbc:synclite_sqlite:" + dbPath);
Statement s = c.createStatement()) {
s.execute("CREATE TABLE IF NOT EXISTS orders(id INT, item TEXT, qty INT)");
s.execute("INSERT INTO orders VALUES(1, 'widget', 100)");
// ↑ captured in a log file and shipped to staging storage automatically
}
SQLite.closeAll();
For other embedded databases replace SQLite / synclite_sqlite with DuckDB / synclite_duckdb, Derby / synclite_derby, H2 / synclite_h2, or HyperSQL / synclite_hsqldb.
Full configuration reference: lib/logger/synclite.conf · Documentation
STORE device types (SQLITE_STORE, DUCKDB_STORE, DERBY_STORE, H2_STORE, HYPERSQL_STORE) expose the SyncLiteStore API: typed insert / update / delete / selectAll methods that handle schema evolution automatically and log every operation to the replication pipeline.
import io.synclite.SQLiteStore;
import io.synclite.SyncLiteStore;
Class.forName("io.synclite.SQLiteStore");
SQLiteStore.initialize(dbPath, conf);
try (SyncLiteStore store = SQLiteStore.open(dbPath)) {
store.createTable("orders", new LinkedHashMap<>(Map.of(
"id", "INTEGER PRIMARY KEY",
"item", "TEXT",
"qty", "INTEGER"
)));
store.insert("orders", Map.of("id", 1, "item", "widget", "qty", 100));
store.update("orders", Map.of("qty", 150), Map.of("id", 1));
store.delete("orders", Map.of("id", 1));
List<Map<String, Object>> rows = store.selectAll("orders");
}
SQLiteStore.closeDevice(dbPath);
SyncLiteStream wraps the STREAMING device with a fluent insert / insertBatch API. UPDATE and DELETE are intentionally absent — this API models event flow, not mutable records.
import io.synclite.Streaming;
import io.synclite.SyncLiteStream;
Class.forName("io.synclite.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"));
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")
));
}
io.synclite.Jedis is a drop-in subclass of redis.clients.jedis.Jedis. Every write is durably committed to a SQLITE_STORE device before being forwarded to Redis, and the cache is repopulated from the store on restart.
import io.synclite.Jedis;
// Managed mode — Jedis handles SQLiteStore initialise / open / close
try (Jedis jedis = Jedis.builder(dbPath, conf, "cache-device")
.host("localhost").port(6379).build()) {
jedis.set("user:1:name", "Alice");
jedis.hset("session:42", Map.of("token", "abc123", "status", "active"));
jedis.rpush("queue", "job-1", "job-2");
jedis.sadd("tags", "etl", "cdc");
jedis.zadd("leaderboard", Map.of("Alice", 100.0, "Bob", 200.0));
jedis.del("tmp");
}
Alternatively, supply a pre-opened SyncLiteStore via Jedis.builder(store) when managing the store lifecycle externally.
SyncLite DB is a local-first HTTP/JSON database service that wraps embedded databases with built-in SyncLite logging and replication (by coupling it with SyncLite Consolidator), so any language can call it over HTTP.
The sample below uses Python for brevity; the same HTTP calls work from Go (net/http) and Node.js (fetch / axios).
Deploy synclite-db-1.1.0.war (bundled under tools/synclite-db/) to Tomcat, open http://localhost:8080/synclite-db, and configure + start the DB server from the browser GUI. It then serves the HTTP/JSON API on the port set in the GUI.
# Python client (plain HTTP — no SDK needed)
import requests, json
BASE = "http://localhost:5555/synclite"
requests.post(BASE, json={"db-type": "SQLITE", "db-name": "myapp",
"synclite-logger-options": {"local-data-stage-directory": "/tmp/stage"},
"sql": "initialize"})
requests.post(BASE, json={"db-name": "myapp",
"sql": "CREATE TABLE IF NOT EXISTS t1(a INT, b TEXT)"})
requests.post(BASE, json={"db-name": "myapp",
"sql": "INSERT INTO t1 VALUES(?, ?)", "arguments": [[1, "hello"], [2, "world"]]})
// Go client (plain HTTP — no SDK needed)
package main
import (
"bytes"
"net/http"
)
func postJSON(url string, body string) error {
_, err := http.Post(url, "application/json", bytes.NewBufferString(body))
return err
}
func main() {
base := "http://localhost:5555/synclite"
_ = postJSON(base, `{"db-type":"SQLITE","db-name":"myapp","synclite-logger-options":{"local-data-stage-directory":"/tmp/stage"},"sql":"initialize"}`)
_ = postJSON(base, `{"db-name":"myapp","sql":"CREATE TABLE IF NOT EXISTS t1(a INT, b TEXT)"}`)
_ = postJSON(base, `{"db-name":"myapp","sql":"INSERT INTO t1 VALUES(?, ?)","arguments":[[1,"hello"],[2,"world"]]}`)
}
// Node.js client (plain HTTP — no SDK needed)
const BASE = "http://localhost:5555/synclite";
async function post(body) {
await fetch(BASE, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
}
await post({
"db-type": "SQLITE",
"db-name": "myapp",
"synclite-logger-options": { "local-data-stage-directory": "/tmp/stage" },
sql: "initialize",
});
await post({
"db-name": "myapp",
sql: "CREATE TABLE IF NOT EXISTS t1(a INT, b TEXT)",
});
await post({
"db-name": "myapp",
sql: "INSERT INTO t1 VALUES(?, ?)",
arguments: [[1, "hello"], [2, "world"]],
});
SDK samples for Java, Python, C#, C++, Go, Rust, Ruby, Node.js: synclite-db/sdk-source/
Configure local-data-stage-directory in synclite.conf for local/NFS staging. For remote staging (SFTP, S3, MinIO, Kafka, OneDrive, Google Drive) configure the appropriate properties and use the matching Docker helper scripts in bin/stage/.
Docker staging helpers:
bin/stage/sftp/docker-deploy.sh # SFTP server
bin/stage/minio/docker-deploy.sh # MinIO object storage
⚠️ The stage Docker scripts use default credentials. Always change usernames, passwords, and add TLS before production use.
| Resource | Link |
|---|---|
| Full Documentation | https://github.com/syncliteio/SyncLite/blob/main/DOCUMENTATION.md |
| Website | https://www.synclite.io |
SyncLite is backed by patented technology, more info: https://www.synclite.io/about
SyncLite is open source and free to use. If it adds value to your work, please consider sponsoring its continued development — sponsorships fund maintenance, new features, and community support.
You can sponsor us via GitHub Sponsors (see .github/FUNDING.yml). Every contribution, big or small, is greatly appreciated. ⭐ Starring the repo and spreading the word helps too!
We welcome contributions! Please read CONTRIBUTING.md and CODE_OF_CONDUCT.md before opening a pull request.
SyncLite is licensed under the Apache License 2.0.
Shell
20.6%
Java
14.3%
Batchfile
14.1%
Python
10.3%
Dockerfile
9.4%
Rust
8.6%
CMake
8.3%
JavaScript
7.3%
C++
7.1%
Website · Documentation ·
Your app writes to a local embedded database. SyncLite makes every write land in PostgreSQL transactionally — automatically, durably, offline-tolerant.
Drop one library into your app and get a fully-featured embedded database (SQLite, DuckDB, Derby, H2, or HyperSQL) whose every transaction is durably logged and continuously consolidated to your central database or data lake.
Offline by default. Transactionally consistent. No CDC. No Kafka. No replication agent to wire up.
Your hot path never touches the network. Your app keeps working offline and catches up when connectivity returns.
flowchart TB
classDef app fill:#eef6ff,stroke:#2b6cb0,stroke-width:1px,color:#1a365d
classDef bad fill:#fff5f5,stroke:#c53030,stroke-width:1.5px,color:#742a2a
classDef edb fill:#fffaf0,stroke:#c98a00,stroke-width:1px,color:#5c3a00
classDef rt fill:#fff8e6,stroke:#c98a00,stroke-width:1.5px,color:#5c3a00
classDef dst fill:#f0fff4,stroke:#2f855a,stroke-width:1px,color:#22543d
subgraph Before["TRADITIONAL — app bound to a remote DB"]
direction LR
A1["Your App / Agent"]:::app
PG1[("PostgreSQL<br/>(network-bound)")]:::bad
A1 -- "every read/write<br/>over the network" --> PG1
end
subgraph After["SYNCLITE — local-first + background sync"]
direction LR
A2["Your App / Agent"]:::app
EDB[("Embedded DB<br/>SQLite / DuckDB<br/>local, always available")]:::edb
RT["SyncLite Runtime<br/>log + shipper + sync"]:::rt
PG2[("PostgreSQL")]:::dst
A2 -- "in-process read/write<br/>(no network in the hot path)" --> EDB
EDB --> RT
RT -- "async · durable · offline-tolerant" --> PG2
end
Before ==>|"drop in one library"| After
| Traditional (app → remote DB) | SyncLite (local-first) | |
|---|---|---|
| Read/write latency | network round-trip every op | in-process, memory-speed |
| Works offline | ✗ fails without connectivity | ✓ fully offline, syncs on reconnect |
| Delivery guarantee | app must build retries/dedup | ✓ durable log, exactly-once |
| Moving parts | app + DB + custom CDC/queue | ✓ one embedded library |
Each agent gets fast, private, persistent local memory in a lightweight embedded database. That knowledge flows upward into a shared intelligence layer thousands of agents can draw on — and collective knowledge flows back down so every agent learns faster and adapts sooner.
Write locally. Learn collectively. Sync automatically.
flowchart TB
classDef hub fill:#f0fff4,stroke:#2f855a,stroke-width:1.5px,color:#22543d
classDef agent fill:#eef6ff,stroke:#2b6cb0,stroke-width:1px,color:#1a365d
classDef mem fill:#fffaf0,stroke:#c98a00,stroke-width:1px,color:#5c3a00
Hub[("CENTRAL INTELLIGENCE<br/>shared, always-current<br/>collective memory")]:::hub
subgraph Fleet["Agent fleet — local-first memory, no network on the hot path"]
direction LR
subgraph A["Agent A"]
direction TB
AA["Agent A"]:::agent
MA[("Local Memory<br/>embedded DB")]:::mem
AA -- "in-process<br/>read/write" --> MA
end
subgraph B["Agent B"]
direction TB
AB["Agent B"]:::agent
MB[("Local Memory<br/>embedded DB")]:::mem
AB -- "in-process<br/>read/write" --> MB
end
subgraph C["Agent C"]
direction TB
AC["Agent C"]:::agent
MC[("Local Memory<br/>embedded DB")]:::mem
AC -- "in-process<br/>read/write" --> MC
end
end
MA <== "learn ⇅ contribute" ==> Hub
MB <== "learn ⇅ contribute" ==> Hub
MC <== "learn ⇅ contribute" ==> Hub
Local memory stays fast, private, and resilient. Central intelligence stays current — without forcing any agent onto a slow, fragile network dependency in its hot path. Knowledge flows up (each agent contributes) and back down (every agent learns).
Pick your language and add one dependency. No server to install. No service to configure.
pip install synclite # Python — self-contained wheel, batteries included
npm install synclite # Node.js — self-contained N-API package, batteries included
cargo add synclite-rs # Rust — cdylib also embeddable from C/C++, Go, Node, Ruby, C#
<!-- Java / Maven -->
<dependency>
<groupId>io.synclite</groupId>
<artifactId>synclite</artifactId>
<version>1.1.0</version>
</dependency>
// Java / Gradle
implementation 'io.synclite:synclite:1.1.0'
The Python wheel and Java jar are self-contained — they bundle the native runtime, DuckDB, and the PostgreSQL driver. pip install is genuinely all you need on Windows, Linux, and macOS.
import synclite as sl
DB_PATH = "sampledevice.db"
POSTGRES = "postgresql://postgres:postgres@localhost:5432/syncdb"
DB = "syncdb"
SCHEMA = "syncschema"
# One call wires up the local logger, the segment shipper,
# and the embedded consolidator that drains into PostgreSQL.
sl.initialize(
device_type="SQLITE",
device_name="sampledevice",
db_path=DB_PATH,
destination=sl.DestinationOptions(
dst_type="POSTGRES",
dst_connection_string=POSTGRES,
dst_database=DB,
dst_schema=SCHEMA,
dst_sync_mode="REPLICATION",
),
)
conn = sl.Connection.open(DB_PATH)
conn.execute("CREATE TABLE IF NOT EXISTS orders(id INTEGER PRIMARY KEY, item TEXT, qty INTEGER)")
conn.execute("INSERT INTO orders(id, item, qty) VALUES(?, ?, ?)", [1, "widget", 100])
conn.execute("INSERT INTO orders(id, item, qty) VALUES(?, ?, ?)", [2, "gadget", 50])
conn.commit()
conn.flush() # ensure pending writes are shipped before process exits
sl.await_sync(DB_PATH, 30.0) # optional: block until PostgreSQL confirms apply
conn.close()
Every INSERT / UPDATE / DELETE — and every ALTER TABLE — is durably logged and applied to PostgreSQL in the background, whether your app is online or not. Full sample: synclite-code-samples/python/synclite_rusqlite_postgres.py.
const { initialize, SqliteConnection, awaitSync } = require('synclite');
// One call wires up the local logger, the segment shipper,
// and the embedded consolidator that drains into PostgreSQL.
initialize({
device_type: 'SQLITE',
device_name: 'sampledevice',
db_path: 'sampledevice.db',
destination: {
dst_type: 'POSTGRES',
dst_connection_string: 'postgresql://postgres:postgres@localhost:5432/syncdb',
dst_database: 'syncdb',
dst_schema: 'syncschema',
dst_sync_mode: 'REPLICATION',
},
});
const conn = SqliteConnection.open('sampledevice.db');
conn.execute('CREATE TABLE IF NOT EXISTS orders(id INTEGER PRIMARY KEY, item TEXT, qty INTEGER)');
conn.execute('INSERT INTO orders(id, item, qty) VALUES(?, ?, ?)', [1, 'widget', 100]);
conn.execute('UPDATE orders SET qty = ? WHERE id = ?', [150, 1]);
conn.commit();
conn.flush();
awaitSync('sampledevice.db', 30);
conn.close();
The npm package contains the same embedded Rust runtime as the Python wheel: local SQLite/DuckDB, change capture, shipper, and consolidator. Full sample: synclite-code-samples/nodejs/synclite_sqlite_postgres.js.
<!-- Maven -->
<dependency>
<groupId>io.synclite</groupId>
<artifactId>synclite</artifactId>
<version>1.1.0</version>
</dependency>
import io.synclite.*;
import java.nio.file.Path;
import java.sql.*;
import java.time.Duration;
private static final Path DB_PATH = Path.of("sampledevice.db");
private static final String DEVICE_NAME = "sampledevice";
private static final String POSTGRES_URL =
"jdbc:postgresql://localhost:5432/syncdb?user=postgres&password=postgres";
private static final String POSTGRES_DB = "syncdb";
private static final String POSTGRES_SCHEMA = "syncschema";
// One call wires up the local logger, the segment shipper,
// and the embedded consolidator that drains into PostgreSQL.
SQLite.initialize(DB_PATH, DEVICE_NAME,
DestinationOptions.builder()
.dstType(DstType.POSTGRES)
.connectionString(POSTGRES_URL)
.database(POSTGRES_DB).schema(POSTGRES_SCHEMA)
.syncMode(DstSyncMode.REPLICATION)
.build());
try (Connection conn = DriverManager.getConnection(
"jdbc:synclite_sqlite:" + DB_PATH);
Statement s = conn.createStatement()) {
s.execute("CREATE TABLE IF NOT EXISTS orders(id INTEGER PRIMARY KEY, item TEXT, qty INTEGER)");
s.execute("INSERT INTO orders VALUES(1, 'widget', 100)");
s.execute("INSERT INTO orders VALUES(2, 'gadget', 50)");
}
SyncLite.awaitSync(DB_PATH, Duration.ofSeconds(30)); // optional: block until PostgreSQL confirms apply
SQLite.closeDevice(DB_PATH);
The in-process consolidator is bundled inside the jar — no external service needed. Full JDBC surface: arbitrary SELECT, JOIN, multi-statement transactions, stored procedures. Full sample: synclite-code-samples/java/SyncliteSqlitePostgresApp.java.
Sample failing? Check
<dbPath>.synclite/<dbName>.trace(logger errors) and<userHome>/synclite/job1/workDir/synclite_<deviceName>_<uuid>/synclite_device.trace(consolidator errors).
cargo add synclite-rscargo add synclite-rs
use synclite::rusqlite::Connection;
use synclite::{DestinationOptions, DeviceType, DstSyncMode, DstType, Result,
SyncLiteOptions, Value};
const DB_PATH: &str = "sampledevice.db";
const DEVICE_NAME: &str = "sampledevice";
const POSTGRES: &str = "postgresql://postgres:postgres@localhost:5432/syncdb";
const DB: &str = "syncdb";
const SCHEMA: &str = "syncschema";
// One call wires up the local logger, the segment shipper,
// and the embedded consolidator that drains into PostgreSQL.
synclite::initialize(
DeviceType::SQLITE,
DEVICE_NAME,
DB_PATH,
Some(DestinationOptions {
dst_type: DstType::Postgres,
dst_connection_string: POSTGRES.into(),
dst_database: Some(DB.into()),
dst_schema: Some(SCHEMA.into()),
dst_sync_mode: DstSyncMode::Replication,
}),
SyncLiteOptions::default(),
)?;
let mut conn = Connection::open(DB_PATH)?;
conn.execute("CREATE TABLE IF NOT EXISTS orders(id INTEGER PRIMARY KEY, item TEXT, qty INTEGER)", &[])?;
conn.execute("INSERT INTO orders(id, item, qty) VALUES(?, ?, ?)",
&[Value::Int(1), Value::Text("widget".into()), Value::Int(100)])?;
conn.execute("INSERT INTO orders(id, item, qty) VALUES(?, ?, ?)",
&[Value::Int(2), Value::Text("gadget".into()), Value::Int(50)])?;
conn.flush()?; // ensure pending writes are shipped before process exits
synclite::await_sync(DB_PATH, std::time::Duration::from_secs(30))?; // optional: block until PG confirms
conn.close()?;
The Rust crate is also a cdylib — embeddable from Python, Node.js, C/C++, Go, Ruby, C# via a single native library. Full sample: synclite-code-samples/rust/synclite_rusqlite_postgres.rs.
SELECT, JOIN, multi-statement transactions, ad-hoc DDL.insert/update/delete/selectAll — no SQL, schema evolves automatically), or a fluent Stream append-only API for high-throughput event ingestion.cdylib ABI: call it from C/C++, Go, Node.js, Ruby, or C#.Any number of apps embed their own runtime, in any language, all shipping to the same staging store and applying to the same destinations:
flowchart LR
classDef app fill:#eef6ff,stroke:#2b6cb0,stroke-width:1px,color:#1a365d
classDef rt fill:#fff8e6,stroke:#c98a00,stroke-width:1.5px,color:#5c3a00
classDef dst fill:#f0fff4,stroke:#2f855a,stroke-width:1px,color:#22543d
subgraph Fleet["Many apps · many devices — laptops · servers · edge boxes · containers · phones · IoT"]
direction TB
subgraph App1["App / Device 1 — Java"]
API1["SQL · Store · Stream"]:::app
RT1["SyncLite Runtime<br/>DB → Log → Shipper → Consolidator"]:::rt
API1 --> RT1
end
subgraph App2["App / Device 2 — Python"]
API2["SQL · Store · Stream"]:::app
RT2["SyncLite Runtime<br/>DB → Log → Shipper → Consolidator"]:::rt
API2 --> RT2
end
subgraph App3["App / Device 3 — Rust"]
API3["SQL · Store · Stream"]:::app
RT3["SyncLite Runtime<br/>DB → Log → Shipper → Consolidator"]:::rt
API3 --> RT3
end
subgraph AppN["App / Device N — Node · C/C++ · Go · Ruby · C# ..."]
APIN["SQL · Store · Stream"]:::app
RTN["SyncLite Runtime<br/>DB → Log → Shipper → Consolidator"]:::rt
APIN --> RTN
end
end
Stage[("Shared Stage<br/>FS · S3 · MinIO · SFTP")]
RT1 -- async --> Stage
RT2 -- async --> Stage
RT3 -- async --> Stage
RTN -- async --> Stage
Stage -- apply --> Dst
Dst["Current embedded-runtime target<br/>PostgreSQL<br/><br/>Standalone Consolidator<br/>PostgreSQL · MySQL · MSSQL · MongoDB<br/>Iceberg · DuckDB · S3"]:::dst
Inside each runtime: SQL (JDBC for Java, rusqlite for Rust, native bindings for Python / Node / C/C++ / Go / Ruby / C#) plus the Store CRUD and Stream APIs all sit on top of the same embedded DB, WAL logger, shipper, and in-process consolidator. The current embedded runtime path targets PostgreSQL; the standalone Consolidator is the broader multi-destination path.
A "device" is a logical embedded DB that the runtime owns end-to-end (storage + log + sync). Pick the API that fits your code:
SQLite, DuckDB, Derby, H2, HyperSQL). Arbitrary CREATE/ALTER/SELECT/INSERT/UPDATE/DELETE. Use this when you want a real embedded SQL DB that also happens to sync.SyncLiteStore typed CRUD (SQLITE_STORE, DUCKDB_STORE, …). insert/update/delete/selectAll against plain maps; schema evolves automatically. Highest consolidation throughput — no SQL-log parsing on the apply path.SyncLiteStream fluent insert/insertBatch over the append-only STREAMING device. For high-throughput event capture where UPDATE/DELETE are not needed.Which should I pick? For a new app,
SQLITE_STOREis usually the fastest and simplest starting point. Reach for SQL devices when you need raw SQL, JOINs, or multi-statement transactions.
SyncLite ships as two things:
| Component | Description | README |
|---|---|---|
SyncLite Runtime (Java) (synclite-<version>.jar) | One jar = JDBC / Store / Stream APIs + logger + shipper + (optional) in-process consolidator (via bundled synclite_jni native). Call initialize(dbPath, deviceName, destinationOptions) for the single-jar topology, or initialize(dbPath, conf) for logger-only mode paired with the standalone Consolidator WAR. | → |
| SyncLite Runtime (Rust) | Same runtime in Rust (logger + in-process consolidator) as a single cdylib. Consumable from Rust, Python, Node.js, C/C++, Go, Ruby, C# — anywhere you can load a native library. | → |
Deploy these only when you want a managed platform. They are standard webapps that consume the same runtime under the hood.
| Component | Description | README |
|---|---|---|
| SyncLite DB | Wraps the runtime as a tiny local-first HTTP/JSON service. Use it when you want the runtime accessible from a language that doesn't embed the native lib, or when multiple processes share one device. | → |
| SyncLite Client | Interactive CLI for inspecting and querying SyncLite devices. | → |
| SyncLite Consolidator | Standalone consolidation service for the central topology — accepts log segments from many devices and applies them to destinations. | → |
| SyncLite DBReader | Configurable database ETL / replication / migration jobs (source DB → SyncLite devices → destinations). | → |
| SyncLite QReader | MQTT / IoT connector that lands broker traffic into SyncLite devices. | → |
| SyncLite Job Monitor | Unified job management and scheduling UI for DBReader / QReader / Consolidator jobs. | → |
| SyncLite Validator | End-to-end integration test harness for SyncLite pipelines. | → |
| Sample Web App | JSP/Servlet demo that embeds SyncLite (Java) in logger-only mode and pairs with the standalone Consolidator WAR for sync. | → |
flowchart LR
classDef src fill:#eef6ff,stroke:#2b6cb0,color:#1a365d
classDef tool fill:#fef5ff,stroke:#805ad5,stroke-width:1.5px,color:#44337a
classDef dev fill:#fff8e6,stroke:#c98a00,color:#5c3a00
classDef ops fill:#edf2f7,stroke:#4a5568,color:#1a202c
classDef dst fill:#f0fff4,stroke:#2f855a,color:#22543d
SrcDB["Source DBs<br/>Oracle · MySQL · SQL Server<br/>Postgres · DB2 · MongoDB"]:::src
Brokers["IoT / MQTT brokers<br/>Mosquitto · HiveMQ · ..."]:::src
Apps["Your Apps<br/>(running embedded runtime)"]:::dev
DBReader["SyncLite DBReader<br/><sub>scheduled DB → device replication / ETL</sub>"]:::tool
QReader["SyncLite QReader<br/><sub>broker → device ingest</sub>"]:::tool
Devices[("SyncLite Stage")]:::ops
Consolidator["SyncLite Consolidator<br/><sub>standalone service<br/>applies log segments to destinations</sub>"]:::tool
JobMon["Job Monitor UI<br/><sub>schedule · monitor · alert<br/>DBReader · QReader · Consolidator jobs</sub>"]:::ops
Client["SyncLite Client<br/><sub>CLI — inspect / query devices</sub>"]:::ops
Dst["Destinations<br/>Postgres · MySQL · MSSQL · MongoDB<br/>Iceberg · DuckDB · S3"]:::dst
SrcDB --> DBReader --> Devices
Brokers --> QReader --> Devices
Apps --> Devices
Devices --> Consolidator --> Dst
JobMon -. orchestrates .-> DBReader
JobMon -. orchestrates .-> QReader
JobMon -. orchestrates .-> Consolidator
Client -. inspects .-> Devices
Solid lines are data flow. Dashed lines are the control plane. Nothing in this diagram is required by the runtime — reach for these only when you want a managed platform on top of the embedded runtime.
Use this when you want the central Consolidator + DBReader + QReader + Job Monitor running as services. No build required — grab the prebuilt platform zip:
Download the latest
synclite-platform-<version>.zipfrom GitHub Releases, unzip it, and open a terminal in the extracted folder.
cd bin/
./deploy.sh # or deploy.bat on Windows (downloads Tomcat + JDK, deploys all WARs)
./start.sh # or start.bat on Windows (starts Tomcat + every SyncLite app)
| URL | App |
|---|---|
| http://localhost:8080/synclite-consolidator | Configure and monitor consolidation jobs |
| http://localhost:8080/synclite-sample-app | Create devices, run SQL workloads, see live sync |
| http://localhost:8080/synclite-dbreader | Set up database ETL/replication pipelines |
| http://localhost:8080/synclite-qreader | Set up IoT MQTT connector pipelines |
| http://localhost:8080/synclite-jobmonitor | Manage and schedule all SyncLite jobs |
| http://localhost:8080/manager | Tomcat manager (user: synclite / pwd: synclite) |
Sample Web App → Consolidator (browser-driven, no external source DB)
DBReader → Consolidator (database → database replication/ETL)
Docker (all-in-one)
cd bin/
./docker-deploy.sh # Builds synclite-platform image (+ optional SFTP/MinIO + PostgreSQL/MySQL)
./docker-start.sh # Starts synclite-platform container and optional helpers
./docker-stop.sh # Stops synclite-platform container and optional helpers
⚠️ Docker helper scripts use default credentials. Change usernames, passwords, and enable TLS before any production use.
Full walkthrough: GETTING_STARTED.md - Try the tools together.
flowchart TB
classDef app fill:#eef6ff,stroke:#2b6cb0,stroke-width:1px,color:#1a365d
classDef bad fill:#fff5f5,stroke:#c53030,stroke-width:1.5px,color:#742a2a
classDef edb fill:#fffaf0,stroke:#c98a00,stroke-width:1px,color:#5c3a00
classDef rt fill:#fff8e6,stroke:#c98a00,stroke-width:1.5px,color:#5c3a00
classDef dst fill:#f0fff4,stroke:#2f855a,stroke-width:1px,color:#22543d
subgraph Before["TRADITIONAL — app bound to a remote DB"]
direction LR
A1["Your App / Agent"]:::app
PG1[("PostgreSQL<br/>(network-bound)")]:::bad
A1 -- "every read/write<br/>over the network" --> PG1
end
subgraph After["SYNCLITE — local-first + background sync"]
direction LR
A2["Your App / Agent"]:::app
EDB[("Embedded DB<br/>SQLite / DuckDB<br/>local, always available")]:::edb
RT["SyncLite Runtime<br/>log + shipper + sync"]:::rt
PG2[("PostgreSQL")]:::dst
A2 -- "in-process read/write<br/>(no network in the hot path)" --> EDB
EDB --> RT
RT -- "async · durable · offline-tolerant" --> PG2
end
Before ==>|"drop in one library"| After
Why the SyncLite path wins: your app reads and writes a local embedded DB at in-process speed and keeps working offline, while the SyncLite Runtime durably logs every write and syncs it to your destination in the background. The network moves off the critical path — so you get lower latency, offline resilience, and exactly-once delivery without wiring up a separate CDC tool, message bus, or replication agent.
| Traditional (app → remote DB) | SyncLite (local-first) | |
|---|---|---|
| Read/write latency | network round-trip every op | in-process, memory-speed |
| Works offline | ✗ fails without connectivity | ✓ fully offline, syncs on reconnect |
| Delivery guarantee | app must build retries/dedup | ✓ durable log, exactly-once |
| Moving parts | app + DB + custom CDC/queue | ✓ one embedded library |
Architecture support. SyncLite is 64-bit only —
x86_64andaarch64on Windows / Linux / macOS. 32-bit hosts are not supported because SyncLite Runtime (Rust) depends on the DuckDB engine, which requires a 64-bit host.
Prerequisites (Java-only build): Java 25, Apache Maven 3.8.6+
Additional prerequisites (build all loggers including Rust):
link.exe). Install from https://visualstudio.microsoft.com/visual-cpp-build-tools/ and select the "Desktop development with C++" workload.build-essential + cmake — e.g. sudo apt install build-essential pkg-config cmake.xcode-select --install.cargo-zigbuild and the Zig compiler on PATHmaturin — for the host-platform Python wheel:
python -m pip install maturin
# Windows: pip install delvewheel | Linux: pip install auditwheel | macOS: pip install delocate
Skip with -DskipPythonWheel=true (also skipped automatically by -DskipNonJavaLoggers=true).lib/nodejs/:
node --version
npm --version
Skip with -DskipNodePackage=true (also skipped automatically by -DskipNonJavaLoggers=true).git clone --recurse-submodules https://github.com/syncliteio/SyncLite.git SyncLite
cd SyncLite
| # | Flavor | What it produces |
|---|---|---|
| 1 | Full platform (default) | target/synclite-platform-<rev>.zip — Tomcat scripts + WARs + tools + samples + multi-arch native runtime |
| 2 | Full platform, Java-only | Same as #1 but no lib/native/ (no Rust toolchain required) |
| 3 | Runtime (recommended for app developers) | target/synclite-runtime-<rev>.zip — slim zip with lib/java/ (synclite jar) + multi-arch lib/native/ (Rust cdylibs) + lib/python/ wheels + lib/nodejs/ npm package + cross-language sample-apps/{cpp,java,nodejs,python,rust} |
# 1. Full platform (default)
mvn -Drevision=1.1.0 clean install
# 2. Full platform, Java-only (no Rust toolchain required)
mvn -Drevision=1.1.0 -DskipNonJavaLoggers=true clean install
# 3. Runtime only — slim embeddable zip
mvn -Drevision=1.1.0 -DruntimeOnly=true clean install
# Fastest build on a host without zig
mvn -Drevision=1.1.0 -DruntimeOnly=true -DskipRustCrossCompile=true -DskipTests clean install
For just the Java jar or just the Rust cdylibs, build individual subprojects directly:
cd synclite-logger-java && mvn installorcd synclite-logger-rust && cargo build --workspace --release.
The cross-compile toolchain (for Linux x86_64 and aarch64 cdylibs):
cargo install cargo-zigbuild
rustup target add x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu
# zig must be on PATH — download from https://ziglang.org/download/
The
bin/deploy.sh/bin/deploy.batscripts download Apache Tomcat 9.0.117 and OpenJDK 25 automatically. No manual installation needed for a quick start.
-DruntimeOnly=true)synclite-runtime-1.1.0/
+-- lib/
| +-- java/
| | +-- synclite-<version>.jar # Add to your app classpath
| | +-- synclite.conf # Default logger configuration
| +-- native/ # Multi-arch native cdylibs (Rust runtime)
| | +-- include/ # C / C++ ABI headers (synclite.h, synclite.hpp)
| | +-- synclite_<version>.dll # Windows host build
| | +-- synclite_<version>.dll.lib # Windows import library
| | +-- synclite_<version>.lib # Windows static library
| | +-- libsynclite_<version>_linux_x86_64.so # cross-compiled
| | +-- libsynclite_<version>_linux_aarch64.so # cross-compiled
| | +-- libsynclite_<version>.dylib # only if built on macOS
| | +-- synclite.conf
| +-- python/
| | +-- synclite-<version>-cp38-abi3-win_amd64.whl
| | +-- synclite-<version>-cp38-abi3-manylinux_2_28_x86_64.whl
| | +-- synclite-<version>-cp38-abi3-manylinux_2_28_aarch64.whl
| +-- nodejs/
| | +-- synclite-1.1.0.tgz
| | +-- synclite-native-win32-x64-msvc-1.1.0.tgz
| | +-- synclite-native-linux-arm64-gnu-1.1.0.tgz
| | +-- synclite-native-linux-x64-gnu-1.1.0.tgz
| +-- rust/
| +-- synclite-source/ # Self-contained Cargo workspace
+-- sample-apps/ # One sample per language
| +-- cpp/ # CMake-based, links lib/native
| +-- java/ # javac + lib/java/synclite-<version>.jar
| +-- python/ # pip install lib/python/*.whl
| +-- rust/ # cargo run, path-deps into lib/rust
| +-- nodejs/ # npm install lib/nodejs/synclite-1.1.0.tgz
+-- LICENSE
+-- synclite_platform_version.txt
synclite-platform-1.1.0/
+-- bin/
| +-- deploy.sh / deploy.bat # One-command setup: downloads Tomcat + JDK, deploys WARs
| +-- start.sh / start.bat # Start Tomcat + all SyncLite apps
| +-- stop.sh / stop.bat # Graceful shutdown
| +-- docker-deploy.sh / docker-start.sh / docker-stop.sh
| +-- stage/sftp/ stage/minio/ # Docker scripts for staging servers
| +-- dst/postgresql/ dst/mysql/ # Docker scripts for destination DBs
+-- lib/ # Same as runtime-only zip above
+-- tools/
| +-- synclite-client/ synclite-db/ synclite-dbreader/
| +-- synclite-qreader/ synclite-jobmonitor/ synclite-validator/
| +-- synclite-sample-app/
+-- sample-apps/ # cpp / java / python / rust / nodejs
Add synclite-<version>.jar to your project, then:
import io.synclite.*;
import java.nio.file.Path;
import java.sql.*;
Path dbDir = Path.of(System.getProperty("user.home"), "synclite", "db");
Path dbPath = dbDir.resolve("myapp.db");
Path conf = dbDir.resolve("synclite.conf");
Class.forName("io.synclite.SQLite");
SQLite.initialize(dbPath, conf);
try (Connection c = DriverManager.getConnection("jdbc:synclite_sqlite:" + dbPath);
Statement s = c.createStatement()) {
s.execute("CREATE TABLE IF NOT EXISTS orders(id INT, item TEXT, qty INT)");
s.execute("INSERT INTO orders VALUES(1, 'widget', 100)");
// ↑ captured in a log file and shipped to staging storage automatically
}
SQLite.closeAll();
For other embedded databases replace SQLite / synclite_sqlite with DuckDB / synclite_duckdb, Derby / synclite_derby, H2 / synclite_h2, or HyperSQL / synclite_hsqldb.
Full configuration reference: lib/logger/synclite.conf · Documentation
STORE device types (SQLITE_STORE, DUCKDB_STORE, DERBY_STORE, H2_STORE, HYPERSQL_STORE) expose the SyncLiteStore API: typed insert / update / delete / selectAll methods that handle schema evolution automatically and log every operation to the replication pipeline.
import io.synclite.SQLiteStore;
import io.synclite.SyncLiteStore;
Class.forName("io.synclite.SQLiteStore");
SQLiteStore.initialize(dbPath, conf);
try (SyncLiteStore store = SQLiteStore.open(dbPath)) {
store.createTable("orders", new LinkedHashMap<>(Map.of(
"id", "INTEGER PRIMARY KEY",
"item", "TEXT",
"qty", "INTEGER"
)));
store.insert("orders", Map.of("id", 1, "item", "widget", "qty", 100));
store.update("orders", Map.of("qty", 150), Map.of("id", 1));
store.delete("orders", Map.of("id", 1));
List<Map<String, Object>> rows = store.selectAll("orders");
}
SQLiteStore.closeDevice(dbPath);
SyncLiteStream wraps the STREAMING device with a fluent insert / insertBatch API. UPDATE and DELETE are intentionally absent — this API models event flow, not mutable records.
import io.synclite.Streaming;
import io.synclite.SyncLiteStream;
Class.forName("io.synclite.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"));
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")
));
}
io.synclite.Jedis is a drop-in subclass of redis.clients.jedis.Jedis. Every write is durably committed to a SQLITE_STORE device before being forwarded to Redis, and the cache is repopulated from the store on restart.
import io.synclite.Jedis;
// Managed mode — Jedis handles SQLiteStore initialise / open / close
try (Jedis jedis = Jedis.builder(dbPath, conf, "cache-device")
.host("localhost").port(6379).build()) {
jedis.set("user:1:name", "Alice");
jedis.hset("session:42", Map.of("token", "abc123", "status", "active"));
jedis.rpush("queue", "job-1", "job-2");
jedis.sadd("tags", "etl", "cdc");
jedis.zadd("leaderboard", Map.of("Alice", 100.0, "Bob", 200.0));
jedis.del("tmp");
}
Alternatively, supply a pre-opened SyncLiteStore via Jedis.builder(store) when managing the store lifecycle externally.
SyncLite DB is a local-first HTTP/JSON database service that wraps embedded databases with built-in SyncLite logging and replication (by coupling it with SyncLite Consolidator), so any language can call it over HTTP.
The sample below uses Python for brevity; the same HTTP calls work from Go (net/http) and Node.js (fetch / axios).
Deploy synclite-db-1.1.0.war (bundled under tools/synclite-db/) to Tomcat, open http://localhost:8080/synclite-db, and configure + start the DB server from the browser GUI. It then serves the HTTP/JSON API on the port set in the GUI.
# Python client (plain HTTP — no SDK needed)
import requests, json
BASE = "http://localhost:5555/synclite"
requests.post(BASE, json={"db-type": "SQLITE", "db-name": "myapp",
"synclite-logger-options": {"local-data-stage-directory": "/tmp/stage"},
"sql": "initialize"})
requests.post(BASE, json={"db-name": "myapp",
"sql": "CREATE TABLE IF NOT EXISTS t1(a INT, b TEXT)"})
requests.post(BASE, json={"db-name": "myapp",
"sql": "INSERT INTO t1 VALUES(?, ?)", "arguments": [[1, "hello"], [2, "world"]]})
// Go client (plain HTTP — no SDK needed)
package main
import (
"bytes"
"net/http"
)
func postJSON(url string, body string) error {
_, err := http.Post(url, "application/json", bytes.NewBufferString(body))
return err
}
func main() {
base := "http://localhost:5555/synclite"
_ = postJSON(base, `{"db-type":"SQLITE","db-name":"myapp","synclite-logger-options":{"local-data-stage-directory":"/tmp/stage"},"sql":"initialize"}`)
_ = postJSON(base, `{"db-name":"myapp","sql":"CREATE TABLE IF NOT EXISTS t1(a INT, b TEXT)"}`)
_ = postJSON(base, `{"db-name":"myapp","sql":"INSERT INTO t1 VALUES(?, ?)","arguments":[[1,"hello"],[2,"world"]]}`)
}
// Node.js client (plain HTTP — no SDK needed)
const BASE = "http://localhost:5555/synclite";
async function post(body) {
await fetch(BASE, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
}
await post({
"db-type": "SQLITE",
"db-name": "myapp",
"synclite-logger-options": { "local-data-stage-directory": "/tmp/stage" },
sql: "initialize",
});
await post({
"db-name": "myapp",
sql: "CREATE TABLE IF NOT EXISTS t1(a INT, b TEXT)",
});
await post({
"db-name": "myapp",
sql: "INSERT INTO t1 VALUES(?, ?)",
arguments: [[1, "hello"], [2, "world"]],
});
SDK samples for Java, Python, C#, C++, Go, Rust, Ruby, Node.js: synclite-db/sdk-source/
Configure local-data-stage-directory in synclite.conf for local/NFS staging. For remote staging (SFTP, S3, MinIO, Kafka, OneDrive, Google Drive) configure the appropriate properties and use the matching Docker helper scripts in bin/stage/.
Docker staging helpers:
bin/stage/sftp/docker-deploy.sh # SFTP server
bin/stage/minio/docker-deploy.sh # MinIO object storage
⚠️ The stage Docker scripts use default credentials. Always change usernames, passwords, and add TLS before production use.
| Resource | Link |
|---|---|
| Full Documentation | https://github.com/syncliteio/SyncLite/blob/main/DOCUMENTATION.md |
| Website | https://www.synclite.io |
SyncLite is backed by patented technology, more info: https://www.synclite.io/about
SyncLite is open source and free to use. If it adds value to your work, please consider sponsoring its continued development — sponsorships fund maintenance, new features, and community support.
You can sponsor us via GitHub Sponsors (see .github/FUNDING.yml). Every contribution, big or small, is greatly appreciated. ⭐ Starring the repo and spreading the word helps too!
We welcome contributions! Please read CONTRIBUTING.md and CODE_OF_CONDUCT.md before opening a pull request.
SyncLite is licensed under the Apache License 2.0.
Shell
20.6%
Java
14.3%
Batchfile
14.1%
Python
10.3%
Dockerfile
9.4%
Rust
8.6%
CMake
8.3%
JavaScript
7.3%
C++
7.1%