npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@martini-kit/transport-colyseus

v0.2.0

Published

Colyseus transport adapter for martini-kit multiplayer SDK

Downloads

21

Readme

@martini-kit/transport-colyseus

Colyseus room adapter for martini-kit multiplayer SDK. Use Colyseus for matchmaking, rooms, and server infrastructure while using martini-kit's declarative API for game logic.

Why Use This?

Best of Both Worlds:

  • Colyseus handles rooms, matchmaking, and server management
  • martini-kit handles game logic with a clean, declarative API

Perfect For:

  • Web games that need matchmaking/lobbies
  • Server-authoritative multiplayer games
  • Production deployments with managed infrastructure
  • Teams familiar with Colyseus who want cleaner game logic code

Installation

pnpm add @martini-kit/core @martini-kit/transport-colyseus colyseus.js

Quick Start

Client-Side

import { Client } from 'colyseus.js';
import { defineGame, GameRuntime } from '@martini-kit/core';
import { ColyseusTransport } from '@martini-kit/transport-colyseus';

// Define your game logic with martini-kit
const game = defineGame({
  minPlayers: 2,
  maxPlayers: 4,
  setup: () => ({
    players: {},
    score: {}
  }),
  actions: {
    move: (state, playerId, input) => {
      state.players[playerId] = input.position;
    },
    updateScore: (state, playerId, points) => {
      state.score[playerId] = (state.score[playerId] || 0) + points;
    }
  }
});

// Connect to Colyseus
const client = new Client('ws://localhost:2567');
const room = await client.joinOrCreate('my_game_room');

// Create martini-kit transport from Colyseus room
const transport = new ColyseusTransport(room);

// Create game runtime
const runtime = new GameRuntime(game, transport, {
  isHost: room.sessionId === '...' // Determine host via Colyseus
});

// Use martini-kit's clean API
runtime.submitAction('move', { position: { x: 10, y: 20 } });

Server-Side (Colyseus)

Create a Colyseus room that relays martini-kit messages:

import { Room, Client } from '@colyseus/core';

export class martini-kitGameRoom extends Room {
  private hostId: string | null = null;

  onCreate(options: any) {
    console.log('martini-kitGameRoom created');

    // Handle martini-kit messages
    this.onMessage('martini-kit', (client, message) => {
      // Relay to all clients or targeted client
      if (message.targetId) {
        const targetClient = Array.from(this.clients).find(
          c => c.sessionId === message.targetId
        );
        targetClient?.send('martini-kit', message);
      } else {
        // Broadcast to all except sender
        this.broadcast('martini-kit', message, { except: client });
      }
    });
  }

  onJoin(client: Client, options: any) {
    console.log(client.sessionId, 'joined');

    // Elect first player as host
    if (!this.hostId) {
      this.hostId = client.sessionId;
      this.broadcast('martini-kit', {
        type: 'host_announce',
        hostId: this.hostId,
        senderId: 'server'
      });
    }

    // Notify all clients of new player
    this.broadcast('martini-kit', {
      type: 'player_join',
      payload: { playerId: client.sessionId },
      senderId: 'server'
    });

    // Send current peers list to new player
    const peers = Array.from(this.clients).map(c => c.sessionId);
    client.send('martini-kit', {
      type: 'peers_list',
      payload: { peers },
      senderId: 'server'
    });
  }

  onLeave(client: Client, consented: boolean) {
    console.log(client.sessionId, 'left');

    // Notify remaining clients
    this.broadcast('martini-kit', {
      type: 'player_leave',
      payload: { playerId: client.sessionId },
      senderId: 'server'
    });

    // Elect new host if needed
    if (client.sessionId === this.hostId && this.clients.length > 0) {
      this.hostId = Array.from(this.clients)[0].sessionId;
      this.broadcast('martini-kit', {
        type: 'host_announce',
        hostId: this.hostId,
        senderId: 'server'
      });
    }
  }
}

API Reference

new ColyseusTransport(room: Room)

Create a martini-kit transport from a Colyseus room.

Parameters:

  • room: A connected Colyseus room instance

Example:

const room = await client.joinOrCreate('game_room');
const transport = new ColyseusTransport(room);

Transport Methods

All standard martini-kit Transport methods are supported:

send(message: WireMessage, targetId?: string): void

Send a message through the Colyseus room.

transport.send({
  type: 'action',
  payload: { action: 'move', x: 10, y: 20 }
});

// Send to specific player
transport.send({
  type: 'state_sync',
  payload: { state: {...} }
}, 'player-123');

onMessage(handler: (message, senderId) => void): () => void

Listen for messages from other players.

const unsubscribe = transport.onMessage((message, senderId) => {
  console.log('Received message from:', senderId, message);
});

// Stop listening
unsubscribe();

onPeerJoin(handler: (peerId: string) => void): () => void

Listen for players joining.

transport.onPeerJoin((peerId) => {
  console.log('Player joined:', peerId);
});

onPeerLeave(handler: (peerId: string) => void): () => void

Listen for players leaving.

transport.onPeerLeave((peerId) => {
  console.log('Player left:', peerId);
});

getPlayerId(): string

Get your player ID (same as room.sessionId).

const myId = transport.getPlayerId();

getPeerIds(): string[]

Get list of all connected players (excluding yourself).

const peers = transport.getPeerIds();
console.log('Connected players:', peers);

isHost(): boolean

Check if you are the current host.

if (transport.isHost()) {
  console.log('I am the host');
}

Additional Methods

onError(handler: (error: Error) => void): () => void

Listen for connection errors.

transport.onError((error) => {
  console.error('Transport error:', error);
});

disconnect(): void

Leave the Colyseus room and clean up.

transport.disconnect();

getRoom(): Room

Get the underlying Colyseus room (for advanced use cases).

const room = transport.getRoom();
console.log('Room ID:', room.id);
console.log('Room state:', room.state);

Message Protocol

The transport uses a 'martini-kit' message type on the Colyseus room. All martini-kit messages are wrapped in this format:

{
  type: 'action' | 'state_sync' | 'player_join' | 'player_leave' | 'host_announce' | ...,
  payload?: any,
  senderId: string,
  targetId?: string  // For targeted messages
}

Control Messages

The server should send these control messages:

player_join

{
  type: 'player_join',
  payload: { playerId: string },
  senderId: 'server'
}

player_leave

{
  type: 'player_leave',
  payload: { playerId: string },
  senderId: 'server'
}

host_announce

{
  type: 'host_announce',
  hostId: string,
  senderId: 'server'
}

peers_list

{
  type: 'peers_list',
  payload: { peers: string[] },
  senderId: 'server'
}

Complete Example

See examples/colyseus-game for a full working example with:

  • Colyseus server setup
  • Client-side game logic with martini-kit
  • Matchmaking UI
  • Host migration handling

Comparison to Other Approaches

Pure Colyseus

// ❌ Imperative, verbose
room.onMessage('move', (message) => {
  const player = players.get(message.playerId);
  player.x = message.x;
  player.y = message.y;
  // Sync to other clients...
  // Handle edge cases...
  // Validate input...
});

martini-kit + Colyseus

// ✅ Declarative, concise
const game = defineGame({
  actions: {
    move: (state, playerId, input) => {
      state.players[playerId] = input.position;
    }
  }
});

Best Practices

  1. Use Colyseus for Infrastructure

    • Room management
    • Matchmaking
    • Persistence (via Colyseus state)
    • Authentication
  2. Use martini-kit for Game Logic

    • Player actions
    • Game state updates
    • Collision detection
    • Game rules
  3. Host Election

    • Let Colyseus determine the host
    • Send host_announce messages
    • martini-kit will handle host-authoritative logic
  4. Error Handling

    • Listen to both Colyseus and martini-kit errors
    • Handle reconnection via Colyseus
    • martini-kit will auto-resume game state

Troubleshooting

"Messages not being received"

  • Ensure your Colyseus server relays 'martini-kit' messages
  • Check that onMessage('martini-kit', ...) is set up on the server
  • Verify the message format includes senderId

"Host is always false"

  • Server must send host_announce messages
  • Check that hostId matches a client's sessionId

"Peers list is empty"

  • Server should send player_join/player_leave messages
  • Or send a peers_list message on join

"TypeScript errors"

  • Ensure colyseus.js is installed
  • Check that types are imported correctly: import type { Room } from 'colyseus.js'

See Also

License

MIT