# @suaveplan/storage-uri - Universal URI-routed dispatch over @suaveplan StorageLike adapters. One resolver, one URI string in, one stream/byte/metadata out; scheme→plugin registry, AbortSignal everywhere, full OTel surface, zero I/O of its own. Universal URI-routed dispatch over `@suaveplan` `StorageLike` adapters. One resolver, one URI string in, one stream/byte/metadata out. Scheme→plugin registry; the resolver performs no I/O of its own — every operation delegates to a registered `StorageLike` after URI parsing. ## Documentation tree - [conformance](./src/conformance/conformance.md) — `runUriPluginConformance(plugin, fixtures)` is the test harness adapter packages call from their own suites to prove th… - [conformance](./src/conformance/conformance.md) — `runUriPluginConformance(plugin, fixtures)` is the test harness adapter packages call from their own suites to prove th… - [error-codes](./src/error-codes/error-codes.md) — Defines the canonical `STORAGE_URI_CODES` registry and the `StorageUriError` class for the `@suaveplan/storage-uri` pac… - [error-codes](./src/error-codes/error-codes.md) — Defines the canonical `STORAGE_URI_CODES` registry and the `StorageUriError` class for the `@suaveplan/storage-uri` pac… - [otel](./src/otel/otel.md) — Owns the OpenTelemetry instrument bag for `@suaveplan/storage-uri`: meter, tracer, and the six counter + one histogram… - [otel](./src/otel/otel.md) — Owns the OpenTelemetry instrument bag for `@suaveplan/storage-uri`: meter, tracer, and the six counter + one histogram… - [parse-uri](./src/parse-uri/parse-uri.md) — `parseUri(uri)` is the RFC 3986 subset parser the resolver uses to extract a scheme, optional authority, path, and opti… - [parse-uri](./src/parse-uri/parse-uri.md) — `parseUri(uri)` is the RFC 3986 subset parser the resolver uses to extract a scheme, optional authority, path, and opti… - [resolver](./src/resolver/resolver.md) — The `StorageUriResolver` class is the entry point for URI-routed dispatch in `@suaveplan/storage-uri`. Constructed once… - [resolver.list](./src/resolver/resolver.list.md) — Implements the `list(uri, options?, signal?)` semantics. Treats the input URI's key portion as a listing prefix, delega… - [resolver](./src/resolver/resolver.md) — The `StorageUriResolver` class is the entry point for URI-routed dispatch in `@suaveplan/storage-uri`. Constructed once… - [resolver.streams](./src/resolver/resolver.streams.md) — Implements the `open(uri, signal?)` semantics for the resolver: forward a native `createReadStream` when the adapter ex… - [resolver.writes](./src/resolver/resolver.writes.md) — Implements the `write(uri, body, signal?)` semantics. Accepts either `Uint8Array` or `ReadableStream` for t… ## Install ```bash bun add @suaveplan/storage-uri ``` Pair with one or more adapter packages — each ships its own `uriPlugin`: ```bash bun add @suaveplan/storage-file @suaveplan/storage-http ``` ## Modules | Subpath | What | |---|---| | `@suaveplan/storage-uri` | Full surface — re-exports everything below | | `@suaveplan/storage-uri/error-codes` | `STORAGE_URI_CODES`, `StorageUriError` | | `@suaveplan/storage-uri/parse-uri` | `parseUri(uri)` RFC 3986 subset | | `@suaveplan/storage-uri/resolver` | `StorageUriResolver`, `createStorageUriResolver` | | `@suaveplan/storage-uri/otel` | OTel meter+tracer instruments | | `@suaveplan/storage-uri/conformance` | `runUriPluginConformance` for adapter authors | | `@suaveplan/storage-uri/types` | `UriPlugin`, `ParsedUri`, `StatResult`, list types | ## Quick start ```ts import { createStorageUriResolver } from "@suaveplan/storage-uri"; import { fileUriPlugin } from "@suaveplan/storage-file"; import { httpsUriPlugin, createHttpStorage } from "@suaveplan/storage-http"; const resolver = createStorageUriResolver([ fileUriPlugin({ rootDir: "/var/cache/media" }), httpsUriPlugin(createHttpStorage({ baseUrl: "https://cdn.example.com" })), ]); const stream = await resolver.open("https://cdn.example.com/clip/master.m3u8"); await resolver.write("file:///cache/poster.jpg", thumbBytes); ``` ## Cross-scheme media-pipeline recipe ```ts const resolver = createStorageUriResolver([ fileUriPlugin({ rootDir: "/var/media" }), httpsUriPlugin(httpStorage), ]); async function thumbnail(srcUri: string, dstUri: string): Promise { const src = await resolver.open(srcUri); const out = await encodeJpegFromStream(src); await resolver.write(dstUri, out); } ``` A single call site composes a filesystem cache, a CDN origin, and any other adapter you register — the resolver hides which scheme served the request. ## Authoring a third-party `UriPlugin` ```ts import type { UriPlugin } from "@suaveplan/storage-uri"; import type { StorageLike } from "@suaveplan/types"; export function memoryUriPlugin(adapter: StorageLike): UriPlugin { return { scheme: "memory", adapter, parse(uri) { const url = new URL(uri); return { key: url.pathname.replace(/^\//, ""), meta: { authority: url.host } }; }, build(key, meta) { const auth = meta?.authority ?? "default"; return `memory://${auth}/${key}`; }, }; } ``` Verify your plugin against the conformance harness from your own test suite: ```ts import { runUriPluginConformance } from "@suaveplan/storage-uri/conformance"; runUriPluginConformance(memoryUriPlugin(adapter), { validUris: ["memory://x/a"], invalidUris: ["bad", "memory:"], buildRoundTrip: [{ key: "a", uri: "memory://x/a", meta: { authority: "x" } }], }); ``` ## Error codes | Code | When | |---|---| | `STORAGE_URI.UNKNOWN_SCHEME` | URI scheme has no plugin registered | | `STORAGE_URI.INVALID_URI` | `parseUri` rejected the input | | `STORAGE_URI.SCHEME_CONFLICT` | Two distinct plugins claim the same scheme | | `STORAGE_URI.NOT_FOUND` | Adapter returned `undefined` for `get`/`open` | | `STORAGE_URI.LIST_UNSUPPORTED` | Plugin has no `build()` | | `STORAGE_URI.WRITE_UNSUPPORTED` | Adapter threw on `set` (e.g. read-only HTTP) | | `STORAGE_URI.ABORTED` | `AbortSignal` fired | | `STORAGE_URI.ADAPTER_ERROR` | Adapter threw something else — cause preserved | ## OpenTelemetry Tracer + meter name: `@suaveplan/storage-uri`. Counters: `reads_total`, `writes_total`, `deletes_total`, `errors_total`, `stream_fallback_total`, `buffered_write_total`. Histogram: `op_duration_ms{op,scheme,outcome}`. Spans on every public op with `storage.uri`, `storage.uri.scheme`, `storage.uri.key` attributes; `storage.uri` is redacted (query string + fragment stripped) so presigned URLs cannot leak into telemetry sinks. ## License MIT