Skip to content
TypeWire

@tahanabavi/typesocket

v2.2.0

Contract-driven, type-safe Socket.IO client — one Zod contract validated in both directions, with acks, middleware, queueing and a devtools instrumentation seam.

Migrating from v1

v2 replaces SocketService with SocketClient. The rewrite also fixes correctness bugs that were not fixable without changing behaviour, so upgrading is worth doing deliberately rather than mechanically.

Contract: merge the two maps into one, tagging each event's direction and grouping into modules. response becomes payload, callback becomes ack.

ts
// v1
const onEvents = { message: { response: MessageSchema } };
const emitEvents = { sendMessage: { request: ReqSchema, callback: AckSchema } };

// v2
const wsContracts = defineSocketContracts({
chat: {
message: { direction: "server->client", payload: MessageSchema },
sendMessage: { direction: "client->server", request: ReqSchema, ack: AckSchema },
},
});

Call sites:

v1v2
new SocketService(cfg, on, emit, handlers).init()new SocketClient(cfg, contracts, options).connect()
socket.on("message", fn)client.modules.chat.message.on(fn)
socket.off("message", fn)client.modules.chat.message.off(fn) — now actually detaches
socket.emit("sendMessage", d)client.modules.chat.sendMessage(d)
socket.emitAsync("sendMessage", d)client.modules.chat.sendMessage(d) — ack now validated, and it times out
socket.emitQueued("sendMessage", d)client.modules.chat.sendMessage.queue(d)
socket.waitFor("message", ms)client.modules.chat.message.wait({ timeoutMs: ms })
socket.enableDebug()debug: true in config
socket.reconnectWithBackoff()removed — socket.io's own backoff is configured via reconnectionDelay / reconnectionDelayMax
getSocketConfig()socketConfigFromEnv(prefix)

Behaviour changes to plan for:

  • Handlers used to fire twice on the first connection (and once more per reconnect) because listeners were registered on the socket and re-registered on connect. They now fire once. Code that compensated for the duplicate needs the workaround removed.

  • off() never removed anything in v1. It does now — check nothing relied on a handler surviving its own removal.

  • Invalid outbound payloads used to be logged and dropped. They now throw (or reject). Wrap emits that can receive user input.

  • Emits with no connection used to be silently discarded. They now throw SocketNotConnectedError; use .queue() where buffering was the intent.

  • emitAsync had no timeout and never validated the ack. Both are enforced now, so a previously-hanging call fails loudly and a non-conforming ack rejects.


Examples & release notes

examples/basictypesocket in four files — contract, server, client, run.
examples/chatMulti-room chat with presence, typing and a live frame inspector.
docs/releases/v2.1.0.mdContract-linked permissions on client->server events.
docs/releases/v2.0.0.mdThe full 2.0 release note, with rationale and the complete migration table.
bash
pnpm --filter @typewire-examples/basic start   # runs and exits
pnpm --filter @typewire-examples/chat dev # server + UI

License

MIT © Taha Nabavi