World Manufacturer Identifier (WMI) tables change slowly, but they do change: new assignments, manufacturer string updates, and corrections. If you ship a free VIN product, you probably keep a local WMI map so you can sanity-check the first three characters without calling NHTSA on every keystroke.
TTL-only caches are blunt. You either refresh too often and waste quota, or too rarely and serve a renamed maker for weeks. HTTP already solved a sibling problem with validators: ETag and If-None-Match. You can reuse that idea even when your "origin" is a periodic pull of NHTSA WMI data into your own store.
This post shows a local ETag-style freshness pattern for WMI caches: content hash as validator, conditional refresh, 304-equivalent short circuits, and what to do when the body is unchanged but your product copy still needs a bump.
Why TTL alone is not enough
A fixed TTL answers "when may I ask again?" It does not answer "did anything change?"
- Refresh at T+TTL, download the same payload, rewrite disk, bump metrics -- noise
- Skip refresh because TTL remains, miss a real WMI assignment -- stale trust
- Negative TTL tricks help unknown WMIs; they do not help detecting a changed known row
You want two clocks: when you are allowed to revalidate, and whether the bytes (or mapped rows) actually moved.
Model a cache entry with a validator
import { createHash } from "node:crypto";
export type WmiRow = {
wmi: string;
manufacturer: string;
country: string | null;
};
export type WmiCacheSnapshot = {
/** Opaque validator, like an ETag value (include quotes if you speak HTTP). */
etag: string;
fetchedAt: number;
maxAgeMs: number;
rows: Record<string, WmiRow>;
};
export function etagForRows(rows: Record<string, WmiRow>): string {
const canonical = Object.keys(rows)
.sort()
.map((wmi) => {
const r = rows[wmi];
return `${r.wmi}|${r.manufacturer}|${r.country ?? ""}`;
})
.join("\n");
const digest = createHash("sha256").update(canonical, "utf8").digest("hex");
return `"wmi-${digest.slice(0, 16)}"`;
}
export function isFresh(snap: WmiCacheSnapshot, now = Date.now()): boolean {
return now - snap.fetchedAt < snap.maxAgeMs;
}
The etag is not magic from NHTSA. It is your strong validator over the mapped table you trust. If upstream JSON churns whitespace but mapped rows are identical, a good mapper keeps the same etag.
Conditional refresh flow
Think in the same states browsers use:
- Fresh -- serve local rows, no network
- Stale but revalidating -- serve local rows, ask origin with validator
-
Not modified -- keep rows, only update
fetchedAt - Modified -- replace rows and etag atomically
export type RevalidateResult =
| { status: "not-modified"; snapshot: WmiCacheSnapshot }
| { status: "modified"; snapshot: WmiCacheSnapshot }
| { status: "error"; error: Error; snapshot: WmiCacheSnapshot };
export async function revalidateWmiCache(
current: WmiCacheSnapshot,
pull: (ifNoneMatch: string) => Promise<
| { status: 304 }
| { status: 200; rows: Record<string, WmiRow> }
>,
): Promise<RevalidateResult> {
try {
const res = await pull(current.etag);
const now = Date.now();
if (res.status === 304) {
return {
status: "not-modified",
snapshot: { ...current, fetchedAt: now },
};
}
const etag = etagForRows(res.rows);
return {
status: "modified",
snapshot: {
etag,
fetchedAt: now,
maxAgeMs: current.maxAgeMs,
rows: res.rows,
},
};
} catch (error) {
return {
status: "error",
error: error instanceof Error ? error : new Error(String(error)),
snapshot: current,
};
}
}
If your puller talks to a private object store or a build artifact, emulate 304 by comparing the origin's hash header to ifNoneMatch. If you always download, compute etagForRows locally and treat equal etags as not-modified so you still avoid rewriting consumers.
Client-facing API shape
Expose a small read API that never leaks mid-swap tables:
export function lookupWmi(
snap: WmiCacheSnapshot,
wmi: string,
): WmiRow | null {
const key = wmi.trim().toUpperCase().slice(0, 3);
return snap.rows[key] ?? null;
}
Swap WmiCacheSnapshot references atomically (single assignment to a module-level let, or React/query cache set). Readers should never see half of old rows and half of new ones.
When etag matches but product still changes
Sometimes you change how you display manufacturer strings without the WMI map moving. Keep a separate presentationVersion for UI copy. Do not burn the content etag for that; otherwise every copy tweak looks like a data change and confuses ops dashboards.
Operational tips
- Log
revalidate.not_modifiedvsrevalidate.modifiedas separate counters - On pull error, keep serving stale rows and surface a quiet "WMI directory may be outdated" only in admin views
- Persist etag next to rows so process restarts do not force a fake "modified"
- Prefer strong hashes over timestamps; clocks lie across regions
Takeaway
ETag/If-None-Match is not only for CDNs. For a local WMI cache, a content validator plus max-age gives you cheap no-op refreshes and honest updates when NHTSA assignments move. Serve fresh rows from disk, revalidate with a validator when stale, and treat identical mapped tables as 304 even if the download path is clumsy. Your VIN sanity checks stay fast without pretending the manufacturer directory is frozen forever.
I maintain VIN Lookup, a free VIN decode based on NHTSA data.
Top comments (0)