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