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";
export * from "./report";
export * from "./mentions";        // @mention tokenizer shared by api and mobile

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

mentions.ts holds the one definition of an @mention (MENTION_PATTERN, findMentions, extractMentionUsernames). The API resolves tags with it and the app renders them with it, so the two cannot disagree about where a tag ends.

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