Files
2026-05-27 23:47:53 +01:00
..
2026-05-27 23:47:53 +01:00
2026-05-27 23:47:53 +01:00
2026-05-27 23:47:53 +01:00
2026-05-27 23:47:53 +01:00
2026-05-27 23:47:53 +01:00
2026-05-27 23:47:53 +01:00
2026-05-27 23:47:53 +01:00
2026-05-27 23:47:53 +01:00

@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 classify module hosts the pure error-classification function that governs fall-through eligibility for every walk…
    • classify — The classify module hosts the pure error-classification function that governs fall-through eligibility for every walk…
  • conformance — The fallback-conformance module exports runFallbackConformance, a black-box harness consumers call from their own i…
    • fallback-conformance — The fallback-conformance module exports runFallbackConformance, a black-box harness consumers call from their own i…
  • error-codes — The error-codes module hosts the structured-error registry for the @suaveplan/storage-uri-plugin-fallback package.…
    • error-codes — The error-codes module hosts the structured-error registry for the @suaveplan/storage-uri-plugin-fallback package.…
  • events — The observability/events module exposes thin emit helpers that forward the package's five canonical event topics to a…
  • otel — The observability/otel module owns the OpenTelemetry instrument bundle for the package: the per-candidate `storage_ur…
  • delete — The ops/delete module implements fallbackDelete, the mirror-aware removal operation. It dispatches remove to ever…
  • list — The ops/list module implements fallbackList, the enumeration operation for fallback:// URIs. It invokes list on…
  • metadata — The ops/metadata module hosts fallbackExists and fallbackStat, the metadata-side adapter operations. Both walk th…
  • read — The ops/read module implements the read-side of the fallback adapter surface. It wires the fallback:// URI parser,…
  • write — The ops/write module implements the write-side semantics of the fallback adapter: fallbackSet for byte writes and `…
  • abort-any — The abort-any module provides a universal AbortSignal composition helper. It prefers the native `AbortSignal.a…
  • parse-fallback-uri — The parse-fallback-uri module owns the canonical grammar for the fallback:// URI scheme. It parses input strings in…
  • plugin — The plugin module hosts the fallbackUriPlugin factory — the public entry point of the package. The factory validate…
    • plugin — The plugin module hosts the fallbackUriPlugin factory — the public entry point of the package. The factory validate…
  • recursion — The depth-context module tracks the recursion state threaded through nested fallback:// dispatches: the per-call de…
    • depth-context — The depth-context module tracks the recursion state threaded through nested fallback:// dispatches: the per-call de…
  • parallel — The walk/parallel module implements the Promise.any-style race strategy activated when policy.maxParallel > 1. It…
  • sequential — The walk/sequential module 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 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:

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