3.3 KiB
Scalar bundled-asset registry
(no submodules)
Purpose
The assets module owns the in-package bundled Scalar runtime that
ships with the package — a small self-contained JS bootstrap and a tiny
companion stylesheet — plus the asset-URL resolver consumed by the
handler factory. Air-gapped hosts toggle assetMode = "bundled" and the
shell loads JS / CSS from <uiEndpoint>/assets/<hash>.<ext>; hosts on
the public internet toggle assetMode = "cdn" and the shell loads the
upstream @scalar/api-reference bundle from JSDelivr. Either way the
shell embeds the bootstrap config block at render time so the runtime
target is fixed at handler construction.
Features
SCALAR_ASSETS— frozen registry keyed by logical asset name, every entry carrying its bytes, MIME type, and an FNV-1a-32 cache-busting hash.hashedAssetPath(name)— formats the<stem>.<hash>.<ext>filename embedded in the rendered shell.resolveAsset(filename)— reverse lookup tolerating stale hashes (rolling deploy) and missing-hash bare filenames.resolveAssetUrl({ mode, cdnUrl, uiEndpoint, probe })— returns the Scalar runtime URL the shell should embed.bundled+ missing asset raisesOPENAPI_UI_SCALAR.ASSETS_MISSING.defaultBundledAssetProbe— checks the in-package registry is non-empty (default behaviour); tests inject a stub to simulate the stripped-bundle case.
Usage: Basic
import { resolveAssetUrl } from "@suaveplan/api-openapi-ui-scalar/assets";
const runtime = resolveAssetUrl({
mode: "cdn",
cdnUrl: "https://cdn.jsdelivr.net/npm/@scalar/api-reference",
uiEndpoint: "/docs/ui/scalar",
});
console.log(runtime.url);
Usage: Advanced
import {
resolveAssetUrl,
type BundledAssetProbe,
} from "@suaveplan/api-openapi-ui-scalar/assets";
const stubbedProbe: BundledAssetProbe = { hasBundle: () => false };
// Simulate the air-gapped "asset stripped from dist" failure case —
// `resolveAssetUrl` throws OPENAPI_UI_SCALAR.ASSETS_MISSING.
try {
resolveAssetUrl({
mode: "bundled",
cdnUrl: "",
uiEndpoint: "/docs/ui/scalar",
probe: stubbedProbe,
});
} catch (err) {
console.error("bundled asset is missing", err);
}
API Reference
ScalarAssetinterface —{ name, content, contentType, hash }.SCALAR_ASSETSReadonlyMap<string, ScalarAsset>— frozen registry.fnv1a32(input)— 8-char hex FNV-1a-32 of the asset bytes.hashedAssetPath(name)— throwsASSET_NOT_REGISTEREDwhen missing.resolveAsset(filename)— returns the asset entry or throwsASSET_NOT_REGISTERED.resolveAssetUrl(input)— returns{ mode, url }; throwsASSETS_MISSINGin bundled mode without a bundle.listScalarAssetNames()— listing of registered names for tests.
Implementation Notes
The bundled JS bootstrap is intentionally minimal — it parses the
<script id="scalar-config" type="application/json"> block and renders
a status line + path listing so air-gapped hosts have a functional
fallback. Operators that need the full Scalar React UI install the
upstream @scalar/api-reference and overwrite dist/assets/scalar.js
at build time; the FNV-1a hash refreshes automatically. The hash is NOT
security-relevant — it exists only to invalidate browser caches.