Skip to content

refactor(server): share event stream encoding#36484

Open
kitlangton wants to merge 2 commits into
v2from
event-feed
Open

refactor(server): share event stream encoding#36484
kitlangton wants to merge 2 commits into
v2from
event-feed

Conversation

@kitlangton

@kitlangton kitlangton commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

What

Encode public server events once before delivering them to connected TUI and API clients.

Previously, every /api/event connection independently schema-encoded, JSON-serialized, and SSE-framed the same event. The new Server feed performs that work once and offers the same immutable frame string to each connection's independently bounded queue.

Disposable encoding benchmark for 1,000 representative 8 KiB public events, using one warmup and nine measured runs:

Clients Before median After median Change
1 9.488 ms 9.554 ms +0.7%
10 96.312 ms 10.352 ms -89.3%
50 553.928 ms 12.389 ms -97.8%

The benchmark isolated schema, JSON, and SSE encoding. It was intentionally not committed because it did not exercise sockets or the complete HTTP stack.

How

  • packages/server/src/event-feed.ts installs one global Core observer, filters public events once, encodes one complete SSE frame, and offers it to per-connection Queue.dropping queues.
  • Each queue retains the existing 4,096-event lag budget and fails independently on overflow.
  • The feed skips encoding when no HTTP subscribers are connected.
  • packages/server/src/handlers/event.ts keeps the raw HttpApi route, connection-specific server.connected, heartbeat, headers, and wire contract unchanged.
  • packages/core/src/event.ts removes the dead liveBounded helper after its only production caller moved to the Server feed.
  • specs/v2/event-stream-architecture.md records the ownership boundary, rejected PubSub design, lifecycle, capacity semantics, and benchmark results.

Scope

  • Protocol, OpenAPI, generated Promise/Effect clients, TUI consumption, and global cross-location delivery are unchanged.
  • Existing Core listeners and EventLogger behavior are unchanged.
  • This does not add replay, Last-Event-ID, byte-based capacity, or shared PubSub cursor eviction.
  • Capacity remains 4,096 pending real queue and stalled-socket measurements.
  • A local follow-up tracks a complete multi-client HTTP benchmark.

Testing

  • bun run test in packages/server: 5 passed, including exact SSE frame compatibility, encode-once fan-out, public filtering, independent overflow, and encoding failure isolation.
  • bun typecheck in packages/server: passed.
  • bun run test test/event.test.ts in packages/core: 51 passed.
  • bun typecheck in packages/core: passed.
  • bun run test test/embedded.test.ts in packages/sdk-next: 7 passed against the real embedded router and generated client.
  • Push hook full monorepo typecheck: 31 packages passed.
  • Targeted Oxlint: no new errors; existing warnings remain in packages/core/src/event.ts.
  • Two simplify/correctness passes completed; unrelated EventLogger cleanup was removed from scope.

Additional unrelated failures observed:

  • packages/client Promise tests expect the stale server.mcp generated group while the current client exposes mcp; event Effect and Promise stream tests pass.
  • Legacy packages/opencode HTTP SDK tests return existing 500s in unrelated session/prompt scenarios.

Flow

sequenceDiagram
  participant Core as EventV2
  participant Feed as Server EventFeed
  participant A as TUI A queue
  participant B as TUI B queue

  Core->>Feed: publish one public payload
  Feed->>Feed: schema + JSON + SSE encode once
  Feed->>A: offer shared frame reference
  Feed->>B: offer shared frame reference
  alt A is full
    Feed--xA: fail only A with overflow
  end
  B-->>B: continue streaming in order
Loading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant