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

@omnitronix/game-engine-sdk

v2.7.0

Published

Shared NestJS infrastructure SDK for Omnitronix game engine services

Readme

@omnitronix/game-engine-sdk

Shared NestJS infrastructure SDK for Omnitronix game engine services. Provides health checks, gRPC adapters, auto-registration, metrics, logging, secrets management, error handling, and application bootstrap — eliminating duplicated infrastructure code across game engine services.

Installation

npm install @omnitronix/game-engine-sdk

Peer dependencies (your service must provide these):

npm install @nestjs/common @nestjs/core @nestjs/config @nestjs/platform-express @omnitronix/game-engine-contract reflect-metadata rxjs

Optional peer dependencies:

npm install @aws-sdk/client-secrets-manager  # For AWS Secrets Manager
npm install @nestjs/swagger                   # For Swagger API docs

Quick Start

A minimal game engine service using the SDK:

main.ts

import { createGameEngineApp } from '@omnitronix/game-engine-sdk';
import { AppModule } from './app.module';

async function bootstrap() {
  await createGameEngineApp({
    serviceName: 'My Game Engine Service',
    appModule: AppModule,
    bootstrapOptions: { driver: 'database' },
  });
}

bootstrap();

app.module.ts

import { MiddlewareConsumer, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import {
  HealthModule,
  LoggerModule,
  LoggingMiddleware,
  MetricsModule,
  SecretsModule,
} from '@omnitronix/game-engine-sdk';
import { PrometheusModule } from '@willsoto/nestjs-prometheus';

import { MyGameEngineModule } from './modules/my-game-engine.module';

@Module({})
export class AppModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggingMiddleware).forRoutes('*');
  }

  static register() {
    return {
      module: AppModule,
      imports: [
        ConfigModule.forRoot({ isGlobal: true, envFilePath: '.env' }),
        SecretsModule.register(),
        LoggerModule,
        PrometheusModule.register({
          path: '/metrics',
          defaultMetrics: { enabled: true },
        }),
        HealthModule,
        MetricsModule,
        MyGameEngineModule,
      ],
    };
  }
}

my-game-engine.module.ts

import { Module } from '@nestjs/common';
import {
  GAME_ENGINE,
  GAME_ENGINE_IN_PORT,
  GAME_ENGINE_REGISTRY_OUT_PORT,
  GameEngineAutoRegisterService,
  GameEngineGrpcInAdapter,
  GameEngineRegistryGrpcOutAdapter,
  MetricsModule,
} from '@omnitronix/game-engine-sdk';
import { MyGameEngine } from '@omnitronix/my-game-engine';

import { MyGameEngineService } from './services/game-engine.service';

@Module({
  imports: [MetricsModule],
  providers: [
    GameEngineGrpcInAdapter,
    GameEngineAutoRegisterService,
    MyGameEngineService,
    {
      provide: GAME_ENGINE_IN_PORT,
      useExisting: MyGameEngineService,
    },
    {
      provide: GAME_ENGINE,
      useClass: MyGameEngine,
    },
    {
      provide: GAME_ENGINE_REGISTRY_OUT_PORT,
      useClass: GameEngineRegistryGrpcOutAdapter,
    },
  ],
  exports: [GameEngineGrpcInAdapter],
})
export class MyGameEngineModule {}

Package Hierarchy

@omnitronix/game-engine-contract   -- Pure TS interfaces, zero framework deps
         |
@omnitronix/game-engine-sdk       -- NestJS modules, gRPC adapters, infrastructure (this package)
         |
game-engine-service-*              -- Individual game engine services

The SDK depends on the contract. Game engine services depend on the SDK. Game engine packages (pure math/logic) are unaffected.

Sub-path Exports

The SDK exposes sub-path exports for selective imports:

| Import Path | Contents | |---|---| | @omnitronix/game-engine-sdk | Everything (recommended for services) | | @omnitronix/game-engine-sdk/common | Logger, error handling, secrets, retry, metrics, API docs | | @omnitronix/game-engine-sdk/health | Health check module, readiness probes, shutdown | | @omnitronix/game-engine-sdk/registration | Auto-register service, registry gRPC adapter | | @omnitronix/game-engine-sdk/grpc | gRPC in-adapter, Connect RPC router | | @omnitronix/game-engine-sdk/streaming | Streaming types, lifecycle service | | @omnitronix/game-engine-sdk/bootstrap | createGameEngineApp() factory | | @omnitronix/game-engine-sdk/testing | Dummy engines for integration testing |

Modules

Bootstrap

Factory function that creates a fully configured NestJS application.

import { createGameEngineApp } from '@omnitronix/game-engine-sdk';

await createGameEngineApp({
  serviceName: 'Happy Panda Game Engine Service',
  appModule: AppModule,
  bootstrapOptions: { driver: 'database' },
});

What it does:

  1. Creates NestJS app with custom logger and CORS enabled
  2. Sets up global validation pipe (whitelist + transform)
  3. Configures global exception filter (ExceptionsFilter)
  4. Sets /api prefix (excludes /health, /ready, /metrics)
  5. Registers Swagger docs at /docs
  6. Creates a separate HTTP/2 cleartext (h2c) server for Connect RPC on GRPC_PORT
  7. Handles graceful shutdown on uncaughtException / unhandledRejection

Environment variables:

| Variable | Default | Description | |---|---|---| | PORT | 3000 | HTTP server port | | GRPC_PORT | 50051 | gRPC (Connect RPC) server port |

Health Module

Provides /health and /ready HTTP endpoints with system health checks.

import { HealthModule } from '@omnitronix/game-engine-sdk';

@Module({
  imports: [HealthModule],
})

Endpoints:

| Endpoint | Purpose | Response | |---|---|---| | GET /health | Liveness probe | 200 if healthy, 500 if unhealthy | | GET /ready | Readiness probe | 200 if accepting traffic, 503 if draining |

Health checks:

  • Heap memory usage (configurable threshold)
  • System load average

Environment variables:

| Variable | Default | Description | |---|---|---| | HEALTH_MAX_HEAP_USAGE_PERCENTAGE | 98 | Max heap usage before unhealthy | | HEALTH_MAX_MEMORY_USAGE_PART | 10 | Max load average divisor |

Registration Module

Auto-registers the game engine with the RGS Game Engine Registry on startup.

import {
  GAME_ENGINE_REGISTRY_OUT_PORT,
  GameEngineAutoRegisterService,
  GameEngineRegistryGrpcOutAdapter,
} from '@omnitronix/game-engine-sdk';

@Module({
  providers: [
    GameEngineAutoRegisterService,
    {
      provide: GAME_ENGINE_REGISTRY_OUT_PORT,
      useClass: GameEngineRegistryGrpcOutAdapter,
    },
  ],
})

Registration happens asynchronously on module init (non-blocking). Uses exponential backoff retry (5 attempts, 1s initial delay, 2x multiplier, 30s max).

Environment variables:

| Variable | Required | Description | |---|---|---| | HOST_NAME | Yes | Hostname for the gRPC URL advertised to the registry | | GRPC_PORT | No (default 50051) | gRPC port advertised to the registry | | GAME_ENGINE_REGISTRY_GRPC_URL | Yes | URL of the Game Engine Registry service |

gRPC Module

Connect RPC adapter that exposes ProcessCommand and GetGameEngineInfo RPCs.

import {
  GAME_ENGINE_IN_PORT,
  GameEngineGrpcInAdapter,
} from '@omnitronix/game-engine-sdk';

@Module({
  providers: [
    GameEngineGrpcInAdapter,
    {
      provide: GAME_ENGINE_IN_PORT,
      useExisting: MyGameEngineService,
    },
  ],
  exports: [GameEngineGrpcInAdapter],
})

The gRPC adapter handles:

  • Deserializing protobuf requests into domain commands
  • Converting RNG seed types (number internally, string on wire)
  • Error handling with state preservation on failure

Streaming Module

Real-time streaming support for game engines that need server-push or bidirectional communication (crash games, multiplayer table games, live dealer).

Interaction Models

| Model | Use Case | RPCs | |---|---|---| | UNARY | Slots, instant games | ProcessCommand, GetGameEngineInfo | | SERVER_STREAM | Crash, dice | + StreamGameState, ReconstructState | | BIDIRECTIONAL | Multiplayer, table games | + StreamPlayerActions |

Implementing StreamableGameEngine

Extend your engine with three streaming methods:

import {
  StreamableGameEngine,
  StreamableGameEngineInfo,
  StreamRequest,
  GameStateUpdate,
  PlayerAction,
  ReconstructRequest,
  ReconstructResponse,
  UpdateType,
  InteractionModel,
} from '@omnitronix/game-engine-sdk';

export class CrashGameEngine implements StreamableGameEngine<CrashPublicState> {
  // Required: standard GameEngine methods
  processCommand(command) { /* ... */ }

  getGameEngineInfo(): StreamableGameEngineInfo {
    return {
      gameCode: 'crash-v1',
      version: '1.0.0',
      rtp: 97.0,
      gameType: 'crash',
      gameName: 'Crash',
      provider: 'my-studio',
      capabilities: {
        interactionModel: InteractionModel.SERVER_STREAM,
        supportedActions: ['PLACE_BET', 'CASHOUT'],
        maxPlayersPerRound: 100,
        maxStreamDurationSeconds: 300,
        requiresRoundLifecycle: true,
      },
    };
  }

  // Server-push: yield multiplier ticks until crash
  async *streamGameState(request: StreamRequest): AsyncGenerator<GameStateUpdate<CrashPublicState>> {
    let multiplier = 1.0;
    let sequenceId = 0;

    while (multiplier < this.crashPoint) {
      multiplier *= 1.01;
      yield {
        roundId: request.roundId,
        type: UpdateType.STATE_TICK,
        publicState: { multiplier },
        timestampMs: Date.now(),
        sequenceId: ++sequenceId,
      };
      await sleep(100); // 10 ticks/sec
    }

    yield {
      roundId: request.roundId,
      type: UpdateType.ROUND_END,
      publicState: { multiplier, crashed: true },
      timestampMs: Date.now(),
      sequenceId: ++sequenceId,
    };
  }

  // Handle player actions mid-stream
  async handlePlayerAction(action: PlayerAction): Promise<GameStateUpdate<CrashPublicState>[]> {
    if (action.actionType === 'CASHOUT') {
      return [{
        roundId: action.roundId,
        type: UpdateType.PLAYER_EVENT,
        publicState: { cashedOut: action.playerId, multiplier: this.currentMultiplier },
        timestampMs: Date.now(),
        sequenceId: this.nextSequenceId(),
      }];
    }
    return [];
  }

  // Session recovery after disconnect
  async reconstructState(request: ReconstructRequest): Promise<ReconstructResponse<CrashPublicState>> {
    const missed = this.getUpdatesSince(request.lastKnownSequenceId);
    return {
      roundId: request.roundId,
      missedUpdates: missed,
      roundStillActive: !this.roundEnded,
    };
  }
}

Auto-Detection

Streaming routes are auto-registered when the engine implements StreamableGameEngine. No configuration needed:

// In your module, provide GAME_ENGINE as usual
{
  provide: GAME_ENGINE,
  useClass: CrashGameEngine,
}

The bootstrap factory calls isStreamableEngine() to detect streaming support and registers the appropriate RPCs. To override auto-detection:

await createGameEngineApp({
  serviceName: 'My Service',
  appModule: AppModule,
  // Force streaming on/off regardless of engine type
  // enableStreaming: true,
});

Capability Advertisement

Capabilities declared in getGameEngineInfo() are sent to the RGS during registration. The RGS uses them to:

  • Route player connections to the correct protocol (unary vs streaming)
  • Enforce maxPlayersPerRound limits
  • Set stream timeout based on maxStreamDurationSeconds
  • Validate player actions against supportedActions

Stream Lifecycle

The SDK manages stream lifecycle automatically via StreamLifecycleService:

  • Keepalive pings — Prevents load balancer timeouts (default: every 15s)
  • Max duration — Closes streams exceeding the limit (default: 5 min)
  • Backpressure — Tracks buffer size, signals when consumer is too slow
  • Sequence tracking — Monitors monotonic sequence IDs
  • Graceful close — On ROUND_END, waits a grace period before cleanup

Error Handling (OWASP A01)

All streaming errors are sanitized before reaching clients. The adapter:

  1. Logs full error details server-side (including stack traces)
  2. Sends only an InternalErrorCode enum value to the client
  3. Validates all input at the gRPC boundary (field presence, length limits)

Streaming-specific error codes:

| Code | HTTP | Description | |---|---|---| | STREAM_ALREADY_ACTIVE | 409 | Duplicate stream for same round+session | | STREAM_NOT_FOUND | 404 | Referenced stream doesn't exist | | STREAM_ROUND_NOT_ACTIVE | 400 | Round not in progress | | STREAM_SESSION_INVALID | 400 | Session mismatch | | STREAM_MAX_DURATION_EXCEEDED | 400 | Stream exceeded time limit | | STREAM_BACKPRESSURE | 503 | Consumer too slow | | STREAM_ENGINE_ERROR | 500 | Engine threw during streaming | | STREAM_SEQUENCE_GAP | 500 | Non-monotonic sequence IDs |

Game Handler Abstractions

Abstract base classes for each game type. Extend these in your game engine to get type-safe contracts and SDK integration.

SlotGameHandler

For spin-based games with optional bonus rounds.

import { SlotGameHandler, BetConfig, SlotState, SpinResult, BonusTrigger, BonusResult } from '@omnitronix/game-engine-sdk';

class MySlotEngine extends SlotGameHandler {
  async spin(bet: BetConfig, state: SlotState): Promise<SpinResult> { /* ... */ }
  async startBonus(trigger: BonusTrigger, state: SlotState): Promise<BonusResult> { /* ... */ }
  async bonusSpin?(state: SlotState): Promise<SpinResult> { /* optional */ }
}

CrashGameHandler

For multiplier-based crash games where players cash out before the round crashes.

import { CrashGameHandler, CrashRoundConfig, CrashBetResult, CashOutResult } from '@omnitronix/game-engine-sdk';

class MyCrashEngine extends CrashGameHandler {
  async initRound(seed: string): Promise<CrashRoundConfig> { /* ... */ }
  async placeBet(player: string, amount: number): Promise<CrashBetResult> { /* ... */ }
  async cashOut(player: string, multiplier: number): Promise<CashOutResult> { /* ... */ }
  computeMultiplier(elapsed: number): number { /* ... */ }
  getCrashPoint(seed: string): number { /* ... */ }
}

Error types: RoundAlreadyCrashedError (409), CashOutTooLateError (409), InvalidCrashSeedError (400), DuplicateBetError (409)

TableGameHandler

For card/table games with deal-action-resolve flow (blackjack, poker, baccarat).

import { TableGameHandler, TableAction, DealResult, PlayerAction, ActionResult, GameResult } from '@omnitronix/game-engine-sdk';

class MyBlackjackEngine extends TableGameHandler {
  async deal(bet: BetConfig): Promise<DealResult> { /* ... */ }
  async playerAction(action: PlayerAction): Promise<ActionResult> { /* ... */ }
  async resolve(): Promise<GameResult> { /* ... */ }
  async splitHand?(handId: string, state: unknown): Promise<MultiHandDealResult> { /* optional */ }
}

Actions: HIT, STAND, DOUBLE, SPLIT, SURRENDER, INSURANCE

Error types: InvalidActionError (400), HandBustedError (409), HandAlreadyStoodError (409), MaxHandsExceededError (400)

DiceGameHandler

For provably fair dice/hash games with seed-based verification.

import { DiceGameHandler, DiceResult, SeedPair } from '@omnitronix/game-engine-sdk';
import { validateSeed, combineSeeds, computeResult, verifyResult } from '@omnitronix/game-engine-sdk';

class MyDiceEngine extends DiceGameHandler {
  async roll(bet: BetConfig, clientSeed?: string): Promise<DiceResult> { /* ... */ }
  verifyRoll(result: DiceResult, seeds: SeedPair): boolean { /* ... */ }
}

Provably fair utilities: validateSeed(), sha256(), combineSeeds(), computeResult(), generateServerSeedHash(), verifyServerSeedHash(), verifyResult(), generateHashChain(), verifyHashChainLink()

Bet types: specific (exact numbers), range (min-max), over (threshold), under (threshold) — evaluated via evaluateDiceBet()

Error types: InvalidSeedError (400), VerificationFailedError (422), InvalidBetTypeError (400)

Test Utilities

Reusable dummy implementations for integration testing in downstream services. Import from @omnitronix/game-engine-sdk/testing:

import {
  DummySlotEngine,
  DummyCrashEngine,
  DummyBlackjackEngine,
  DummyDiceEngine,
} from '@omnitronix/game-engine-sdk/testing';

| Dummy Engine | Extends | Features | |---|---|---| | DummySlotEngine | SlotGameHandler | Configurable win/loss, deterministic results, bonus rounds | | DummyCrashEngine | CrashGameHandler | SHA-256 crash point, bet tracking, proper error throwing | | DummyBlackjackEngine | TableGameHandler | Real card values, dealer AI (draw to 17), split/double/surrender | | DummyDiceEngine | DiceGameHandler | Provably fair chain, nonce tracking, bet evaluation |

Example: Testing a game engine service with DummyCrashEngine:

const engine = new DummyCrashEngine();
const round = await engine.initRound('test-seed-123');

await engine.placeBet('player-1', 100);
const result = await engine.cashOut('player-1', 1.5);
expect(result.cashedOut).toBe(true);
expect(result.winAmount).toBe(150);

Logger Module

Structured Winston-based logger implementing NestJS LoggerService.

import { Logger, LoggerModule, LoggingMiddleware } from '@omnitronix/game-engine-sdk';

// As a module
@Module({ imports: [LoggerModule] })

// Direct usage
const logger = new Logger('MyService');
logger.log('Processing command', { commandId: '123' });
logger.error('Failed to process', error);

Features:

  • JSON or pretty-print output (configurable)
  • HTTP request/response logging middleware
  • Sensitive field sanitization (password, token redacted)
  • DomainException-aware (includes errorCode in logs)

Environment variables:

| Variable | Default | Description | |---|---|---| | LOG_FORMAT | json | Log format: json or pretty |

Secrets Module

Dynamic module for secrets management with local and AWS providers.

import { SecretsModule, SECRETS_PROVIDER, SecretsProvider } from '@omnitronix/game-engine-sdk';

// In module
@Module({ imports: [SecretsModule.register()] })

// In service
@Injectable()
class MyService {
  constructor(@Inject(SECRETS_PROVIDER) private secrets: SecretsProvider) {}

  async getApiKey() {
    return this.secrets.get('API_KEY');
  }
}

Environment variables:

| Variable | Default | Description | |---|---|---| | LOCAL_SECRETS | false | true to load from .secrets.env file | | AWS_SECRET_ID | - | AWS Secrets Manager secret ID | | AWS_REGION | eu-central-1 | AWS region |

Metrics Module

Prometheus metrics collection using prom-client and @willsoto/nestjs-prometheus.

import { MetricsModule, METRICS_PORT, MetricsPort } from '@omnitronix/game-engine-sdk';

@Module({ imports: [MetricsModule] })

// In a service
@Injectable()
class MyMetricsHandler {
  constructor(@Inject(METRICS_PORT) private metrics: MetricsPort) {}

  recordCommand(duration: number) {
    this.metrics.getHistogram('game_engine_command_duration_ms').observe(duration);
    this.metrics.getCounter('game_engine_commands_total').inc();
  }
}

Pre-configured metrics: HTTP request duration, WebSocket connections, session tracking, wallet operations, game engine commands, RNG buffer stats, database connection pool, event store operations, and more (40+ metrics).

Error Handling

Global exception filter and domain exception hierarchy.

import {
  DomainException,
  InternalErrorCode,
  ExceptionsFilter,
} from '@omnitronix/game-engine-sdk';

// Throw domain exceptions
throw new DomainException(
  InternalErrorCode.GAME_NOT_FOUND,
  'Game engine not registered',
);

// The ExceptionsFilter (auto-configured by createGameEngineApp) maps
// InternalErrorCode to HTTP status codes:
//   400 - BAD_REQUEST, INVALID_*
//   401 - UNAUTHORIZED
//   403 - FORBIDDEN
//   404 - *_NOT_FOUND
//   409 - CONCURRENT_MODIFICATION
//   422 - INSUFFICIENT_FUNDS
//   503 - SERVICE_UNAVAILABLE, HEALTH_CHECK_FAILED

Retry Policies

Configurable retry with exponential backoff.

import { RetryPolicy, RetryPolicies } from '@omnitronix/game-engine-sdk';

// Pre-built policies
const result = await RetryPolicies.externalApi().execute(() =>
  fetch('https://api.example.com/data'),
);

// Custom policy
const policy = new RetryPolicy({
  maxAttempts: 5,
  delayMs: 200,
  backoffMultiplier: 2,
  maxDelayMs: 5000,
  retryableErrors: ['ECONNREFUSED', 'TIMEOUT'],
  operationName: 'fetchData',
});

await policy.execute(() => riskyOperation());

Built-in policies:

| Policy | Attempts | Initial Delay | Backoff | Max Delay | |---|---|---|---|---| | walletOperation() | 5 | 200ms | 2x | 3s | | externalApi() | 3 | 100ms | 2x | 1s | | database() | 3 | 50ms | 2x | 500ms |

Core Types

| Type | Description | |---|---| | GameEngine<P, S, O> | Main engine interface (re-exported from contract) | | GameEngineInfo | Engine metadata (code, version, RTP, type, name, provider) | | GameActionCommand<T> | Command sent to the engine | | CommandProcessingResult<P, S, O> | Result of processing a command | | RngOutcome | Collection of RNG call records | | GameEngineInPort | Port interface for the engine service layer | | StreamableGameEngine<P, S> | Streaming engine interface (extends GameEngine) | | StreamableGameEngineInfo | Engine info with capabilities declaration | | GameCapabilities | Interaction model, supported actions, limits | | StreamRequest | Request to start a game state stream | | GameStateUpdate<P> | Single state update (tick, event, round end, error) | | PlayerAction | Player action during active stream | | ReconstructRequest | Session recovery request | | ReconstructResponse<P> | Missed updates for recovery | | InteractionModel | Enum: UNARY, SERVER_STREAM, BIDIRECTIONAL | | UpdateType | Enum: STATE_TICK, PLAYER_EVENT, ROUND_END, ERROR |

Dependency Injection Tokens

| Token | Type | Description | |---|---|---| | GAME_ENGINE | GameEngine | The game engine implementation | | GAME_ENGINE_IN_PORT | GameEngineInPort | Engine service (business logic layer) | | GAME_ENGINE_REGISTRY_OUT_PORT | GameEngineRegistryOutPort | Registry client adapter | | HEALTH_IN_PORT | HealthInPort | Health check service | | METRICS_PORT | MetricsPort | Prometheus metrics service | | SECRETS_PROVIDER | SecretsProvider | Secrets management provider | | SYSTEM_HEALTH_OUT_PORT | SystemHealthOutPort | System health adapter | | SHUTDOWN_OUT_PORT | ShutdownOutPort | Graceful shutdown handler |

Environment Variables

Complete reference of all environment variables used by the SDK:

| Variable | Default | Module | Description | |---|---|---|---| | PORT | 3000 | Bootstrap | HTTP server port | | GRPC_PORT | 50051 | Bootstrap / Registration | gRPC server port | | HOST_NAME | - | Registration | Hostname for registry registration | | GAME_ENGINE_REGISTRY_GRPC_URL | - | Registration | Registry service URL | | LOG_FORMAT | json | Logger | json or pretty | | LOCAL_SECRETS | false | Secrets | Use local .secrets.env file | | AWS_SECRET_ID | - | Secrets | AWS Secrets Manager secret ID | | AWS_REGION | eu-central-1 | Secrets | AWS region | | HEALTH_MAX_HEAP_USAGE_PERCENTAGE | 98 | Health | Max heap usage threshold | | HEALTH_MAX_MEMORY_USAGE_PART | 10 | Health | Max load average divisor |

Proto Definitions

The SDK includes proto files and generated TypeScript code for:

  • GameEngineServiceProcessCommand, GetGameEngineInfo, StreamGameState (server streaming), StreamPlayerActions (bidirectional), ReconstructState (unary)
  • GameEngineRegistryServiceRegisterGameEngine RPC

Proto generation is handled at SDK build time. Consumers do not need to run protoc.

Development

npm install          # Install dependencies
npm run build        # Build CJS + ESM
npm run test         # Run tests
npm run lint         # Lint with auto-fix
npm run format:check # Check formatting

Migration Guide: Adding Streaming (v2 → v3)

Breaking Changes in v3.0.0

  • Proto schema expandedGameEngineService now includes 3 new RPCs. Existing unary-only engines are unaffected (streaming RPCs are only registered when the engine implements StreamableGameEngine).
  • New error codes — 8 streaming-specific InternalErrorCode values added. ErrorCodeMapper updated with HTTP status mappings.
  • getGameEngineInfo() return type — Streaming engines must return StreamableGameEngineInfo (adds capabilities field).

Step-by-Step: Adding Streaming to an Existing Engine

  1. Implement StreamableGameEngine instead of GameEngine:
// Before (v2):
export class MyEngine implements GameEngine<MyPublicState, MyPrivateState, MyOutcome> {
  processCommand(command) { /* ... */ }
  getGameEngineInfo(): GameEngineInfo { /* ... */ }
}

// After (v3):
export class MyEngine implements StreamableGameEngine<MyPublicState, MyPrivateState> {
  processCommand(command) { /* ... */ }
  getGameEngineInfo(): StreamableGameEngineInfo {
    return {
      ...baseInfo,
      capabilities: {
        interactionModel: InteractionModel.SERVER_STREAM,
        supportedActions: ['PLACE_BET', 'CASHOUT'],
        maxPlayersPerRound: 100,
        maxStreamDurationSeconds: 300,
        requiresRoundLifecycle: true,
      },
    };
  }
  async *streamGameState(request) { /* yield updates */ }
  async handlePlayerAction(action) { /* return updates */ }
  async reconstructState(request) { /* return missed updates */ }
}
  1. Register GAME_ENGINE token (unchanged — auto-detection handles the rest):
{ provide: GAME_ENGINE, useClass: MyEngine }
  1. No bootstrap changes neededcreateGameEngineApp() auto-detects streaming support.

Keeping Unary-Only

Engines that don't implement StreamableGameEngine continue to work without changes. The SDK only registers streaming RPCs when the engine passes the isStreamableEngine() type guard.

Related Packages

License

PROPRIETARY