Core concepts¶
A small vocabulary that the rest of the documentation assumes you've internalized.
All concepts at a glance¶
| Concept | Where it lives | What it owns |
|---|---|---|
| Awareness | Ephemeral, in-memory | Cursors, presence, "user typing" — never persisted |
| Backplane | Shared infra (Redis) | Cluster fan-out and ownership coordination |
| Comment | Shared storage | Collaborative feedback, threaded replies, anchors |
| Comment Router | One per server process | Routing of comment mutations to the owning node |
| Document | Logical unit | Id, type, state, revision counter |
| Document Router | One per server process | Auth, ownership, routing, awareness |
| Health Checks | Network edge | Diagnostic endpoints for liveness and readiness |
| History | Cold storage | Permanent immutable log, state milestones, versioning |
| Multitenancy | Core plumbing | Isolation of data via document ID globalization |
| Op | In-flight / op log | A single atomic change to the document |
| Peer | Per connection | One connected client, one peer id |
| Revision | Per document | Monotonic counter; used to rebase concurrent ops |
| Session | One per open document, one node | In-memory state, apply lock, persistence |
| Snapshot | Storage | Compact state checkpoint; shortens replay on reload |
| Storage | External infra (DB / Redis / …) | Op log, snapshots, durable state |
| Transport | Network edge | Wire protocol between clients and server |
Awareness¶
Presence / cursors / "user is typing" / live selections — ephemeral
state that's broadcast in real time but never persisted to the op log.
Each peer publishes their state via UpdateAwarenessAsync; the server
keeps it for ~30 seconds, fans it out to other peers, and drops it on
disconnect or expiry. See Awareness.
Backplane¶
The cluster-wide pub/sub fabric. In single-node mode this is the
in-process LocalBackplane; for production you swap in UseRedisBackplane().
Its job is to ensure that an op applied on node A is broadcast to peers
connected to nodes B, C, …, and that ownership of a document is moved
between nodes when needed. See Backplane.
Comment¶
A piece of collaborative feedback attached to a document at a specific point in its timeline (revision). Comments support threading (replies) and can be anchored to specific document locations. Unlike Awareness, comments are durable and persisted to shared storage.
Comment Router¶
The internal component that manages comment lifecycle operations. It ensures that comment mutations (create, edit, resolve, delete) are routed to the node currently owning the document, allowing them to be atomically anchored to the document's current revision under the session lock.
Document¶
A document is whatever your application calls "the thing two users co-edit": a markdown file, a Notion page, a settings dialog, a spreadsheet, a CAD model. OpStream doesn't care about its semantics — only that you've picked an engine that knows how to merge concurrent edits on its shape.
Each document has:
- A document id — opaque string, scoped by tenant (see Multitenancy).
- A document type — a discriminator string (
"text","rich-text","tree", …) that tells the router which engine and which session factory to use. - A state of type
TDoc— what the engine produces after applying ops. - A revision counter — monotonic per document, incremented once per accepted op.
Document Router¶
The DocumentRouter is the single entry point on the server side.
It handles:
- Authorization — every join / op / awareness call goes through your
IDocumentAuthorizer. - Ownership — in a multi-node cluster, exactly one node owns each document at a time. The router transparently proxies calls to the owning node.
- Awareness — presence data for connected peers.
- Idle cleanup — closes sessions after inactivity.
You don't typically instantiate it; you call services.AddOpStream()
and resolve it from DI.
Health Checks¶
Diagnostic endpoints that report the operational status of the server and its dependencies. OpStream integrates with standard ASP.NET Core Health Checks to expose liveness and readiness probes (e.g., verifying Redis connectivity or storage availability) for monitoring and orchestration tools like Kubernetes.
History¶
Also known as Cold Storage, history is a permanent record of every operation and important state checkpoint (milestone) in a document's lifecycle. While the active op log may be compacted, the history store allows for features like audit logs, "time travel" browsing of past versions, and restoration to specific revisions. See History.
Multitenancy¶
The ability for a single OpStream cluster to serve multiple independent customers (tenants). It uses Document ID Globalization to isolate data, ensuring that a document ID sent by a client is transparently scoped by the tenant's identity on the server, preventing cross-tenant data leaks. See Multi-tenancy.
Op¶
An op is a single, atomic change to the document. The engine knows how to:
Applyan op to a state, producing a new state.Transforman incoming op against a concurrent op so its intent survives the rebase.Invertan op against the pre-state, producing the op that undoes it.Composetwo ops into one (when supported).IsNoOp— recognize an op that has no effect.
Engines either follow Operational Transformation (Text, Rich Text)
or CRDT semantics (JSON, Tree, Table, Form). The choice is internal —
your code interacts with the same IOpEngine<TDoc, TOp> contract either
way. See Engine contracts.
Peer¶
A peer is a single connected client — typically one browser tab or one desktop app instance. Multiple peers under the same user account are treated as independent for concurrency purposes; they're identified by a peer id the transport assigns at connect time.
Revision¶
The current revision is the index of the last accepted op. Clients
include their baseRevision with every op they send; if the server's
revision is higher, the server rebases the op through OT / CRDT
transforms before applying it.
This is what lets two clients edit the same document without locks: each client thinks it's editing revision N, and the server reconciles.
Session¶
A DocumentSession<TDoc, TOp> is the in-memory home of an open document
on the server: it owns the current state, holds the apply lock, talks to
the store, and broadcasts via the backplane. Sessions are created lazily
when the first peer joins and closed after an idle timeout (default 5
minutes).
Snapshot¶
To avoid re-applying every op from genesis on every load, OpStream takes snapshots — compact serialized states tagged with their revision. At rehydration time the session loads the latest snapshot and replays only the ops applied after it. Snapshots are policy-driven (default: every 100 ops or 5 minutes). See Snapshots.
Storage¶
Storage is where OpStream persists the op log and snapshots — the source of truth that lets sessions be rebuilt after a restart or a failover.
Storage sits behind a single IDocumentStore interface. Swap the provider and
nothing else changes:
| Provider | Good for |
|---|---|
memory |
Local dev and tests — lost on restart |
sqlite |
Single-tenant edge boxes, small teams |
postgres |
Recommended default for new production projects |
mysql |
Existing MySQL / MariaDB shops |
sqlserver |
Microsoft stacks, Azure SQL |
mongo |
Document-heavy workloads, flexible schemas |
redis |
Lowest write latency, ephemeral or RDB-backed |
All EF Core providers run migrations on first connect — no manual dotnet ef
step. Switch provider with one env var:
OPSTREAM__STORAGE__PROVIDER=postgres
OPSTREAM__STORAGE__CONNECTIONSTRING="Host=...;Database=opstream;..."
See Storage for connection-string examples and tuning options per backend.
Transport¶
A transport is the wire layer that connects clients to the OpStream server. OpStream ships three transports; any combination can run on the same process and the same port simultaneously:
| Transport | Best for | Client requirement |
|---|---|---|
| SignalR | Browsers, .NET apps, mobile with a SignalR library | Official SignalR client (JS, .NET, Java, Swift, …) |
| WebSocket | Any stack — the most universal option | Native WebSocket in every browser and language |
| gRPC | Backend-to-backend, strongly-typed contracts | Generated gRPC stub (11+ languages) |
Clients pick the transport that fits their stack. A React app on SignalR and a Python bot on WebSocket can collaborate on the same document through the same server at the same time. The transport is invisible to the engine and the storage layers — it only carries ops and awareness in and fan-out out.
Enable transports at startup:
See Transports for per-transport configuration.