first commit
This commit is contained in:
167
docs/persistence/storage/storage-uri-plugin-bundle/README.md
Normal file
167
docs/persistence/storage/storage-uri-plugin-bundle/README.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# @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.
|
||||
|
||||
Reference in New Issue
Block a user