Documents (CRuntime and AbstractDoc)¶
In Collabs, a document is a “unit of collaboration” - some collaborative state that is shared together. This could be a single shared whiteboard, a rich-text document, a recipe, etc.
Each document comes in two parts:
A fixed set of Collabs that store the collaborative state. For example, a shared whiteboard document could have a single
CValueMap<[x: number, y: number], Color>
, like we described on the previous page.A CRuntime that manages those Collabs. It is responsible for creating the Collabs, connecting them to network and storage providers, filtering out duplicate updates, etc.
Using CRuntime¶
To use a document, first construct a CRuntime:
import { CRuntime } from "@collabs/collabs";
const doc = new CRuntime();
Next, “register” its Collabs using CRuntime.registerCollab. E.g. for a shared whiteboard document:
const boardState: CValueMap<[x: number, y: number], Color> = doc.registerCollab(
"boardState",
(init) => new CValueMap(init)
);
// Other calls to doc.registerCollab...
There are a few weird things going on in this registerCollab
call:
You have to give a name to your Collab - here
"boardState"
. It must be unique among all calls todoc.registerCollab
but is otherwise arbitrary. We suggest using the same name as the Collab’s variable, like here.Instead of constructing the CValueMap directly, you supply a callback that does so:
(init) => new CValueMap(init)
. Internally,registerCollab
will invoke this callback and return the constructed Collab. That letsdoc
configureboardState
.
Registering Collabs essentially defines a “schema” for your document. That schema tells CRuntime how to interpret updates from remote collaborators and persistent storage.
To ensure that they can understand each other, collaborators must all use the same “schema”. Specifically, they must make the same calls to registerCollab
, with the same names, Collab classes, and Collab constructor arguments. This is easy to guarantee if they all run the same code.
Once you register all Collabs, your document is ready to use. You can handle changes, connect providers, and perform collaborative operations on its Collabs.
Using AbstractDoc¶
In the above example, our document consisted of two variables: doc
of type CRuntime, and boardState
of type CValueMap. If you have many Collabs in the same document, or many documents in the same app, it becomes convenient to group each document into a single object.
You can do that by extending the AbstractDoc class, as shown below.
import { AbstractDoc, DocOptions } from "@collabs/collabs";
class MyWhiteboardDoc extends AbstractDoc {
/** The whiteboard's state, exposed publicly for convenience. */
readonly boardState: CValueMap<[x: number, y: number], Color>;
constructor(options?: DocOptions) {
super(options);
// this.runtime is a CRuntime provided by our superclass.
this.boardState = this.runtime.registerCollab(
"boardState",
(init) => new CValueMap(init)
);
}
}
The AbstractDoc superclass gives MyWhiteboardDoc a similar API to CRuntime - in particular, you can still pass it as an argument to providers. But it hides internal methods like registerCollab
, and subclassing lets you encapsulate your Collabs in a type-specific API. (For example, you could make boardState
private and instead expose getPixel
and setPixel
methods.)
Multiple Documents¶
You can use as many documents as you like in a single app. For example, in a sticky notes app, each sticky note could have its own document. That way, the user can choose to share each sticky note with a different group of collaborators. Or, you can wait to load each sticky note from storage until it is scrolled into view.
Our network and storage providers use docID
s (arbitrary strings) to keep track of which documents are supposed to be kept in sync. For example, when using our Websocket client and server, documents on different devices that subscribe to the same docID
will be kept in sync via the server.
Next Steps¶
Continue following the Guide with Handling Changes.