first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
# `bustSpecifier`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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`.