Skip to content

๐Ÿค Collaborative Flows โ€‹

For real-time collaborative apps, flows use CRDT-based conflict resolution so multiple users can edit the same resource without "your changes were overwritten" errors.
A
User A
CRDT Engine
User B
B
Initial Document

The Concept โ€‹

When User A edits a title and User B edits a body simultaneously, traditional apps either lose one user's changes or show a conflict dialog. Collaborative Flows use Last-Writer-Wins registers and custom merge strategies to resolve conflicts seamlessly.

Quick Start โ€‹

ts
const { data, execute } = useFlow(updateDocument, {
  collaborative: {
    enabled: true,
    strategy: "merge",
    merge: (local, remote) => ({
      ...local,
      ...remote,
      updatedAt: Math.max(local.updatedAt, remote.updatedAt),
    }),
    presence: true,
    channel: "doc-123",
  },
});

Conflict Resolution Strategies โ€‹

StrategyBehaviorBest For
last-writer-winsLatest timestamp winsSimple key-value data
mergeDeep merge local + remoteDocument editing
customYour custom merge functionComplex domain logic

Presence Tracking โ€‹

Track who is editing what in real-time:

ts
import { CollaborativeState } from "@asyncflowstate/core";

const collab = new CollaborativeState({
  enabled: true,
  presence: true,
  channel: "doc-123",
});

// Update your presence
collab.updatePresence("user-alice", "title-field");

// Listen for all active editors
collab.onPresenceChange((entries) => {
  entries.forEach((entry) => {
    showCursorFor(entry.userId, entry.field);
  });
});

How It Works โ€‹

mermaid
sequenceDiagram
    participant UserA
    participant CRDT
    participant UserB

    UserA->>CRDT: update({ title: "New" })
    UserB->>CRDT: update({ body: "Content" })
    CRDT->>CRDT: merge(local, remote)
    CRDT->>UserA: { title: "New", body: "Content" }
    CRDT->>UserB: { title: "New", body: "Content" }

API Reference โ€‹

CollaborativeState โ€‹

ts
import { CollaborativeState } from "@asyncflowstate/core";

const collab = new CollaborativeState({ enabled: true, channel: "my-doc" });

// Apply a local update (broadcasts to peers)
collab.update({ title: "New Title", body: "Content" });

// Listen for updates (local + remote)
collab.onUpdate((data, source) => {
  console.log(`Update from ${source}:`, data);
});

// Get current resolved value
const value = collab.getValue();

// Cleanup
collab.dispose();

LWWRegister โ€‹

A standalone Last-Writer-Wins register:

ts
import { LWWRegister } from "@asyncflowstate/core";

const register = new LWWRegister<string>();
register.set("hello", Date.now());
register.set("world", Date.now() + 1); // Wins because timestamp is later
register.get(); // 'world'

Configuration โ€‹

OptionTypeDefaultDescription
enabledbooleanfalseEnable collaborative mode
strategystring'last-writer-wins'Conflict resolution strategy
merge(local, remote) => mergedโ€”Custom merge function
presencebooleanfalseTrack who is editing
channelstring'af-collab'BroadcastChannel name

Built with by AsyncFlowState Contributors
Open Source ยท MIT License