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

188 lines
9.2 KiB
Markdown

# @suaveplan/storage-uri-plugin-fallback - Universal `fallback://` URI plugin for @suaveplan/storage-uri: ordered multi-source resolution with cycle detection, depth cap, mirror writes, list union, AbortSignal propagation, and OTel + EventBus observability.
Universal `fallback://` URI plugin for
[`@suaveplan/storage-uri`](https://github.com/SuavePlan/suaveplan/tree/main/packages/storage/storage-uri).
Ships ordered multi-source resolution with cycle detection, depth cap,
mirror writes, list union, AbortSignal propagation, and OTel +
EventBusLike observability.
<!-- begin:auto-docs -->
## Documentation tree
- [classify](./src/classify/classify.md) — The `classify` module hosts the pure error-classification function that governs fall-through eligibility for every walk…
- [classify](./src/classify/classify.md) — The `classify` module hosts the pure error-classification function that governs fall-through eligibility for every walk…
- [conformance](./src/conformance/fallback-conformance.md) — The `fallback-conformance` module exports `runFallbackConformance`, a black-box harness consumers call from their own i…
- [fallback-conformance](./src/conformance/fallback-conformance.md) — The `fallback-conformance` module exports `runFallbackConformance`, a black-box harness consumers call from their own i…
- [error-codes](./src/error-codes/error-codes.md) — The `error-codes` module hosts the structured-error registry for the `@suaveplan/storage-uri-plugin-fallback` package.…
- [error-codes](./src/error-codes/error-codes.md) — The `error-codes` module hosts the structured-error registry for the `@suaveplan/storage-uri-plugin-fallback` package.…
- [events](./src/observability/events.md) — The `observability/events` module exposes thin emit helpers that forward the package's five canonical event topics to a…
- [otel](./src/observability/otel.md) — The `observability/otel` module owns the OpenTelemetry instrument bundle for the package: the per-candidate `storage_ur…
- [delete](./src/ops/delete.md) — The `ops/delete` module implements `fallbackDelete`, the mirror-aware removal operation. It dispatches `remove` to ever…
- [list](./src/ops/list.md) — The `ops/list` module implements `fallbackList`, the enumeration operation for `fallback://` URIs. It invokes `list` on…
- [metadata](./src/ops/metadata.md) — The `ops/metadata` module hosts `fallbackExists` and `fallbackStat`, the metadata-side adapter operations. Both walk th…
- [read](./src/ops/read.md) — The `ops/read` module implements the read-side of the fallback adapter surface. It wires the `fallback://` URI parser,…
- [write](./src/ops/write.md) — The `ops/write` module implements the write-side semantics of the fallback adapter: `fallbackSet` for byte writes and `…
- [abort-any](./src/parse-uri/abort-any.md) — The `abort-any` module provides a universal `AbortSignal` composition helper. It prefers the native `AbortSignal.a…
- [parse-fallback-uri](./src/parse-uri/parse-fallback-uri.md) — The `parse-fallback-uri` module owns the canonical grammar for the `fallback://` URI scheme. It parses input strings in…
- [plugin](./src/plugin/plugin.md) — The `plugin` module hosts the `fallbackUriPlugin` factory — the public entry point of the package. The factory validate…
- [plugin](./src/plugin/plugin.md) — The `plugin` module hosts the `fallbackUriPlugin` factory — the public entry point of the package. The factory validate…
- [recursion](./src/recursion/depth-context.md) — The `depth-context` module tracks the recursion state threaded through nested `fallback://` dispatches: the per-call de…
- [depth-context](./src/recursion/depth-context.md) — The `depth-context` module tracks the recursion state threaded through nested `fallback://` dispatches: the per-call de…
- [parallel](./src/walk/parallel.md) — The `walk/parallel` module implements the `Promise.any`-style race strategy activated when `policy.maxParallel > 1`. It…
- [sequential](./src/walk/sequential.md) — The `walk/sequential` module implements the default ordered fall-through walk strategy. It iterates the candidate list…
<!-- end:auto-docs -->
## Install
```bash
bun add @suaveplan/storage-uri-plugin-fallback
```
## URI grammar
```text
fallback://?u=<pct-encoded-uri>&u=<pct-encoded-uri>[&u=...]
```
- Always empty authority and empty path.
- One or more ordered `u=` parameters.
- Encoding via `URLSearchParams` (RFC 3986-compliant).
- Default `maxCandidates` is 16; hard ceiling 64.
- Each candidate may itself be a `fallback://` URI (nested up to
`policy.maxDepth`, default 4 / hard ceiling 8).
Use the bundled `buildFallbackUri` helper rather than handcrafting:
```ts
import { buildFallbackUri } from "@suaveplan/storage-uri-plugin-fallback";
const uri = buildFallbackUri([
"cdn://zone-eu/avatars/alice.jpg",
"drive://primary/avatars/alice.jpg",
]);
```
## Simple example
```ts
import { createStorageUriResolver } from "@suaveplan/storage-uri";
import {
buildFallbackUri,
fallbackUriPlugin,
} from "@suaveplan/storage-uri-plugin-fallback";
// Late-bind the resolver so the fallback plugin can recurse through it.
let resolver: ReturnType<typeof createStorageUriResolver> | undefined;
const lazy = new Proxy({}, {
get(_t, prop) {
const v = (resolver as unknown as Record<string, unknown>)[prop as string];
return typeof v === "function" ? v.bind(resolver) : v;
},
}) as ReturnType<typeof createStorageUriResolver>;
resolver = createStorageUriResolver([
cdnPlugin,
drivePlugin,
fallbackUriPlugin({ resolver: lazy }),
]);
const uri = buildFallbackUri([
"cdn://zone-eu/avatars/alice.jpg",
"drive://primary/avatars/alice.jpg",
]);
const bytes = await resolver.read(uri);
```
## Advanced example — mirror writes + dedup list
```ts
import { createEventBus } from "@suaveplan/events";
import {
buildFallbackUri,
fallbackUriPlugin,
type FallbackEventMap,
} from "@suaveplan/storage-uri-plugin-fallback";
const bus = createEventBus<FallbackEventMap>();
bus.on("storage-uri.fallback.write-mirror-partial", (p) => {
console.warn("mirror drift:", p);
});
const plugin = fallbackUriPlugin({
resolver,
policy: {
writeMode: "all",
listFailureMode: "partial",
maxParallel: 2,
},
events: bus,
});
const uri = buildFallbackUri([
"cdn://zone-eu/photos/2026/",
"drive://primary/photos/2026/",
]);
await resolver.write(uri, photoBytes); // mirrors to both
const { uris } = await resolver.list(uri); // union, deduped
```
## Policy reference
| Knob | Default | Notes |
|---|---|---|
| `errorClass` | `"transient"` | `"any"` re-classifies semantic errors as fall-through |
| `maxParallel` | `1` | `>1` enables `Promise.any`-style race |
| `stopOnAbort` | `true` | `false` lets the current candidate finish before throwing `ABORTED` |
| `writeMode` | `"all"` | `"first-success"` returns on first ok + background remainder |
| `deleteMode` | `"any-success"` | `"all"` raises `WRITE_MIRROR_FAILED` on any reject |
| `listMode` | `"union"` | `"concat"` returns raw concatenation |
| `listFailureMode` | `"throw"` | `"partial"` skips failed legs |
| `maxDepth` | `4` | Hard ceiling 8 |
| `maxCandidates` | `16` | Hard ceiling 64 |
## Error codes
`FALLBACK_URI.NO_CANDIDATES`, `INVALID_URI`, `ALL_CANDIDATES_FAILED`,
`WRITE_MIRROR_FAILED`, `LIST_UNION_FAILED`, `ABORTED`,
`MAX_DEPTH_EXCEEDED`, `CIRCULAR_REFERENCE` — every code is reachable
from a real call site exercised by the test suite.
## Complementarity with `@suaveplan/storage-mux`
`storage-mux` composes pre-constructed `StorageLike` backends at the
adapter layer; `storage-uri-plugin-fallback` composes URI strings at the
scheme layer. They share a classification table so transient/semantic/
abort semantics line up across the stack. Use `storage-mux` when you
own the backend instances; use `fallback://` when callsites receive
URIs from manifests, config, or the wire.
## Modules
- [`error-codes`](./src/error-codes/error-codes.md) — `FALLBACK_URI.*`
registry + `FallbackUriError`.
- [`types/policy.types`](./src/types/policy.types.md) — `FallbackPolicy`,
`FallbackPluginOptions`, event payloads.
- [`types/candidate.types`](./src/types/candidate.types.md) —
`CandidateError`, `CandidateAttempt`, `MirrorOutcome`.
- [`parse-uri/parse-fallback-uri`](./src/parse-uri/parse-fallback-uri.md)
— grammar, parser, builder.
- [`parse-uri/abort-any`](./src/parse-uri/abort-any.md) —
`AbortSignal.any` shim.
- [`classify/classify`](./src/classify/classify.md) — error class table.
- [`recursion/depth-context`](./src/recursion/depth-context.md) — depth +
visited set + registry.
- [`walk/sequential`](./src/walk/sequential.md) — ordered fall-through.
- [`walk/parallel`](./src/walk/parallel.md) — race + abort.
- [`ops/read`](./src/ops/read.md),
[`write`](./src/ops/write.md),
[`metadata`](./src/ops/metadata.md),
[`delete`](./src/ops/delete.md),
[`list`](./src/ops/list.md) — per-operation surfaces.
- [`observability/events`](./src/observability/events.md) — `EventBusLike`
emit helpers.
- [`observability/otel`](./src/observability/otel.md) — OTel instruments.
- [`plugin/plugin`](./src/plugin/plugin.md) — `fallbackUriPlugin` factory.
- [`conformance/fallback-conformance`](./src/conformance/fallback-conformance.md)
— `runFallbackConformance` consumer harness.