# `bustSpecifier` _(no submodules)_ ## Purpose Compute the cache-busted ESM specifier used by the API plugin host during a hot reload. Bun's ESM cache is keyed on the full specifier string (including query parameters), so `file:///plugin/index.js` and `file:///plugin/index.js?v=2` are distinct cache entries even though both resolve to the same file on disk. `bustSpecifier` is the canonical helper the host calls on every reload to produce a fresh specifier — bumping the importing module's URL forces Bun to re-evaluate the entire transitive dependency subgraph without paying for an explicit cache invalidation API. The function is pure and idempotent under monotonically-increasing version inputs: repeated calls **replace** the existing `?v=N` segment rather than concatenate, so a long-lived host can bust the same plugin thousands of times without growing the specifier string. This is the canonical algorithm referenced by design §8.2. ## Features - Replaces (never concatenates) the `?v=N` query suffix. - Preserves unrelated query parameters and hash fragments. - Works with `file://`, `http://`, `https://`, and `data:` URLs. - Side-effect-free, allocation-light (one `URL` instance per call). - Strict input contract: throws synchronously on a malformed URL. ## Usage: Basic ```ts import { bustSpecifier } from "@suaveplan/api-loader/cache-bust"; const specifier = bustSpecifier("file:///opt/plugin/index.js", 1); // → "file:///opt/plugin/index.js?v=1" const next = bustSpecifier(specifier, 2); // → "file:///opt/plugin/index.js?v=2" (NOT "?v=1?v=2") const mod = await import(next); ``` ## Usage: Advanced Within a reload loop, the host keeps a monotonically-increasing counter and busts the specifier on every flip. Other parameters (e.g. an integrity hash) survive the rewrite: ```ts import { bustSpecifier } from "@suaveplan/api-loader/cache-bust"; let reload = 0; const base = "file:///opt/plugin/index.js?integrity=abc"; function nextSpecifier(): string { reload += 1; return bustSpecifier(base, reload); } // 1st call → "file:///opt/plugin/index.js?integrity=abc&v=1" // 2nd call → "file:///opt/plugin/index.js?integrity=abc&v=2" ``` ## API Reference ```ts export function bustSpecifier(url: string, version: number): string; ``` - `url`: an absolute URL (a `file://` URL is typical inside the loader, but any URL supported by the WHATWG `URL` parser works). - `version`: a non-negative integer reload counter. Zero is permitted as a boundary case (used by the host on the initial load). Returns the input URL with its `v` query parameter set to the string form of `version`. Other query parameters, the path, the fragment, and the authority are preserved verbatim. ## Implementation Notes The implementation uses the WHATWG `URL` API (`URL` and `URLSearchParams`) because it round-trips correctly: `URLSearchParams.set("v", ...)` replaces any existing `v` entry rather than appending a duplicate. Hand-rolled string concatenation (`${url}?v=${version}`) was rejected because it cannot detect an existing suffix — repeated busts under that approach would grow the specifier without bound (`?v=1?v=2?v=3`), and Bun's cache would treat each mutation as a distinct module forever, defeating the GC contract the rollback journal relies on. The function throws `TypeError` from the WHATWG parser on a malformed URL input; the host's reload path catches and rewraps as `LOADER.MODULE_IMPORT_FAILED`.