@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.
Ships ordered multi-source resolution with cycle detection, depth cap,
mirror writes, list union, AbortSignal propagation, and OTel +
EventBusLike observability.
Documentation tree
- classify — The
classifymodule hosts the pure error-classification function that governs fall-through eligibility for every walk…- classify — The
classifymodule hosts the pure error-classification function that governs fall-through eligibility for every walk…
- classify — The
- conformance — The
fallback-conformancemodule exportsrunFallbackConformance, a black-box harness consumers call from their own i…- fallback-conformance — The
fallback-conformancemodule exportsrunFallbackConformance, a black-box harness consumers call from their own i…
- fallback-conformance — The
- error-codes — The
error-codesmodule hosts the structured-error registry for the@suaveplan/storage-uri-plugin-fallbackpackage.…- error-codes — The
error-codesmodule hosts the structured-error registry for the@suaveplan/storage-uri-plugin-fallbackpackage.…
- error-codes — The
- events — The
observability/eventsmodule exposes thin emit helpers that forward the package's five canonical event topics to a… - otel — The
observability/otelmodule owns the OpenTelemetry instrument bundle for the package: the per-candidate `storage_ur… - delete — The
ops/deletemodule implementsfallbackDelete, the mirror-aware removal operation. It dispatchesremoveto ever… - list — The
ops/listmodule implementsfallbackList, the enumeration operation forfallback://URIs. It invokesliston… - metadata — The
ops/metadatamodule hostsfallbackExistsandfallbackStat, the metadata-side adapter operations. Both walk th… - read — The
ops/readmodule implements the read-side of the fallback adapter surface. It wires thefallback://URI parser,… - write — The
ops/writemodule implements the write-side semantics of the fallback adapter:fallbackSetfor byte writes and `… - abort-any — The
abort-anymodule provides a universalAbortSignalcomposition helper. It prefers the native `AbortSignal.a… - parse-fallback-uri — The
parse-fallback-urimodule owns the canonical grammar for thefallback://URI scheme. It parses input strings in… - plugin — The
pluginmodule hosts thefallbackUriPluginfactory — the public entry point of the package. The factory validate…- plugin — The
pluginmodule hosts thefallbackUriPluginfactory — the public entry point of the package. The factory validate…
- plugin — The
- recursion — The
depth-contextmodule tracks the recursion state threaded through nestedfallback://dispatches: the per-call de…- depth-context — The
depth-contextmodule tracks the recursion state threaded through nestedfallback://dispatches: the per-call de…
- depth-context — The
- parallel — The
walk/parallelmodule implements thePromise.any-style race strategy activated whenpolicy.maxParallel > 1. It… - sequential — The
walk/sequentialmodule implements the default ordered fall-through walk strategy. It iterates the candidate list…
Install
bun add @suaveplan/storage-uri-plugin-fallback
URI grammar
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
maxCandidatesis 16; hard ceiling 64. - Each candidate may itself be a
fallback://URI (nested up topolicy.maxDepth, default 4 / hard ceiling 8).
Use the bundled buildFallbackUri helper rather than handcrafting:
import { buildFallbackUri } from "@suaveplan/storage-uri-plugin-fallback";
const uri = buildFallbackUri([
"cdn://zone-eu/avatars/alice.jpg",
"drive://primary/avatars/alice.jpg",
]);
Simple example
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
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—FALLBACK_URI.*registry +FallbackUriError.types/policy.types—FallbackPolicy,FallbackPluginOptions, event payloads.types/candidate.types—CandidateError,CandidateAttempt,MirrorOutcome.parse-uri/parse-fallback-uri— grammar, parser, builder.parse-uri/abort-any—AbortSignal.anyshim.classify/classify— error class table.recursion/depth-context— depth + visited set + registry.walk/sequential— ordered fall-through.walk/parallel— race + abort.ops/read,write,metadata,delete,list— per-operation surfaces.observability/events—EventBusLikeemit helpers.observability/otel— OTel instruments.plugin/plugin—fallbackUriPluginfactory.conformance/fallback-conformance—runFallbackConformanceconsumer harness.