@playpals/types
packages/types
Shared TypeScript types. Its most important job is holding the socket event contract so that the mobile client and esu compile against the same definitions.
What it exports
export * from "./socket-events"; // the realtime contract
export * from "./reactions";
export * from "./feed";
export * from "./trending";Plus domain interfaces defined in src/index.ts — Profile, Channel, ChannelRoom, Link, APIResponse<T>, and payload types.
The socket contract
Three event maps, mirroring the three directions traffic flows:
export type ClientToServerEvents = {
"presence:ping": (payload: { ts: number }, ack: (res: { ts: number }) => void) => void;
"dm:join": (payload: { conversationId: string }, ack: Ack) => void;
"dm:leave": (payload: { conversationId: string }, ack: Ack) => void;
"dm:typing:start": (payload: { conversationId: string }, ack?: Ack) => void;
"dm:typing:stop": (payload: { conversationId: string }, ack?: Ack) => void;
"room:join": (payload: { roomId: string }, ack: Ack) => void;
"room:leave": (payload: { roomId: string }, ack: Ack) => void;
"room:typing:start": (payload: { roomId: string }, ack?: Ack) => void;
"room:typing:stop": (payload: { roomId: string }, ack?: Ack) => void;
};ServerToClientEvents covers messages, read receipts, typing, channel lifecycle, notifications, and media processing updates. InterServiceEvents documents the emit:* payloads the API sends to esu's admin router.
Full listing with the current wire format: Socket Events.
Ack helpers
export type Ack = (res: { ok: true } | { ok: false; error: string; code?: string }) => void;
export function ok(): { ok: true };
export function fail(error: string, code?: string): { ok: false; error: string; code?: string };esu imports ok() and fail() directly, so ack shapes are consistent across every handler.
Known problems
The contract has drifted from the implementation
esu registers handlers on a loosely typed Server, so TypeScript does not enforce this file against the actual handlers. Several events differ — presence:ping vs ping, room:message:typing vs room:typing, lastSeenAt vs lastSeen. There are also two typos in the contract itself: messagedId in dm:message:deleted and memeber in emit:channel:memeber:joined.
Details and the full comparison: Socket Events.
Message payloads are any
"dm:message:new": (payload: { conversationId: string; message: any }) => void;Every message payload is any, so the type system provides no protection on the thing clients actually read. Defining a ConversationMessageDTO here and using it on both sides would be a worthwhile, contained improvement.
Also note export interface User {} and export interface ChannelMember {} — empty placeholder interfaces that accept anything. Do not use them as if they were real types.
Adding a type
- Add it to the appropriate file in
packages/types/src/(or create one and re-export it fromindex.ts). - Rebuild:
pnpm build --filter=@playpals/types. - Consumers pick it up on their next build.
If you are adding a socket event, change all three of: this package, apps/esu/src/socket/handlers.ts, and whichever producer triggers it. See Daily Workflows.
