morphit/apps/web/scripts/profile-hydrate-retry-smoke.ts

95 lines
3.5 KiB
TypeScript

#!/usr/bin/env tsx
/**
* profile-hydrate-retry — v1.8.12 (Ken).
*
* THE BUG. Display names and avatars were absent "every once in a while" and
* appeared after a manual refresh. Ken's standard for it was right: "i should
* never have to refresh the page to see the truth."
*
* `profileCache` has always separated a TRANSIENT fetch failure (soft-cached
* 5s) from an authoritative "no profile" (90s), on the stated reasoning that
* the short entry would expire and "the next render re-fetches". Nothing ever
* did: each surface hydrates once per page load (and per loadMore), so on a
* settled page the soft entry expired into silence and the row kept its
* identicon until the user navigated away or refreshed.
*
* WHY THE RETRY LIVES IN THE CALLER. Two earlier attempts put it inside
* profileCache and both failed: one blocked first render and broke the tested
* fail-fast contract; the other needed a notification channel, because updating
* the cache re-renders nothing. Each surface already owns reactive state —
* writing to it IS the refresh — so the caller retries and the cache answers
* the one question only it can: was that a real answer, or a blip?
*
* Tamper tests (each must turn this red):
* - Remove the retry from either surface.
* - Retry on ALL misses rather than only soft ones (re-asking forever for
* accounts that genuinely have no profile).
* - Make the retry unbounded.
*/
import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const HERE = dirname(fileURLToPath(import.meta.url));
const WEB = join(HERE, '..');
const read = (p: string): string => readFileSync(join(WEB, p), 'utf8');
const SURFACES = [
['orderbook', 'src/routes/[lang]/orderbook/+page.svelte'],
['profile', 'src/routes/[lang]/[x+40][account=account]/+page.svelte']
] as const;
let passed = 0;
let failed = 0;
const check = (name: string, cond: boolean, detail = ''): void => {
if (cond) {
console.log(`${name}`);
passed++;
} else {
console.log(`${name}${detail ? `: ${detail}` : ''}`);
failed++;
}
};
console.log('\n── profile-hydrate-retry (v1.8.12) ───────────────────\n');
check(
'the cache reports transient-vs-authoritative misses',
/export function isSoftMiss\(/.test(read('src/lib/indexer/profileCache.ts')),
'without this the caller cannot tell a blip from a real absence'
);
for (const [label, path] of SURFACES) {
const src = read(path);
const code = src
.split('\n')
.filter((l) => !/^\s*(\/\/|\*|\/\*|<!--|-->)/.test(l.trim()))
.join('\n');
check(
`${label}: re-asks after a transient miss`,
/isSoftMiss\(/.test(code),
'a soft-cached failure expires into silence unless something asks again'
);
check(
`${label}: retries ONLY soft misses, not every empty profile`,
!/filter\(\(a\) => next\[a\] == null\)/.test(code),
're-asking for accounts that genuinely have no profile is pointless traffic'
);
check(
`${label}: the retry is bounded`,
/attempt >= PROFILE_HYDRATE_RETRIES/.test(code),
'an unbounded retry against a down indexer is a loop'
);
check(
`${label}: waits past the cache's soft TTL before re-reading`,
/PROFILE_HYDRATE_RETRY_MS = 5_500/.test(code),
'retrying inside the 5s soft window just replays the same cached miss'
);
}
console.log(
`\n${passed} passed, ${failed} failed\n${failed === 0 ? `✓ all ${passed} profile-hydrate-retry checks passed` : '✗ profile-hydrate-retry FAILED'}`
);
process.exit(failed === 0 ? 0 : 1);