@omnitronix/game-engine-sdk
v2.7.0
Published
Shared NestJS infrastructure SDK for Omnitronix game engine services
Maintainers
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-sdkPeer dependencies (your service must provide these):
npm install @nestjs/common @nestjs/core @nestjs/config @nestjs/platform-express @omnitronix/game-engine-contract reflect-metadata rxjsOptional peer dependencies:
npm install @aws-sdk/client-secrets-manager # For AWS Secrets Manager
npm install @nestjs/swagger # For Swagger API docsQuick 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 servicesThe 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:
- Creates NestJS app with custom logger and CORS enabled
- Sets up global validation pipe (whitelist + transform)
- Configures global exception filter (
ExceptionsFilter) - Sets
/apiprefix (excludes/health,/ready,/metrics) - Registers Swagger docs at
/docs - Creates a separate HTTP/2 cleartext (h2c) server for Connect RPC on
GRPC_PORT - 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 (
numberinternally,stringon 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
maxPlayersPerRoundlimits - 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:
- Logs full error details server-side (including stack traces)
- Sends only an
InternalErrorCodeenum value to the client - 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,tokenredacted) DomainException-aware (includeserrorCodein 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_FAILEDRetry 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:
GameEngineService—ProcessCommand,GetGameEngineInfo,StreamGameState(server streaming),StreamPlayerActions(bidirectional),ReconstructState(unary)GameEngineRegistryService—RegisterGameEngineRPC
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 formattingMigration Guide: Adding Streaming (v2 → v3)
Breaking Changes in v3.0.0
- Proto schema expanded —
GameEngineServicenow includes 3 new RPCs. Existing unary-only engines are unaffected (streaming RPCs are only registered when the engine implementsStreamableGameEngine). - New error codes — 8 streaming-specific
InternalErrorCodevalues added.ErrorCodeMapperupdated with HTTP status mappings. getGameEngineInfo()return type — Streaming engines must returnStreamableGameEngineInfo(addscapabilitiesfield).
Step-by-Step: Adding Streaming to an Existing Engine
- Implement
StreamableGameEngineinstead ofGameEngine:
// 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 */ }
}- Register
GAME_ENGINEtoken (unchanged — auto-detection handles the rest):
{ provide: GAME_ENGINE, useClass: MyEngine }- No bootstrap changes needed —
createGameEngineApp()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
@omnitronix/game-engine-contract- Pure TypeScript interfaces (zero framework deps)@omnitronix/service-client- Service client for non-game services
License
PROPRIETARY
