import { type MountResolver } from './resolver.ts'; import type { BridgeDispatchFn } from './types.ts'; import type { SetAttrFields } from '../types.ts'; /** One directory entry as the mounts report it. */ export interface VFSEntry { path: string; size: number; isDir: boolean; isLink?: boolean; mode?: number; mtimeMs?: number; rdev?: number; } /** One path's metadata, in the shape every guest encoder needs. */ export interface VFSStat { size: number; isDir: boolean; mtimeMs: number; mode: number; isLink?: boolean; rdev?: number; } export declare function concatBytes(head: Uint8Array, tail: Uint8Array): Uint8Array; /** * The mount-facing op vocabulary a sandboxed runtime encodes into. * * One instruction set (read/write/append/stat/readdir/create/truncate/ * unlink/mkdir/rmdir/rename/symlink/readlink/setattr), one routing * table, one place that knows an append may have to become a * whole-file write. The last three reach the name plane rather than a * backend, which is what lets a guest create a link or stamp a time on * a mount whose store has neither. Encoders hold one of these; they * never inherit it, because a monty encoder is the binding's own `os` * callback and a quickjs encoder is a table of host functions. * * The surface is async, unlike Python's: a JS guest either suspends at * the call (quickjs asyncify) or records the mutation and replays it * after the run (pyodide), so nothing here has to block a worker * thread the way the Python runtimes do. * * Args: * dispatch: the workspace op dispatch this runtime was attached to. * resolver: the workspace mount routing table; the default answers * no mounts, so routing questions answer null. */ export declare class RuntimeVFS { private readonly dispatch; private readonly resolver; private readonly noAppend; constructor(dispatch: BridgeDispatchFn, resolver?: MountResolver); /** * The workspace mount prefixes, longest first, trailing-slash * normalized. Longest first is what makes mountOf's first match the * right one when one mount nests inside another. */ prefixes(): string[]; /** * The mount prefix serving `path`, longest match first, or null. The * resolver answers in the mount table's own spelling; this surface * re-spells to its trailing-slash convention, the form `prefixes` * reports. */ mountOf(path: string): string | null; read(path: string): Promise; write(path: string, bytes: Uint8Array): Promise; /** * One path's metadata, projected for a guest encoder. * * @param path guest-absolute virtual path. * @param nofollow report a trailing symlink itself rather than its * target (a guest's lstat). The row is then the node table's own, * so it carries the target string's length as the size, the link's * mtime, and whatever a `chown -h` wrote; the dispatcher consumes * the flag and gates that read exactly as it gates `readlink`. */ stat(path: string, nofollow?: boolean): Promise; /** * List a directory as resolved entries (Python's `readdir` shape). * * A backend that slash-marks directories skips the stat; every other * entry is classified by the stat the readdir just populated the * index with, so the lookup is RAM, not another API call. An entry * that vanished between list and stat (or a dangling link) rides as * a size-0 file instead of failing the whole listing: the guest's * own open reports the miss. * * A row that did stat carries its mode and stamp too, since the * struct is already in hand: a guest that seeds a whole tree from * one listing (Emscripten does) then needs no second stat per file. * The two slash-marked rows report neither, which is the honest * answer for a listing that never asked. * * The link mark comes from the name plane, since stat follows and no * backend listing reports a link. One table read per listing, and it * only ever marks a name the listing itself returned, so a link the * session hides stays hidden: the dispatcher filtered it out of the * entries above and an unmatched mark marks nothing. */ readdir(path: string): Promise; /** * Establish an empty file at `path` through the mount, so write * modes and a missing parent answer at open time and the ledger * records the op a create is. */ create(path: string): Promise; /** * Discard `path`'s content. Only ever a truncate-to-zero: the guest * surfaces that reach this are fopen-style opens, and a guest * ftruncate to a length operates on its open handle's buffer. */ truncate(path: string): Promise; unlink(path: string): Promise; /** * Create a directory; `parents` asks the mount to create missing * ancestors too (pathlib's mkdir(parents=True), which the backend op * takes as a flag on both hosts). */ mkdir(path: string, parents?: boolean): Promise; rmdir(path: string): Promise; /** * Rename within one mount. * * Args: * src: guest-absolute source path. * dst: guest-absolute destination path. * * Throws: * CrossMountError: the two ends resolve to different mounts. */ rename(src: string, dst: string): Promise; /** * Create a namespace symlink at `path` pointing at `target`. * * A link is namespace state, so no backend stores one and the target * is kept verbatim as the guest typed it. The dispatcher answers this * op from the node table itself, which is why a runtime can serve * `os.symlink` at all: the door a surface already holds reaches the * name plane, not just a mount. * * Args: * path: guest-absolute path of the link to create. * target: link target, stored as typed. */ symlink(path: string, target: string): Promise; /** * The target of the symlink at `path`. * * Throws EINVAL when `path` is not a link, which is what the node * table answers and what POSIX readlink says. */ readlink(path: string): Promise; /** * Write metadata fields, natively where the backend can hold them. * * The door reads the whole set and stores in the namespace overlay * whatever the backend cannot keep, so a mount with no setattr op * still answers: a utime on an s3 or dropbox mount lands in the name * plane and stat reports it back. Stored, not enforced; the mount * mode is the access control. * * Args: * path: guest-absolute virtual path. * attrs: the fields to write, unset ones omitted. */ setattr(path: string, attrs: SetAttrFields): Promise; /** * Extend `path` by `tail`, falling back to a whole-file write. * * `append` is optional per backend (S3 registers `write` and * `rename` without it), so a mount that declines is remembered: the * fallback then costs one failed dispatch per mount rather than one * per call. * * The fallback needs the whole file. An encoder that already holds * it (monty's in-memory tree, a closing file handle) passes it; one * that does not (pyodide's mutation replay, which recorded only the * tail) omits it and the fallback reads the base first. Only a * confirmed absence starts from an empty base, since an append may * create the file — every other read failure propagates, because * writing the tail alone over a file that exists but is momentarily * unreadable would replace content this run never saw. * * Args: * path: guest-absolute virtual path. * tail: only the newly appended bytes. * whole: the file's full content, when the caller has it. */ append(path: string, tail: Uint8Array, whole?: Uint8Array): Promise; private appendDelta; /** * Send a closing handle's buffer as a delta when it can be one. * * Args: * path: guest-absolute virtual path. * baseLen: length the file had when the handle opened. * lowWrite: lowest offset this handle wrote at. * buf: the handle's whole buffer. */ flush(path: string, baseLen: number, lowWrite: number, buf: Uint8Array): Promise; } //# sourceMappingURL=vfs.d.ts.map