The JavaScript SDK ships its own declaration file — TypeScript is a first-class consumer, not an afterthought with a community types package.
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.
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 });
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.
moduleResolution: "nodenext" / "bundler" — resolves through the exports map; verified under --strict.moduleResolution: "node10" (legacy "node") — resolves via the types field; works, but prefer nodenext.RealtimeWebSocket), so the package compiles in pure-Node projects..js specifiers as the runtime expects — no allowImportingTsExtensions needed.