// Package redisstate hosts the Runtime Manager Redis adapters that share // a single keyspace. Each sibling subpackage (e.g. `streamoffsets`) // implements one port and uses Keyspace to compose its keys, so the // Redis namespace stays under one document and one prefix. // // The package itself only declares the keyspace; concrete stores live in // nested packages so dependencies (testcontainers, miniredis) stay out // of consumer build graphs that do not need them. package redisstate import "encoding/base64" // defaultPrefix is the mandatory `rtmanager:` namespace prefix shared by // every Runtime Manager Redis key. const defaultPrefix = "rtmanager:" // Keyspace builds the Runtime Manager Redis keys. The namespace covers // the stream consumer offsets and the per-game lifecycle lease in v1. // // Dynamic key segments are encoded with base64url so raw key structure // does not depend on caller-provided characters; this matches the // encoding chosen by `lobby/internal/adapters/redisstate.Keyspace`. type Keyspace struct{} // StreamOffset returns the Redis key that stores the last successfully // processed entry id for one Redis Stream consumer. The streamLabel is // the short logical identifier of the consumer (e.g. `start_jobs`, // `stop_jobs`), not the full stream name; it stays stable when the // underlying stream key is renamed. func (Keyspace) StreamOffset(streamLabel string) string { return defaultPrefix + "stream_offsets:" + encodeKeyComponent(streamLabel) } // GameLease returns the Redis key that stores the per-game lifecycle // lease guarding start / stop / restart / patch / cleanup operations // against the same game. The gameID is base64url-encoded so callers can // pass any opaque identifier without escaping raw key characters. func (Keyspace) GameLease(gameID string) string { return defaultPrefix + "game_lease:" + encodeKeyComponent(gameID) } func encodeKeyComponent(value string) string { return base64.RawURLEncoding.EncodeToString([]byte(value)) }