Skip to content

@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

ts
export * from "./socket-events";   // the realtime contract
export * from "./reactions";
export * from "./feed";
export * from "./trending";

Plus domain interfaces defined in src/index.tsProfile, Channel, ChannelRoom, Link, APIResponse<T>, and payload types.

The socket contract

Three event maps, mirroring the three directions traffic flows:

ts
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

ts
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

ts
"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

  1. Add it to the appropriate file in packages/types/src/ (or create one and re-export it from index.ts).
  2. Rebuild: pnpm build --filter=@playpals/types.
  3. 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.

Internal documentation — PlayPalz platform