v0.57.0 — push updated state to clients over SSE (#129) #131

Merged
kamilkrzywda merged 1 commits from feat/sse-state-push into master 2026-09-22 22:55:42 +02:00
Owner

Closes #129

Clients no longer rely on pure polling for live updates: new GET /api/events streams full game-state snapshots as Server-Sent Events — the current snapshot immediately on connect, then one after every sim tick (~1 s), so the UI updates push-driven even while idle. Polling remains a fully functional automatic fallback (5 s silence watchdog + stream-error trigger) and /api/state behavior is unchanged.

  • lib/sse.ts — tiny in-process fan-out registry (subscribe/broadcast, per-subscriber try/catch).
  • app/api/events/route.ts — SSE endpoint: snapshot-first-then-subscribe ordering, 15 s : ping heartbeat, X-Accel-Buffering: no, cleanup on cancel; broadcast pushes wrapped in data: <json>\n\n frames.
  • lib/sim/engine.ts — broadcast hook in tickNow() after each locked tick, serializing via the same getStateSnapshot() as /api/state; failures logged, never fatal.
  • Process-wide singletons on globalThis (SSE subscriber set, sql.js DB promise, tick lock) because Next.js gives instrumentation.ts and route handlers separate module instances — without this, background-tick pushes would never reach route-handler subscribers.
  • lib/useGameState.ts — EventSource primary with polling fallback; native reconnect, no custom logic.
  • OpenAPI spec gains GET /api/events; exact-endpoint-list assertions move twelve → thirteen (lib/openapi.test.ts, e2e/api.spec.ts).
  • Tests: unit lib/sse.test.ts (3) + app/api/events/route.test.ts (1); e2e e2e/events.spec.ts (+1) — node-side fetch asserts content-type, first event parses as a snapshot, and ≥1 more event arrives with an advanced clock.
Closes #129 Clients no longer rely on pure polling for live updates: new `GET /api/events` streams full game-state snapshots as Server-Sent Events — the current snapshot immediately on connect, then one after every sim tick (~1 s), so the UI updates push-driven even while idle. Polling remains a fully functional automatic fallback (5 s silence watchdog + stream-error trigger) and `/api/state` behavior is unchanged. - `lib/sse.ts` — tiny in-process fan-out registry (`subscribe`/`broadcast`, per-subscriber try/catch). - `app/api/events/route.ts` — SSE endpoint: snapshot-first-then-subscribe ordering, 15 s `: ping` heartbeat, `X-Accel-Buffering: no`, cleanup on cancel; broadcast pushes wrapped in `data: <json>\n\n` frames. - `lib/sim/engine.ts` — broadcast hook in `tickNow()` after each locked tick, serializing via the same `getStateSnapshot()` as `/api/state`; failures logged, never fatal. - Process-wide singletons on `globalThis` (SSE subscriber set, sql.js DB promise, tick lock) because Next.js gives instrumentation.ts and route handlers separate module instances — without this, background-tick pushes would never reach route-handler subscribers. - `lib/useGameState.ts` — EventSource primary with polling fallback; native reconnect, no custom logic. - OpenAPI spec gains `GET /api/events`; exact-endpoint-list assertions move twelve → thirteen (`lib/openapi.test.ts`, `e2e/api.spec.ts`). - Tests: unit `lib/sse.test.ts` (3) + `app/api/events/route.test.ts` (1); e2e `e2e/events.spec.ts` (+1) — node-side fetch asserts content-type, first event parses as a snapshot, and ≥1 more event arrives with an advanced clock.
kamilkrzywda added 1 commit 2026-09-22 22:44:31 +02:00
push updated state to clients over SSE (ISS-129)
CI / build + unit + e2e (pull_request) Successful in 11m7s
1983d17099
kamilkrzywda scheduled this pull request to auto merge when all checks succeed 2026-09-22 22:44:38 +02:00
kamilkrzywda merged commit 18e5c832e3 into master 2026-09-22 22:55:42 +02:00
Sign in to join this conversation.