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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@objectql/plugin-sync

v4.2.2

Published

Offline-first sync plugin for ObjectQL - mutation logging, sync engine, and conflict resolution

Readme

@objectql/plugin-sync

Offline-first sync plugin for ObjectQL — mutation logging, sync engine, and conflict resolution.

Features

📝 Mutation Logging

  • Client-side append-only mutation log
  • Automatic sequence numbering and timestamping
  • Per-object mutation filtering
  • Batch acknowledgment after successful sync

🔄 Sync Engine

  • Push/pull synchronization between client and server
  • Configurable sync direction (push-only, pull-only, bidirectional)
  • Debounced auto-sync on mutation
  • Checkpoint-based delta tracking

⚔️ Conflict Resolution

  • Last-Write-Wins (LWW): Timestamp-based resolution
  • CRDT: Field-level LWW-Register merge
  • Manual: Callback-driven resolution for user intervention
  • Factory function for strategy selection

📡 Event System

  • Lifecycle listeners (onSyncStart, onSyncComplete, onSyncError)
  • Conflict and server change notifications
  • Global and per-engine listener support

Installation

pnpm add @objectql/plugin-sync

Quick Start

import { SyncPlugin } from '@objectql/plugin-sync';
import { createKernel } from '@objectstack/runtime';

const kernel = createKernel({
  plugins: [
    new SyncPlugin({
      clientId: 'device-abc-123',
      transport: myHttpTransport,
      defaultConfig: {
        enabled: true,
        strategy: 'last-write-wins',
        debounce_ms: 1000,
        batch_size: 50,
      },
      listeners: [{
        onSyncComplete(response) {
          console.log('Sync complete:', response.checkpoint);
        },
        onConflict(conflicts) {
          console.warn('Conflicts detected:', conflicts.length);
        },
      }],
    })
  ]
});

await kernel.start();

MutationLogger

The MutationLogger records all client-side mutations in an append-only log for later synchronization.

import { MutationLogger } from '@objectql/plugin-sync';

const logger = new MutationLogger('device-abc-123');

// Record a mutation
const entry = logger.append({
  objectName: 'task',
  recordId: 'task-1',
  operation: 'update',
  data: { status: 'completed' },
  baseVersion: 3,
});

// Get all pending mutations
const pending = logger.getPending();

// Get pending for a specific object
const taskMutations = logger.getPendingForObject('task');

// Acknowledge synced mutations
logger.acknowledge(['mutation-id-1', 'mutation-id-2']);

// Check pending count
console.log(logger.size); // => 0

Conflict Resolution Strategies

Last-Write-Wins (LWW)

Compares client and server timestamps. The most recent write wins.

import { LastWriteWinsResolver } from '@objectql/plugin-sync';

const resolver = new LastWriteWinsResolver();
const result = resolver.resolve(conflict);
// result.status => 'applied' (client wins) or 'conflict' (server wins)

CRDT (Field-Level Merge)

Performs field-level LWW-Register merge. Non-conflicting fields from the client are applied; conflicting fields retain the server value.

import { CrdtResolver } from '@objectql/plugin-sync';

const resolver = new CrdtResolver();
const result = resolver.resolve(conflict);
// Merges non-conflicting fields from both sides

Manual Resolution

Flags conflicts for manual resolution via a user-provided callback.

import { ManualResolver } from '@objectql/plugin-sync';

const resolver = new ManualResolver((conflict) => {
  // Return merged data to resolve, or undefined to keep as conflict
  return {
    ...conflict.serverRecord,
    ...conflict.clientMutation.data,
    resolved_by: 'user',
  };
});

Factory Function

Use createResolver() to instantiate a resolver by strategy name.

import { createResolver } from '@objectql/plugin-sync';

const resolver = createResolver('crdt');
// => CrdtResolver instance

SyncEngine Configuration

The SyncEngine orchestrates the full push/pull sync cycle.

import { SyncEngine } from '@objectql/plugin-sync';
import type { SyncTransport } from '@objectql/plugin-sync';

const transport: SyncTransport = {
  async push(request) {
    const res = await fetch('/api/sync/push', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(request),
    });
    return res.json();
  },
};

const engine = new SyncEngine({
  clientId: 'device-abc-123',
  transport,
  config: {
    enabled: true,
    strategy: 'last-write-wins',
    direction: 'bidirectional',
    debounce_ms: 2000,
    batch_size: 50,
  },
});

// Record a mutation (auto-triggers debounced sync)
engine.recordMutation({
  objectName: 'task',
  recordId: 'task-42',
  operation: 'update',
  data: { title: 'Updated title' },
  baseVersion: 5,
});

// Manual sync trigger
const response = await engine.sync();

// Listen to sync events
engine.addListener({
  onSyncStart() { console.log('Syncing...'); },
  onSyncComplete(res) { console.log('Done:', res.checkpoint); },
  onSyncError(err) { console.error('Failed:', err.message); },
  onServerChanges(changes) { console.log('Server changes:', changes.length); },
});

SyncConfig Options

| Property | Type | Default | Description | |-----------------|----------------------------------------------|--------------------|--------------------------------------| | enabled | boolean | true | Enable sync for this object | | strategy | 'last-write-wins' | 'crdt' | 'manual' | 'last-write-wins' | Conflict resolution strategy | | direction | 'push-only' | 'pull-only' | 'bidirectional' | 'bidirectional' | Sync direction | | debounce_ms | number | 1000 | Debounce interval for auto-sync (ms) | | batch_size | number | 50 | Maximum mutations per sync request |

API Reference

Classes

| Class | Description | |------------------------|------------------------------------------------------| | SyncPlugin | RuntimePlugin — registers sync capabilities on kernel | | SyncEngine | Client-side push/pull sync orchestrator | | MutationLogger | Append-only client-side mutation log | | LastWriteWinsResolver| Timestamp-based LWW conflict resolver | | CrdtResolver | Field-level LWW-Register merge resolver | | ManualResolver | Callback-driven manual conflict resolver |

Functions

| Function | Description | |------------------------|------------------------------------------------------| | createResolver(strategy, onConflict?) | Factory for ConflictResolver instances |

Types

| Type | Description | |------------------------|------------------------------------------------------| | SyncPluginConfig | Plugin constructor options | | SyncTransport | Transport interface for push requests | | SyncEventListener | Listener interface for sync lifecycle events | | ConflictResolver | Interface for conflict resolution strategies | | SyncConfig | Per-object sync configuration | | MutationLogEntry | Single mutation log record | | SyncConflict | Conflict descriptor with client/server data |

License

MIT