Summary (AI Generated) Adds a session-native, multi-source sync layer on top of automerge-atproto's primitives. Everything that a document's changes flow to or from becomes a uniform Source; a Session coordinates them; and the client gets a familiar DocHandle-like Handle. Core abstractions (src/sync/):
- Source — a transport-agnostic backend for a document: push (send our state out), pull (fetch state in), subscribe (deliver inbound changes). Hides all transport detail.
- Session — a pure per-document coordinator (documentId / pull / flush / dispose). Holds a thin canonical doc, merges any Source's changes into it, and fans out to the other Sources only when heads advance. Loops are prevented structurally (never pushes back to the delivering Source). It knows nothing about clients or transports.
- SourcePolicy — bound per-Source (autoPush / pushDebounceMs / pullOnOpen), neutral by default and configurable. Lets a host keep default behavior conservative and opt specific Sources into eager propagation.
- Handle — the client-facing, DocHandle-shaped adapter (url / doc() / change() / on()). The editor is a Source (createEditorSource): Source and Handle have disjoint method names, so one object plays both roles — the session treats it as a Source (push/pull/subscribe), the client treats it as a Handle (doc/change/on). Because client change() and session push() are distinct methods, there's no loop and no source-tag filtering. Concrete Sources:
- local — LocalDocumentStore (persistence) + LocalSyncChannel (cross-tab delivery).
- PDS — wraps OplogDocumentSync (durable remote), neutral (autoPush: false) by default.
- injectable SourceProviders — the extensibility hook for a future libp2p live transport or any custom backend. MultiSourceSessionHost (implements SessionHost) — manages documents purely as sessions: create / find return a Handle backed by a session over [editor, local, pds, …injected]; plus delete / listDocuments. Self-contained over a LocalDocumentStore + injected documentSync. Scope / compatibility
- Purely additive — repo.ts and the existing Repo/DocHandle/automerge-repo compatibility layer are untouched. All new code lives under src/sync/ and src/multi-source-session-host.ts, with exports added to index.ts.
- Full test suite green (node-env vitest), including new coverage for the session coordinator, editor source, local/PDS sources, and the host.
- One known wart documented as a TODO in pdsSource.ts: the PDS Source should be a pure remote abstraction but currently carries an internal scratch LocalDocumentStore, because OplogDocumentSync is store-first rather than Doc-first. The clean fix is a Doc-oriented method on OplogDocumentSync — left out of scope. Future Sources The SourceProvider injection hook is the seam where new backends plug in — a session gets [editor, local, pds, …injected], so adding a transport is purely additive (no host or session changes). Planned:
- libp2p (live multiplayer) — a Source that carries the Automerge sync protocol (generateSyncMessage/receiveSyncMessage) over a gossipsub topic, providing low-latency peer-to-peer editing. It composes cleanly with the PDS Source on the same document: libp2p is the live tail, the PDS is the durable/authoritative backstop. A peer that was offline reconstructs history from the PDS, then joins the live gossip for the tail — the session merges both through the same canonical, with no special-casing. This is largely a port of an existing libp2p sync provider, adapted behind the Source interface with relay/auth injected as config rather than hardcoded. Attached per-document via a SourceProvider (e.g. sources: [makeLibp2pSource(nodeConfig)]), likely with an eager policy (autoPush: true, no debounce) since it's the live path.
- Presence / awareness (multiplayer cursors) is a natural follow-on — an ephemeral, non-durable channel that rides the same transport, kept separate from durable document sync.
- Others the seam invites — a WebSocket relay Source, or the current cross-tab LocalSyncChannel generalized into its own live Source rather than living inside the local Source.
⚠️ Disclaimer — treat as very experimental
This PR somewhat changes the identity of the repo. It introduces a second, parallel document/sync model (Source / Session / Handle / SessionHost) alongside the existing durable-sync Repo. Conceptually it reframes the library from atproto-backed automerge-repo (durable document sync) toward a session-native, multi-source sync engine where any backend — local store, PDS, libp2p, even the editor — is just a Source. That's a meaningful shift in the mental model and public surface. It's additive and doesn't break anything today, but the direction is exploratory and not yet settled — consider it very experimental and open to being reshaped or dropped.