From 5e38f661a75ded7d4e969baa968ebeaffd0c63bb Mon Sep 17 00:00:00 2001 From: everpcpc Date: Sat, 24 Oct 2026 11:00:58 +0800 Subject: [PATCH 1/2] docs: document SSE recovery --- AGENTS.md | 2 +- .../docs/server/enhancements/index.mdx | 1 + .../docs/server/enhancements/sse-recovery.mdx | 51 +++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 src/content/docs/server/enhancements/sse-recovery.mdx diff --git a/AGENTS.md b/AGENTS.md index 7a7933b..e0a14e5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,7 +10,7 @@ Read and follow [AGENT.md](./AGENT.md) — the canonical guide for the Nimbus ma pnpm typecheck && pnpm build && pnpm exec nimbus-docs check && pnpm lint:docs ``` -Then confirm `dist/` still serves every public route: `/`, `/server/`, `/reader/`, `/faq/`, `/compare/`, `/architecture/`, `/server/{intro,installation,configuration,webui,search,compatibility,enhancements,limitations,development,migration}/`, `/server/enhancements/{webhooks,history-events,epub2-metadata,natural-sort,komf,thumbnail-storage,komga-riir,next-previous,reading-stats,smart-lists,server-stats,update-check}/`, `/reader/{intro,reading,offline,library,translation,settings,privacy}/`. +Then confirm `dist/` still serves every public route: `/`, `/server/`, `/reader/`, `/faq/`, `/compare/`, `/architecture/`, `/server/{intro,installation,configuration,webui,search,compatibility,enhancements,limitations,development,migration}/`, `/server/enhancements/{webhooks,history-events,epub2-metadata,natural-sort,komf,thumbnail-storage,komga-riir,next-previous,reading-stats,smart-lists,server-stats,update-check,sse-recovery}/`, `/reader/{intro,reading,offline,library,translation,settings,privacy}/`. ## Content conventions diff --git a/src/content/docs/server/enhancements/index.mdx b/src/content/docs/server/enhancements/index.mdx index 3f4b945..81a693b 100644 --- a/src/content/docs/server/enhancements/index.mdx +++ b/src/content/docs/server/enhancements/index.mdx @@ -23,6 +23,7 @@ Improvements KMServer adds on top of Java parity, plus intentional behavior diff - [Smart lists](/server/enhancements/smart-lists): saved search filters evaluated live per requesting user, with visibility scopes and generated covers - [Library and server statistics](/server/enhancements/server-stats): per-library content counts for any user, and an admin server snapshot with task and process state - [Update check](/server/enhancements/update-check): periodic check for new releases, surfaced in the admin UI and `/api/v1/update-check` +- [SSE recovery](/server/enhancements/sse-recovery): bounded event replay after a disconnect, with an explicit `Reset` when complete replay is no longer possible ## Behavior differences diff --git a/src/content/docs/server/enhancements/sse-recovery.mdx b/src/content/docs/server/enhancements/sse-recovery.mdx new file mode 100644 index 0000000..3ce37fc --- /dev/null +++ b/src/content/docs/server/enhancements/sse-recovery.mdx @@ -0,0 +1,51 @@ +--- +title: "SSE recovery" +description: "Resume event streams after a disconnect, with an explicit reset when complete replay is no longer possible." +sidebar: + order: 14 +--- + +KMServer assigns an ID to every domain event on `/sse/v1/events` and keeps the most recent 1024 events in memory. +The Java version only forwards live events and has no recovery history. + +## Replay after a reconnect + +Native `EventSource` clients automatically reconnect and send the last received event ID in `Last-Event-ID`. +If every event after that ID is still available, the server replays them in order before continuing with live events. +Replay applies the same administrator and per-user visibility rules as live delivery. +The server takes the live subscription and the replay decision atomically, so an event published while reconnecting cannot fall between the two phases. + +Heartbeat comments and `TaskQueueStatus` snapshots do not carry event IDs. +A fresh task queue snapshot is generated after each connection, so it does not need replay. + +## Reset when replay is incomplete + +The server sends a `Reset` event when it cannot guarantee complete recovery, including when: + +- some events after the requested ID are no longer retained +- the ID belongs to an earlier server process +- the ID is invalid or ahead of the current event stream + +`Reset` carries the latest event ID and an empty JSON object: + +```text +id: +event:Reset +data:{} + +``` + +The server skips the incomplete history and continues with events published after that cursor. +Advancing the cursor also prevents the next reconnect from repeating the same reset. + +A client that receives `Reset` must replace missed history with a full refresh of its current API state. +The web UI does this by invalidating all queries and reloading cover images. +Other clients need a `Reset` listener of their own; native `EventSource` handles transport reconnection, but it does not perform this application-level refresh. + +## Scope and limits + +The event history is process-local and is not persisted in a database. +Events missed while the server is stopped are covered by the reset-and-refresh path rather than replayed from a durable log. +Recovery applies to automatic reconnects within the current browser connection lifecycle, not to restoring an event ID across page loads. + +`Reset` restores current state; it does not guarantee delivery of every one-off notification that occurred during a gap. From 6c522826b021a2343a451d7a324a7d33c9fe9571 Mon Sep 17 00:00:00 2001 From: everpcpc Date: Sat, 24 Oct 2026 11:18:45 +0800 Subject: [PATCH 2/2] docs: clarify best-effort SSE recovery --- .../docs/server/enhancements/index.mdx | 2 +- .../docs/server/enhancements/sse-recovery.mdx | 34 ++++++------------- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/src/content/docs/server/enhancements/index.mdx b/src/content/docs/server/enhancements/index.mdx index 81a693b..8092861 100644 --- a/src/content/docs/server/enhancements/index.mdx +++ b/src/content/docs/server/enhancements/index.mdx @@ -23,7 +23,7 @@ Improvements KMServer adds on top of Java parity, plus intentional behavior diff - [Smart lists](/server/enhancements/smart-lists): saved search filters evaluated live per requesting user, with visibility scopes and generated covers - [Library and server statistics](/server/enhancements/server-stats): per-library content counts for any user, and an admin server snapshot with task and process state - [Update check](/server/enhancements/update-check): periodic check for new releases, surfaced in the admin UI and `/api/v1/update-check` -- [SSE recovery](/server/enhancements/sse-recovery): bounded event replay after a disconnect, with an explicit `Reset` when complete replay is no longer possible +- [SSE recovery](/server/enhancements/sse-recovery): bounded event replay when complete, without adding a custom recovery event ## Behavior differences diff --git a/src/content/docs/server/enhancements/sse-recovery.mdx b/src/content/docs/server/enhancements/sse-recovery.mdx index 3ce37fc..142ef05 100644 --- a/src/content/docs/server/enhancements/sse-recovery.mdx +++ b/src/content/docs/server/enhancements/sse-recovery.mdx @@ -1,6 +1,6 @@ --- title: "SSE recovery" -description: "Resume event streams after a disconnect, with an explicit reset when complete replay is no longer possible." +description: "Replay complete event sequences when available after a disconnect." sidebar: order: 14 --- @@ -18,34 +18,20 @@ The server takes the live subscription and the replay decision atomically, so an Heartbeat comments and `TaskQueueStatus` snapshots do not carry event IDs. A fresh task queue snapshot is generated after each connection, so it does not need replay. -## Reset when replay is incomplete +## When history is incomplete -The server sends a `Reset` event when it cannot guarantee complete recovery, including when: +The server does not replay a partial history. +If some events after the requested ID are no longer retained, the ID belongs to an earlier server process, or the ID is invalid or ahead of the current stream, the connection starts with live events instead. +No application-specific reset event is added to the SSE protocol. -- some events after the requested ID are no longer retained -- the ID belongs to an earlier server process -- the ID is invalid or ahead of the current event stream - -`Reset` carries the latest event ID and an empty JSON object: - -```text -id: -event:Reset -data:{} - -``` - -The server skips the incomplete history and continues with events published after that cursor. -Advancing the cursor also prevents the next reconnect from repeating the same reset. - -A client that receives `Reset` must replace missed history with a full refresh of its current API state. -The web UI does this by invalidating all queries and reloading cover images. -Other clients need a `Reset` listener of their own; native `EventSource` handles transport reconnection, but it does not perform this application-level refresh. +The web UI applies events as they arrive and does not run a separate full refresh merely because a reconnect succeeded. +A user who notices stale state can reload the page manually. +Third-party clients can choose their own reconciliation policy when they need stronger consistency; no custom event handling is required by the server. ## Scope and limits The event history is process-local and is not persisted in a database. -Events missed while the server is stopped are covered by the reset-and-refresh path rather than replayed from a durable log. +Events missed while the server is stopped are not replayed from a durable log. Recovery applies to automatic reconnects within the current browser connection lifecycle, not to restoring an event ID across page loads. -`Reset` restores current state; it does not guarantee delivery of every one-off notification that occurred during a gap. +Replay is best-effort: events outside the retained history and one-off notifications during a gap can be missed.