# @suaveplan/storage-uri-plugin-bundle - Per-package bundle:// URI scheme plugin for @suaveplan/storage-uri. Universal; resolves bundle:/// 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:///` 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. ## 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:///` grammar declared in `design.md §1` and rejects every malfo… - [parse](./src/parse/parse.md) — `parse.ts` enforces the `bundle:///` 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… ## 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=-` (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(); 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.