Quickstart
Learn how to quickly setup and get started with @pluv/io.
Installation
pluv.io is broken up into multiple packages, so that you can install only what you need, particular to your codebase and framework selections.
Purpose | Location | Install command |
---|---|---|
Register websockets and custom events | Server | npm install @pluv/io |
Call and listen to events. Interact with shared storage | Client | npm install @pluv/client |
React-bindings for @pluv/client | Client | npm install @pluv/react |
Adapter for Node.js runtime | Server | npm install @pluv/platform-node ws |
Adapter for Cloudflare Workers runtime | Server | npm install @pluv/platform-cloudflare |
yjs CRDT | Both | npm install @pluv/crdt-yjs yjs |
loro CRDT | Both | npm install @pluv/crdt-loro loro-crdt |
Installation Example
Here is an example installation for npm, assuming you are building for Node.js, React and TypeScript.
# For the server
npm install @pluv/io @pluv/platform-node
# Server peer-dependencies
npm install ws zod
# For the client
npm install @pluv/react
# Client peer-dependencies
npm install react react-dom zod
# If you want to use storage features, install your preferred CRDT
npm install @pluv/crdt-yjs yjs
Defining a backend PluvIO instance
Let's step through how we'd put together a real-time API for Node.js. In this example, this API will define 2 type-safe events.
Create PluvIO instance
Define an io (websocket client) instance on the server codebase:
// backend/io.ts
import { yjs } from "@pluv/crdt-yjs";
import { createIO } from "@pluv/io";
import { platformNode } from "@pluv/platform-node";
export const io = createIO({
// Optional: Only if you require CRDT features
crdt: yjs,
platform: platformNode(),
});
const ioServer = io.server();
// Export the ioServer type, so that this can be type-imported on the frontend
export type AppPluvIO = typeof ioServer;
Create type-safe server events
Use io.event
to define type-safe websocket events on the io instance. The 2 parts of an event procedure are:
input
: zod validation schema that validates and casts the input for the event.broadcast
: This is the implementation of the event. It accepts an input of the validated input of the incoming event, and returns an event record to emit back to the frontend client.
// backend/io.ts
import { yjs } from "@pluv/crdt-yjs";
import { createIO } from "@pluv/io";
import { platformNode } from "@pluv/platform-node";
import { z } from "zod";
export const io = createIO({
// Optional: Only if you require CRDT features
crdt: yjs,
platform: platformNode(),
});
// Create your custom events as procedures
const sendMessage = io.procedure
.input(z.object({ message: z.string() }))
.broadcast(({ message }) => ({ receiveMessage: { message } }));
const doubleValue = io.procedure
.input(z.object({ value: z.number() }))
.broadcast(({ value }) => ({ receiveValue: { value: value * 2 } }));
const wave = io.procedure
.broadcast(() => ({ receiveMessage: { message: "Hello world!" } }));
// Then add the procedures to your router
const router = io.router({
sendMessage,
doubleValue,
wave,
});
// Lastly, add the router to your server, so that it can begin accepting custom events
const ioServer = io.server({ router });
Integrate PluvIO with ws
Important: Demonstration is for Node.js only.
Create a WS.Server
instance from ws on Node.js. For more information, read about createPluvHandler.
// backend/server.ts
import { createPluvHandler } from "@pluv/platform-node";
import express from "express";
import Http from "http";
import { ioServer } from "./io";
const PORT = 3000;
const app = express();
const server = Http.createServer();
const Pluv = createPluvHandler({
io: ioServer,
server
});
// WS.Server instance from the ws module
const wsServer = Pluv.createWsServer();
app.use(Pluv.handler);
server.listen(PORT);
Connecting the frontend to PluvIO
Now that the io instance is setup on the backend, we can setup the frontend client and connect the exported io type from the server.
Create the React bundle
// frontend/io.ts
import { yjs } from "@pluv/crdt-yjs";
import { createBundle, createClient, y } from "@pluv/react";
// Use a type-import to import only the type we want to use
import type { AppPluvIO } from "server/io";
const client = createClient({
infer: (i) => ({ io: i<AppPluvIO> }),
initialStorage: yjs.doc(() => ({
messages: yjs.array(["hello world!"]),
})),
});
export const {
// components
MockedRoomProvider,
PluvProvider,
PluvRoomProvider,
// utils
event,
// hooks
useBroadcast,
useCanRedo,
useCanUndo,
useClient,
useConnection,
useDoc,
useEvent,
useMyPresence,
useMyself,
useOther,
useOthers,
useRedo,
useRoom,
useStorage,
useTransact,
useUndo,
} = createBundle(client);
Wrap with your pluv.io providers
Wrap your component with PluvRoomProvider
to connect to a realtime room and enable the rest of your room react hooks.
// frontend/Room.tsx
import { FC } from "react";
import { PluvRoomProvider } from "./io";
import { ChatRoom } from "./ChatRoom";
export const Room: FC = () => {
return (
<PluvRoomProvider room="my-example-room">
<ChatRoom />
</PluvRoomProvider>
);
};
Send and receive events
Use useBroadcast
and useEvent
to send and receive type-safe events in your React component.
// frontend/ChatRoom.tsx
import { FC, useCallback, useState } from "react";
import { emojiMap } from "./emojiMap";
import { event, useBroadcast } from "./io";
export const ChatRoom: FC = () => {
const broadcast = useBroadcast();
const [messages. setMessages] = useState<string[]>([]);
event.receiveMessage.useEvent(({ data }) => {
// ^? (property) data: { message: string }
setMessages((prev) => [...prev, data.message]);
});
const sendMessage = useCallback((message: string): void => {
// Parameter will be statically typed from server/io.ts
broadcast.sendMessage({ message });
}, [broadcast]);
// ...
};
Next steps
This example only scratches the surface of the realtime capabilities offered by pluv.io.