Distributed sync,
beautifully composed.

The typed CRDT framework. Collaborative documents that look like plain objects, migrate themselves and live in PostgreSQL.

Web, mobile and desktop clients synchronized through Pulsify with local and remote databases

Why Pulsify

Local-first apps, without the hard parts

Write to an object. Pulsify renders it instantly, keeps it on the device, syncs it when it can and never lets a bad write in.

Local-first UI

A document is a plain object

Assign a field, push to an array. The UI re-renders instantly, the change is persisted and synced in the background. No stores, no reducers, no fetch-then-mutate.

editor.ts
const note = await loadPulse(Note, "note-42");
subscribePulse(note, render);

note.title = "Shipped";      // rendered now
note.tags.push("launch");    // synced later
Offline

Offline is the default, not a feature

Writes never wait for the network. A tab that lost its connection keeps working, and when it comes back every edit merges without a conflict handler.

offline.ts
disconnectPulse(note);        // airplane mode
note.body.insert(0, "Draft: ");

await connectPulse(note).synced;
// merged with everything your team did meanwhile
Typed · Validated

A CRDT that cannot be corrupted

One schema gives you the TypeScript type, runtime validation of every local and remote write, and the wire format. A bad payload from a peer is rejected, not merged.

typed.ts
note.tags.push(42);
// Type 'number' is not assignable to type 'string'
Batteries included

Everything around the CRDT, shipped

The parts you would otherwise glue together yourself come in the box, and all speak the same protocol.

  • Token auth
  • Lazy migrations
  • Sub-documents
  • PostgreSQL · SQLite
  • Rich text editors
  • Hosted cloud

Three steps

Define. Edit. Sync.

No operation objects, no transactions to open, no conflict handlers. Write normal TypeScript and let the engine record it.

01

Define

A codec schema is the single source of truth for types, validation and bytes.

note.ts
import { array, definePulse, object, richText } from "pulsify-core";

export const Note = definePulse("Note", object({
  title: "string",
  body: richText(),
  tags: array("string"),
})).materialize();
02

Edit

A pulse is a plain object. Assign, push, splice. Every change becomes a patch.

app.ts
const note = createPulse(Note, {
  title: "Draft", body: "Hello", tags: [],
});

note.title = "Ready";
note.tags.push("review");
note.body.insert(5, " world");
03

Sync

Add a connector. Offline edits merge on reconnection, no conflict code.

sync.ts
const connector = createWebSocketConnector(
  "wss://sync.example.com",
);
const note = await loadPulse(Note, "note-42", {
  connectors: [connector],
});

note.title = "Synced everywhere";

Benchmarks

Faster than Yjs. Much faster than Loro.

Same document, same edits, same machine. Pulsify's native engine skips binary encoding on local writes and compiles the schema into straight-line code. Lower is better.

Create and encode

1,000-item document, first export

  • Pulsify319 µs
  • Yjs506 µs
  • Loro4,742 µs

Initial load

Import 1,000 items and materialize a JSON view

  • Pulsify367 µs
  • Yjs547 µs
  • Loro1,625 µs

Delta round trip

Edit, encode delta, import on a peer, read

  • Pulsify12.4 µs
  • Yjs26.7 µs
  • Loro47.1 µs

Single mutation

One field write after 5,000 edits of history

  • Pulsify0.16 µs
  • Yjs0.93 µs
  • Loro1.26 µs

Encoded size

Bytes for a 1,000-item document

  • Pulsify17.0 KB
  • Yjs30.0 KB
  • Loro49.7 KB

How we measured

Apple M4, Bun 1.4.4, Yjs 13.6.33 (V2 encoding), Loro 1.17.1 (WASM). Medians of 25 samples after warm-up, shuffled order, forced GC outside timing. Yjs still wins the two-peer offline merge scenario. Explore every number, json-joy, payload sizes and database results on the benchmarks page.

Full results and method

Compared

What you get out of the box

PulsifyYjsLoro
Schema with inferred types and runtime validation
Lazy, per-document migrations
Materialized public type over stored fields
Official PostgreSQL and SQLite storageCommunity
Sync queries without loading the document
Array move with preserved identity
Movable tree
Rich text with ProseMirror and Lexical bindingsProseMirror

One protocol

From the browser to PostgreSQL

A versioned binary envelope carries every change. The same frames go over WebSocket, in memory between tests, into a PostgreSQL row and through the Rust implementation of the protocol. Pick a local connector for instant rendering and a remote one for durability.

  • Browsers
  • Bun
  • Node 22+
  • Deno 2
  • Cloudflare Workers
Pulsify Cloud is coming. A serverless sync backend on Cloudflare Durable Objects. One connector, no database to run.
Learn more
Web, mobile, local database, remote PostgreSQL and cloud storage synchronized through Pulsify