Files
HealthLog/src/lib/tz/intl-cache.ts
T
Marc-André Bombeck f790e14edd fix(api,lib): consent before reuse, tighter inputs and caches that tell on themselves
Per-status texts check AI consent before the unchanged-data refresh can
re-stamp them, the comprehensive fingerprint folds in the comparison
baseline and generation language so toggling either regenerates once,
and profile, threshold and source-priority writes evict the analytics,
targets and derived caches they feed. CSV export neutralises spreadsheet
formula prefixes on free-text cells while numeric columns stay typed,
the derived batch validates its type token against the measurement enum
and carries a per-user rate limit, and a dose synced from the phone app
counts as the interactive write it is. Background cache refresh
failures log a structured event instead of vanishing, the warm queue
runs visit-triggered jobs alongside the nightly walk, the formatter
memo evicts one entry instead of flushing wholesale with its option
objects frozen, the targets cold build bounds its fan-out, and time in
bed unions every writer's envelope while stages stay with the richest
recorder.
2026-06-11 23:20:38 +02:00

84 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Module-level memo for `Intl.DateTimeFormat` construction.
*
* Constructing a formatter resolves locale + timezone data on every call
* and dominates the CPU cost of the wall-clock helpers (~10x the
* `format` / `formatToParts` call it feeds). The instances are stateless
* for `format` / `formatToParts`, so one shared formatter per
* `(locale, timeZone, options)` signature is safe to hand to every
* caller — compliance band expansion, rollups, the dose-history ledger
* and the export formatters all hit the same small set of signatures.
*
* `timeZone` is a first-class key segment (not folded into `options`) so
* call sites can hoist their options object to a module constant; a
* WeakMap memoises that constant's serialised signature, leaving a warm
* lookup as two string concats + one Map get.
*/
const formatterCache = new Map<string, Intl.DateTimeFormat>();
/**
* Hard cap. The realistic population is bounded (valid IANA zones × a
* handful of option shapes), but the timezone segment is user-supplied
* upstream of validation in `isValidTimezone`, so the memo carries a
* safety valve: on overflow the single OLDEST entry is evicted (Map
* iteration order is insertion order, so the first key is the oldest).
* A full-map reset on every insert past the cap would let one burst of
* unusual zones re-pay formatter construction for every hot signature.
*/
const MAX_FORMATTERS = 1000;
const signatureByOptions = new WeakMap<
Omit<Intl.DateTimeFormatOptions, "timeZone">,
string
>();
function optionsSignature(
options: Omit<Intl.DateTimeFormatOptions, "timeZone">,
): string {
const memoised = signatureByOptions.get(options);
if (memoised !== undefined) return memoised;
const record = options as Record<string, unknown>;
const signature = Object.keys(record)
.sort()
.map((key) => `${key}=${String(record[key])}`)
.join(",");
signatureByOptions.set(options, signature);
return signature;
}
/**
* Shared, memoised `Intl.DateTimeFormat`. Throws (like the constructor)
* on an invalid `timeZone` — failures are never cached, so
* `isValidTimezone` can probe through this without poisoning the map.
*/
export function getDateTimeFormat(
locale: string,
timeZone: string | undefined,
options: Omit<Intl.DateTimeFormatOptions, "timeZone"> = {},
): Intl.DateTimeFormat {
const key = `${locale}|${timeZone ?? ""}|${optionsSignature(options)}`;
const hit = formatterCache.get(key);
if (hit) return hit;
const formatter = new Intl.DateTimeFormat(
locale,
timeZone === undefined ? options : { ...options, timeZone },
);
if (formatterCache.size >= MAX_FORMATTERS) {
const oldest = formatterCache.keys().next().value;
if (oldest !== undefined) formatterCache.delete(oldest);
}
formatterCache.set(key, formatter);
return formatter;
}
/** Test-only escape hatch — clear the memo between assertions. */
export function __resetIntlCacheForTests(): void {
formatterCache.clear();
}
/** Test-only introspection — current memo population. */
export function __intlCacheSizeForTests(): number {
return formatterCache.size;
}