MultiSubscription: multi-projection SSE with single loop, debounce, and ticker reset #130

Closed
opened 2026-03-01 10:48:15 +00:00 by ash · 0 comments
Owner

Problem

Dashboard pages need to watch multiple projections in one SSE connection. Currently requires manually managing N channels, N unsubs, and an N-way select. Every new projection adds boilerplate.

Solution

Add MultiSubscription to eskit:

multi := eskit.NewMultiSubscription(notifier,
    eskit.OnChange("orders.order-list", func() {
        orders, _ := reader.ListOrders(ctx)
        sse.MergeFragmentTempl(OrderTable(orders), datastar.WithSelectorID("order-table"))
    }),
    eskit.OnChange("orders.order-stats", func() {
        stats, _ := reader.GetStats(ctx)
        sse.MergeFragmentTempl(StatsCard(stats), datastar.WithSelectorID("stats-card"))
    }),
    eskit.OnEntityChange("alerts.alert-list", tenantID, func() {
        alerts, _ := reader.GetAlerts(ctx, tenantID)
        sse.MergeFragmentTempl(AlertPanel(alerts), datastar.WithSelectorID("alert-panel"))
    }),
    eskit.WithPollFallback(5*time.Second),
    eskit.WithDebounce(16*time.Millisecond),
)
defer multi.Close()
multi.RenderAll()
multi.Run(ctx)

Implementation

  • Fan-in all subscription channels into one merged channel with index tagging
  • RenderAll() fires every handler once (initial render)
  • Run(ctx) blocks, dispatches to correct handler on change
  • Fallback poll ticker resets after every real render (see 48cdaf6)
  • Debounce: collect changes for N ms, then render once per affected projection
  • Close() unsubs everything

API

  • OnChange(projection, handler) — subscribe to all changes for a projection
  • OnEntityChange(projection, streamID, handler) — watch specific entity
  • WithPollFallback(d) — periodic safety net (resets after real events)
  • WithDebounce(d) — collapse rapid changes into single render (default: 16ms / one frame)

Requirements

  • Tests: multi-projection dispatch, debounce coalescing, poll fallback with reset, Close cleanup
  • Benchmarks: overhead per notification with 1/5/10 projections
  • Godoc with full usage examples
  • Update docs/state-views.md and docs/subscriptions.md with MultiSubscription usage
## Problem Dashboard pages need to watch multiple projections in one SSE connection. Currently requires manually managing N channels, N unsubs, and an N-way select. Every new projection adds boilerplate. ## Solution Add `MultiSubscription` to eskit: ```go multi := eskit.NewMultiSubscription(notifier, eskit.OnChange("orders.order-list", func() { orders, _ := reader.ListOrders(ctx) sse.MergeFragmentTempl(OrderTable(orders), datastar.WithSelectorID("order-table")) }), eskit.OnChange("orders.order-stats", func() { stats, _ := reader.GetStats(ctx) sse.MergeFragmentTempl(StatsCard(stats), datastar.WithSelectorID("stats-card")) }), eskit.OnEntityChange("alerts.alert-list", tenantID, func() { alerts, _ := reader.GetAlerts(ctx, tenantID) sse.MergeFragmentTempl(AlertPanel(alerts), datastar.WithSelectorID("alert-panel")) }), eskit.WithPollFallback(5*time.Second), eskit.WithDebounce(16*time.Millisecond), ) defer multi.Close() multi.RenderAll() multi.Run(ctx) ``` ### Implementation - Fan-in all subscription channels into one merged channel with index tagging - `RenderAll()` fires every handler once (initial render) - `Run(ctx)` blocks, dispatches to correct handler on change - Fallback poll ticker resets after every real render (see 48cdaf6) - Debounce: collect changes for N ms, then render once per affected projection - `Close()` unsubs everything ### API - `OnChange(projection, handler)` — subscribe to all changes for a projection - `OnEntityChange(projection, streamID, handler)` — watch specific entity - `WithPollFallback(d)` — periodic safety net (resets after real events) - `WithDebounce(d)` — collapse rapid changes into single render (default: 16ms / one frame) ### Requirements - Tests: multi-projection dispatch, debounce coalescing, poll fallback with reset, Close cleanup - Benchmarks: overhead per notification with 1/5/10 projections - Godoc with full usage examples - Update docs/state-views.md and docs/subscriptions.md with MultiSubscription usage
ash closed this issue 2026-03-01 10:52:12 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ash/eskit#130
No description provided.