npm package: @lavedon/sdk
Description: TypeScript SDK for building Lavedon games and clients.
Latest version: 2.0.0

# Lavedon SDK

TypeScript SDK for building Lavedon games and clients.

## Install

```sh
npm install @lavedon/sdk
```

## Entry Points

```ts
import { LavedonClient, type GameDefinition, type PlayerAction } from "@lavedon/sdk";
```

```ts
import { LavedonClient } from "@lavedon/sdk/client";
import type { Context, GameDefinition, PlayerAction, SystemAction } from "@lavedon/sdk/server";
```

`@lavedon/sdk` exports the client and shared types.

`@lavedon/sdk/client` exports the WebSocket client only.

`@lavedon/sdk/server` currently exports the server-side type definitions used to describe a game.

## Client

`LavedonClient` manages a WebSocket connection, tracks the latest state snapshot, applies JSON Patch updates, and notifies subscribers when state changes.

### Basic Usage

```ts
import { LavedonClient } from "@lavedon/sdk";

type State = {
  count: number;
};

type Action = {
  type: "INC";
};

const client = new LavedonClient<State, Action>();

const unsubscribe = client.subscribe((state) => {
  console.log("state", state);
});

client.connect("ws://localhost:8080");
client.send({ type: "INC" });

// later
unsubscribe();
```

### Client Options

```ts
const client = new LavedonClient({
  autoReconnect: true,
  initialBackoff: 1000,
  maxBackoff: 30000,
});
```

Available options:

- `autoReconnect`: reconnect automatically after the socket closes. Defaults to `true`.
- `initialBackoff`: starting reconnect delay in milliseconds. Defaults to `1000`.
- `maxBackoff`: maximum reconnect delay in milliseconds. Defaults to `30000`.

### Supported Server Messages

The client accepts full state payloads and patch payloads:

```json
{
  "type": "FULL_STATE",
  "state": { "count": 1 },
  "version": 3
}
```

```json
{
  "type": "PATCH_UPDATE",
  "patch": [{ "op": "replace", "path": "/count", "value": 2 }],
  "baseVersion": 3,
  "version": 4
}
```

Compatibility notes:

- Full snapshots are accepted as `FULL_STATE`, `STATE`, or any payload with a `state` field.
- Patch payloads are accepted as `PATCH_UPDATE` or `PATCH`.
- If a patch arrives with the wrong `baseVersion`, the client ignores it and sends `{ "type": "FULL_STATE_REQUEST" }`.
- If the server sends `RELOAD`, the client keeps the connection alive and waits for the next state payload.

## Game Definition Types

The package also exports types for describing a Lavedon game on the server side.

```ts
import type { GameDefinition, PlayerAction, Context } from "@lavedon/sdk/server";

type State = {
  count: number;
};

type Action =
  | { type: "INC"; payload?: { amount?: number } }
  | { type: "DEC"; payload?: { amount?: number } };

export const game: GameDefinition<State, Action> = {
  setup: (_ctx: Context) => ({ count: 0 }),

  actions: {
    INC: (state, payload) => {
      state.count += payload?.amount ?? 1;
    },
    DEC: (state, payload) => {
      state.count -= payload?.amount ?? 1;
    },
  },

  afterAction: (state, action, ctx) => {
    if (action.type === "INC" && state.count >= 10) {
      ctx.chat.announce("Target reached");
    }
  },
};
```

### `Context`

The `setup`, `actions`, and `afterAction` hooks receive a `Context` with:

- `playerId`
- `roomId`
- `timestamp`
- `random.next()`
- `random.chance(p)`
- `chat.announce(msg)`
- `chat.sendSystem(msg)`
- `events.emit(event, payload)`

### `SystemAction`

Reserved engine actions are exported as:

```ts
type SystemAction =
  | { type: "@@INIT" }
  | { type: "@@TICK" };
```

Use `afterAction` if you need to react to these lifecycle events.

### `onTurnEnd`

`onTurnEnd` is still present for compatibility, but it is deprecated. Prefer `afterAction`.

## Development

```sh
bun install
bun test
bun run build
```