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.

Permissions

(v2.1.0) A client->server event may carry an optional permission requirement — the flag names a gateway guard and the client both check. Only outbound events carry it: the client authorizes what it sends, never what it receives.

ts
export const contracts = defineSocketContracts({
chat: {
deleteAny: {
direction: "client->server",
permission: { require: ["chat.MANAGE_MESSAGES"] }, // ← new, optional
request: z.object({ id: z.string() }),
ack: z.object({ ok: z.boolean() }),
},
},
});

The value is a PermissionRequirement{ require?, any?, reason? } (require = all flags, any = at least one). The flag names reference a @tahanabavi/type-permission bit map, but the type is redeclared structurally, so typesocket gains no new dependency.

  • Server — a Socket.IO gateway guard reads def.permission and rejects the frame when the actor lacks the flags.

  • Client — register createPermissionMiddleware via the authorizeOutbound option; a denied emit throws before it reaches the wire.

ts
import { SocketClient, createPermissionMiddleware } from "@tahanabavi/typesocket";
import { P } from "./permissions";

const client = new SocketClient(config, contracts, {
authorizeOutbound: createPermissionMiddleware({
getPermissions: () => store.getSnapshot().global, // synchronous
authorize: P.authorize,
}),
});

// blocked emits throw PermissionDeniedError — an ack'd emit rejects, a
// fire-and-forget one throws synchronously; other events are untouched:
await client.modules.chat.deleteAny({ id });

It goes through authorizeOutbound rather than client.use() on purpose: a SocketMiddleware can only drop a frame silently, whereas this throws to the call site. getPermissions is synchronous so a fire-and-forget emit can fail synchronously. The check is UX only — the server re-authorizes every frame.

Additive: events without a permission key are unaffected. See docs/releases/v2.1.0.md.