TypeScript SDK and Type-Safe Integration

The JavaScript SDK ships its own declaration file — TypeScript is a first-class consumer, not an afterthought with a community types package.

How the typing is delivered

The package's types field and both exports entries point at a hand-maintained index.d.ts covering every class, method signature, options object, response interface and error type. We chose bundled declarations over a TypeScript source build deliberately: the SDK is zero-dependency ESM that browsers can import verbatim, and a TS→JS toolchain would add a build step without changing what consumers receive. The declarations are compile-checked against a strict-mode consumer project in CI.

Autocomplete works the moment the package is installed — no @types/* package, no declaration: true gymnastics.

Typed example

import {
  MultiplayerClient,
  RegisterResponse,
  ApiResponse,
  TransportError,
  ConfigurationError,
} from 'michitai-multiplayer';

const mp = new MultiplayerClient(process.env.MP_API_TOKEN!, {
  timeout: 10_000,
});

const reg: RegisterResponse = await mp.players.register('Alice', {
  playerData: { wins: 0 },
});
// reg.private_key is typed; reg.error narrows the failure branch
if (!reg.success) throw new Error(reg.error);

const room: ApiResponse = await mp.rooms.create(reg.private_key!, 'Lobby', {
  maxPlayers: 4,
  realtime: true,
});

const rt = mp.createRealtime({
  onReceive: (command, data, sender) => {
    // sender is typed SenderInfo | undefined
    console.log(command, sender?.player_name, data);
  },
});

// compile-time target narrowing: 'all' | 'host' | 'others' | 'specific'
rt.send('others', 'pos', { x: 1, y: 2 });

What the types guarantee — and don't

Guaranteed at compile time: method names and signatures, options field names (camelCase mapped to wire snake_case for you), realtime target literals, error class hierarchy, and the documented fields on response interfaces like RegisterResponse.private_key and RealtimeTokenResponse.realtime_server.

Not guaranteed: response bodies are parsed JSON, not runtime-validated schemas. ApiResponse carries an index signature for endpoint-specific fields — typed where the contract is stable, accessible-but-unverified elsewhere. If your game needs strict response validation, wrap the calls in your own schema layer (zod, valibot) at the boundary.

There is also no any in the public surface — payloads are unknown where the wire shape is caller-defined (room data, action payloads), which is accurate rather than permissive.

Module resolution notes

  • moduleResolution: "nodenext" / "bundler" — resolves through the exports map; verified under --strict.
  • moduleResolution: "node10" (legacy "node") — resolves via the types field; works, but prefer nodenext.
  • TS ≥ 5.x recommended. DOM lib is not required — the WebSocket type used is a minimal structural interface (RealtimeWebSocket), so the package compiles in pure-Node projects.
  • Verbatim ESM: imports use .js specifiers as the runtime expects — no allowImportingTsExtensions needed.