pluv.io is in preview! Please wait for a v1.0.0 stable release before using this in production.

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.

PurposeLocationInstall command
Register websockets and custom eventsServernpm install @pluv/io
Call and listen to events. Interact with shared storageClientnpm install @pluv/client
React-bindings for @pluv/clientClientnpm install @pluv/react
Adapter for Node.js runtimeServernpm install @pluv/platform-node ws
Adapter for Cloudflare Workers runtimeServernpm install @pluv/platform-cloudflare
yjs CRDTBothnpm install @pluv/crdt-yjs yjs
loro CRDTBothnpm 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.

1# For the server
2npm install @pluv/io @pluv/platform-node
3# Server peer-dependencies
4npm install ws zod
5
6# For the client
7npm install @pluv/react
8# Client peer-dependencies
9npm install react react-dom zod
10
11# If you want to use storage features, install your preferred CRDT
12npm 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:

1// backend/io.ts
2
3import { yjs } from "@pluv/crdt-yjs";
4import { createIO } from "@pluv/io";
5import { platformNode } from "@pluv/platform-node";
6
7export const io = createIO({
8 // Optional: Only if you require CRDT features
9 crdt: yjs,
10 platform: platformNode(),
11});
12
13const ioServer = io.server();
14
15// Export the ioServer type, so that this can be type-imported on the frontend
16export 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.
1// backend/io.ts
2
3import { yjs } from "@pluv/crdt-yjs";
4import { createIO } from "@pluv/io";
5import { platformNode } from "@pluv/platform-node";
6import { z } from "zod";
7
8export const io = createIO({
9 // Optional: Only if you require CRDT features
10 crdt: yjs,
11 platform: platformNode(),
12});
13
14// Create your custom events as procedures
15const sendMessage = io.procedure
16 .input(z.object({ message: z.string() }))
17 .broadcast(({ message }) => ({ receiveMessage: { message } }));
18
19const doubleValue = io.procedure
20 .input(z.object({ value: z.number() }))
21 .broadcast(({ value }) => ({ receiveValue: { value: value * 2 } }));
22
23const wave = io.procedure
24 .broadcast(() => ({ receiveMessage: { message: "Hello world!" } }));
25
26// Then add the procedures to your router
27const router = io.router({
28 sendMessage,
29 doubleValue,
30 wave,
31});
32
33// Lastly, add the router to your server, so that it can begin accepting custom events
34const 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.

1// backend/server.ts
2
3import { createPluvHandler } from "@pluv/platform-node";
4import express from "express";
5import Http from "http";
6import { ioServer } from "./io";
7
8const PORT = 3000;
9
10const app = express();
11const server = Http.createServer();
12
13const Pluv = createPluvHandler({
14 io: ioServer,
15 server
16});
17
18// WS.Server instance from the ws module
19const wsServer = Pluv.createWsServer();
20
21app.use(Pluv.handler);
22
23server.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

1// frontend/io.ts
2
3import { yjs } from "@pluv/crdt-yjs";
4import { createBundle, createClient, y } from "@pluv/react";
5// Use a type-import to import only the type we want to use
6import type { AppPluvIO } from "server/io";
7
8const client = createClient<AppPluvIO>();
9
10export const {
11 // factories
12 createRoomBundle,
13
14 // components
15 PluvProvider,
16
17 // hooks
18 useClient,
19} = createBundle(client);
20
21export const {
22 // components
23 MockedRoomProvider,
24 PluvRoomProvider,
25
26 // utils
27 event,
28
29 // hooks
30 useBroadcast,
31 useCanRedo,
32 useCanUndo,
33 useConnection,
34 useDoc,
35 useEvent,
36 useMyPresence,
37 useMyself,
38 useOther,
39 useOthers,
40 useRedo,
41 useRoom,
42 useStorage,
43 useTransact,
44 useUndo,
45} = createRoomBundle({
46 initialStorage: yjs.doc(() => ({
47 messages: yjs.array(["hello world!"]),
48 })),
49});

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.

1// frontend/Room.tsx
2
3import { FC } from "react";
4import { PluvRoomProvider } from "./io";
5import { ChatRoom } from "./ChatRoom";
6
7export const Room: FC = () => {
8 return (
9 <PluvRoomProvider room="my-example-room">
10 <ChatRoom />
11 </PluvRoomProvider>
12 );
13};

Send and receive events

Use useBroadcast and useEvent to send and receive type-safe events in your React component.

1// frontend/ChatRoom.tsx
2
3import { FC, useCallback, useState } from "react";
4import { emojiMap } from "./emojiMap";
5import { event, useBroadcast } from "./io";
6
7export const ChatRoom: FC = () => {
8 const broadcast = useBroadcast();
9
10 const [messages. setMessages] = useState<string[]>([]);
11
12 event.receiveMessage.useEvent(({ data }) => {
13 // ^? (property) data: { message: string }
14 setMessages((prev) => [...prev, data.message]);
15 });
16
17 const sendMessage = useCallback((message: string): void => {
18 // Parameter will be statically typed from server/io.ts
19 broadcast.sendMessage({ message });
20 }, [broadcast]);
21
22 // ...
23};

Next steps

This example only scratches the surface of the realtime capabilities offered by pluv.io.