import { withMappedErrors } from "./internal/error-mapping.js"; import type { NapiSnapshotHandle, NapiSnapshotInfo, } from "./internal/napi.js"; import { Snapshot, type SnapshotScope } from "./snapshot.js"; const READ_ONLY_MSG = "SnapshotHandle is read-only — fetch a live handle via Snapshot.get(name) for lifecycle methods."; /** * Lightweight handle backed by an index row. * * Returned by `Snapshot.list()` and `Snapshot.get(...)`. Values are * snapshotted from the index at construction time — call * `Snapshot.get(...)` again for a fresh reading if needed. */ export class SnapshotHandle { private readonly inner: NapiSnapshotHandle | NapiSnapshotInfo; /** Manifest digest (`sha256:hex`) — canonical identity. */ readonly digest: string; /** Convenience name. `null` for digest-only entries. */ readonly name: string | null; /** Manifest digest of the parent snapshot, or `null` for a root. */ readonly parentDigest: string | null; /** Snapshot payload scope. */ readonly scope: SnapshotScope; /** Image reference the snapshot was taken from. */ readonly imageRef: string; /** Closed descriptor state discriminant. */ readonly stateKind: "file" | "checkpoint"; /** On-disk format for file state. */ readonly format: "raw" | "qcow2" | null; /** Filesystem type for file state. */ readonly fstype: string | null; /** Checkpoint manifest digest for checkpoint state. */ readonly checkpointManifestDigest: string | null; /** Apparent size of the upper file at index time. */ readonly sizeBytes: bigint | null; /** Embedded versus provider-linked payload placement. */ readonly locality: string; /** Current local availability. */ readonly availability: string; /** Adjacent-release artifact migration status. */ readonly migrationState: string; /** Stable migration failure code, when blocked. */ readonly migrationErrorCode: string | null; /** Snapshot creation time (from manifest). */ readonly createdAt: Date; /** Local artifact directory path. */ readonly path: string; /** @internal */ constructor(inner: NapiSnapshotHandle | NapiSnapshotInfo) { this.inner = inner; this.digest = inner.digest; this.name = (inner.name ?? null) as string | null; this.parentDigest = (inner.parentDigest ?? null) as string | null; this.scope = inner.scope as SnapshotScope; this.imageRef = inner.imageRef; this.stateKind = inner.stateKind as "file" | "checkpoint"; this.format = (inner.format as "raw" | "qcow2" | undefined) ?? null; this.fstype = inner.fstype ?? null; this.checkpointManifestDigest = inner.checkpointManifestDigest ?? null; this.sizeBytes = sizeBytesToBigInt(inner.sizeBytes); this.locality = inner.locality; this.availability = inner.availability; this.migrationState = inner.migrationState; this.migrationErrorCode = inner.migrationErrorCode ?? null; this.createdAt = new Date(inner.createdAt); this.path = inner.path; } /** Open and metadata-validate the underlying artifact. */ async open(): Promise { if (typeof (this.inner as NapiSnapshotHandle).open !== "function") { throw new Error(READ_ONLY_MSG); } const raw = await withMappedErrors(() => (this.inner as NapiSnapshotHandle).open(), ); return new Snapshot(raw); } /** * Remove the artifact and its index row. Refuses if the snapshot * has indexed children unless `force` is set. */ async remove(opts?: { force?: boolean }): Promise { if (typeof (this.inner as NapiSnapshotHandle).remove !== "function") { throw new Error(READ_ONLY_MSG); } await withMappedErrors(() => (this.inner as NapiSnapshotHandle).remove({ force: opts?.force ?? false }), ); } } function sizeBytesToBigInt( v: bigint | number | null | undefined, ): bigint | null { if (v === null || v === undefined) return null; return typeof v === "bigint" ? v : BigInt(v); } /** @internal */ export function snapshotInfoToHandle(info: NapiSnapshotInfo): SnapshotHandle { return new SnapshotHandle(info); }