Durable Objects System

Overview

PadawanForge implements a sophisticated Durable Objects system that provides stateful, real-time communication capabilities. This system includes ChatLobby for managing chat rooms and RoomManager for coordinating game sessions with persistent state management.

Architecture

Core Components

  • ChatLobby: Real-time chat room management
  • RoomManager: Game session coordination
  • WebSocket Management: Connection handling and message routing
  • State Persistence: Durable state across restarts
  • Message Broadcasting: Real-time message distribution

Durable Objects Features

  • Stateful Sessions: Persistent state across Cloudflare Worker restarts
  • Real-time Communication: WebSocket-based messaging
  • Room Management: Dynamic room creation and management
  • Player Coordination: Multi-player session handling
  • Message History: Persistent chat and game message storage

ChatLobby Implementation

Core ChatLobby Class

import { ChatLobby } from '@/durable-objects/ChatLobby';

// ChatLobby manages real-time chat rooms with persistent state
class ChatLobby {
  private state: DurableObjectState;
  private env: Env;
  private sessions: Map<string, WebSocket>;
  private rooms: Map<string, ChatRoom>;
  private messageHistory: Map<string, ChatMessage[]>;

  constructor(state: DurableObjectState, env: Env) {
    this.state = state;
    this.env = env;
    this.sessions = new Map();
    this.rooms = new Map();
    this.messageHistory = new Map();
  }

  // Handle WebSocket connections
  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);
    
    if (url.pathname === '/websocket') {
      return this.handleWebSocket(request);
    }
    
    return new Response('Not found', { status: 404 });
  }

  // WebSocket connection handler
  private async handleWebSocket(request: Request): Promise<Response> {
    const pair = new WebSocketPair();
    const [client, server] = Object.values(pair);

    server.accept();
    server.addEventListener('message', (event) => {
      this.handleMessage(server, event.data);
    });

    server.addEventListener('close', () => {
      this.handleDisconnect(server);
    });

    return new Response(null, { status: 101, webSocket: client });
  }
}

Chat Room Management

interface ChatRoom {
  id: string;
  name: string;
  participants: Set<string>;
  maxParticipants: number;
  isPrivate: boolean;
  createdAt: Date;
  lastActivity: Date;
}

interface ChatMessage {
  id: string;
  roomId: string;
  senderId: string;
  senderName: string;
  content: string;
  timestamp: Date;
  type: 'text' | 'system' | 'join' | 'leave';
}

// Room management methods
class ChatLobby {
  // Create a new chat room
  async createRoom(roomData: {
    id: string;
    name: string;
    maxParticipants?: number;
    isPrivate?: boolean;
  }): Promise<ChatRoom> {
    const room: ChatRoom = {
      id: roomData.id,
      name: roomData.name,
      participants: new Set(),
      maxParticipants: roomData.maxParticipants || 50,
      isPrivate: roomData.isPrivate || false,
      createdAt: new Date(),
      lastActivity: new Date()
    };

    this.rooms.set(room.id, room);
    this.messageHistory.set(room.id, []);
    
    // Persist room data
    await this.state.storage.put(`room:${room.id}`, room);
    
    return room;
  }

  // Join a chat room
  async joinRoom(roomId: string, playerId: string, playerName: string): Promise<boolean> {
    const room = this.rooms.get(roomId);
    if (!room) return false;

    if (room.participants.size >= room.maxParticipants) {
      return false;
    }

    room.participants.add(playerId);
    room.lastActivity = new Date();

    // Add join message
    const joinMessage: ChatMessage = {
      id: generateMessageId(),
      roomId,
      senderId: 'system',
      senderName: 'System',
      content: `${playerName} joined the room`,
      timestamp: new Date(),
      type: 'join'
    };

    await this.addMessage(roomId, joinMessage);
    await this.broadcastToRoom(roomId, {
      type: 'user_joined',
      playerId,
      playerName,
      participants: Array.from(room.participants)
    });

    return true;
  }

  // Leave a chat room
  async leaveRoom(roomId: string, playerId: string, playerName: string): Promise<void> {
    const room = this.rooms.get(roomId);
    if (!room) return;

    room.participants.delete(playerId);
    room.lastActivity = new Date();

    // Add leave message
    const leaveMessage: ChatMessage = {
      id: generateMessageId(),
      roomId,
      senderId: 'system',
      senderName: 'System',
      content: `${playerName} left the room`,
      timestamp: new Date(),
      type: 'leave'
    };

    await this.addMessage(roomId, leaveMessage);
    await this.broadcastToRoom(roomId, {
      type: 'user_left',
      playerId,
      playerName,
      participants: Array.from(room.participants)
    });
  }

  // Get room information
  async getRoomInfo(roomId: string): Promise<ChatRoom | null> {
    return this.rooms.get(roomId) || null;
  }

  // List all rooms
  async listRooms(): Promise<ChatRoom[]> {
    return Array.from(this.rooms.values());
  }
}

Message Handling

// Message processing and broadcasting
class ChatLobby {
  // Handle incoming WebSocket messages
  private async handleMessage(websocket: WebSocket, data: any): Promise<void> {
    try {
      const message = JSON.parse(data as string);
      
      switch (message.type) {
        case 'join_room':
          await this.handleJoinRoom(websocket, message);
          break;
        case 'leave_room':
          await this.handleLeaveRoom(websocket, message);
          break;
        case 'send_message':
          await this.handleSendMessage(websocket, message);
          break;
        case 'get_history':
          await this.handleGetHistory(websocket, message);
          break;
        case 'ping':
          websocket.send(JSON.stringify({ type: 'pong' }));
          break;
        default:
          websocket.send(JSON.stringify({ 
            type: 'error', 
            message: 'Unknown message type' 
          }));
      }
    } catch (error) {
      websocket.send(JSON.stringify({ 
        type: 'error', 
        message: 'Invalid message format' 
      }));
    }
  }

  // Handle join room request
  private async handleJoinRoom(websocket: WebSocket, message: any): Promise<void> {
    const { roomId, playerId, playerName } = message;
    
    const success = await this.joinRoom(roomId, playerId, playerName);
    
    if (success) {
      // Store websocket connection
      this.sessions.set(playerId, websocket);
      
      // Send room info
      const room = await this.getRoomInfo(roomId);
      websocket.send(JSON.stringify({
        type: 'room_joined',
        room,
        participants: Array.from(room!.participants)
      }));
    } else {
      websocket.send(JSON.stringify({
        type: 'join_failed',
        message: 'Failed to join room'
      }));
    }
  }

  // Handle leave room request
  private async handleLeaveRoom(websocket: WebSocket, message: any): Promise<void> {
    const { roomId, playerId, playerName } = message;
    
    await this.leaveRoom(roomId, playerId, playerName);
    
    // Remove websocket connection
    this.sessions.delete(playerId);
    
    websocket.send(JSON.stringify({
      type: 'room_left',
      roomId
    }));
  }

  // Handle send message request
  private async handleSendMessage(websocket: WebSocket, message: any): Promise<void> {
    const { roomId, playerId, playerName, content } = message;
    
    const chatMessage: ChatMessage = {
      id: generateMessageId(),
      roomId,
      senderId: playerId,
      senderName: playerName,
      content,
      timestamp: new Date(),
      type: 'text'
    };

    await this.addMessage(roomId, chatMessage);
    await this.broadcastToRoom(roomId, {
      type: 'new_message',
      message: chatMessage
    });
  }

  // Handle get history request
  private async handleGetHistory(websocket: WebSocket, message: any): Promise<void> {
    const { roomId, limit = 50 } = message;
    
    const history = await this.getMessageHistory(roomId, limit);
    
    websocket.send(JSON.stringify({
      type: 'message_history',
      roomId,
      messages: history
    }));
  }

  // Add message to history
  private async addMessage(roomId: string, message: ChatMessage): Promise<void> {
    const history = this.messageHistory.get(roomId) || [];
    history.push(message);
    
    // Keep only last 1000 messages
    if (history.length > 1000) {
      history.splice(0, history.length - 1000);
    }
    
    this.messageHistory.set(roomId, history);
    
    // Persist message
    await this.state.storage.put(`message:${message.id}`, message);
  }

  // Get message history
  private async getMessageHistory(roomId: string, limit: number): Promise<ChatMessage[]> {
    const history = this.messageHistory.get(roomId) || [];
    return history.slice(-limit);
  }

  // Broadcast message to room participants
  private async broadcastToRoom(roomId: string, message: any): Promise<void> {
    const room = this.rooms.get(roomId);
    if (!room) return;

    const messageStr = JSON.stringify(message);
    
    for (const playerId of room.participants) {
      const websocket = this.sessions.get(playerId);
      if (websocket && websocket.readyState === WebSocket.READY_STATE_OPEN) {
        websocket.send(messageStr);
      }
    }
  }

  // Handle WebSocket disconnection
  private async handleDisconnect(websocket: WebSocket): Promise<void> {
    // Find and remove the disconnected session
    for (const [playerId, session] of this.sessions.entries()) {
      if (session === websocket) {
        this.sessions.delete(playerId);
        
        // Remove from all rooms
        for (const room of this.rooms.values()) {
          if (room.participants.has(playerId)) {
            await this.leaveRoom(room.id, playerId, 'Unknown Player');
          }
        }
        break;
      }
    }
  }
}

RoomManager Implementation

Core RoomManager Class

import { RoomManager } from '@/durable-objects/RoomManager';

// RoomManager coordinates game sessions and player state
class RoomManager {
  private state: DurableObjectState;
  private env: Env;
  private rooms: Map<string, GameRoom>;
  private sessions: Map<string, WebSocket>;
  private gameStates: Map<string, GameState>;

  constructor(state: DurableObjectState, env: Env) {
    this.state = state;
    this.env = env;
    this.rooms = new Map();
    this.sessions = new Map();
    this.gameStates = new Map();
  }

  // Handle HTTP requests and WebSocket connections
  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);
    
    if (url.pathname === '/websocket') {
      return this.handleWebSocket(request);
    }
    
    if (url.pathname === '/rooms') {
      return this.handleRoomsRequest(request);
    }
    
    return new Response('Not found', { status: 404 });
  }
}

Game Room Management

interface GameRoom {
  id: string;
  name: string;
  gameType: 'cognitive' | 'social' | 'competitive';
  maxPlayers: number;
  currentPlayers: Set<string>;
  status: 'waiting' | 'playing' | 'finished';
  createdAt: Date;
  startedAt?: Date;
  endedAt?: Date;
  settings: GameSettings;
}

interface GameState {
  roomId: string;
  currentTurn: number;
  players: PlayerState[];
  gameData: any;
  lastUpdate: Date;
}

interface PlayerState {
  playerId: string;
  playerName: string;
  score: number;
  status: 'ready' | 'playing' | 'finished';
  lastActivity: Date;
}

interface GameSettings {
  difficulty: 'easy' | 'medium' | 'hard';
  duration: number; // in seconds
  maxRounds: number;
  allowSpectators: boolean;
}

// Room management methods
class RoomManager {
  // Create a new game room
  async createRoom(roomData: {
    id: string;
    name: string;
    gameType: GameRoom['gameType'];
    maxPlayers?: number;
    settings?: Partial<GameSettings>;
  }): Promise<GameRoom> {
    const room: GameRoom = {
      id: roomData.id,
      name: roomData.name,
      gameType: roomData.gameType,
      maxPlayers: roomData.maxPlayers || 4,
      currentPlayers: new Set(),
      status: 'waiting',
      createdAt: new Date(),
      settings: {
        difficulty: 'medium',
        duration: 420, // 7 minutes
        maxRounds: 10,
        allowSpectators: true,
        ...roomData.settings
      }
    };

    this.rooms.set(room.id, room);
    
    // Initialize game state
    const gameState: GameState = {
      roomId: room.id,
      currentTurn: 0,
      players: [],
      gameData: {},
      lastUpdate: new Date()
    };
    
    this.gameStates.set(room.id, gameState);
    
    // Persist room data
    await this.state.storage.put(`room:${room.id}`, room);
    await this.state.storage.put(`gamestate:${room.id}`, gameState);
    
    return room;
  }

  // Join a game room
  async joinRoom(roomId: string, playerId: string, playerName: string): Promise<boolean> {
    const room = this.rooms.get(roomId);
    if (!room) return false;

    if (room.status !== 'waiting') {
      return false; // Game already started
    }

    if (room.currentPlayers.size >= room.maxPlayers) {
      return false; // Room is full
    }

    room.currentPlayers.add(playerId);
    
    // Add player to game state
    const gameState = this.gameStates.get(roomId);
    if (gameState) {
      gameState.players.push({
        playerId,
        playerName,
        score: 0,
        status: 'ready',
        lastActivity: new Date()
      });
    }

    // Broadcast player joined
    await this.broadcastToRoom(roomId, {
      type: 'player_joined',
      playerId,
      playerName,
      players: gameState?.players || []
    });

    return true;
  }

  // Start a game
  async startGame(roomId: string, initiatorId: string): Promise<boolean> {
    const room = this.rooms.get(roomId);
    if (!room) return false;

    // Check if initiator is in the room
    if (!room.currentPlayers.has(initiatorId)) {
      return false;
    }

    // Check if enough players
    if (room.currentPlayers.size < 2) {
      return false;
    }

    room.status = 'playing';
    room.startedAt = new Date();

    // Initialize game session
    await this.initializeGameSession(roomId);

    // Broadcast game started
    await this.broadcastToRoom(roomId, {
      type: 'game_started',
      roomId,
      startedAt: room.startedAt
    });

    return true;
  }

  // End a game
  async endGame(roomId: string): Promise<void> {
    const room = this.rooms.get(roomId);
    if (!room) return;

    room.status = 'finished';
    room.endedAt = new Date();

    // Calculate final scores
    const gameState = this.gameStates.get(roomId);
    if (gameState) {
      const finalScores = gameState.players.map(player => ({
        playerId: player.playerId,
        playerName: player.playerName,
        finalScore: player.score
      }));

      // Broadcast game ended
      await this.broadcastToRoom(roomId, {
        type: 'game_ended',
        roomId,
        endedAt: room.endedAt,
        finalScores
      });
    }
  }

  // Update player score
  async updateScore(roomId: string, playerId: string, score: number): Promise<void> {
    const gameState = this.gameStates.get(roomId);
    if (!gameState) return;

    const player = gameState.players.find(p => p.playerId === playerId);
    if (player) {
      player.score = score;
      player.lastActivity = new Date();
      gameState.lastUpdate = new Date();

      // Broadcast score update
      await this.broadcastToRoom(roomId, {
        type: 'score_updated',
        playerId,
        score,
        players: gameState.players
      });
    }
  }
}

WebSocket Message Handling

// WebSocket message processing for RoomManager
class RoomManager {
  // Handle WebSocket connections
  private async handleWebSocket(request: Request): Promise<Response> {
    const pair = new WebSocketPair();
    const [client, server] = Object.values(pair);

    server.accept();
    server.addEventListener('message', (event) => {
      this.handleMessage(server, event.data);
    });

    server.addEventListener('close', () => {
      this.handleDisconnect(server);
    });

    return new Response(null, { status: 101, webSocket: client });
  }

  // Handle incoming messages
  private async handleMessage(websocket: WebSocket, data: any): Promise<void> {
    try {
      const message = JSON.parse(data as string);
      
      switch (message.type) {
        case 'join_room':
          await this.handleJoinRoom(websocket, message);
          break;
        case 'leave_room':
          await this.handleLeaveRoom(websocket, message);
          break;
        case 'start_game':
          await this.handleStartGame(websocket, message);
          break;
        case 'update_score':
          await this.handleUpdateScore(websocket, message);
          break;
        case 'game_action':
          await this.handleGameAction(websocket, message);
          break;
        case 'ping':
          websocket.send(JSON.stringify({ type: 'pong' }));
          break;
        default:
          websocket.send(JSON.stringify({ 
            type: 'error', 
            message: 'Unknown message type' 
          }));
      }
    } catch (error) {
      websocket.send(JSON.stringify({ 
        type: 'error', 
        message: 'Invalid message format' 
      }));
    }
  }

  // Handle join room request
  private async handleJoinRoom(websocket: WebSocket, message: any): Promise<void> {
    const { roomId, playerId, playerName } = message;
    
    const success = await this.joinRoom(roomId, playerId, playerName);
    
    if (success) {
      // Store websocket connection
      this.sessions.set(playerId, websocket);
      
      // Send room info
      const room = this.rooms.get(roomId);
      const gameState = this.gameStates.get(roomId);
      
      websocket.send(JSON.stringify({
        type: 'room_joined',
        room,
        gameState
      }));
    } else {
      websocket.send(JSON.stringify({
        type: 'join_failed',
        message: 'Failed to join room'
      }));
    }
  }

  // Handle start game request
  private async handleStartGame(websocket: WebSocket, message: any): Promise<void> {
    const { roomId, playerId } = message;
    
    const success = await this.startGame(roomId, playerId);
    
    if (success) {
      websocket.send(JSON.stringify({
        type: 'game_started_success'
      }));
    } else {
      websocket.send(JSON.stringify({
        type: 'start_game_failed',
        message: 'Failed to start game'
      }));
    }
  }

  // Handle score update
  private async handleUpdateScore(websocket: WebSocket, message: any): Promise<void> {
    const { roomId, playerId, score } = message;
    
    await this.updateScore(roomId, playerId, score);
  }

  // Handle game action
  private async handleGameAction(websocket: WebSocket, message: any): Promise<void> {
    const { roomId, playerId, action, data } = message;
    
    // Process game-specific actions
    await this.processGameAction(roomId, playerId, action, data);
  }

  // Broadcast to room participants
  private async broadcastToRoom(roomId: string, message: any): Promise<void> {
    const room = this.rooms.get(roomId);
    if (!room) return;

    const messageStr = JSON.stringify(message);
    
    for (const playerId of room.currentPlayers) {
      const websocket = this.sessions.get(playerId);
      if (websocket && websocket.readyState === WebSocket.READY_STATE_OPEN) {
        websocket.send(messageStr);
      }
    }
  }
}

Integration Examples

Client-Side Integration

// Connect to ChatLobby
class ChatClient {
  private websocket: WebSocket | null = null;
  private playerId: string;
  private currentRoom: string | null = null;

  constructor(playerId: string) {
    this.playerId = playerId;
  }

  // Connect to chat lobby
  async connect(): Promise<void> {
    const response = await fetch('/api/lobby/websocket');
    this.websocket = response.webSocket!;
    
    this.websocket.addEventListener('message', (event) => {
      this.handleMessage(JSON.parse(event.data));
    });

    this.websocket.addEventListener('close', () => {
      console.log('Chat connection closed');
    });
  }

  // Join a chat room
  joinRoom(roomId: string, playerName: string): void {
    if (!this.websocket) return;

    this.currentRoom = roomId;
    this.websocket.send(JSON.stringify({
      type: 'join_room',
      roomId,
      playerId: this.playerId,
      playerName
    }));
  }

  // Send a message
  sendMessage(content: string): void {
    if (!this.websocket || !this.currentRoom) return;

    this.websocket.send(JSON.stringify({
      type: 'send_message',
      roomId: this.currentRoom,
      playerId: this.playerId,
      playerName: 'Player Name',
      content
    }));
  }

  // Handle incoming messages
  private handleMessage(message: any): void {
    switch (message.type) {
      case 'new_message':
        this.displayMessage(message.message);
        break;
      case 'user_joined':
        this.displaySystemMessage(`${message.playerName} joined`);
        break;
      case 'user_left':
        this.displaySystemMessage(`${message.playerName} left`);
        break;
    }
  }

  private displayMessage(message: ChatMessage): void {
    // Update UI with new message
    console.log(`${message.senderName}: ${message.content}`);
  }

  private displaySystemMessage(content: string): void {
    // Display system message
    console.log(`System: ${content}`);
  }
}

// Connect to RoomManager
class GameClient {
  private websocket: WebSocket | null = null;
  private playerId: string;
  private currentRoom: string | null = null;

  constructor(playerId: string) {
    this.playerId = playerId;
  }

  // Connect to room manager
  async connect(): Promise<void> {
    const response = await fetch('/api/room-manager/websocket');
    this.websocket = response.webSocket!;
    
    this.websocket.addEventListener('message', (event) => {
      this.handleMessage(JSON.parse(event.data));
    });
  }

  // Join a game room
  joinRoom(roomId: string, playerName: string): void {
    if (!this.websocket) return;

    this.currentRoom = roomId;
    this.websocket.send(JSON.stringify({
      type: 'join_room',
      roomId,
      playerId: this.playerId,
      playerName
    }));
  }

  // Start a game
  startGame(): void {
    if (!this.websocket || !this.currentRoom) return;

    this.websocket.send(JSON.stringify({
      type: 'start_game',
      roomId: this.currentRoom,
      playerId: this.playerId
    }));
  }

  // Update score
  updateScore(score: number): void {
    if (!this.websocket || !this.currentRoom) return;

    this.websocket.send(JSON.stringify({
      type: 'update_score',
      roomId: this.currentRoom,
      playerId: this.playerId,
      score
    }));
  }

  // Handle incoming messages
  private handleMessage(message: any): void {
    switch (message.type) {
      case 'game_started':
        this.onGameStarted(message);
        break;
      case 'score_updated':
        this.onScoreUpdated(message);
        break;
      case 'game_ended':
        this.onGameEnded(message);
        break;
    }
  }

  private onGameStarted(message: any): void {
    console.log('Game started!');
    // Initialize game UI
  }

  private onScoreUpdated(message: any): void {
    console.log(`Score updated: ${message.score}`);
    // Update score display
  }

  private onGameEnded(message: any): void {
    console.log('Game ended!', message.finalScores);
    // Show final results
  }
}

API Integration

// API endpoints for Durable Objects
export async function GET(request: Request, locals: any) {
  const url = new URL(request.url);
  const path = url.pathname;

  if (path.startsWith('/api/lobby/')) {
    const lobbyId = locals.runtime.env.CHAT_LOBBY.idFromName('main');
    const lobby = locals.runtime.env.CHAT_LOBBY.get(lobbyId);
    return lobby.fetch(request);
  }

  if (path.startsWith('/api/room-manager/')) {
    const roomManagerId = locals.runtime.env.ROOM_MANAGER.idFromName('main');
    const roomManager = locals.runtime.env.ROOM_MANAGER.get(roomManagerId);
    return roomManager.fetch(request);
  }

  return new Response('Not found', { status: 404 });
}

// Create chat room
export async function POST(request: Request, locals: any) {
  const url = new URL(request.url);
  
  if (url.pathname === '/api/lobby/rooms') {
    const body = await request.json();
    const { roomId, name, maxParticipants, isPrivate } = body;

    const lobbyId = locals.runtime.env.CHAT_LOBBY.idFromName('main');
    const lobby = locals.runtime.env.CHAT_LOBBY.get(lobbyId);
    
    const response = await lobby.fetch(new Request(request.url, {
      method: 'POST',
      body: JSON.stringify({ roomId, name, maxParticipants, isPrivate })
    }));

    return response;
  }

  return new Response('Not found', { status: 404 });
}

Testing

Durable Objects Testing

describe('Durable Objects', () => {
  let chatLobby: ChatLobby;
  let roomManager: RoomManager;

  beforeEach(() => {
    chatLobby = new ChatLobby(mockState, mockEnv);
    roomManager = new RoomManager(mockState, mockEnv);
  });

  it('should create chat room', async () => {
    const room = await chatLobby.createRoom({
      id: 'test-room',
      name: 'Test Room',
      maxParticipants: 10
    });

    expect(room.id).toBe('test-room');
    expect(room.name).toBe('Test Room');
    expect(room.maxParticipants).toBe(10);
  });

  it('should join chat room', async () => {
    await chatLobby.createRoom({
      id: 'test-room',
      name: 'Test Room'
    });

    const success = await chatLobby.joinRoom('test-room', 'player1', 'Player 1');
    expect(success).toBe(true);

    const room = await chatLobby.getRoomInfo('test-room');
    expect(room?.participants.has('player1')).toBe(true);
  });

  it('should create game room', async () => {
    const room = await roomManager.createRoom({
      id: 'game-room',
      name: 'Game Room',
      gameType: 'cognitive'
    });

    expect(room.id).toBe('game-room');
    expect(room.gameType).toBe('cognitive');
    expect(room.status).toBe('waiting');
  });

  it('should start game', async () => {
    await roomManager.createRoom({
      id: 'game-room',
      name: 'Game Room',
      gameType: 'cognitive'
    });

    await roomManager.joinRoom('game-room', 'player1', 'Player 1');
    await roomManager.joinRoom('game-room', 'player2', 'Player 2');

    const success = await roomManager.startGame('game-room', 'player1');
    expect(success).toBe(true);

    const room = await roomManager.getRoomInfo('game-room');
    expect(room?.status).toBe('playing');
  });
});

This comprehensive Durable Objects system provides robust real-time communication and game session management with persistent state across Cloudflare Worker restarts.

PadawanForge v1.4.1