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

@synckit-js/sdk

v0.2.2

Published

Production-ready local-first sync with rich text editing, undo/redo, live cursors, and framework adapters for React, Vue, and Svelte

Readme

@synckit-js/sdk

TypeScript SDK for SyncKit - Production-grade local-first sync with real-time collaboration.

Bundle Size: 59KB gzipped (full) or 45KB gzipped (lite) - Competitive with Yjs (~19KB), Automerge (~60-78KB), and Firebase (~150KB).

🚀 Quick Start

Offline-Only Mode

import { SyncKit } from '@synckit-js/sdk'

// Initialize (offline-only)
const sync = new SyncKit({
  storage: 'indexeddb',
  name: 'my-app'
})

await sync.init()

// Create a typed document
interface Todo {
  title: string
  completed: boolean
}

const doc = sync.document<Todo>('todo-1')

// Initialize document
await doc.init()

// Set fields
await doc.set('title', 'Buy milk')
await doc.set('completed', false)

// Subscribe to changes
doc.subscribe((todo) => {
  console.log('Updated:', todo)
})

// Get current state
const todo = doc.get()

With Network Sync (v0.1.0)

import { SyncKit } from '@synckit-js/sdk'

// Initialize with server sync
const sync = new SyncKit({
  storage: 'indexeddb',
  name: 'my-app',
  serverUrl: 'ws://localhost:8080',  // Enable network sync
  clientId: 'user-123',
  network: {
    reconnect: {
      enabled: true,
      initialDelay: 1000,
      maxDelay: 30000
    }
  }
})

await sync.init()

// Monitor network status
sync.onNetworkStatusChange((status) => {
  console.log('Connection:', status.connectionState)
  console.log('Queue size:', status.queueSize)
})

// Create and sync document
const doc = sync.document<Todo>('todo-1')
await doc.init()  // Automatically subscribes to real-time server updates!
await doc.update({ title: 'Buy milk', completed: false })

// Changes sync instantly to server and other clients

📦 Installation

npm install @synckit-js/sdk
# or
yarn add @synckit-js/sdk
# or
pnpm add @synckit-js/sdk

🎯 Features

Core Features

  • Type-safe: Full TypeScript support with generics
  • Reactive: Observable pattern for real-time updates
  • Persistent: IndexedDB storage with unlimited capacity
  • Offline-first: Works completely without network
  • Zero-config: Sensible defaults, no setup required

Network Features (v0.1.0)

  • Real-time sync: WebSocket-based server synchronization
  • Conflict resolution: Automatic LWW with vector clocks
  • Offline queue: Persistent operation queue with retry logic
  • Auto-reconnection: Exponential backoff with jitter
  • Network monitoring: Connection state tracking
  • Sync state tracking: Per-document sync status

Framework Integration

  • React hooks: Built-in hooks for React 18+
  • Vue composables: Full Vue 3 Composition API support
  • Svelte stores: Hybrid Svelte 4/5 store implementation
  • Network-aware hooks: Monitor connection and sync state
  • TypeScript support: Full type inference throughout

Undo/Redo (v0.2.0)

  • Intelligent merging: Automatically merges consecutive operations
  • Cross-tab sync: Undo/redo state syncs across browser tabs
  • Persistent history: Survives page refreshes via IndexedDB
  • Framework adapters: Native React/Vue/Svelte integrations
  • Customizable: Configure merge strategies and stack size

🔌 React Integration

Basic Usage

import { SyncProvider, useSyncDocument } from '@synckit-js/sdk/react'

// 1. Wrap your app
function App() {
  return (
    <SyncProvider synckit={sync}>
      <TodoList />
    </SyncProvider>
  )
}

// 2. Use in components
function TodoItem({ id }: { id: string }) {
  const [todo, { set, update, delete: deleteFn }, doc] = useSyncDocument<Todo>(id)

  return (
    <div>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={(e) => set('completed', e.target.checked)}
      />
      <span>{todo.title}</span>
      <button onClick={() => update({ completed: !todo.completed })}>
        Toggle
      </button>
    </div>
  )
}

Network-Aware Components (v0.1.0)

import { useNetworkStatus, useSyncState } from '@synckit-js/sdk/react'

function NetworkIndicator() {
  const status = useNetworkStatus()

  if (!status) return null // Offline-only mode

  return (
    <div>
      <span>Status: {status.connectionState}</span>
      <span>Queue: {status.queueSize} operations</span>
      <span>{status.isOnline ? '🟢 Online' : '🔴 Offline'}</span>
    </div>
  )
}

function DocumentSyncStatus({ docId }: { docId: string }) {
  const syncState = useSyncState(docId)

  if (!syncState) return null

  return (
    <div>
      {syncState.isSynced ? '✅ Synced' : '⏳ Syncing...'}
      <span>Last sync: {new Date(syncState.lastSyncedAt).toLocaleString()}</span>
    </div>
  )
}

↩️ Undo/Redo

SyncKit includes a powerful undo/redo system with cross-tab synchronization and intelligent operation merging.

React

import { useUndo } from '@synckit-js/sdk/react'

function TextEditor() {
  const [text, setText] = useState('')
  const { canUndo, canRedo, undo, redo, add } = useUndo('doc-123')

  const handleChange = (newText: string) => {
    const oldText = text
    setText(newText)

    add({
      type: 'text-change',
      data: { from: oldText, to: newText }
    })
  }

  const handleUndo = () => {
    const op = undo()
    if (op?.data) setText(op.data.from)
  }

  const handleRedo = () => {
    const op = redo()
    if (op?.data) setText(op.data.to)
  }

  return (
    <div>
      <button onClick={handleUndo} disabled={!canUndo}>Undo</button>
      <button onClick={handleRedo} disabled={!canRedo}>Redo</button>
      <textarea value={text} onChange={e => handleChange(e.target.value)} />
    </div>
  )
}

Vue

<script setup lang="ts">
import { ref } from 'vue'
import { useUndo } from '@synckit-js/sdk/vue'

const text = ref('')
const { canUndo, canRedo, undo, redo, add } = useUndo('doc-123')

const handleChange = (newText: string) => {
  const oldText = text.value
  text.value = newText

  add({
    type: 'text-change',
    data: { from: oldText, to: newText }
  })
}

const handleUndo = () => {
  const op = undo()
  if (op?.data) text.value = op.data.from
}

const handleRedo = () => {
  const op = redo()
  if (op?.data) text.value = op.data.to
}
</script>

<template>
  <div>
    <button @click="handleUndo" :disabled="!canUndo">Undo</button>
    <button @click="handleRedo" :disabled="!canRedo">Redo</button>
    <textarea :value="text" @input="handleChange($event.target.value)" />
  </div>
</template>

Svelte

<script>
  import { undo } from '@synckit-js/sdk/svelte'

  let text = ''
  const undoStore = undo('doc-123')

  function handleChange(event) {
    const newText = event.target.value
    const oldText = text
    text = newText

    undoStore.add({
      type: 'text-change',
      data: { from: oldText, to: newText }
    })
  }

  function handleUndo() {
    const op = undoStore.undo()
    if (op?.data) text = op.data.from
  }

  function handleRedo() {
    const op = undoStore.redo()
    if (op?.data) text = op.data.to
  }
</script>

<button on:click={handleUndo} disabled={!$undoStore.canUndo}>Undo</button>
<button on:click={handleRedo} disabled={!$undoStore.canRedo}>Redo</button>
<textarea value={text} on:input={handleChange} />

Key Features

  • Intelligent Merging: Consecutive operations automatically merge (e.g., typing becomes one undo unit)
  • Cross-Tab Sync: Undo/redo state syncs across browser tabs in real-time
  • Persistent: History survives page refreshes via IndexedDB
  • Customizable: Configure merge windows, stack size, and custom merge strategies
  • Keyboard Shortcuts: Built-in support for Ctrl+Z and Ctrl+Y

See UNDO_REDO.md for complete API documentation.

📚 API Reference

SyncKit

Constructor:

new SyncKit(config?: SyncKitConfig)

interface SyncKitConfig {
  storage?: 'indexeddb' | 'memory' | StorageAdapter
  name?: string
  serverUrl?: string        // Enable network sync
  clientId?: string         // Client identifier
  network?: NetworkConfig   // Network options
}

Core Methods:

  • init() - Initialize the SDK
  • document<T>(id) - Get or create a document
  • listDocuments() - List all document IDs
  • deleteDocument(id) - Delete a document
  • clearAll() - Clear all documents
  • getClientId() - Get client identifier
  • isInitialized() - Check initialization status

Network Methods (v0.1.0):

  • getNetworkStatus() - Get current network status
  • getSyncState(documentId) - Get document sync state
  • onNetworkStatusChange(callback) - Subscribe to network changes
  • onSyncStateChange(documentId, callback) - Subscribe to sync state
  • syncDocument(documentId) - Manually trigger sync

SyncDocument

Methods:

  • init() - Initialize document (required before use)
  • get() - Get current state (synchronous)
  • getField(field) - Get a single field
  • set(field, value) - Set a field (async)
  • update(updates) - Update multiple fields (async)
  • delete(field) - Delete a field (async)
  • subscribe(callback) - Subscribe to changes
  • unsubscribe(callback) - Unsubscribe from changes
  • toJSON() - Export as JSON
  • merge(other) - Merge with another document

Important: Always call await doc.init() before using a document. When a serverUrl is configured, init() automatically subscribes the document to real-time server updates, enabling instant synchronization with other clients.

React Hooks

Core Hooks:

  • useSyncKit() - Get SyncKit instance from context
  • useSyncDocument<T>(id) - Sync a document (returns [data, actions, document])
  • useSyncField<T, K>(id, field) - Sync a single field
  • useSyncDocumentList() - List all document IDs

Network Hooks (v0.1.0):

  • useNetworkStatus() - Monitor connection status
  • useSyncState(documentId) - Monitor document sync state
  • useSyncDocumentWithState<T>(id) - Document + sync state combined

📊 Bundle Size

Production Bundles (gzipped)

| Build | Total Size | JavaScript | WASM | Use Case | |-------|------------|------------|------|----------| | Full SDK | 59KB | 10KB | 49KB | Complete with network sync | | Lite SDK | 45KB | 1.5KB | 44KB | Offline-only, no network |

Network overhead: Only 14KB gzipped for complete WebSocket + sync implementation.

Uncompressed Sizes

| Build | Total | JavaScript | WASM | |-------|-------|------------|------| | Full (ESM) | 138KB | 45KB | 93KB | | Full (CJS) | 156KB | 63KB | 93KB | | Lite (ESM) | 85KB | 5.1KB | 80KB | | Lite (CJS) | 102KB | 22KB | 80KB |

Comparison

| Library | Size (gzipped) | Offline-First | Real-time Sync | |---------|----------------|---------------|----------------| | SyncKit Full | 59KB | ✅ Native | ✅ Built-in | | SyncKit Lite | 45KB | ✅ Native | ❌ No | | Yjs | ~19KB | ⚠️ Limited | ✅ Yes | | Automerge | ~60-78KB | ✅ Native | ✅ Yes | | Supabase | ~45KB | ❌ Cache only | ✅ Yes | | Firebase | ~150KB | ⚠️ Cache only | ✅ Yes |

Competitive and feature-complete - Best balance of size and functionality.

🔧 Storage Adapters

IndexedDB (Browser - Recommended)

const sync = new SyncKit({ storage: 'indexeddb' })

Features:

  • Unlimited storage capacity
  • Persistent across sessions
  • Async operations
  • Works in all modern browsers

Memory (Testing/Development)

const sync = new SyncKit({ storage: 'memory' })

Features:

  • Fast in-memory storage
  • No persistence
  • Great for testing
  • No browser APIs needed

Custom Adapter

import type { StorageAdapter } from '@synckit-js/sdk'

class MyStorage implements StorageAdapter {
  async get(key: string): Promise<string | null> {
    // Your implementation
  }

  async set(key: string, value: string): Promise<void> {
    // Your implementation
  }

  async delete(key: string): Promise<void> {
    // Your implementation
  }

  async clear(): Promise<void> {
    // Your implementation
  }

  async keys(): Promise<string[]> {
    // Your implementation
  }
}

const sync = new SyncKit({ storage: new MyStorage() })

🌐 Network Configuration

Basic Configuration

const sync = new SyncKit({
  serverUrl: 'ws://localhost:8080',
  clientId: 'user-123',
  network: {
    reconnect: {
      enabled: true,
      initialDelay: 1000,      // 1 second
      maxDelay: 30000,          // 30 seconds
      backoffMultiplier: 1.5,
      maxAttempts: Infinity
    },
    heartbeat: {
      interval: 30000,          // 30 seconds
      timeout: 5000             // 5 seconds
    },
    queue: {
      maxSize: 1000,            // Max queued operations
      persistentStorage: true   // Survive restarts
    }
  }
})

Network Status

const status = sync.getNetworkStatus()

console.log(status.connectionState) // 'connected' | 'connecting' | 'disconnected' | 'reconnecting' | 'failed'
console.log(status.isOnline)        // Network connectivity
console.log(status.queueSize)       // Pending operations
console.log(status.lastConnectedAt) // Last successful connection
console.log(status.reconnectAttempts) // Failed connection attempts

Sync State

const state = sync.getSyncState('doc-1')

console.log(state.isSynced)      // All changes synced?
console.log(state.isSyncing)     // Currently syncing?
console.log(state.hasError)      // Sync error occurred?
console.log(state.lastSyncedAt)  // Last successful sync
console.log(state.pendingOps)    // Operations waiting to sync

🧪 Development Status

v0.1.0 - Current Release ✅

Core Infrastructure:

  • ✅ Document API with TypeScript generics
  • ✅ Storage adapters (IndexedDB, Memory)
  • ✅ React hooks integration
  • ✅ LWW conflict resolution with vector clocks

Network Layer (NEW in v0.1.0):

  • ✅ WebSocket client with auto-reconnection
  • ✅ Binary message protocol
  • ✅ Offline queue with persistent storage
  • ✅ Sync manager with conflict resolution
  • ✅ Network state tracking
  • ✅ React network hooks

Test Coverage:

  • ✅ 100% test pass rate (100/100 tests)
  • ✅ Unit tests: 100% passing
  • ✅ Integration tests: 100% passing
  • ✅ Performance benchmarks included

v0.2.0 - Planned

Advanced CRDTs:

  • 🚧 Text CRDTs for character-level editing
  • 🚧 Counters for distributed counting
  • 🚧 Sets for unique collections
  • 🚧 Maps for nested structures

Enhanced Network:

  • 🚧 End-to-end encryption
  • 🚧 Compression for large payloads
  • 🚧 Presence indicators (who's online)
  • 🚧 Advanced conflict resolution strategies

📝 Examples

Complete working examples available:

🚀 Performance

Benchmarks (v0.1.0)

| Operation | Performance | Notes | |-----------|-------------|-------| | Single field update | ~371ns | <1ms consistently | | Document merge | ~74µs | Extremely fast | | Message encoding | 5.05ms/1000 | 0.005ms per message | | Message decoding | 19.62ms/1000 | 0.020ms per message | | Queue operations | 21.21ms/1000 | 47K ops/sec | | Vector clock merge | 0.30ms/100 | Conflict resolution |

See PERFORMANCE.md for detailed benchmarks.

🔒 Type Safety

Full TypeScript support with strict type inference:

interface User {
  name: string
  email: string
  age: number
}

const doc = sync.document<User>('user-1')
await doc.init()

// ✅ Type-safe field access
await doc.set('name', 'Alice')      // Valid
await doc.set('age', 25)            // Valid

// ❌ TypeScript errors
await doc.set('name', 123)          // Error: Type 'number' not assignable to 'string'
await doc.set('invalid', 'value')   // Error: 'invalid' not in type 'User'

// ✅ Type-safe updates
await doc.update({
  name: 'Bob',
  age: 30
})

// ❌ TypeScript error
await doc.update({
  invalid: 'field'                  // Error: Object literal may only specify known properties
})

🤝 Contributing

See CONTRIBUTING.md for development guidelines.

📄 License

MIT - see LICENSE for details.

🔗 Links