Contract-driven Socket.IO for TypeScript. Declare each event once — with its direction and its Zod schemas — and the client generates itself. Every frame is validated on the way out and on the way in, acknowledgements included.
Part of TypeWire ↗, so it shares
typefetch's "module.event" identifier scheme and its instrument() seam:
HTTP and WebSocket traffic land in one devtools timeline with no adapter glue.
Why direction belongs in the contract
v1 took two maps — onEvents and emitEvents — named from the client's point
of view. That works for a client and only a client: a server gateway consuming
the same object needs them swapped, so it had to declare a mirrored copy that
could drift.
Tagging each event with direction makes the contract readable from both ends.
The client emits client->server events and listens to server->client ones;
a server adapter does exactly the reverse, from the same object. It also gives
every event a stable eventId ("chat.sendMessage") — the same
"module.endpoint" shape typefetch uses — so a cache or a devtools panel can
key both transports the same way.
client.modules.chat.sendMessage.eventId; // "chat.sendMessage"
client.modules.chat.sendMessage.def; // the contract definition
client.events; // every event, flattened, for tooling