# @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. ## 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… ## Install ```bash bun add @suaveplan/storage-uri-plugin-fallback ``` ## URI grammar ```text fallback://?u=&u=[&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 | undefined; const lazy = new Proxy({}, { get(_t, prop) { const v = (resolver as unknown as Record)[prop as string]; return typeof v === "function" ? v.bind(resolver) : v; }, }) as ReturnType; 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(); 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.