Every server serves its client at /client.js, so a page can import it straight from your socket. For bundlers it's also on npm as @slash.tools/socket. No dependencies; it works in browsers and Node 22+.
npm install @slash.tools/socketpage.js
123456789101112import { connect } from "https://live.example.com/client.js"// or: import { connect } from "@slash.tools/socket"
const socket = connect("wss://live.example.com", { name: "Ada" })
const chat = socket.field("chat")const off = chat.subscribe((items, event) => render(items))
await chat.push({ text: "hello" }) // resolves when the server accepts itsocket.field("likes").incr()socket.field("presence").set({ page: location.pathname })socket.field("cursor").send({ x: 0.5, y: 0.25 }) // at most once per framePropTypeDescription
connect(url, options)SocketOpens the connection and keeps it open, reconnecting with backoff. url can be wss://, https:// or end in /live.options{ name, avatar, color, token, visitor, reconnect }How you show up. token is a string or an async function (called on every connect). visitor overrides the anonymous id kept in localStorage; false for none.socket.field(name)FieldA field by name. Its type, access and state arrive with the welcome.field.state—The current state: value → the value · counter → number · list → items · map → { key: entry } · stream → { connectionId: { from, value, at } } · presence → members. Replaced, never mutated, on each change.field.subscribe(fn)() => voidCalls fn(state, event) on every change, and straight away once the state is known. Returns an unsubscribe.field.listen / field.get—For React's useSyncExternalStore(field.listen, field.get).set · incr · decr · push · put · dropPromiseWrites, acknowledged by the server; they reject with a SocketError (code, message) when refused. Writes made while reconnecting wait for the connection.send(value)voidstream: broadcast to everyone else; calls within one animation frame collapse into the last.field.mine()—map: your own entry, if you've put one.socket.meAuthorWho the server says you are: { id, visitor, name, color, avatar, signedIn }.socket.on(event, fn)() => void"status" (connecting · open · closed), "welcome", "error", "message".socket.readyPromiseResolves after the first welcome.socket.identify({ name, avatar, color, token })—Reconnects as someone else.socket.close()—Closes for good.Types: every server describes its own fields as TypeScript at /types.d.ts. Save it next to your code and pass it to connect, and field names, values and state are all checked.
1curl https://live.example.com/types.d.ts -o socket-fields.d.tssocket.ts
12345import { connect } from "@slash.tools/socket"import type { Fields } from "./socket-fields"
const socket = connect<Fields>("wss://live.example.com")socket.field("chat").push({ text: "typed" }) // { text: string }