3.4 KiB
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=Nquery suffix. - Preserves unrelated query parameters and hash fragments.
- Works with
file://,http://,https://, anddata:URLs. - Side-effect-free, allocation-light (one
URLinstance per call). - Strict input contract: throws synchronously on a malformed URL.
Usage: Basic
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:
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
export function bustSpecifier(url: string, version: number): string;
url: an absolute URL (afile://URL is typical inside the loader, but any URL supported by the WHATWGURLparser 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.