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

Client vs Server Events

In pluv.io, you can define type-safe custom events both on the client and server. Both client and server event procedures represent a message that a user has sent to be transformed before being emitted to connected peers. If a procedure does not exist to capture a received message, that message will be forwarded as-is to the next receiver of the message. i.e. if a client procedure does not exist, the message is forwarded to the server procedure. If that does not exist, then the message is forwarded to connected peers.

The full broadcasted message flow is like so:

┌──────────────────┐    ┌──────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│                  │    │ Optional:        │    │ Optional:        │    │                 │
│ User broadcasts  ├───►│ Client procedure ├───►│ Server Procedure ├───►│ Broadcasted to  │
│ message          │    │ receives message │    │ receives message │    │ connected peers │
│                  │    │                  │    │                  │    │                 │
└──────────────────┘    └──────────────────┘    └──────────────────┘    └─────────────────┘

Server Procedures

Server procedures are created from the PluvIO instance created from createIO. Below is an example of their usage:

import { createIO } from "@pluv/io";
import { platformNode } from "@pluv/platform-node";
import { db } from "./db";

const io = createIO({
    // Provide a context object that can be accessed from any of the event procedures
    context: { db },
    platform: platformNode(),
});

// You can define events as procedures that can exist in separate or the same file.
const sendHelloWorld = io.procedure
    .broadcast(() => ({ receiveHelloWorld: "hello world!" }));

const sendGreeting = io.procedure
    // Optional: You can specify inputs for the procedure if needed
    .input(z.object({ name: z.string() }))
    .broadcast(({ name }) => ({ receiveGreeting: `Hi my name is ${name}!` }));

const doubleNumber = io.procedure
    .input(z.object({ value: z.number() }))
    .broadcast(({ value }) => ({ numberDoubled: value * 2 }));

const serverExample = io.procedure
    .input(z.object({ data: z.any() }))
    .broadcast((
        { data },
        {
            // The room's context from `createIO`. Also may contain platform-specific values like
            // Cloudflare Worker's env and state
            context,
            // The room's copy of the CRDT state
            doc,
            // The id (string) of the room
            room,
            // The session of the connection that is invoking this procedure
            session,
            // A list of all sessions connected to this room on this server
            // This includes the emitter's session as well
            sessions,
        }
    ) => {
        return { finishExample: data };
    });

export const ioServer = io.server({
    // Register your procedures to the PluvServer via `router`
    router: io.router({
        sendHelloWorld,
        sendGreeting,
        doubleNumber,
    }),
});

Client Procedures

Client procedures are created from the PluvClient instance created from createClient. Below is an example of their usage:

import { createClient } from "@pluv/client";
import type { ioServer } from "./backend/pluv-io";

const io = createClient({
    infer: (i) => ({ io: i<typeof ioServer> }),
});

// Basic example of a procedure, that works like a server procedure
const add5 = io.procedure
    // Optional: Input is optional like in the server example
    .input(z.object({ value: z.number() }))
    .broadcast(({ value }) => ({ numberIncreased: value + 5 }));

const subtract5 = io.procedure
    .input(z.object({ value: z.number() }))
    // This procedure returns an event that is caught by the above
    // server's router. This will first subtract 5, then double the
    // result on the server
    .broadcast(({ value }) => ({ doubleNumber: value - 5 }));

const clientExample = io.procedure
    .input(z.object({ data: z.any() }))
    .broadcast((
        { data },
        {
            // The room's copy of the CRDT state
            doc,
            // A list of all other connected peers
            others,
            // The id (string) of the room
            room,
            // The current connected user
            user,
        }
    ) => {
        return { finishExample: data };
    })

const room = io.createRoom("my-custom-room", {
    router: io.router({
        add5,
        subtract5,
    }),
});