feat(sidecar): store and serve the player-vendor market index #19

Merged
whitlocktech merged 1 commits from feat/vendor-listing into edge 2026-07-29 20:03:42 +00:00

1 Commits

Author SHA1 Message Date
480423090a feat(sidecar): store and serve the player-vendor market index
Protocol 3.0 §8. Ingests vendor.listing / vendor.listing.remove into a `vendors`
table and serves GET /market.

The frame is authoritative for one vendor, so the upsert is a whole-row
overwrite. Unlike the other 3.0 boards there IS a remove: a vendor is dismissed,
expires, or its owner switches off the in-game Vendor Search flag — the last of
those is a privacy control, so dropping the row promptly is the point.

Items ride inside the stored blob and are deliberately not normalized into a
vendor_items table. The sidecar's job for the market is outage resilience
(PROTOCOL_2.md §12.2), not search; search lives in MariaDB on the website side,
where the query surface, the indexes and the cliloc-resolved names already are.

/market is the only PAGED read the sidecar serves, because it is the only board
that can be a whole world's inventory. limit clamps to 1..1000 (default 200) and
`total` comes back so a caller knows when to stop rather than paging until it
sees a short page, which would race a concurrent sweep. Ordering is by SERIAL,
not shop name: a serial is stable while a shop name is renameable, so a rename
mid-walk cannot make a vendor skip or repeat a page.

The route is /market and not /vendors: /vendors/:account next door is the
per-account RPC, and two routes a prefix apart meaning "this player's shops" and
"every shop on the shard" is a readability trap.

Frames are served verbatim, owner names and coordinates included — the sidecar
defines no audiences (v3.md §3.2).

Verified against the live shard: 27 vendors / 1,040 listings ingested from the
plugin, plus a synthetic insert-then-remove confirming the delete path.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-29 09:51:14 -05:00