- Valkey client that fails commands at once while Valkey is down instead
of queueing them, and logs connection errors once
- ValkeyLiveGameStore: live game snapshots and finished games waiting to
be saved, loaded with SCAN on startup
- ValkeyCache: JSON values with a TTL; errors count as cache misses
- Integration tests against a real Valkey, including an unreachable one
- Connection pool and a small migration runner (schema_migrations, one
transaction per migration, advisory lock)
- games and game_players tables with indexes for a user's games and for
popular games
- PostgresGameHistoryRepository: idempotent save, get, recent games of a
user, popular games
- Integration tests against a real Postgres via testcontainers; a custom
Jest environment gives tests Node's web globals, which Jest 27 leaves out
- GameRecord carries the number of distinct spectators
- Apps and @mancala/core depend on the packages in this repo through
link: dependencies instead of the npm releases, so changes no longer
need a publish
- Root package.json gets a build:packages script that installs and
builds both packages
- Backend and frontend Docker images are built from the repo root so
they can include the packages; compose builds them that way
- Metro watches the packages folder and resolves their imports from the
app's node_modules
- New auth module verifies Keycloak access tokens offline against the
realm's signing keys (issuer, RS256, typ=Bearer, allowed azp); guests
are recognised by the guest realm role
- Clients send an auth message after connecting and get on_auth or
on_auth_error; unauthenticated sockets are closed after 5 seconds and
their messages ignored; a socket can refresh its token but not switch user
- A user can be connected from several sockets; a late close of an old
socket no longer drops the new one
- Malformed messages are ignored instead of throwing
- Games store player names and guest flags and send them with the
connection info
- Legacy ?userKey= clients and /register stay available behind
LEGACY_USERKEY_AUTH; legacy keys must be UUIDs so they cannot take a
Keycloak user's kc: id
- TypeScript target raised to es2020 for async/await