import type { BulkError, BulkOptions, Files, StoredFile } from "../index.js"; /** * How `sync` decides a destination object is already up to date — the predicate * that splits the source walk into uploads vs. skips. * * - `"etag"` (the default) — unchanged when size **and** etag both match. etags * are only comparable within one scheme: S3↔S3 single-part uploads match, but * across heterogeneous backends (S3 → R2 / GCS / Azure) or for multipart * objects they differ even for identical bytes, so the object is conservatively * re-uploaded. A missing etag on either side counts as changed. * - `"size"` — unchanged when byte length matches. Comparable across every * backend, but blind to same-size edits; the right default for cross-provider * mirrors. * - a function — full control. Receives the source and destination * {@link StoredFile} metadata and returns `true` when the object should be * skipped (treated as unchanged). * * `lastModified` is deliberately not used: the destination stamps its own upload * time, so it never matches the source after a sync — comparing it would * re-upload everything on every run. */ export type SyncCompare = "etag" | "size" | ((source: StoredFile, dest: StoredFile) => boolean); /** * A single per-key report, delivered to {@link SyncOptions.onProgress} once a * key has settled (failures surface in the result's `errors`, not here). Skips * are reported first, then uploads as each streams through, then prunes. */ export interface SyncProgress { /** Keys settled so far — uploaded plus skipped plus deleted. Monotonically increasing. */ done: number; /** Uploads, skips, and prunes the plan turned up — the denominator for `done`. */ total: number; /** The source key (upload / skip) or destination key (delete) just settled. */ key: string; /** What happened to the key. */ status: "uploaded" | "skipped" | "deleted"; } export interface SyncOptions extends BulkOptions { /** * Only mirror keys under this prefix. Forwarded to `source.listAll`, so it's * scoped on top of the source instance's own `prefix`. Also the default scope * for the destination walk — see `destPrefix`. */ prefix?: string; /** * Scope the destination walk (the comparison + prune set) to this prefix. * Defaults to `prefix`. Set it when `transformKey` re-homes keys under a * different namespace, so prune only ever considers the mirror's own keys. */ destPrefix?: string; /** * Map each source key to its destination key. Defaults to identity (the same * key on both sides). Both sides are *logical* keys — each instance applies * its own `prefix` independently, so this maps the un-prefixed key. */ transformKey?: (key: string) => string; /** * Mirror mode: after uploading, delete destination keys that no source key * maps onto. **Destructive** — an empty source prunes the entire destination * scope. Defaults to `false`. */ prune?: boolean; /** * Change detection — how an existing destination object is judged up to date. * See {@link SyncCompare}. Defaults to `"etag"`. */ compare?: SyncCompare; /** * Compute the full reconciliation plan (`uploaded` / `skipped` / `deleted`) * without uploading or deleting anything. `onProgress` does not fire — nothing * settled. Defaults to `false`. */ dryRun?: boolean; /** * Page size for the underlying `listAll` walks of both sides — how many keys * each `list` call fetches, not a cap on the total mirrored. */ limit?: number; /** Called once per key after it settles. See {@link SyncProgress}. */ onProgress?: (progress: SyncProgress) => void; /** * Abort the sync. Forwarded to every `list` / `download` / `upload` (the bulk * `delete` carries no signal). Aborting during a walk rejects the call; * aborting during the upload phase surfaces the cancelled keys in `errors`. */ signal?: AbortSignal; } export interface SyncResult { /** Source keys written to the destination (new or changed), in walk order. */ uploaded: string[]; /** Source keys left untouched because the destination copy was up to date. */ skipped: string[]; /** Destination keys pruned. Present only when `prune` is set (even if empty). */ deleted?: string[]; /** Per-key failures (uploads and prunes), each a normalized `FilesError`. Omitted when none. */ errors?: BulkError[]; } /** * Mirror the `source` onto the `dest`: upload every object that's new or * changed, skip the ones already identical, and — with `prune` — delete the * destination keys the source no longer has. Both arguments are full * {@link Files} instances, so each leg honors its own instance's `prefix`, * retries, timeouts, and hooks. Changed objects stream download-to-upload, so * the destination never sees a buffered copy of a large file. * * Both sides are walked in full before any work begins (so `total` is known and * `dryRun` can report the plan), but only metadata is buffered — every body * still streams. Only the body, content type, and user metadata travel with * each object, exactly as in {@link transfer}. * * Like the bulk array methods, this does **not** throw on a partial failure: * successes land in `uploaded` / `deleted`, per-key failures in `errors`. Pass * `stopOnError` to bail at the first upload failure (sequential; the prune phase * is then skipped). Uploads run before prunes, so an interrupted run never * leaves the destination missing data it was about to gain. * * ```ts * import { Files, sync } from "files-sdk"; * import { s3 } from "files-sdk/s3"; * import { r2 } from "files-sdk/r2"; * * const from = new Files({ adapter: s3({ bucket: "live" }) }); * const to = new Files({ adapter: r2({ bucket: "backup", ... }) }); * * // Incremental, pruning mirror — re-running only moves the delta. * const { uploaded, deleted } = await sync(from, to, { * prefix: "uploads/", * prune: true, * compare: "size", // cross-provider — etags aren't comparable * }); * ``` */ export declare const sync: (source: Files, dest: Files, opts?: SyncOptions) => Promise; //# sourceMappingURL=sync.d.ts.map