import type { FileInfo, FilesPlugin } from "../index.js"; /** * A saved snapshot of a key, as returned by {@link VersioningApi.versions}. * Pass {@link FileVersion.versionId} back to {@link VersioningApi.restoreVersion} to * roll a key back to this point. */ export interface FileVersion { /** * Opaque id for this version, ordered by when the snapshot was taken; hand * it to `restoreVersion()`. */ versionId: string; /** The underlying storage key this snapshot lives at, under the version prefix. */ key: string; /** Byte length of the snapshot (the logical size, after inner plugins). */ size: number; /** * When the snapshotted object was last modified (ms epoch), parsed from the * id. Versions are ordered by when they were *snapshotted*, not by this: a * native move (fs, memory, FTP, SFTP) carries the source's older time onto * the key, so it isn't necessarily in order. */ lastModified: number; /** The snapshot's ETag, when the adapter reports one. */ etag?: string; } /** * The methods {@link versioning} grafts onto a {@link Files} instance. A `type` * rather than an `interface` so it satisfies the `Record` * constraint on {@link FilesPlugin}'s extension parameter — an interface has no * implicit index signature and wouldn't be assignable. */ export type VersioningApi = { /** * List the saved versions of `key`, **newest first**. Each entry's * `versionId` can be passed to {@link VersioningApi.restoreVersion}. Returns an empty * array when the key has no history. */ versions: (key: string) => Promise; /** * Roll `key` back to a prior version — the newest one when `versionId` is * omitted (an undo of the last change). The current bytes are snapshotted * first, so a restore is itself reversible; with a `limit`, the oldest * versions are pruned only after the restore lands, so restoring the oldest * kept version always works. Resolves to the restored key's {@link FileInfo} * (via `head`). Throws when the key has no versions, or the given * `versionId` doesn't exist. */ restoreVersion: (key: string, versionId?: string) => Promise; }; export interface VersioningOptions { /** * Where snapshots are stored, as a key prefix. Defaults to `".versions"`. * Versions of `photos/a.jpg` live at `".versions/photos/a.jpg/"`. Objects * under this prefix are hidden from `list()` (unless you list within it) and * are never themselves versioned. Don't store your own data under it. */ prefix?: string; /** * Cap the number of versions kept per key. After each write that took a * snapshot lands, the oldest versions beyond this many are pruned (a write * that fails prunes nothing, so history may briefly exceed the cap until the * next successful write). Omit to keep every version (history grows * unbounded). Must be a positive integer. */ limit?: number; /** * Key prefixes that are never snapshotted, in addition to the version prefix * itself. Writes and deletes under these pass through un-versioned. Use it to * keep another plugin's housekeeping out of the history — most usefully * `softDelete()`'s trash, so a `purge()` really frees the bytes instead of * versioning them: `versioning({ ignore: [".trash"] })`. */ ignore?: string[]; } /** * Snapshot the prior bytes of any object before an overwrite or delete, and add * `versions()` / `restoreVersion()` so you can roll a key back. Before an `upload`, * `delete`, or the destination of a `copy` / `move` clobbers an existing object, * the plugin server-side-copies it to a time-stamped key under a version prefix * (`.versions/` by default); the live object is untouched. * * Snapshots are plain object copies, so it's **body-transparent** — unlike * `encryption()` / `compression()` it never buffers, transforms, or reads the * body, which leaves streaming, range downloads, `url()`, and `signedUploadUrl()` * all working normally. It composes with the transforming plugins by copying * whatever they stored (a version of an encrypted object stays encrypted and * still restores cleanly), so place it **first** (outermost): * `plugins: [versioning(), compression(), encryption(key)]`. * * This is the first plugin to use `extend`, so reach for {@link createFiles} to * surface `files.versions()` / `files.restoreVersion()` on the type. * * Trade-offs, by design: * - **A `head` + `copy` per overwrite/delete.** Snapshotting costs two extra * adapter round-trips on writes that hit an existing object; first writes * (nothing to snapshot) cost only the `head`. * - **Direct presigned writes bypass it.** A client `PUT` to a `signedUploadUrl` * never runs the plugin, so no snapshot is taken; write through the instance * to version. (It's a safety net, not a security control, so it doesn't fail * closed the way `validation()` does.) * - **`move` snapshots only its destination.** A rename relocates the bytes * rather than destroying them, so the source isn't snapshotted. * - **History is unbounded** unless you set `limit`. * - **Ordered by the app's clock.** Versions sort by when each snapshot was * taken, per the clock of the process that took it, so instances writing * the same key need reasonably synchronized clocks. A `copy` or `move` of a * key onto itself changes nothing and takes no snapshot. * - **Pairs with `softDelete()`, versioning outermost.** Place it before the * trash plugin so deletes are snapshotted, and pass the trash prefix as * `ignore` so a `purge()` isn't itself versioned: * `plugins: [versioning({ ignore: [".trash"] }), softDelete()]`. With a * `limit`, the versions it prunes are deleted through the trash plugin too, * so they sit in the trash (and show in `trashed()`) until you `purge()`. * * @param options optional `{ prefix, limit, ignore }` — where snapshots live, * how many to keep per key, and which prefixes to leave un-versioned. * @example * ```ts * import { createFiles } from "files-sdk"; * import { s3 } from "files-sdk/s3"; * import { versioning } from "files-sdk/versioning"; * * const files = createFiles({ * adapter: s3({ bucket: "uploads" }), * plugins: [versioning({ limit: 10 })], * }); * * await files.upload("notes.txt", "v1"); * await files.upload("notes.txt", "v2"); // "v1" snapshotted first * * const [previous] = await files.versions("notes.txt"); * await files.restoreVersion("notes.txt", previous.versionId); // back to "v1" * ``` */ export declare const versioning: (options?: VersioningOptions) => FilesPlugin; //# sourceMappingURL=index.d.ts.map