Getting Started
This guide gets one app connected to a running swu-simulator server. The SDK does not start the engine; it speaks to the HTTP and WebSocket API exposed by the server.
First-party consumer
The SDK's primary consumer today is the swu Nuxt 4 app that lives beside this repository at ../swu. It exercises the whole surface rather than a subset: createSimulatorClient for browser and server-side calls, MatchSession for live matches, and the offline path through @my-swu/simulator-client/local, /local/assets, /local/routing, and the shipped wasm engine.
That app does not install a published version. It depends on this workspace directly:
{
"dependencies": {
"@my-swu/simulator-client": "link:../swu-simulator/packages/ts-sdk"
}
}A linked consumer still resolves through the package exports map, which points at dist/. Editing SDK source therefore changes nothing in ../swu until the SDK is rebuilt. When you change the SDK for that app, the loop includes a build:
# From the swu-simulator repo root. Re-exports the schema and rebuilds the SDK.
pnpm sdk:build# TypeScript only, when the Rust schema has not changed.
pnpm --filter @my-swu/simulator-client run buildEverything below applies equally to a normal npm install.
Install
pnpm add @my-swu/simulator-clientnpm install @my-swu/simulator-clientyarn add @my-swu/simulator-clientStart the simulator
From the simulator repository:
cargo run --bin swu-simulatorThe examples assume the server is reachable at http://127.0.0.1:4000. They also use card ids from the simulator card cache; see card ids for the mapping used in snippets.
Create a client
import { createSimulatorClient } from '@my-swu/simulator-client'
export const simulatorClient = createSimulatorClient({
// This is the HTTP origin of the running simulator server.
baseUrl: 'http://127.0.0.1:4000',
})Check connectivity:
const health = await simulatorClient.system.health()
console.log(health.status) // "ok"Runtime requirements
- Node.js 18+ or any runtime with global
fetch. - A browser-style
WebSocketglobal for live match sockets. - ESM only. Use
"type": "module"or dynamicimport()in CommonJS projects.
Node does not provide a browser WebSocket global. Inject one when you need live match sockets:
import WebSocket from 'ws'
import { createSimulatorClient } from '@my-swu/simulator-client'
import type { WebSocketFactory } from '@my-swu/simulator-client'
// Node examples use `ws` to provide the browser-compatible socket shape.
const nodeWebSocketFactory: WebSocketFactory = url => new WebSocket(url)
const simulatorClient = createSimulatorClient({
baseUrl: 'http://127.0.0.1:4000',
// Required only for live match sockets in runtimes without global WebSocket.
webSocketFactory: nodeWebSocketFactory,
})First match
// Use card ids; these example card ids map to real cards.
const premierHostDeck = {
leader: 46102, // Leia Organa - Someone Who Loves You
// Twin Suns decks list every leader here; single-leader formats send an empty array.
leaders: [],
base: 308, // Echo Base
cards: Array.from({ length: 50 }, () => 45), // Alliance X-Wing
// Only formats whose validation allows a sideboard accept entries here.
sideboard: [],
}
const hostAccess = await simulatorClient.matches.create({
// Seeds make examples deterministic.
seed: '7',
format: 'premier',
// Twin Suns supports 2-4 players; every other format is exactly 2.
playerCount: 2,
hostDeck: premierHostDeck,
// Every viewer sees this name on the host seat.
hostName: 'Host',
// True only when the host arrived from an authenticated app session.
hostAuthenticated: false,
// Identities that should own this match in server-side history.
historyIdentities: [],
// One entry per AI-controlled seat; empty means a human opponent joins.
aiOpponents: [],
})
// The session stores the returned private access and owns socket state.
const matchSession = simulatorClient.session.create(hostAccess)
await matchSession.connect()
matchSession.on('snapshot', matchSnapshot => {
// Render from snapshots; they are the authoritative game state.
console.log(matchSnapshot.prompt)
})
// Signal that this seat is ready to leave the lobby.
matchSession.ready()Read next:
