docs(server-storage): conditional user-state writes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-10-12 08:58:16 +02:00
1 parent 340b1c3efa
commit 0183ddc61d
2 files changed
+16 -9

No files matched your search

+10 -4
View File
@@ -88,7 +88,10 @@ Server storage adds versioned routes under `/v1`, which the PWA uses in
replaced
- `GET /v1/user-state`, `PUT|DELETE /v1/user-state/:namespace/:playlistId`:
Xtream favorites, recently viewed, collection snapshots and playback
positions, one JSON value per namespace and playlist
positions, one JSON value per namespace and playlist; `GET` adds a
content tag per entry (`tags`), and a write with `If-Match: "<tag>"` or
`If-None-Match: *` answers `412` with the current value and tag when the
entry changed since
- `GET|PUT /v1/app-state/:key`
Server storage lives in `apps/web-backend/src/app/storage/`. With
@@ -208,9 +211,12 @@ settings and guards read them in every build.
(`iptvnator-server:cache-owner`); another one behind the same address
starts clean. Also,
re-reads run one at a time and keep entries edited while they were in
flight or not yet saved. A re-read whose result does not fit the cache
keeps the old cache and baseline together. A response body cut off after
its headers counts as offline. Edits made before the first successful read
flight or not yet saved. A response body cut off after its headers counts
as offline. Each write is sent on the tag of the entry it was made
against (`UserDataWriteQueue`); after a `412` the edit (before → after) is
replayed on the server's current value, so another device's change to the
same playlist's entry is kept. Deleting a playlist first drops its queued
edits and waits for writes still travelling. Edits made before the first successful read
are held as before → after and applied to the server's value once it
arrives (`applyEdit`): a new device or a stale cache adds its favorite
instead of replacing the server's list. They are saved in the tab too
@@ -132,8 +132,8 @@ All JSON. Errors: `{ message, status, code? }`.
| `DELETE /v1/playlists/:id` | Delete the row and its user-state entries. |
| `DELETE /v1/playlists` | Delete all playlists and all user state. |
| `GET/PUT /v1/app-state/:key` | `{ value }`. |
| `GET /v1/user-state` | `{ entries: { [namespace]: { [playlistId]: value } } }`. |
| `PUT/DELETE /v1/user-state/:namespace/:playlistId` | One entry. Namespaces are an allow-list. |
| `GET /v1/user-state` | `{ entries: { [namespace]: { [playlistId]: value } }, tags }`, a content tag per entry. |
| `PUT/DELETE /v1/user-state/:namespace/:playlistId` | One entry. Namespaces are an allow-list. `If-Match: "<tag>"` or `If-None-Match: *` makes it conditional: `412` with the current value and tag. |
ETags are `"<bootId>-<revision>"` from an in-memory per-playlist revision
counter; the server is the only writer of its database.
@@ -209,9 +209,10 @@ https://claude.ai/artifact/3WheJAgeNL5ag6kmtPrJcD
## 11. Risks
- **Concurrent edits on two devices**: last write wins per document or per
Xtream map entry. Every playlist mutation re-reads first, so the window is
one round trip.
- **Concurrent edits on two devices**: last write wins per document.
Every playlist mutation re-reads first, so the window is one round trip.
Xtream map entries are written on the tag they were read at and replayed
on the server's value after a `412`, so both devices' edits are kept.
- **Large payloads**: M3U global favorites and backup export read all full
documents, as IndexedDB does today.
- **Open instances**: storing credentials raises the cost of an unprotected