Storage
pluv.io supports conflict-free replicated data-types (CRDT) storage with yjs and loro. This enables modifying shared data between multiple users with strong eventual consistency.
Specify CRDT on PluvIO
To get started with storage for pluv.io, first specify which CRDT you intend to use on createIO
.
import { yjs } from "@pluv/crdt-yjs";
import { loro } from "@pluv/crdt-loro";
import { platformPluv } from "@pluv/platform-pluv";
export const io = createIO(
platformPluv({
// ...
crdt: yjs,
})
);
export const io = createIO(
platformPluv({
// ...
crdt: loro,
})
);
Set initialStorage
Then set an initialStorage
config on your createClient
config. The examples below will use Yjs.
// frontend/io
import { createClient, infer } from "@pluv/client";
import { yjs } from "@pluv/crdt-yjs";
import { createBundle } from "@pluv/react";
import type { ioServer } from "./backend/io";
const types = infer((i) => ({ io: i<typeof ioServer> }));
const client = createClient({
// Set the initial storage value, and type here
// Don't worry, we can set a different default value in the room component
initialStorage: yjs.doc(() => ({
messages: yjs.array<string>([]),
})),
types,
// ...
});
export const {
PluvRoomProvider,
useStorage,
useTransact,
} = createBundle(client);
Setup PluvRoomProvider
Then, setup PluvRoomProvider
with your new initialStorage
if it is different than your default from createClient
.
Note: This only affects rooms that have not loaded an initial storage via
getInitialStorage
.
import { yjs } from "@pluv/crdt-yjs";
import type { FC } from "react";
import { PluvRoomProvider } from "./frontend/io";
export const MyPage: FC = () => {
return (
<PluvRoomProvider
room="my-room-id"
initialStorage={() => ({
messages: yjs.array(),
})}
>
<MyRoom />
</PluvRoomProvider>
);
};
Use CRDT native types
We can then use yjs shared-types or loro container types to leverage shared CRDTs between connected clients using useStorage
.
// Yjs example
import { useCallback } from "react";
import type { Array as YArray } from "yjs";
import { useStorage } from "./frontend/io";
// "messages" is a key from the root properties of `initialStorage`.
// sharedType is a shared-type from Yjs
const [messages, sharedType] = useStorage("messages");
// ^? const sharedType: YArray<string> | null
const addMessage = useCallback((message: string) => {
sharedType?.push([message]);
}, [sharedType]);
messages?.map((message, i) => <div key={i}>{message}</div>);
Loro usage
When using loro, the loro document has to be committed for value subscriptions to register changes. So to do this, wrap your mutations in the transact
function from useTransact
.
// Loro example
import { useCallback } from "react";
import { useStorage, useTransact } from "./frontend/io";
// "messages" is a key from the root properties of `initialStorage`.
const [messages] = useStorage("messages");
const transact = useTransact();
const addMessage = useCallback((message: string) => {
transact((tx) => {
// tx.messages is a container-type from loro
tx.messages.insert(tx.messages.length, message);
});
}, [container]);
messages?.map((message, i) => <div key={i}>{message}</div>);