Release · v3.14.22
Collapse concurrent callers onto one request
perf(db): collapse concurrent callers onto one request
Details
Every fetch in this file caches into sessionStorage, but the cache is written
only once the response lands. The boot script hydrates each section shell and
each `db://` placeholder in the same tick, so those callers all started while
the first request was still in the air, all missed the cache, and all issued
their own.
Measured at 1440 on a real build of `/`: 33 REST queries per page load where 10
are distinct — the three asset tables went out 8 times each, and two of the six
sections queries went out twice.
Keying an in-flight promise by request collapses them. The entry is dropped
once it settles, so success hands over to the sessionStorage cache and failure
leaves the next caller free to retry instead of inheriting an old rejection.
The cleanup is attached to a branch of the chain rather than returned, so a
rejection still reaches the caller exactly once. Verified under forced 500s and
forced network aborts: requests still collapse while failing, a later retry
genuinely re-requests, and nothing surfaces as an unhandled rejection.
`fetchDbAssets` also awaited its three independent tables in turn, spending
three round trips where one would do; they now go together.
/ 33 REST queries -> 10 (140.6 KiB -> 36.3 KiB)
/v2/ 34 -> 10
/v1/ 7 -> 4
No duplicate REST URL remains on any page. Each query is still two HTTP
requests, because `apikey` and `Authorization` are not CORS-simple headers so
every GET carries its own preflight — a floor set by PostgREST auth, not
something this change can remove.
Adds `site-images.test.mjs` to pin the whole fix. It scans the SOURCE tree
rather than the built site, for two reasons a first draft of it got wrong:
`test:frontend` runs in CI behind `tsc` alone, with no Ruby and no Jekyll, so a
suite that required built pages was a guaranteed red gate on every PR; and a
built-pages check has to name the pages it inspects, which is a list of the
versions that existed when it was written. Walking every layout, include and
page instead covers a NEW version directory the day it appears — which is the
real regression vector, since a branch in flight still carries these
placeholders in `_includes/v4/` and `_layouts/v4.html`.
The portrait can come back by more routes than a placeholder in a layout, so
each is pinned by a test that was confirmed to fail when the route is used:
* a placeholder in a page file — `index.md` builds the flagship `/`
* a placeholder in any client script — not just the rotator
* a raw Storage URL on any element — needs no placeholder at all, and an
<img> can carry neither marker the
portrait check looks for
* an unresized original under assets/img/ in a subdirectory
Confirmed green against a tsc-only build, and red on exactly the two v4 files
when that merge is simulated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DAJJ63FFnLoD5XLpu6yJaG
Files changed (2)
| frontend/client/tests/site-images.test.mjs | +273 | −0 |
| frontend/client/ts/core/db_assets.ts | +56 | −8 |