import type { Adapter, FilesOperation, FilesPlugin } from "../index.js"; import { FilesError } from "../internal/errors.js"; /** * Decide whether a failed backend should fail over to the next one. Receives the * error normalized to a {@link FilesError} (so `code` and `aborted` are always * available) and returns `true` to try the next backend, `false` to surface the * error as-is. * * The default ({@link defaultShouldFailover}) fails over **only** on `Provider` * errors — network failures, timeouts (`timedOut`), and 5xx, i.e. "the backend * is down" — and never on a caller-aborted request. A `NotFound` / `Unauthorized` / `Conflict` / * `ReadOnly` is a *definitive answer from a healthy backend*, so it's surfaced * rather than masked by probing a replica. Pass your own to widen this (e.g. * also fail over on `NotFound` to read through to a replica) or narrow it. */ export type ShouldFailover = (error: FilesError) => boolean; /** * Reported (fire-and-forget) each time an operation fails over from one backend * to the next. Lightweight and caller-facing by design — it carries indices into * the `[primary, ...secondaries]` chain, never an internal prefixed path. Throwing * from the handler is swallowed, so it can't break the operation. */ export interface FailoverEvent { /** Which verb failed over. */ operation: FilesOperation["kind"]; /** * Index of the backend that just errored, into `[primary, ...secondaries]` — * `0` is the primary, `1` the first secondary, and so on. */ failed: number; /** Index of the backend being tried next. */ next: number; /** The error (normalized) that triggered the failover. */ error: FilesError; } export interface FailoverOptions { /** * The backup adapter(s) to fall back to, tried in order after the primary. A * single {@link Adapter} or an array — pass several for a multi-region failover * chain. Each is driven through its own internal {@link Files} (so it gets the * same retry, capability gating, and `StoredFile` normalization the primary * does) and receives **caller-facing keys** — the instance `prefix` is **not** * applied to it, so give each secondary its own bucket / container (or avoid a * client `prefix` on a failover instance). */ secondaries: Adapter | Adapter[]; /** * Decide whether a backend's error should trigger a fail over to the next one. * Defaults to {@link defaultShouldFailover} — fail over only on `Provider` * errors (network / timeout / 5xx), never on an aborted request or a * definitive answer (`NotFound`, `Unauthorized`, …). See {@link ShouldFailover}. */ shouldFailover?: ShouldFailover; /** * Called (fire-and-forget) whenever an operation fails over to the next * backend — wire it to your metrics / alerting to learn a backend is degraded. * A throw from it is swallowed. See {@link FailoverEvent}. */ onFailover?: (event: FailoverEvent) => void; } /** * Read/write the primary and **fall back to one or more secondary adapters when * a backend is down** — a live, per-operation failover chain. The **primary** is * the instance's own adapter (reached through the rest of the onion, so it keeps * retry and prefixing); the **secondaries** are backup adapters passed in * {@link FailoverOptions.secondaries}, tried in order. * * Every verb runs the same way: try the primary; if it throws and * {@link FailoverOptions.shouldFailover} says so (by default, only on a * `Provider` error — network / timeout / 5xx), try the next backend, and so on. * The first backend that succeeds wins; if the chain is exhausted, the last * error is thrown. A definitive answer from a healthy backend (`NotFound`, * `Unauthorized`, an aborted request) is **not** failed over — it's surfaced * directly, so a genuine 404 stays a 404 instead of being masked by a replica. * * This is the **availability** counterpart to `tiering()` (which *partitions* * data by key/size) — failover treats each secondary as a full replica of one * namespace, so it never splits or merges across backends: * - **reads** (`download` / `head` / `exists` / `url` / `list`) return the first * reachable backend's answer; `list` is **not** merged (no composite cursor). * - **writes** (`upload` / `delete` / `copy` / `move`) land on the first * reachable backend — it does **not** fan out to every backend (that's * `replication()`); a write that fails over during a primary outage lands only * on the secondary. * - **`signedUploadUrl`** signs against the first reachable backend. * - a **streaming** `upload` (a `ReadableStream` body) can't be replayed, so it * runs against the primary **alone** and isn't failed over. * * It's **body-transparent** — never buffers or transforms bytes — and adds no * surface (`wrap` only), so it works with plain `new Files({ plugins })`. * * Placement and prefixes: * - Place it **last** (innermost) so body-transforming plugins (`encryption()`, * `compression()`) wrap it and apply to **every** backend. * - Secondaries receive caller-facing keys (no instance `prefix`), so give each * its own bucket / container and avoid a client `prefix` on a failover * instance. * * Consistency: failover buys availability, not convergence. An object written to * a secondary while the primary was down is invisible to reads once the primary * recovers (reads hit the primary first and it answers `NotFound`). Reconcile * with `sync` / `transfer`, keep the replica current with `replication()`, or * pass a `shouldFailover` that also fails over on `NotFound` to read through. * * @param options `{ secondaries, shouldFailover?, onFailover? }` — see * {@link FailoverOptions}. * @example * ```ts * import { Files } from "files-sdk"; * import { s3 } from "files-sdk/s3"; * import { failover } from "files-sdk/failover"; * * const files = new Files({ * adapter: s3({ bucket: "primary", region: "us-east-1" }), // primary * plugins: [ * failover({ * secondaries: s3({ bucket: "backup", region: "us-west-2" }), * onFailover: ({ operation, failed }) => * console.warn(`failover: ${operation} fell off backend ${failed}`), * }), * ], * }); * * await files.download("report.pdf"); // primary, or the backup if it's down * ``` */ export declare const failover: (options: FailoverOptions) => FilesPlugin; //# sourceMappingURL=index.d.ts.map