Document SSE event bus #140

Open
opened 2026-07-18 16:07:59 +00:00 by eliribble · 3 comments
Owner

Within nidus-sync we have a system where logic in the platform layer can emit events. These events have a type and a URI. Web browsers connect over SSE to the event bus and get this event stream. They then make queries against the URI to update the pinia stores which then propagate updates automatically to UI elements.

This is, at least, the architecture. Some stores and resources use it, some don't.

I want to clarify the documentation and design of this system, and create some meaningful standards about how its used to ensure that all new projects are using it consistently. I'd like you to audit the system as it currently stands, identify which components are essential to how the system works, and identify different patterns currently in use. I'd also like you to find places where the UI is not consistent in supporting updates through the pinia stores.

Please write the whole thing up in a response to this ticket. Based on what you find, I'll follow up with requests for documentation to be added to the source code.

Within nidus-sync we have a system where logic in the platform layer can emit events. These events have a type and a URI. Web browsers connect over SSE to the event bus and get this event stream. They then make queries against the URI to update the pinia stores which then propagate updates automatically to UI elements. This is, at least, the architecture. Some stores and resources use it, some don't. I want to clarify the documentation and design of this system, and create some meaningful standards about how its used to ensure that all new projects are using it consistently. I'd like you to audit the system as it currently stands, identify which components are essential to how the system works, and identify different patterns currently in use. I'd also like you to find places where the UI is not consistent in supporting updates through the pinia stores. Please write the whole thing up in a response to this ticket. Based on what you find, I'll follow up with requests for documentation to be added to the source code.
Member

Audit of the SSE Event Bus System in nidus-sync

Hi diddly ho, neighborino! Here’s the full audit you asked for.


1. Architecture Overview

The SSE event bus has three layers:

Layer 1: Go Event Bus (platform/event/event.go)

A singleton channel-based event bus. Code in the platform layer emits events by calling:

  • event.Created(resourceType, orgID, uriID)
  • event.Updated(resourceType, orgID, uriID)
  • event.UpdatedUser(resourceType, userID, uriID)
  • event.Send(envelope) (raw)

Each event is wrapped in an Envelope containing an Event (resource, time, type, URI) plus an OrganizationID and/or UserID for access filtering. The Send function uses a non-blocking select (drops on full channel).

Layer 2: SSE Handler (api/event.go)

GET /api/events (route in api/routes.go:123) calls streamEvents(). This:

  1. Creates a ConnectionSSE struct per client, keyed by (organizationID, userID)
  2. Sends an initial {status:"connected"} event
  3. Loops on a 5-second heartbeat ticker and the connection’s event channel
  4. Passes events through SetEventChannel() which fans out to all matching connections (filtered by orgID or userID)
  5. Detects client disconnect via r.Context().Done()

Events are serialized as {"resource":"...","time":"...","type":"created|updated|...","uri":"..."}.

Layer 3: SSEManager (ts/SSEManager.ts)

A singleton module that wraps EventSource (native browser SSE). Features:

  • Connection lifecycle (connect/disconnect/reconnect with 5s delay)
  • Heartbeat monitor (30s timeout triggers reconnection)
  • Two subscriber maps: subscribersResource and subscribersStatus
  • Routes "heartbeat" events to heartbeat reset
  • Routes "status" events to status subscribers
  • Routes everything else to resource subscribers

Layer 4: Pinia Stores (frontend)

Stores subscribe to the SSEManager and react to events by refetching data.


2. Components Essential to the System

Component File Essential? Notes
platform/event.Event struct platform/event/event.go Core data model
platform/event.Envelope struct platform/event/event.go Carries org/user routing
platform/event.EventType enum platform/event/event.go Created/Updated/Deleted/Heartbeat/Shutdown
platform/event.ResourceType enum platform/event/event.go Typed resource identifiers
platform/event.Send() platform/event/event.go Non-blocking publish to channel
api/event.go SSE handler api/event.go Server-sent events HTTP endpoint
api/event.go SetEventChannel() api/event.go Fans out envelopes to SSE connections
ts/SSEManager.ts ts/SSEManager.ts Browser-side SSE connection manager
Pinia store SSE subscriptions various ts/store/ files Drive UI updates from events

Secondary:

  • platform/event.Created() / Updated() / UpdatedUser() — convenience wrappers, not strictly essential (callers could use Send() directly)
  • api/event.go heartbeat ticker — nice-to-have keepalive, could be tuned
  • platform/event.Shutdown() — not used anywhere in active code
  • platform.SudoEvent() — used only in api/sudo.go for the admin panel

The EventTypeDeleted constant is defined but never emitted anywhere.
The EventTypeSudo constant is used but only via platform.SudoEvent().


3. Patterns Currently in Use

Pattern A: The “Generic” Pattern (newer, preferred)

File: ts/store/resource.tscreateResourceStore() factory

SSEManager.subscribe((msg) => {
    if (msg.resource.startsWith(resource_name)) {
        if (msg.type == "created" || msg.type == "updated") {
            fetchByURI(msg.uri);  // fetches only the changed item
        }
    }
});
  • Reacts to both created and updated events
  • Fetches only the specific URI from the event — efficient, targeted
  • Manages a byURI reactive map for caching
  • Used for: communication, contact, publicreport (sync stores)

Pattern B: The “Refetch All” Pattern (most common)

Files: review-task.ts, site.ts, signal.ts, sync.ts, service_request.ts, upload.ts, user.ts

SSEManager.subscribe((msg) => {
    if (msg.resource.startsWith("sync:xxx")) {
        fetchAll();  // re-fetches the entire list
    }
});
  • Only checks resource prefix; doesn’t distinguish created vs updated
  • Re-fetches the entire list on every event — wasteful for large datasets
  • Breaks EventTypeDeleted handling (can’t delete from cache)

Pattern C: The Direct Subscription (components)

Files: Sidebar.vue, Authenticated.vue, AppSync.vue

SSEManager.subscribe((msg) => {
    if (msg.resource == "sync:session") {
        session.fetchSession();
    }
});

Components subscribing directly instead of going through a store. The Sidebar does this to detect impersonation changes.

Pattern D: The Status Subscriber

File: Authenticated.vue

SSEManager.subscribeStatus((msg) => {
    if (msg.status == "connected") { ... }
    else if (msg.status == "shutdown") { SSEManager.reconnect(5); }
});

Handles server restart and revision-change detection.

Pattern E: RMO frontend — No SSE at All

The RMO (ts/rmo/) frontend has zero SSE integration. Its pinia stores (publicreport.ts, district.ts, address-or-report-suggestion.ts) fetch data directly via API calls and never subscribe to the SSEManager. This means:

  • Staff viewing public reports through the RMO interface don’t see live updates
  • The RMO compliance/publicreport/water/nuisance pages won’t reflect changes until the user manually refreshes

4. Inconsistencies and Issues

🔴 Bug: session.ts checks the wrong field

File: ts/store/session.ts:41

if (msg.type == "sync:session") {  // BUG: should be msg.resource

The type field contains the event type ("created", "updated", etc.), not the resource name. The resource name is in msg.resource. This comparison will never match, so session changes (impersonation start/end) never trigger a session refetch via the store. The Sidebar component has its own separate subscription that checks msg.resource correctly, which is why this hasn’t been noticed in practice.

Fix: Change to msg.resource.startsWith("sync:session").

🟡 Missing Backend Emission (orphan subscriptions)

These frontend stores subscribe to resource names that no backend code ever emits:

Store Subscribes to Backend emits?
user.ts sync:user No TypeUser exists
sync.ts sync:sync No TypeSync exists
upload.ts sync:upload No TypeUpload exists
service_request.ts sync:service-request No TypeServiceRequest exists
resource.ts (contact) sync:contact No TypeContact exists
resource.ts (publicreport) sync:publicreport Backend emits rmo:publicreport (different prefix!)

These subscriptions will never fire, which means these stores only update when fetchAll() is called explicitly or when the page loads fresh.

🟡 Missing Frontend Listeners (orphan emissions)

These backend event types are emitted but have no frontend subscriber:

Resource string Emitted from Listener?
sync:filecsv platform/csv/csv.go
sync:note:audio platform/note.go
sync:note:image platform/note.go
rmo:publicreport platform/publicreport.go, platform/publicreport_notification.go, platform/text/ (sync store listens for sync:publicreport — wrong prefix)
rmo:publicreport.compliance platform/event/event.go (defined but never emitted by name)
rmo:publicreport.nuisance (defined)
rmo:publicreport.water (defined)
sync:review-task platform/review.go
sync:signal platform/signal.go, platform/review.go
sync:site platform/compliance.go

Note: The rmo:publicreport resource string is emitted for updates to public reports (complaints, notification changes, text message updates), but the sync frontend’s resource store listens for sync:publicreport — a different string. So public report updates from backend processing (text message replies, notification creation) are emitted but never reach the UI.

🟡 No EventTypeDeleted Usage

EventTypeDeleted is defined in platform/event/event.go but no backend code ever emits it. The frontend also doesn’t handle "deleted" events — most stores only check for created/updated. If a resource is deleted, the frontend cache keeps a stale copy. Only review-task.ts has a remove() method, but it’s not triggered by SSE events.

🟡 Inefficient Bulk Refetch Pattern

The majority of stores (Pattern B above) re-fetch the entire list on any event. For stores like site or review-task that may have hundreds of items, this is wasteful. The generic pattern in resource.ts (Pattern A) fetches only the specific URI and is much more efficient.

🟡 Subscription Leak Risk

Each call to SSEManager.subscribe() adds a permanent handler. The unsubscribe() method exists but is never called anywhere in the codebase. Since Pinia stores are singletons (only instantiated once), this isn’t a leak in practice. But the Sidebar component’s subscription (Sidebar.vue:309) stores the return value of subscribe() in a local variable sub that is never used — if the Sidebar were to be unmounted/re-mounted, the old subscription would persist.

🟡 RMO Frontend Has No SSE at All

The RMO (ts/rmo/) SPA does not connect to SSE (/api/events) and does not import SSEManager. The RMO stores (publicreport.ts, district.ts, suggestions.ts) work entirely by explicit fetch. This means:

  • When a staff user updates a public report’s compliance status via the admin panel, the changes don’t appear on the RMO side until page reload
  • Real-time updates for public submissions (nuisance, water) to admins are handled separately

Depending on the design intent, this may be acceptable (RMO is public-facing, low-activity) or a gap.

🟡 Heartbeat Timeout Collision

In SSEManager.ts:77-80, the heartbeatTimeoutSet function throws an error if called when a timeout is already set. The heartbeatTimeoutReset function clears before setting, so normal flow works. But the initial heartbeatTimeoutSet in connect() runs inside eventSource.onopen, and the initial send in the Go handler runs immediately — there’s a brief race where the first heartbeat event could arrive before the timeout is set. If the network latency is very low, the sequence could be: onopen sets timeout, heartbeat arrives and resets, timeout fires never. In practice this works fine because the first “connected” status message isn’t a heartbeat and doesn’t reset the counter.

🟡 Reconnection Resets Resource Caches?

When the SSEManager reconnects after a disconnect, the existing pinia store caches aren’t cleared. The stores still hold stale data until a new event arrives for their resource. After reconnection, the server sends an initial {status:"connected"} SSE event, but stores don’t know to re-fetch. They wait for the next created/updated event. This means after a brief network outage:

  • The status subscriber (Authenticated.vue) detects the reconnect
  • But stores don’t refresh their data until a new event comes in
  • The server-side SSE doesn’t re-send any “sync all”-type event on reconnect

5. Summary of Recommendations

  1. Fix the session.ts bug — change msg.type == "sync:session" to msg.resource.startsWith("sync:session")
  2. Add missing ResourceType entries — define TypeUser, TypeSync, TypeUpload, TypeServiceRequest, TypeContact in platform/event/event.go and emit events from the appropriate platform functions
  3. Align publicreport resource prefix — either change the backend to use sync:publicreport or the frontend to listen for rmo:publicreport
  4. Add frontend listeners for orphan emissionsfilecsv, note:audio, note:image have no UI consumers yet; decide if they need them or remove the backend emissions
  5. Implement deleted handling — wire up EventTypeDeleted in backend and handle msg.type == "deleted" in frontend stores to remove items from cache
  6. Standardize on the targeted-fetch pattern (Pattern A / resource.ts) — refetching the entire list on every event is wasteful
  7. Consider adding SSE to RMO — if real-time updates matter for the public-facing site
  8. Add re-sync on reconnect — the server could send a synthetic event or the client could trigger a full refresh after reconnection
  9. Clean up unused subscriptions — verify the Sidebar’s subscribe return value is used for cleanup on unmount
## Audit of the SSE Event Bus System in nidus-sync Hi diddly ho, neighborino! Here’s the full audit you asked for. --- ## 1. Architecture Overview The SSE event bus has three layers: ### Layer 1: Go Event Bus (`platform/event/event.go`) A singleton channel-based event bus. Code in the platform layer emits events by calling: - `event.Created(resourceType, orgID, uriID)` - `event.Updated(resourceType, orgID, uriID)` - `event.UpdatedUser(resourceType, userID, uriID)` - `event.Send(envelope)` (raw) Each event is wrapped in an `Envelope` containing an `Event` (resource, time, type, URI) plus an `OrganizationID` and/or `UserID` for access filtering. The Send function uses a non-blocking select (drops on full channel). ### Layer 2: SSE Handler (`api/event.go`) `GET /api/events` (route in `api/routes.go:123`) calls `streamEvents()`. This: 1. Creates a `ConnectionSSE` struct per client, keyed by (organizationID, userID) 2. Sends an initial `{status:"connected"}` event 3. Loops on a 5-second heartbeat ticker and the connection’s event channel 4. Passes events through `SetEventChannel()` which fans out to all matching connections (filtered by orgID or userID) 5. Detects client disconnect via `r.Context().Done()` Events are serialized as `{"resource":"...","time":"...","type":"created|updated|...","uri":"..."}`. ### Layer 3: SSEManager (`ts/SSEManager.ts`) A singleton module that wraps `EventSource` (native browser SSE). Features: - Connection lifecycle (connect/disconnect/reconnect with 5s delay) - Heartbeat monitor (30s timeout triggers reconnection) - Two subscriber maps: `subscribersResource` and `subscribersStatus` - Routes `"heartbeat"` events to heartbeat reset - Routes `"status"` events to status subscribers - Routes everything else to resource subscribers ### Layer 4: Pinia Stores (frontend) Stores subscribe to the SSEManager and react to events by refetching data. --- ## 2. Components Essential to the System | Component | File | Essential? | Notes | |-----------|------|-----------|-------| | `platform/event.Event` struct | `platform/event/event.go` | ✅ | Core data model | | `platform/event.Envelope` struct | `platform/event/event.go` | ✅ | Carries org/user routing | | `platform/event.EventType` enum | `platform/event/event.go` | ✅ | Created/Updated/Deleted/Heartbeat/Shutdown | | `platform/event.ResourceType` enum | `platform/event/event.go` | ✅ | Typed resource identifiers | | `platform/event.Send()` | `platform/event/event.go` | ✅ | Non-blocking publish to channel | | `api/event.go` SSE handler | `api/event.go` | ✅ | Server-sent events HTTP endpoint | | `api/event.go` `SetEventChannel()` | `api/event.go` | ✅ | Fans out envelopes to SSE connections | | `ts/SSEManager.ts` | `ts/SSEManager.ts` | ✅ | Browser-side SSE connection manager | | Pinia store SSE subscriptions | various `ts/store/` files | ✅ | Drive UI updates from events | Secondary: - `platform/event.Created()` / `Updated()` / `UpdatedUser()` — convenience wrappers, not strictly essential (callers could use `Send()` directly) - `api/event.go` heartbeat ticker — nice-to-have keepalive, could be tuned - `platform/event.Shutdown()` — not used anywhere in active code - `platform.SudoEvent()` — used only in `api/sudo.go` for the admin panel The `EventTypeDeleted` constant is defined but never emitted anywhere. The `EventTypeSudo` constant is used but only via `platform.SudoEvent()`. --- ## 3. Patterns Currently in Use ### Pattern A: The “Generic” Pattern (newer, preferred) **File:** `ts/store/resource.ts` — `createResourceStore()` factory ```typescript SSEManager.subscribe((msg) => { if (msg.resource.startsWith(resource_name)) { if (msg.type == "created" || msg.type == "updated") { fetchByURI(msg.uri); // fetches only the changed item } } }); ``` - Reacts to both `created` and `updated` events - Fetches **only the specific URI** from the event — efficient, targeted - Manages a `byURI` reactive map for caching - Used for: communication, contact, publicreport (sync stores) ### Pattern B: The “Refetch All” Pattern (most common) **Files:** `review-task.ts`, `site.ts`, `signal.ts`, `sync.ts`, `service_request.ts`, `upload.ts`, `user.ts` ```typescript SSEManager.subscribe((msg) => { if (msg.resource.startsWith("sync:xxx")) { fetchAll(); // re-fetches the entire list } }); ``` - Only checks resource prefix; doesn’t distinguish `created` vs `updated` - Re-fetches **the entire list** on every event — wasteful for large datasets - Breaks `EventTypeDeleted` handling (can’t delete from cache) ### Pattern C: The Direct Subscription (components) **Files:** `Sidebar.vue`, `Authenticated.vue`, `AppSync.vue` ```typescript SSEManager.subscribe((msg) => { if (msg.resource == "sync:session") { session.fetchSession(); } }); ``` Components subscribing directly instead of going through a store. The Sidebar does this to detect impersonation changes. ### Pattern D: The Status Subscriber **File:** `Authenticated.vue` ```typescript SSEManager.subscribeStatus((msg) => { if (msg.status == "connected") { ... } else if (msg.status == "shutdown") { SSEManager.reconnect(5); } }); ``` Handles server restart and revision-change detection. ### Pattern E: RMO frontend — No SSE at All The RMO (`ts/rmo/`) frontend has **zero** SSE integration. Its pinia stores (`publicreport.ts`, `district.ts`, `address-or-report-suggestion.ts`) fetch data directly via API calls and never subscribe to the SSEManager. This means: - Staff viewing public reports through the RMO interface don’t see live updates - The RMO compliance/publicreport/water/nuisance pages won’t reflect changes until the user manually refreshes --- ## 4. Inconsistencies and Issues ### 🔴 Bug: session.ts checks the wrong field **File:** `ts/store/session.ts:41` ```typescript if (msg.type == "sync:session") { // BUG: should be msg.resource ``` The `type` field contains the event type ("created", "updated", etc.), not the resource name. The resource name is in `msg.resource`. This comparison will **never** match, so session changes (impersonation start/end) never trigger a session refetch via the store. The Sidebar component has its own separate subscription that checks `msg.resource` correctly, which is why this hasn’t been noticed in practice. **Fix:** Change to `msg.resource.startsWith("sync:session")`. ### 🟡 Missing Backend Emission (orphan subscriptions) These frontend stores subscribe to resource names that **no backend code ever emits**: | Store | Subscribes to | Backend emits? | |-------|--------------|---------------| | `user.ts` | `sync:user` | ❌ No `TypeUser` exists | | `sync.ts` | `sync:sync` | ❌ No `TypeSync` exists | | `upload.ts` | `sync:upload` | ❌ No `TypeUpload` exists | | `service_request.ts` | `sync:service-request` | ❌ No `TypeServiceRequest` exists | | `resource.ts` (contact) | `sync:contact` | ❌ No `TypeContact` exists | | `resource.ts` (publicreport) | `sync:publicreport` | ❌ Backend emits `rmo:publicreport` (different prefix!) These subscriptions will never fire, which means these stores only update when `fetchAll()` is called explicitly or when the page loads fresh. ### 🟡 Missing Frontend Listeners (orphan emissions) These backend event types are emitted but have **no frontend subscriber**: | Resource string | Emitted from | Listener? | |----------------|--------------|----------| | `sync:filecsv` | `platform/csv/csv.go` | ❌ | | `sync:note:audio` | `platform/note.go` | ❌ | | `sync:note:image` | `platform/note.go` | ❌ | | `rmo:publicreport` | `platform/publicreport.go`, `platform/publicreport_notification.go`, `platform/text/` | ❌ (sync store listens for `sync:publicreport` — wrong prefix) | | `rmo:publicreport.compliance` | `platform/event/event.go` (defined but never emitted by name) | ❌ | | `rmo:publicreport.nuisance` | (defined) | ❌ | | `rmo:publicreport.water` | (defined) | ❌ | | `sync:review-task` | `platform/review.go` | ✅ | | `sync:signal` | `platform/signal.go`, `platform/review.go` | ✅ | | `sync:site` | `platform/compliance.go` | ✅ | Note: The `rmo:publicreport` resource string is emitted for updates to public reports (complaints, notification changes, text message updates), but the sync frontend’s resource store listens for `sync:publicreport` — a different string. So public report updates from backend processing (text message replies, notification creation) are emitted but never reach the UI. ### 🟡 No `EventTypeDeleted` Usage `EventTypeDeleted` is defined in `platform/event/event.go` but **no backend code ever emits it**. The frontend also doesn’t handle `"deleted"` events — most stores only check for `created`/`updated`. If a resource is deleted, the frontend cache keeps a stale copy. Only `review-task.ts` has a `remove()` method, but it’s not triggered by SSE events. ### 🟡 Inefficient Bulk Refetch Pattern The majority of stores (Pattern B above) re-fetch the **entire list** on any event. For stores like `site` or `review-task` that may have hundreds of items, this is wasteful. The generic pattern in `resource.ts` (Pattern A) fetches only the specific URI and is much more efficient. ### 🟡 Subscription Leak Risk Each call to `SSEManager.subscribe()` adds a permanent handler. The `unsubscribe()` method exists but is **never called anywhere in the codebase**. Since Pinia stores are singletons (only instantiated once), this isn’t a leak in practice. But the Sidebar component’s subscription (`Sidebar.vue:309`) stores the return value of `subscribe()` in a local variable `sub` that is never used — if the Sidebar were to be unmounted/re-mounted, the old subscription would persist. ### 🟡 RMO Frontend Has No SSE at All The RMO (`ts/rmo/`) SPA does not connect to SSE (`/api/events`) and does not import SSEManager. The RMO stores (`publicreport.ts`, `district.ts`, `suggestions.ts`) work entirely by explicit fetch. This means: - When a staff user updates a public report’s compliance status via the admin panel, the changes don’t appear on the RMO side until page reload - Real-time updates for public submissions (nuisance, water) to admins are handled separately Depending on the design intent, this may be acceptable (RMO is public-facing, low-activity) or a gap. ### 🟡 Heartbeat Timeout Collision In `SSEManager.ts:77-80`, the `heartbeatTimeoutSet` function throws an error if called when a timeout is already set. The `heartbeatTimeoutReset` function clears before setting, so normal flow works. But the initial `heartbeatTimeoutSet` in `connect()` runs inside `eventSource.onopen`, and the initial `send` in the Go handler runs immediately — there’s a brief race where the first heartbeat event could arrive before the timeout is set. If the network latency is very low, the sequence could be: `onopen` sets timeout, heartbeat arrives and resets, timeout fires never. In practice this works fine because the first “connected” status message isn’t a heartbeat and doesn’t reset the counter. ### 🟡 Reconnection Resets Resource Caches? When the SSEManager reconnects after a disconnect, the existing pinia store caches aren’t cleared. The stores still hold stale data until a new event arrives for their resource. After reconnection, the server sends an initial `{status:"connected"}` SSE event, but stores don’t know to re-fetch. They wait for the next `created`/`updated` event. This means after a brief network outage: - The status subscriber (Authenticated.vue) detects the reconnect - But stores don’t refresh their data until a new event comes in - The server-side SSE doesn’t re-send any “sync all”-type event on reconnect --- ## 5. Summary of Recommendations 1. **Fix the session.ts bug** — change `msg.type == "sync:session"` to `msg.resource.startsWith("sync:session")` 2. **Add missing ResourceType entries** — define `TypeUser`, `TypeSync`, `TypeUpload`, `TypeServiceRequest`, `TypeContact` in `platform/event/event.go` and emit events from the appropriate platform functions 3. **Align publicreport resource prefix** — either change the backend to use `sync:publicreport` or the frontend to listen for `rmo:publicreport` 4. **Add frontend listeners for orphan emissions** — `filecsv`, `note:audio`, `note:image` have no UI consumers yet; decide if they need them or remove the backend emissions 5. **Implement `deleted` handling** — wire up EventTypeDeleted in backend and handle `msg.type == "deleted"` in frontend stores to remove items from cache 6. **Standardize on the targeted-fetch pattern** (Pattern A / `resource.ts`) — refetching the entire list on every event is wasteful 7. **Consider adding SSE to RMO** — if real-time updates matter for the public-facing site 8. **Add re-sync on reconnect** — the server could send a synthetic event or the client could trigger a full refresh after reconnection 9. **Clean up unused subscriptions** — verify the Sidebar’s subscribe return value is used for cleanup on unmount
Author
Owner

This is a really good writeup!

We're not going to add SSE to RMO - it's primarily a way for the public to supply us with data and real-time updates aren't useful there.

That said, I'd be interested in seeing a PR with each of your recommendations in separate commits where we can discuss the finer details.

This is a really good writeup! We're not going to add SSE to RMO - it's primarily a way for the public to supply us with data and real-time updates aren't useful there. That said, I'd be interested in seeing a PR with each of your recommendations in separate commits where we can discuss the finer details.
Member

I went ahead and created PR #143 with each recommendation in a separate commit as requested:

  1. fix: session.ts SSE resource check — msg.type → msg.resource
  2. fix: align publicreport prefix — frontend matches backend rmo:publicreport
  3. feat: missing ResourceTypes — TypeContact, TypeServiceRequest, TypeSync, TypeUpload, TypeUser defined
  4. feat: contact event emissions — emits TypeContact events from Update and Notification paths
  5. feat: Deleted event support — event.Deleted() helper + frontend handling in resource.ts, review-task.ts, site.ts
  6. feat: re-sync on reconnect — subscribeReconnect() method + handlers in all stores
  7. fix: Sidebar subscription leak — unsubscribe on unmount
  8. refactor: targeted URI fetch — review-task and site use fetchOne() instead of fetchAll() on events

I left RMO SSE out as discussed, and the remaining stores (signal, sync, user, upload, service-request) still use bulk fetch since they don't have individual fetchOne() endpoints.

I went ahead and created PR #143 with each recommendation in a separate commit as requested: 1. **fix: session.ts SSE resource check** — msg.type → msg.resource 2. **fix: align publicreport prefix** — frontend matches backend `rmo:publicreport` 3. **feat: missing ResourceTypes** — TypeContact, TypeServiceRequest, TypeSync, TypeUpload, TypeUser defined 4. **feat: contact event emissions** — emits TypeContact events from Update and Notification paths 5. **feat: Deleted event support** — event.Deleted() helper + frontend handling in resource.ts, review-task.ts, site.ts 6. **feat: re-sync on reconnect** — subscribeReconnect() method + handlers in all stores 7. **fix: Sidebar subscription leak** — unsubscribe on unmount 8. **refactor: targeted URI fetch** — review-task and site use fetchOne() instead of fetchAll() on events I left RMO SSE out as discussed, and the remaining stores (signal, sync, user, upload, service-request) still use bulk fetch since they don't have individual fetchOne() endpoints.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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
Gleipnir/nidus-sync#140
No description provided.