TypeScript/JavaScript wrapper for SQLite with automatic change tracking for syncing. This library uses sql.js (SQLite compiled to WebAssembly) and automatically tracks all write operations, making it easy to sync data across devices and clients.
- 🔄 Automatic Change Tracking - All write operations are automatically tracked for syncing
- 💾 IndexedDB Persistence - Data is automatically persisted to IndexedDB in the browser
- 🌐 Web Compatible - Works in all modern browsers using WebAssembly
- 📦 TypeScript Support - Full TypeScript definitions included
- 🎯 Framework Agnostic - Works with React, Vue, Svelte, or vanilla JS
- 🔒 Schema Versioning - Built-in schema version management
npm install synclib
# or
pnpm add synclib
# or
yarn add synclibimport { SynclibDatabase, SynclibOperation } from 'synclib';
// Open a database
const db = await SynclibDatabase.open({
databaseName: 'myapp.db'
});
// Create a table
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL
)
`);
// Insert data with change tracking
await db.write({
tableName: 'users',
rowId: '123',
operation: SynclibOperation.INSERT,
sql: `INSERT INTO users (id, name, email) VALUES (?, ?, ?)`,
params: ['123', 'John Doe', 'john@example.com'],
data: JSON.stringify({ id: '123', name: 'John Doe', email: 'john@example.com' })
});
// Read data
const users = db.read('SELECT * FROM users');
console.log(users);
// Get pending changes to sync
const changes = db.getPendingChanges();
console.log('Changes to sync:', changes);
// After syncing, mark changes as synced
if (changes.length > 0) {
await db.markSynced(changes[changes.length - 1].seqnum);
}Here's an example of using synclib in a Svelte component:
<script lang="ts">
import { onMount } from 'svelte';
import { SynclibDatabase, SynclibOperation } from 'synclib';
let db: SynclibDatabase | null = null;
let users: Array<{ id: string; name: string; email: string }> = [];
onMount(async () => {
// Open database
db = await SynclibDatabase.open({
databaseName: 'myapp.db'
});
// Create table if not exists
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL
)
`);
// Load users
loadUsers();
});
function loadUsers() {
if (!db) return;
users = db.read('SELECT * FROM users ORDER BY name');
}
async function addUser(name: string, email: string) {
if (!db) return;
const id = crypto.randomUUID();
await db.write({
tableName: 'users',
rowId: id,
operation: SynclibOperation.INSERT,
sql: `INSERT INTO users (id, name, email) VALUES (?, ?, ?)`,
params: [id, name, email],
data: JSON.stringify({ id, name, email })
});
loadUsers();
}
async function syncChanges() {
if (!db) return;
// Get pending changes
const changes = db.getPendingChanges();
// Send changes to server
const response = await fetch('/api/sync', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ changes })
});
if (response.ok) {
const { lastSeqnum } = await response.json();
await db.markSynced(lastSeqnum);
}
}
</script>
<div>
<h1>Users</h1>
{#each users as user}
<div>{user.name} - {user.email}</div>
{/each}
<button on:click={syncChanges}>Sync Changes</button>
</div>Opens a database connection.
Parameters:
config.databaseName(string): Name of the databaseconfig.wasmUrl(string, optional): URL to sqlite3.wasm file
Returns: Promise<SynclibDatabase>
Executes SQL without change tracking (for DDL, schema changes, etc.).
Parameters:
sql(string): SQL statement to execute
Executes a read-only query.
Parameters:
sql(string): SQL SELECT statement
Returns: Array<Record<string, any>>
Executes a write operation with change tracking.
Parameters:
options.tableName(string): Name of the table being modifiedoptions.rowId(string): Primary key of the rowoptions.operation(SynclibOperation): Operation type (INSERT, UPDATE, DELETE)options.sql(string): SQL statement to executeoptions.params(any[], optional): Parameters for the SQL statementoptions.data(string, optional): JSON-encoded row data for tracking
Returns: Promise<void>
Applies a remote change without creating a local change record (prevents sync loops).
Parameters:
options.tableName(string): Name of the table being modifiedoptions.rowId(string): Primary key of the rowoptions.operation(SynclibOperation): Operation typeoptions.sql(string): SQL statement to executeoptions.params(any[], optional): Parameters for the SQL statementoptions.data(string, optional): JSON-encoded row data
Returns: Promise<void>
Gets pending changes that haven't been synced.
Parameters:
limit(number, optional): Maximum number of changes to retrieve (default: 100)
Returns: Change[]
Marks changes as synced up to the given sequence number.
Parameters:
seqnum(number): Sequence number up to and including which to mark as synced
Returns: Promise<void>
Begins bulk remote operation mode (transaction). Use this for efficient bulk imports.
Executes SQL in bulk remote mode.
Parameters:
sql(string): SQL statement to execute
Ends bulk remote mode and commits or rolls back the transaction.
Parameters:
rollback(boolean, optional): If true, rollback instead of commit
Returns: Promise<void>
Gets the current schema version.
Returns: number
Sets the schema version.
Parameters:
version(number): Version number to set
Returns: Promise<void>
Exports the database as binary data (for backup/export).
Returns: Uint8Array
Closes the database connection.
Returns: Promise<void>
const db = await SynclibDatabase.open({
databaseName: 'myapp.db'
});
const currentVersion = db.getSchemaVersion();
const targetVersion = 3;
if (currentVersion < targetVersion) {
// Run migrations
if (currentVersion < 1) {
db.exec(`
CREATE TABLE users (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL
)
`);
await db.setSchemaVersion(1);
}
if (currentVersion < 2) {
db.exec(`ALTER TABLE users ADD COLUMN created_at INTEGER`);
await db.setSchemaVersion(2);
}
if (currentVersion < 3) {
db.exec(`
CREATE TABLE posts (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
title TEXT NOT NULL,
content TEXT,
FOREIGN KEY (user_id) REFERENCES users(id)
)
`);
await db.setSchemaVersion(3);
}
}Here's a recommended pattern for syncing changes:
// Client side
async function syncWithServer(db: SynclibDatabase) {
// 1. Get pending local changes
const localChanges = db.getPendingChanges();
// 2. Send to server and receive remote changes
const response = await fetch('/api/sync', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ changes: localChanges })
});
const { remoteChanges, lastSeqnum } = await response.json();
// 3. Apply remote changes
for (const change of remoteChanges) {
await db.applyRemote({
tableName: change.tableName,
rowId: change.rowId,
operation: change.operation,
sql: change.sql, // Server should provide the SQL
params: change.params,
data: change.data
});
}
// 4. Mark local changes as synced
if (localChanges.length > 0) {
await db.markSynced(lastSeqnum);
}
}
// Run sync periodically
setInterval(() => syncWithServer(db), 30000); // Every 30 secondsMIT