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

147 lines
6.6 KiB
Markdown

# @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.
<!-- begin:auto-docs -->
## 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<Uint8Array>` for t…
<!-- end:auto-docs -->
## 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<void> {
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