-
Notifications
You must be signed in to change notification settings - Fork 5
Plan: realtime state (Soketi / Pusher protocol + TanStack Query invalidation) #97
Copy link
Copy link
Open
Labels
Description
Summary
Inventory and phased rollout for realtime UI updates on TechDiary: push small signals (e.g. Pusher-compatible broker such as Soketi), then invalidate / refetch TanStack Query caches. Database and server actions remain the source of truth.
Principle
- Realtime delivers event name + optional ids only.
- Client invalidates existing queries (same idea as today in
src/app/dashboard/notifications/page.tsxandsrc/components/render-props/ResourceReactionable.tsx).
flowchart LR
subgraph writers [Writers]
Action[Server action]
Inngest[Inngest persistNotificationFn]
end
subgraph broker [Broker e.g. Soketi]
Pub[HTTP trigger]
end
subgraph clients [Clients]
PusherJS[pusher-js private channel]
QC[TanStack Query invalidate]
end
Action --> Pub
Inngest --> Pub
Pub --> PusherJS
PusherJS --> QC
Resource inventory
| Resource | Primary query keys | Audience | Suggested channel | Phase |
|---|---|---|---|---|
| In-app notifications | my-notifications, unread-notification-count |
One user | private-user.{userId} |
1 |
| Comments | comments, resource_id, resource_type |
Same article/gist page | private-resource.{resourceType}.{resourceId} |
2 |
| Reactions | reaction, resource_id, resource_type |
Same as comments | Same resource channel | 3 |
| Bookmarks + dashboard list | bookmark-status, dashboard-articles |
Per user / per resource | User + optional resource channel | 3–4 |
| Author dashboard articles | dashboard-articles |
One user | private-user.{userId} |
3 |
| Gists | gists, gist |
Owner + viewers | User + gist resource channel | 4 |
| Profile article feed | user-article-feed |
Profile visitors | TBD | 4 |
| Tag feed | tag-articles |
Tag page visitors | e.g. tag.{tagId} |
4 |
| Home article feed | article-feed |
Very wide fanout | Defer (or per-user if follow graph exists) | Defer |
| Sessions | mySessions |
One user | private-user.{userId} |
Optional |
| User card | user |
Profile viewers | Low value | Optional |
| Static widgets | top-tags, etc. |
— | Skip | — |
Notification hook today: comment.action.ts and reaction.actions.ts emit app/notification.requested; persistNotificationFn in src/lib/inngest.ts inserts rows. Phase 1 can publish to the recipient’s user channel after successful insert in that Inngest function.
Phased rollout
- Phase 1 — Notifications: Subscribe to
private-user.{userId}; on persist success trigger e.g.invalidatewith{ scope: "notifications" }; client invalidatesmy-notificationsandunread-notification-count. - Phase 2 — Comments: Subscribe from
CommentSection; publish on create/update/delete; invalidate comments query. - Phase 3 — Reactions / dashboard articles: Same resource channel for reactions; user channel when the signed-in user’s articles change.
- Defer: Global home feed broadcast; tag/profile feeds unless product requires them.
Auth and safety
- Next.js private channel auth endpoint: verify session (
getSession/authIDinsrc/backend/services/session.actions.ts) before signing subscription. - Resource channels: authorize subscribe same as “allowed to read this resource”.
- Do not treat socket payload as authoritative.
Out of scope
- Collaborative editor (OT/CRDT).
- Replacing Inngest for durable delivery (add broadcast as a side effect only).
- Pushing full comment/article bodies over the socket.
Implementation checklist
- Phase 1: private user channel + publish after notification persist (Inngest) + client invalidate
- Phase 2: resource channel + comment mutations + invalidate in
CommentSection - Phase 3 (optional): reactions +
dashboard-articles - Explicitly defer global
article-feeduntil follow-based or other narrow design exists
Reactions are currently unavailable