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

react-native-sync-engine

v0.1.0

Published

Offline-first sync engine for React Native. Read/write locally with zero latency; changes queue while offline and merge conflict-free when the connection returns. Open-core: free client, paid hosted sync.

Readme

React Native Offline Sync

npm version CI license

An offline-first sync engine for React Native. Read and write data locally with zero latency; changes queue while offline and merge conflict-free when the connection returns — even if two devices changed the same record.

Open-core: the client library is free (this repo). Pair it with a hosted sync backend + dashboard (the commercial product) for cross-device sync.


Why this exists

Offline support is notoriously painful: local persistence, a change queue, network detection, retry, and — the hard part — merging conflicting edits without losing data. This engine makes it "just work" for field apps, travel, delivery, and anywhere the network is flaky.

The merge is a CRDT (Last-Writer-Wins per field, ordered by a Hybrid Logical Clock). That means merges are commutative, associative, and idempotent — every device converges to the same result regardless of order, with no central locking. (See the runnable proof in How conflict resolution works.)


What's included (free client)

| Piece | What it does | |---|---| | SyncStore | Local-first DB: instant reads/writes, persistence, outbox, sync loop | | useCollection / useDocument | Reactive hooks — re-render on local writes or incoming sync | | useSyncStatus | { online, pending, lastSyncedAt, syncing } for status UIs | | NetworkController | Online/offline state (wire to NetInfo, or toggle manually) | | MemoryStorage / AsyncStorageAdapter | Pluggable persistence | | InMemorySyncServer | In-process mock backend for demos/tests/local dev | | HttpSyncTransport | Template for a real HTTP/WebSocket backend | | mergeDocState, HLC | The CRDT primitives, exposed for advanced use |


Quick start

import {
  SyncStore, MemoryStorage, InMemorySyncServer,
  SyncProvider, useCollection, useSyncStatus,
} from 'react-native-sync-engine';

const store = new SyncStore({
  storage: new MemoryStorage(),            // or AsyncStorageAdapter for persistence
  transport: new InMemorySyncServer(),     // or HttpSyncTransport({ baseUrl })
});
store.init();

function Tasks() {
  const todos = useCollection('todos');
  const status = useSyncStatus();
  return (
    <>
      <Text>{status.online ? 'Online' : `Offline · ${status.pending} queued`}</Text>
      {todos.map(t => <Text key={t.id}>{t.title}</Text>)}
      <Button title="Add" onPress={() => store.create('todos', { title: 'New', done: false })} />
    </>
  );
}

export default () => <SyncProvider store={store}><Tasks /></SyncProvider>;

Writes apply instantly and persist locally. When online, they push automatically and pull + merge remote changes. When offline, they queue in the outbox and flush on reconnect.


How conflict resolution works

Merging is per field, ordered by HLC timestamp:

  • Two devices edit different fields of a record offline → both edits survive.
  • Two devices edit the same field → the later write wins, deterministically on every device.
  • Deletes are tombstones, so they merge like any other change.

This is verified by a runnable test (commutativity, LWW, idempotency, tombstones, monotonic clock) — all passing. The guarantee is what lets you sync without a server-side lock or a "pick a winner" dialog.

Phone (offline):   rename task → "Buy milk"
Tablet (offline):  tick task done ✓
        ↓ both reconnect ↓
Everyone converges to:  "Buy milk", done ✓     ← both edits kept

Persistence

// npx expo install @react-native-async-storage/async-storage
import { AsyncStorageAdapter } from 'react-native-sync-engine/async-storage';

const store = new SyncStore({ storage: new AsyncStorageAdapter(), transport });

AsyncStorageAdapter is a separate import, so apps using MemoryStorage never bundle AsyncStorage. Implement StorageAdapter to back it with SQLite/MMKV/etc.


Connecting a real backend (the paid part)

The client talks to any SyncTransport. HttpSyncTransport expects a server exposing POST /push and GET /pull?cursor=:

import { HttpSyncTransport } from 'react-native-sync-engine';

const store = new SyncStore({
  storage: new AsyncStorageAdapter(),
  transport: new HttpSyncTransport({ baseUrl: 'https://sync.yourapp.com', authToken }),
});

A runnable reference backend (zero dependencies) lives in server/cd server && npm start gives you a working /push + /pull server on http://localhost:4000 to develop against or self-host. The hosted backend + web dashboard (manage devices, inspect/repair data, auth, retention) is the commercial product — that's the open-core business model: free client drives adoption, paid sync service is the revenue.


Wire up real network detection

// npx expo install @react-native-community/netinfo
import NetInfo from '@react-native-community/netinfo';
NetInfo.addEventListener(s => store.getNetwork().setOnline(!!s.isConnected));

Run the demo

example/ is a two-device playground: a 📱 Phone and 💻 Tablet share one in-memory backend. Toggle each Offline, make conflicting edits to the same task, toggle back Online, and watch them merge — with a live "server truth" panel.

cd react-native-sync-engine && npm install
cd example && npm install && npm start    # press i / a / w

Develop, build & publish

The package ships compiled JS + type declarations from dist/ (built with tsc); the TypeScript source lives in src/.

npm install            # dev deps (typescript, react, @types/react, async-storage)
npm run typecheck      # type-check src/ without emitting
npm test               # build, then run the test suite (node --test)
npm run build          # emit dist/ (JS, .d.ts, source maps)
npm publish            # prepublishOnly re-builds + tests, then publishes dist/

The test suite proves the CRDT guarantees the README describes — commutativity, associativity, idempotency, same-field LWW, different-field survival, tombstones, a monotonic HLC, and the full offline-queue → reconnect → merge lifecycle through InMemorySyncServer.


License

The client in this repo is MIT (see LICENSE); the hosted server + dashboard is the separate commercial, open-core product.