Files
skills/docs/persistence/storage/storage-uri-plugin-bundle/README.md
2026-05-29 08:20:56 +01:00

168 lines
10 KiB
Markdown

# @suaveplan/storage-uri-plugin-bundle - Per-package bundle:// URI scheme plugin for @suaveplan/storage-uri. Universal; resolves bundle://<package-slug>/<rel-path> against an injectable rootUrl via fetch on every read. Read-only StorageLike shim, HTTP Range support with 200/206/416 disambiguation, AbortSignal propagation, optional in-memory LRU, full OpenTelemetry surface.
Per-package `bundle://` URI scheme for `@suaveplan/storage-uri`.
`bundle://<package-slug>/<rel-path>` lets a consuming package ship binary
assets — impulse responses, sample fonts, reference images, fixture
bundles — alongside its npm tarball and resolve them through the existing
`storage-uri` ecosystem. The plugin is universal: zero `node:fs`,
zero browser-only branches, every read goes through an injectable
`FetchLike` against `new URL(encodedRelPath, rootUrl)`. `file:`, `https:`,
`http:`, `data:`, `blob:`, and bundler-virtual schemes all flow through
the same fetch surface.
<!-- begin:auto-docs -->
## Documentation tree
- [abort](./src/abort/abort.md) — `abort.ts` declares the two helpers every read path consumes to honour an `AbortSignal`: `ensureNotAborted` short-circu…
- [abort](./src/abort/abort.md) — `abort.ts` declares the two helpers every read path consumes to honour an `AbortSignal`: `ensureNotAborted` short-circu…
- [cache](./src/cache/cache.md) — `cache.ts` declares the optional in-memory LRU used by the storage shim when a caller passes `options.cache = { maxByte…
- [cache](./src/cache/cache.md) — `cache.ts` declares the optional in-memory LRU used by the storage shim when a caller passes `options.cache = { maxByte…
- [error-codes](./src/error-codes/error-codes.md) — `error-codes.ts` declares the canonical registry of every error code raised by `@suaveplan/storage-uri-plugin-bundle` a…
- [error-codes](./src/error-codes/error-codes.md) — `error-codes.ts` declares the canonical registry of every error code raised by `@suaveplan/storage-uri-plugin-bundle` a…
- [factory](./src/factory/factory.md) — `factory.ts` is the public entry point for `@suaveplan/storage-uri-plugin-bundle`. `bundleUriPlugin(options)` validates…
- [factory](./src/factory/factory.md) — `factory.ts` is the public entry point for `@suaveplan/storage-uri-plugin-bundle`. `bundleUriPlugin(options)` validates…
- [node-fetch](./src/node-fetch/node-fetch.md) — `nodeFileFetch` is a server-tier `FetchLike` that closes a real gap in Node's global `fetch`: undici (the engine backin…
- [node-fetch](./src/node-fetch/node-fetch.md) — `nodeFileFetch` is a server-tier `FetchLike` that closes a real gap in Node's global `fetch`: undici (the engine backin…
- [otel](./src/otel/otel.md) — `otel.ts` exposes the tracer, meter, and instrument bag that every read path in `@suaveplan/storage-uri-plugin-bundle`…
- [otel](./src/otel/otel.md) — `otel.ts` exposes the tracer, meter, and instrument bag that every read path in `@suaveplan/storage-uri-plugin-bundle`…
- [parse](./src/parse/parse.md) — `parse.ts` enforces the `bundle://<package-slug>/<rel-path>` grammar declared in `design.md §1` and rejects every malfo…
- [parse](./src/parse/parse.md) — `parse.ts` enforces the `bundle://<package-slug>/<rel-path>` grammar declared in `design.md §1` and rejects every malfo…
- [range](./src/range/range.md) — `range.ts` implements the building blocks the storage shim uses to issue HTTP `Range` requests and disambiguate the thr…
- [range](./src/range/range.md) — `range.ts` implements the building blocks the storage shim uses to issue HTTP `Range` requests and disambiguate the thr…
- [storage-shim](./src/storage-shim/storage-shim.md) — `storage-shim.ts` declares `BundleStorageShim`, the `StorageLike` adapter the `bundle://` URI plugin exposes. The class…
- [storage-shim.fetch](./src/storage-shim/storage-shim.fetch.md) — `storage-shim.fetch.ts` carries the two cross-cutting helpers every storage-shim method uses: `safeFetch` dispatches an…
- [storage-shim.head](./src/storage-shim/storage-shim.head.md) — `storage-shim.head.ts` implements the two HEAD-routed methods of the bundle storage shim. `performExists` returns a boo…
- [storage-shim](./src/storage-shim/storage-shim.md) — `storage-shim.ts` declares `BundleStorageShim`, the `StorageLike` adapter the `bundle://` URI plugin exposes. The class…
- [storage-shim.range](./src/storage-shim/storage-shim.range.md) — `storage-shim.range.ts` carries the full `getPartial` pipeline: pre-fetch offset/length validation, the `Range: bytes=……
- [streaming](./src/streaming/streaming.md) — `streaming.ts` exposes the single helper the storage shim consumes from its `createReadStream` implementation: given a…
- [streaming](./src/streaming/streaming.md) — `streaming.ts` exposes the single helper the storage shim consumes from its `createReadStream` implementation: given a…
- [types](./src/types/plugin-options.schemas.md) — `plugin-options.schemas.ts` declares the Zod schema that validates `BundleUriPluginOptions` at construction time. The s…
- [plugin-options.schemas](./src/types/plugin-options.schemas.md) — `plugin-options.schemas.ts` declares the Zod schema that validates `BundleUriPluginOptions` at construction time. The s…
<!-- end:auto-docs -->
## Modules
- `./error-codes/error-codes` — `BUNDLE_URI_CODES` (9 codes) + `BundleUriError`.
- `./factory/factory` — `bundleUriPlugin(options)` entry point.
- `./parse/parse` — `parse` + `build` with three-pass traversal prevention.
- `./storage-shim/storage-shim` — `BundleStorageShim` (`StorageLike`).
- `./storage-shim/storage-shim.fetch` — `safeFetch` + `assertSuccess`.
- `./storage-shim/storage-shim.head` — HEAD-backed `exists` / `getMetadata`.
- `./storage-shim/storage-shim.range` — Range pipeline with 206/200/416 disambiguation.
- `./range/range` — `validateRange`, `buildRangeHeader`, `sliceForRange`.
- `./streaming/streaming` — `toReadableStream`.
- `./abort/abort` — `ensureNotAborted` + `wrapAbortError`.
- `./cache/cache` — `UriLruCache`.
- `./otel/otel` — meter + tracer wiring, `recordOp`, fallback counters.
- `./node-fetch` — **server-tier** opt-in `FetchLike` for Node consumers
whose `rootUrl` is a `file://` URL (see [Node users + `file://` roots](#node-users--file-roots)).
## Quick start — register one plugin per consuming package
```ts
import { createStorageUriResolver } from "@suaveplan/storage-uri";
import { bundleUriPlugin } from "@suaveplan/storage-uri-plugin-bundle";
const resolver = createStorageUriResolver({});
// In each consuming package's `register()`:
resolver.register(
bundleUriPlugin({
packageSlug: "audio-ir-room-plugin-club",
rootUrl: new URL("./", import.meta.url),
})
);
const bytes = await resolver.read(
"bundle://audio-ir-room-plugin-club/assets/club.wav"
);
```
## Bundler composition — flatten assets to a CDN URL
Vite, esbuild, and webpack all rewrite `new URL("./assets/x.wav", import.meta.url)`
at build time. Register the plugin with a `rootUrl` pointing at the
flattened asset directory and the runtime fetch transparently lands on
the bundler-produced URL:
```ts
// Vite / esbuild / webpack
import assetsRoot from "./assets/?url"; // → flattened bundle URL
bundleUriPlugin({
packageSlug: "audio-ir-room-plugin-club",
rootUrl: assetsRoot, // bundler-rewritten string is fine
});
```
In production this becomes a real HTTPS URL — `bundle://` still resolves
the relative path inside that root through `fetch`, and the OTel surface
classifies the root as `root_kind="http"`.
## Node users + `file://` roots
Node's global `fetch` (undici, all releases through Node 24) does NOT
support `file:` URLs. If your `rootUrl` is a `file://` URL and you are
running on Node, the default `globalThis.fetch` wiring will reject on
the first `get`. The package ships a dedicated server-tier adapter at
the `./node-fetch` subpath that closes the gap:
```ts
import { bundleUriPlugin } from "@suaveplan/storage-uri-plugin-bundle";
import { nodeFileFetch } from "@suaveplan/storage-uri-plugin-bundle/node-fetch";
const plugin = bundleUriPlugin({
packageSlug: "audio-ir-room-plugin-club",
rootUrl: new URL("./assets/", import.meta.url),
fetch: nodeFileFetch, // ← required on Node when rootUrl is file://
});
```
`nodeFileFetch` reads `file:` URLs via `node:fs/promises`, returns a
real `Response` with `content-length` + `last-modified`, honours `HEAD`
and `Range: bytes=<start>-<end>` (including open-ended `bytes=10-`), and
threads `init.signal` into the underlying read. Any non-`file:` URL
falls through to `globalThis.fetch`, so a single adapter handles both
local-disk and CDN roots without branching at the call site.
Bun's native `fetch` already supports `file:` URLs — Bun consumers can
keep using the default `globalThis.fetch` wiring. The subpath import
exists so the package's default export stays universal-pure (no
`node:*` imports leak into browser, Cloudflare Workers, Deno, or React
Native builds).
## Composition — front the plugin with `storage-plugin-cache`
```ts
import { cachedStorage } from "@suaveplan/storage-plugin-cache";
import { MemoryCache } from "@suaveplan/cache";
const cache = new MemoryCache<Uint8Array>();
const cached = cachedStorage(plugin.adapter, { cache, namespace: "bundle" });
```
## Error codes
`BUNDLE_URI.PACKAGE_MISMATCH`, `PATH_TRAVERSAL`, `READ_ONLY`,
`NOT_FOUND`, `INVALID_URI`, `FETCH_FAILED`, `RANGE_INVALID`,
`RANGE_UNSUPPORTED`, `ABORTED` — all reachable from a real call site
exercised by the test suite.
## OpenTelemetry
- `bundle_uri.op_duration_ms{op, root_kind, outcome}` histogram.
- `bundle_uri.reads_total{root_kind, outcome}` counter.
- `bundle_uri.errors_total{root_kind, code}` counter.
- `bundle_uri.range_fallback_total{root_kind}` counter.
- `bundle_uri.cache_hits_total{root_kind}` /
`bundle_uri.cache_misses_total{root_kind}` counters.
- Per-op span on the `@suaveplan/storage-uri-plugin-bundle` tracer with
`bundle.package`, `bundle.rel_path` (query-stripped + 256-char clip),
`bundle.root_kind` (`file | http | data | blob | other`),
`bundle.outcome` attributes.
Inject a `MeterProvider` with `setBundleUriMeterProvider(provider)` then
`resetInstruments()` to rebind.