import type { Platinum } from './index.js'; /** GET /v1/volumes/{v} — the wire shape behind a {@link Volume}. */ export type SyncMode = 'drive' | 'git'; export interface VolumeData { id: string; name: string; kind: 'volume' | string; temporary: boolean; head_commit_id: string; size_limit_bytes: number; logical_bytes: number; unique_bytes: number; file_count: number; region: string | null; forked_from: { volume_id: string; commit_id: string; } | null; /** `drive`: syncs on its own and keeps both versions on a conflict. `git`: commit and pull. */ sync_mode: SyncMode; created_at: string; updated_at: string; last_commit_at: string | null; live_mounts: number; } export interface VolumeFileEntry { path: string; type: 'file' | 'dir' | 'symlink' | 'other'; size: number; /** Unix milliseconds. */ mtime: number; } export interface VolumeStat extends VolumeFileEntry { mode: number; uid: number; gid: number; symlink_target: string | null; version: string | null; commit_id: string; } export interface VolumeOverwrite { path: string; reason: 'newer_remote_replaced' | 'resurrected' | 'type_change' | string; } export interface VolumeSkip { path: string; reason: 'delete_lost_to_newer_remote' | 'dir_kept_unseen_children' | string; } /** The answer to every mutation: a sandbox commit, a pull, or a file call. */ export interface CommitResult { commit_id: string; outcome: 'fast' | 'merged' | 'queued' | 'noop'; head_commit_id: string; /** Paths whose newer remote version this commit replaced (capped at 1000). */ overwritten: VolumeOverwrite[]; skipped: VolumeSkip[]; overwritten_count: number; duration_ms: number; } export interface VolumeCommit { id: string; volume_id: string; seq: number | null; on_head: boolean; /** How the commit landed: `init` (a new volume), `fast`/`api`/`import` on head, * `side` (a mount's commit that raced another writer; see `merge`), `merge`, * `orphan` (a deleted sandbox's final commit), `fork` or `restore`. */ kind: 'init' | 'fast' | 'api' | 'import' | 'merge' | 'side' | 'orphan' | 'fork' | 'restore' | (string & {}); parent_id: string | null; base_id: string | null; merged_from: string[]; author: { kind: 'sandbox' | 'api_key' | 'user' | 'system' | string; id: string | null; mount_id?: string | null; }; created_at: string; logical_bytes: number; file_count: number; overwritten_count: number; tags: string[]; stats?: { changed_paths: number; deleted_paths: number; dirty_blocks: number; uploaded_blocks: number; uploaded_bytes: number; duration_ms: number; }; } /** `Volume.commit()`: the barrier a reader waits on, and each mount's commit. */ export interface VolumePublish { /** The first head commit that holds every commit below: pass it to `waitFor`. */ commit_id: string; head_commit_id: string; commits: Array<{ mount_id: string; sandbox_id: string; commit_id: string; outcome: CommitResult['outcome']; }>; } /** `Volume.waitFor()`: the mounts it waited on and whether each shows the commit. */ export interface VolumeWaitResult { commit_id: string; /** The first head commit that holds `commit_id`; null while it is still merging. */ head_commit_id: string | null; applied: boolean; mounts: Array<{ mount_id: string; sandbox_id: string; mounted_commit_id: string | null; applied: boolean; }>; } export interface VolumeMount { id: string; sandbox_id: string; volume_id: string; volume_name: string; mount_path: string; read_only: boolean; subdir: string; state: 'pending' | 'mounting' | 'mounted' | 'committing' | 'pulling' | 'unmounting' | 'unmounted' | 'unflushed' | 'lost' | 'failed' | string; generation: number; base_commit_id: string; mounted_commit_id: string; last_commit_id: string | null; last_commit_at: string | null; dirty_bytes: number; unflushed: boolean; created_at: string; } export interface VolumeTag { name: string; commit_id: string; created_at: string; } export interface VolumeLimits { max_volumes: number; max_bytes_per_volume: number; commits_per_minute: number; max_mounts_per_sandbox: number; subdir_max_bytes: number; used_volumes: number; } export interface VolumePage { volumes: Volume[]; nextCursor: string | null; } /** One page of `files.listPage()`. Pass `at: commitId` with `cursor` to keep * reading the same version while others write. */ export interface VolumeFilePage { entries: VolumeFileEntry[]; nextCursor: string | null; commitId: string; } export interface VolumeHistoryPage { commits: VolumeCommit[]; nextCursor: string | null; } /** `files.write` answer: the commit plus what landed. `version` is absent when * a large write went through the upload protocol. */ export type VolumeWriteResult = CommitResult & { path: string; size: number; version?: string; }; /** `files.upload` answer: the last commit, plus totals across every batch. */ export type VolumeUploadResult = CommitResult & { files: number; /** Blocks the server did not already have, and their bytes. Zero on a re-upload. */ uploaded_blocks: number; uploaded_bytes: number; /** One commit per batch of at most 10,000 files. */ commits: CommitResult[]; }; export interface PullResult { commit: CommitResult | null; head_commit_id: string; mounted_commit_id: string; generation: number; } /** Volume block size and upload chunk size: 1 MiB. */ export declare const VOLUME_BLOCK_SIZE = 1048576; /** * Largest body `files.write` sends as one request; anything bigger goes * through the upload protocol (1 MiB blocks, each retried on its own). One * request has to finish inside the edge's ~100 s origin timeout, which 8 MiB * does down to ~100 KB/s. The server itself accepts up to 64 MiB. */ export declare const VOLUME_MAX_PUT_BYTES: number; declare const asyncDispose: typeof Symbol.asyncDispose; /** A volume name or id, validated for use as one URL segment. */ export declare function volumeSegment(nameOrId: string): string; /** What `files.write` accepts: text (sent as UTF-8), bytes, a Blob, or a * stream (read to the end first). Anything else is a TypeError rather than * its `String()` form, so `write(p, 5)` or `write(p, {})` never lands "5" or * "[object Object]" on the volume. */ export type VolumeWriteData = string | Uint8Array | ArrayBuffer | ArrayBufferView | Blob | ReadableStream; /** * How to mount a volume: which one, which subdirectory, read-only or not. * Immutable — `subdir()` and `readOnly()` return new specs, so one spec can be * reused across sandboxes. * * volumes: { '/home': vol.subdir(`/users/${uid}`), '/ref': vol.readOnly() } */ export declare class VolumeMountSpec { /** Volume id or name. */ readonly volume: string; readonly subdirPath: string; readonly isReadOnly: boolean; constructor( /** Volume id or name. */ volume: string, subdirPath?: string, isReadOnly?: boolean); /** Mount only this subdirectory (created on mount if missing). `/` is the whole volume. */ subdir(path: string): VolumeMountSpec; /** Mount read-only. Writes fail with EROFS; `pull()` still works. */ readOnly(): VolumeMountSpec; /** The `POST /v1/sandboxes` map value. */ toJSON(): { volume: string; read_only: boolean; subdir: string; }; } /** Anything a mount accepts: a volume handle, a spec, or a volume name or id. */ export type VolumeMountInput = Volume | VolumeMountSpec | string; export declare function toMountSpec(v: VolumeMountInput): VolumeMountSpec; /** Serialize the `volumes` field of a sandbox create. The map form goes out as * the 4.4 map; a legacy array is passed through untouched. */ export declare function serializeVolumeMounts(volumes: Record | unknown[]): unknown; type UploadSource = { kind: 'bytes'; data: Uint8Array; } | { kind: 'blob'; data: Blob; } | { kind: 'path'; path: string; nofollow?: boolean; }; interface PlannedFile { path: string; size: number; mode: number; mtime: number; source: UploadSource; } export interface UploadFileOptions { mode?: number; mtime?: number; } /** Collects what one `files.upload()` sends. Local paths are read lazily. */ export declare class VolumeUploader { /** @internal */ readonly _entries: Array>; /** @internal */ readonly _dirs: string[]; /** * Upload one file to `dst`. `src` is a local file path, or the bytes * themselves (Uint8Array, ArrayBuffer or Blob). To upload a string's * contents, encode it first: `new TextEncoder().encode(text)`. */ file(src: string | Uint8Array | ArrayBuffer | Blob, dst: string, opts?: UploadFileOptions): this; /** Upload a local directory tree to `dst`. Empty directories are kept; * symlinks and special files are skipped. */ dir(localDir: string, dst: string): this; /** Create an empty directory (and its parents) at `dst`. */ mkdir(dst: string): this; } export interface VolumeReadOptions { /** Commit id, tag, or `head` (default). */ at?: string; /** Byte window: `offset` onward, `length` bytes (to EOF when omitted). */ range?: { offset: number; length?: number; }; signal?: AbortSignal; } export interface VolumeUploadOptions { /** Default false: an existing path fails the batch with `path_exists`. */ overwrite?: boolean; /** Most blocks in flight at once (default 16); fewer on a slow link. */ concurrency?: number; /** Aborts the upload and cancels its plan. */ signal?: AbortSignal; } /** An upload plan that is still open (`files.uploads()`). */ export interface VolumeUploadInfo { upload_id: string; created_at: string; /** When it lapses unless more blocks arrive. */ expires_at: string; /** Bytes held against the volume's stored-bytes budget: the blocks it still has to send. */ reserved_bytes: number; } export interface VolumeWriteOptions { /** Default true. False refuses an existing path with `path_exists`. */ overwrite?: boolean; /** Permission bits, e.g. 0o644. */ mode?: number; timeoutMs?: number; signal?: AbortSignal; } /** A volume handle. Calls address it by id, so a rename never breaks a handle. */ export declare class Volume { readonly client: Platinum; private _data; private _heartbeat?; constructor(client: Platinum, data: VolumeData); get id(): string; get name(): string; get temporary(): boolean; get headCommitId(): string; get sizeLimitBytes(): number; get logicalBytes(): number; get fileCount(): number; get syncMode(): SyncMode; get region(): string | null; get forkedFrom(): VolumeData['forked_from']; get createdAt(): string; get lastCommitAt(): string | null; get liveMounts(): number; /** The last wire object this handle saw. */ get data(): Readonly; /** `JSON.stringify(volume)` gives the wire object (the handle's client is left out). */ toJSON(): VolumeData; /** Keep `headCommitId` current after a call that moved the head. */ private sawHead; private get base(); /** Re-read the volume (head, sizes, live mounts). */ refresh(): Promise; rename(newName: string): Promise; /** Delete the volume. Refused with `VolumeBusyError` while it is mounted. */ delete(opts?: { missingOk?: boolean; }): Promise; /** Mount only this subdirectory. */ subdir(path: string): VolumeMountSpec; /** Mount read-only. */ readOnly(): VolumeMountSpec; /** Commits on this volume, newest first. */ history(opts?: { limit?: number; before?: string; includeSide?: boolean; }): Promise; /** One commit by id, tag, or `head`, with its overwritten list. */ getCommit(ref: string): Promise; /** Point `name` at `commit` (default head). Moves an existing tag. */ tag(name: string, commit?: string): Promise; untag(name: string): Promise; tags(): Promise; /** Make `ref` (commit id or tag) the head again, as a new `restore` commit. * Nothing is lost: the replaced head stays in history. */ restore(ref: string): Promise; /** A new volume starting at `at` (default head). Zero bytes are copied. */ fork(name: string, opts?: { at?: string; }): Promise; /** Live mounts of this volume across every sandbox. */ mounts(): Promise; /** Commit every read/write mount of this volume in a running sandbox (or * only `mountId`) and wait for the merges. `commit_id` is the head that * holds them all: a reader passes it to `waitFor`. */ commit(opts?: { mountId?: string; }): Promise; /** Wait until `mountId` (default: every mount in a running sandbox) shows a * head that holds `commitId`. Throws `PlatinumTimeoutError` after * `timeout` seconds (default 60). */ waitFor(commitId: string, opts?: { mountId?: string; timeout?: number; }): Promise; readonly files: { /** Entries under `path` (all pages). Every page reads the commit the * first page saw, so a write landing mid-listing never shows a file * twice or drops one. */ list: (path?: string, opts?: { recursive?: boolean; at?: string; limit?: number; }) => Promise; /** One page of entries under `path`, with the cursor for the next and the * commit it was read at. */ listPage: (path?: string, opts?: { recursive?: boolean; at?: string; limit?: number; cursor?: string; }) => Promise; stat: (path: string, opts?: { at?: string; }) => Promise; /** Stream a file's bytes. Symlinks are never followed (`not_a_file`). */ read: (path: string, opts?: VolumeReadOptions) => Promise>; readBytes: (path: string, opts?: VolumeReadOptions) => Promise; readText: (path: string, opts?: VolumeReadOptions) => Promise; /** * Write one file as one commit. Bodies over 8 MiB (VOLUME_MAX_PUT_BYTES) * switch to the upload protocol on their own, which also skips blocks the * volume already has and survives a slow or flaky link. */ write: (path: string, data: VolumeWriteData, opts?: VolumeWriteOptions) => Promise; /** * Upload many files as one commit per 10,000 files, sending only the 1 MiB * blocks the volume's org does not already hold: * * await vol.files.upload((up) => { * up.dir('./dataset', '/dataset'); * up.file('./model.bin', '/models/model.bin'); * }, { overwrite: true }); * * Without `overwrite`, an existing path fails the whole batch with `path_exists`. * * Each block is retried on its own (timeouts, dropped connections, 429 * and 5xx), and fewer go at once on a slow link. A plan refused because * the org's upload slots are all taken waits for one (up to 10 minutes). * If the upload fails or `signal` aborts it, its plan is cancelled so it * holds no slot and reserves no bytes. */ upload: (fill: (up: VolumeUploader) => void | Promise, opts?: VolumeUploadOptions) => Promise; /** Uploads planned on this volume that have not committed, been cancelled or lapsed. */ uploads: () => Promise; /** Cancel an upload plan: frees its slot and its reserved bytes. Cancelling one that already ended is a no-op. */ cancelUpload: (uploadId: string) => Promise; /** Copy within this volume. Whole 1 MiB blocks are shared, not copied. */ copy: (src: string, dst: string, opts?: { recursive?: boolean; overwrite?: boolean; }) => Promise; move: (src: string, dst: string, opts?: { overwrite?: boolean; }) => Promise; remove: (path: string, opts?: { recursive?: boolean; }) => Promise; }; private rawRead; private runUpload; /** Best effort, never with the caller's (maybe aborted) signal. */ private cancelPlan; /** @internal */ _startHeartbeat(): void; private stopHeartbeat; /** * Delete a temporary volume and stop heartbeating. Also run by `await using`. * A temporary volume that is still mounted is refused with VolumeBusyError * and keeps heartbeating: detach it, then close again. */ close(): Promise; /** Leaving an `await using` scope: close, and stop the heartbeat even when * the close is refused. Nothing can close this handle again, and a running * heartbeat would keep the volume for the life of the process; a mounted * one stays alive through its mount until it is detached. */ [asyncDispose](): Promise; } /** `pt.volumes.list()` — await it for the first page, or iterate every volume: * * const { volumes, nextCursor } = await pt.volumes.list({ limit: 20 }); * for await (const v of pt.volumes.list()) console.log(v.name); */ export declare class VolumeListing implements AsyncIterable, PromiseLike { private readonly fetchPage; private readonly before?; constructor(fetchPage: (before?: string) => Promise, before?: string | undefined); /** One page. */ page(): Promise; then(ok?: ((v: VolumePage) => A | PromiseLike) | null, fail?: ((e: unknown) => B | PromiseLike) | null): Promise; [Symbol.asyncIterator](): AsyncIterator; } export declare class Volumes { private readonly client; constructor(client: Platinum); private wrap; /** The volume called `name`, created (in `drive` mode unless `syncMode` says otherwise) if it does not exist. With `syncMode`, an existing volume in the other mode is a 409 `sync_mode_mismatch`: switch it with `setSyncMode`. */ open(name: string, opts?: { syncMode?: SyncMode; }): Promise; /** Create a volume (`syncMode` defaults to `drive`). Throws `VolumeExistsError` when the name is taken. */ create(name: string, opts?: { syncMode?: SyncMode; }): Promise; /** Switch a volume between `drive` and `git`. */ setSyncMode(nameOrId: string, syncMode: SyncMode): Promise; get(nameOrId: string): Promise; list(opts?: { limit?: number; before?: string; includeTemporary?: boolean; }): VolumeListing; delete(nameOrId: string, opts?: { missingOk?: boolean; }): Promise; rename(oldName: string, newName: string): Promise; /** * A temporary volume, deleted when you are done with it (and by the server * 10 minutes after the last heartbeat, if your process dies first): * * await using vol = await pt.volumes.temp(); */ temp(): Promise; /** This org's volume limits and current usage. */ limits(): Promise; } /** One mount on one sandbox, addressed by mount path (or `vm_…` id). Creating * the handle makes no request: `sbx.volumes.get('/data').commit()` is one call. */ export declare class SandboxVolume { private readonly client; readonly sandboxId: string; readonly mount: string; constructor(client: Platinum, sandboxId: string, mount: string); private get base(); /** The mount's state, generation, dirty bytes and last commit. */ info(): Promise; /** * Commit this mount's changes now and wait until they are durable in S3 * (and merged, when another writer got there first). `commit_failed` is * retried a few times before it is thrown. A read-only mount throws * `ReadOnlyMountError`. */ commit(): Promise; commit(opts: { wait: false; }): Promise<{ command_id: string; }>; commit(opts?: { wait?: boolean; }): Promise; /** Commit pending changes, then switch the mount to the latest head. * Throws `VolumeBusyError` (with `holders`) while a process holds a file open. */ pull(): Promise; /** Unmount, committing first unless `commit: false`. */ detach(opts?: { commit?: boolean; }): Promise<{ commit: CommitResult | null; }>; } /** `sbx.volumes` — mount, unmount and commit volumes on a running sandbox. */ export declare class SandboxVolumes { private readonly client; private readonly sandboxId; constructor(client: Platinum, sandboxId: string); /** Mount a volume at `path`. Waits up to 30 s for the mount; a slower one * comes back with `state: 'pending'`. The path travels in the body (the URL * segment is a placeholder), since scoped keys are refused `%` in a path. */ attach(path: string, volume: VolumeMountInput): Promise; detach(path: string, opts?: { commit?: boolean; }): Promise<{ commit: CommitResult | null; }>; list(): Promise; /** A handle for the mount at `path` (no request is made). */ get(path: string): SandboxVolume; } export {};