Server plugins
Most plugins are pure-CRDT: client UI + shared state is enough. Add a server part when you need something a browser can’t (or shouldn’t) do:
- call an external API with a secret (the client runs in an opaque sandbox with no secrets),
- enforce logic centrally (e.g. authoritative tallying, timed transitions),
- bridge to a database or another service.
Where it runs
Section titled “Where it runs”The server part runs once, on the machine that started liebstoeckel live. It never runs in the audience’s browsers, and never on the relay. With a relay, your local process connects as a privileged “runner” peer and applies the server plugin’s effects to the shared doc.
The shape
Section titled “The shape”Declare the entry in package.json and export a server(ctx) function:
{ "liebstoeckel": { "client": "./src/client.tsx", "server": "./src/server.ts" }}import type { PluginServerCtx } from "@liebstoeckel/plugin-sdk";import { pluginState } from "@liebstoeckel/plugin-sdk";import { mySchema, type MyState } from "./logic";
export default function server(ctx: PluginServerCtx<MyState>) { const state = pluginState(ctx.doc, "myplugin", mySchema);
// react to shared-state changes, run host-only work, write results back const stop = state.subscribe((snap) => { // …e.g. when a round closes, compute + write the official result });
return () => stop(); // optional teardown}The runtime ctx gives you the shared doc, the session id, and the instance string. Build a typed state accessor yourself by calling pluginState(ctx.doc), as the example above does.
How it’s bundled
Section titled “How it’s bundled”At build time the server entry is bundled (target bun, self-contained) and base64-encoded into the deck’s plugin manifest. A live server decodes it to a temp module and runs it with an injected ctx. The browser never touches it.
Code-first presentations your agent can author. One file out, no server.
Comments