Local-first sync engine for SvelteKit
npm install sveltekit-syncAn experimental, local-first sync engine for SvelteKit with optimistic updates, real-time synchronization, and support for any database.




- ๐ Instant UI Updates - Optimistic updates for zero-latency UX
- ๐ Real-time Sync - Changes appear instantly across all devices
- ๐ก Offline-First - Works seamlessly without internet connection
- ๐๏ธ Database Agnostic - Works with any client DB (IndexedDB, SQLite, PGlite) and server DB (Postgres, MongoDB, MySQL, etc.)
- โก Powered by Remote Functions - Uses SvelteKit's new Remote Functions API
- ๐ฏ Type-Safe - Full TypeScript support with excellent IntelliSense
- ๐ Secure - Built-in row-level security and data filtering
- ๐จ Ergonomic API - Simple, intuitive developer experience
- ๐ Conflict Resolution - Multiple strategies for handling conflicts
- ๐ฆ Modular - Install only what you need
``bash
npm install sveltekit-sync # or your favorite package manager
`
`typescript
// src/lib/server/database/schema.ts
import { pgTable, text, boolean, timestamp, integer, uuid } from 'drizzle-orm/pg-core';
// All synced tables must include these columns
export const syncMetadata = {
_version: integer('_version').notNull().default(1),
_updatedAt: timestamp('_updated_at').notNull().defaultNow(),
_clientId: text('_client_id'),
_isDeleted: boolean('_is_deleted').default(false)
};
export const todos = pgTable('todos', {
id: uuid('id').primaryKey().defaultRandom(),
userId: text('user_id').notNull(),
text: text('text').notNull(),
completed: boolean('completed').default(false),
...syncMetadata
});
// Sync log table - tracks all changes for efficient delta sync
export const syncLog = pgTable('sync_log', {
id: uuid('id').primaryKey().defaultRandom(),
tableName: text('table_name').notNull(),
recordId: text('record_id').notNull(),
operation: text('operation').notNull(), // 'insert', 'update', 'delete'
data: jsonb('data'),
timestamp: timestamp('timestamp').notNull().defaultNow(),
clientId: text('client_id'),
userId: text('user_id').notNull()
});
// Client state table - track last sync for each client
export const clientState = pgTable('client_state', {
clientId: text('client_id').primaryKey(),
userId: text('user_id').notNull(),
lastSync: timestamp('last_sync').notNull().defaultNow(),
lastActive: timestamp('last_active').notNull().defaultNow()
});
`
`typescript
// src/lib/server/sync.ts
import type { SyncConfig } from 'sveltekit-sync/server'
import { db } from '$lib/server/db'
import * as schema from '$lib/server/db/schema'
import { createServerSync } from 'sveltekit-sync/server';
import { DrizzleAdapter } from 'sveltekit-sync/adapters/drizzle';
// Create database adapter
const adapter = new DrizzleAdapter({ db, schema })
// Define your sync config
export const config: SyncConfig = {
tables: {
todos: {
table: 'todos',
columns: ['id', 'text', 'completed', 'userId', '_version', '_updatedAt'],
// Row-level security - only sync user's own data
where: (userId: string) => sqluser_id = ${userId},
conflictResolution: 'last-write-wins'
}
},
realtime: {
authenticate: (request) => {
const user = getUser(request);
if (!user) return null;
return { userId: user.id};
}
}
};
export const { syncEngine, handle } = createServerSync({ adapter, config });
`
typescript
import { sequence } from '@sveltejs/kit/hooks';
import { handle as syncHandle } from '$lib/server/sync';
import
async function customHandle({ event, resolve }) {
return resolve(event);
};export const handle = sequence(customHandle, syncHandle,);
`$3
`typescript
// src/lib/sync.remote.ts
import { query, command } from '$app/server';
import * as v from 'valibot';
import { syncEngine } from '$lib/server/sync';
import { getUser } from '$lib/server/auth';export const pushChanges = command(
v.array(SyncOperationSchema),
async (operations, { request }) => {
const user = await getUser(request);
return await syncEngine.push(operations, user.id);
}
);
export const pullChanges = query(
v.object({ lastSync: v.number(), clientId: v.string() }),
async ({ lastSync, clientId }, { request }) => {
const user = await getUser(request);
return await syncEngine.pull(lastSync, clientId, user.id);
}
);
`$3
`typescript
// src/lib/db.ts
import { SyncEngine, IndexedDBAdapter } from 'sveltekit-sync';
import { pushChanges, pullChanges } from '$lib/sync.remote';const adapter = new IndexedDBAdapter('myapp-db', 1);
export const syncEngine = new SyncEngine({
local: { db: null, adapter },
remote: {
push: data => pushChanges(data),
pull: (lastSync: number, clientId: string) => pullChanges({ lastSync, clientId })
},
syncInterval: 30000, // Sync every 30 seconds
conflictResolution: 'last-write-wins'
});
export async function initDB() {
await adapter.init({ todos: 'id', notes: 'id' });
await syncEngine.init();
}
// Create typed collection stores
export const todosStore = syncEngine.collection('todos');
`$3
`svelte
{#if syncState.isSyncing}
Syncing...
{/if}
`$3
`svelte
e.key === 'Enter' && addTodo()} />
{#each todosStore.data as todo (todo.id)}
type="checkbox"
checked={todo.completed}
onchange={() => toggleTodo(todo.id)}
/>
{todo.text}
{/each}
`๐ Core Concepts
$3
All CRUD operations apply changes immediately to the local database and UI, then sync in the background:
`typescript
await todosStore.create({ text: 'Buy milk' });
// โ
UI updates instantly
// ๐ Syncs to server in background
`$3
Collection stores provide a reactive, ergonomic API:
`typescript
const todosStore = syncEngine.collection('todos');// Reactive state
todosStore.data // Current data array
todosStore.isLoading // Loading state
todosStore.error // Error state
todosStore.count // Item count
todosStore.isEmpty // Empty check
// CRUD operations
await todosStore.create(data)
await todosStore.update(id, data)
await todosStore.delete(id)
await todosStore.findOne(id)
// Utility methods
todosStore.find(predicate)
todosStore.filter(predicate)
todosStore.sort(compareFn)
`$3
Built-in strategies for handling conflicts:
-
client-wins - Client changes always win
- server-wins - Server changes always win
- last-write-wins - Most recent change wins (default)
- manual - Custom resolution logic`typescript
export const syncEngine = new SyncEngine({
conflictResolution: 'last-write-wins',
onConflict: (conflict) => {
console.log('Conflict detected:', conflict);
}
});
`๐๏ธ Database Adapters
$3
- IndexedDB (built-in) - Browser storage
- SQLite - Coming soon
- PGlite - Coming soon
$3
- Drizzle ORM -
sveltekit-sync/adapters/drizzle
- Prisma - Coming soon
- Postgres - Coming soon
- MongoDB - Coming soon๐ฏ Advanced Features (WIP/To be Implemented)
$3
`typescript
const active = await todosStore
.query()
.where('completed', false)
.orderBy('createdAt', 'desc')
.limit(10)
.get();
`$3
`typescript
const projectsStore = syncEngine.collection('projects', {
relations: {
tasks: { type: 'hasMany', collection: 'tasks', key: 'projectId' }
}
});const project = await projectsStore.withRelations(['tasks']).findOne(id);
`$3
`typescript
todosStore.before('create', (data) => ({
...data,
createdBy: currentUser.id
}));todosStore.after('update', (data) => {
analytics.track('todo_updated', data);
});
`$3
`typescript
await todosStore.batch()
.create({ text: 'Task 1' })
.create({ text: 'Task 2' })
.update(id, { completed: true })
.commit();
`$3
`typescript
const unsubscribe = todosStore.subscribe((todos) => {
console.log('Todos updated:', todos);
});
`๐ Security
$3
Control what each user can access:
`typescript
export const syncSchema = {
tables: {
todos: {
where: (userId: string) => sqluser_id = ${userId}
}
}
};
`$3
Remove sensitive fields before syncing:
`typescript
export const syncSchema = {
tables: {
users: {
transform: (user) => {
const { password, internalNotes, ...safe } = user;
return safe;
}
}
}
};
`๐ Performance
- Delta Sync - Only changed records are synced
- Batch Operations - Multiple changes sent in single request
- Intelligent Caching - Frequently accessed data cached in memory
- Connection Pooling - Efficient resource usage
- Compression - Automatic payload compression
๐งช Testing
`bash
npm test # Run all tests
npm run test:unit # Unit tests
npm run test:integration # Integration tests
npm run test:e2e # End-to-end tests
``Full API documentation available at sveltekit-sync.mudiageo.me
We welcome contributions! Please see CONTRIBUTING.md for guidelines.
MIT ยฉ Mudiaga Arharhire
- Built with SvelteKit
- Inspired by LiveStore and other prior sync and local-first libraries