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