npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

sveltekit-sync

v0.1.0

Published

Local-first sync engine for SvelteKit

Readme

🔄 SvelteKit Sync

An experimental, local-first sync engine for SvelteKit with optimistic updates, real-time synchronization, and support for any database.

npm version License: MIT TypeScript Svelte 5

✨ Features

  • 🚀 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

📦 Installation


npm install sveltekit-sync # or your favorite package manager

🚀 Quick Start

1. Set Up Database Schema

// 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()
});

2. Configure Server Sync

// 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) => sql`user_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 });

3. Add sync handle to Hooks

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,);


4. Create Remote Functions

// 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);
  }
);

5. Initialize Client

// 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');

6. Initialize in Root Layout

<!-- src/routes/+layout.svelte -->
<script lang="ts">
  import { onMount } from 'svelte';
  import { initDB, syncEngine } from '$lib/db';
  import { browser } from '$app/environment';

  onMount(async () => {
    if (browser) {
      await initDB();
    }
    return () => syncEngine.destroy();
  });

  const syncState = $derived(syncEngine.state);
</script>

<div class="app">
  {#if syncState.isSyncing}
    <div class="sync-indicator">Syncing...</div>
  {/if}
  <slot />
</div>

7. Use in Components

<!-- src/routes/todos/+page.svelte -->
<script lang="ts">
  import { onMount } from 'svelte';
  import { todosStore } from '$lib/db';

  let newTodo = $state('');

  onMount(() => todosStore.load());

  async function addTodo() {
    await todosStore.create({
      text: newTodo,
      completed: false,
      createdAt: Date.now()
    });
    newTodo = '';
  }

  async function toggleTodo(id: string) {
    const todo = todosStore.find(t => t.id === id);
    if (todo) {
      await todosStore.update(id, { completed: !todo.completed });
    }
  }
</script>

<input bind:value={newTodo} on:keydown={(e) => e.key === 'Enter' && addTodo()} />

<ul>
  {#each todosStore.data as todo (todo.id)}
    <li>
      <input 
        type="checkbox" 
        checked={todo.completed}
        onchange={() => toggleTodo(todo.id)}
      />
      {todo.text}
      <button onclick={() => todosStore.delete(todo.id)}>Delete</button>
    </li>
  {/each}
</ul>

📚 Core Concepts

Optimistic Updates

All CRUD operations apply changes immediately to the local database and UI, then sync in the background:

await todosStore.create({ text: 'Buy milk' }); 
// ✅ UI updates instantly
// 🔄 Syncs to server in background

Collection Stores

Collection stores provide a reactive, ergonomic API:

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)

Conflict Resolution

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
export const syncEngine = new SyncEngine({
  conflictResolution: 'last-write-wins',
  onConflict: (conflict) => {
    console.log('Conflict detected:', conflict);
  }
});

🗄️ Database Adapters

Client Adapters

  • IndexedDB (built-in) - Browser storage
  • SQLite - Coming soon
  • PGlite - Coming soon

Server Adapters

  • Drizzle ORM - sveltekit-sync/adapters/drizzle
  • Prisma - Coming soon
  • Postgres - Coming soon
  • MongoDB - Coming soon

🎯 Advanced Features (WIP/To be Implemented)

Query Builder

const active = await todosStore
  .query()
  .where('completed', false)
  .orderBy('createdAt', 'desc')
  .limit(10)
  .get();

Relationships

const projectsStore = syncEngine.collection('projects', {
  relations: {
    tasks: { type: 'hasMany', collection: 'tasks', key: 'projectId' }
  }
});

const project = await projectsStore.withRelations(['tasks']).findOne(id);

Middleware/Hooks

todosStore.before('create', (data) => ({
  ...data,
  createdBy: currentUser.id
}));

todosStore.after('update', (data) => {
  analytics.track('todo_updated', data);
});

Batch Operations

await todosStore.batch()
  .create({ text: 'Task 1' })
  .create({ text: 'Task 2' })
  .update(id, { completed: true })
  .commit();

Real-time Subscriptions

const unsubscribe = todosStore.subscribe((todos) => {
  console.log('Todos updated:', todos);
});

🔐 Security

Row-Level Security

Control what each user can access:

export const syncSchema = {
  tables: {
    todos: {
      where: (userId: string) => sql`user_id = ${userId}`
    }
  }
};

Data Transformation

Remove sensitive fields before syncing:

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

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

📖 API Reference

Full API documentation available at sveltekit-sync.mudiageo.me

🤝 Contributing

We welcome contributions! Please see CONTRIBUTING.md for guidelines.

📄 License

MIT © Mudiaga Arharhire

🙏 Acknowledgments