UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

197 lines • 12.1 kB
import type { ByteSource } from '../../../io/types.ts'; import type { DispatchFn } from '../../../runtime/types.ts'; import type { PathSpec } from '../../../types.ts'; import { FileStat, type SetAttrFields } from '../../../types.ts'; import { type ResolveGlobFn } from '../../../utils/glob_walk.ts'; import type { MountView, SessionAccess } from '../../../view/types.ts'; import type { Context } from '../../config.ts'; import type { ReadTree } from '../utils/tree.ts'; /** * A command's files, path-first, every call sent to the dispatcher through * the session's files (`ctx.vfs`). * * The dispatcher checks hides, the command's path rule, the mount's mode and * policy, refuses a read of a directory and a write to a slash-terminated * name, serves a warm copy and fills a cold one, fills a stat's unknown size * from the read cache, gives way to the read for a file the read cache holds * where a call stands in for one, settles a write's caches and receipt under * its name's hold, and declines a one-call walk (a subtree read, search, a * tree copy or removal) whose subtree the caller's view restricts, so a * command's call answers what the same call through `ws.vfs` or FUSE * answers. A function the mount's VFS does not answer is the * dispatcher's ENOTSUP; a command asks `answers` before it takes a shortcut. * Mirrors Python's `CommandFiles`. */ export interface CommandFiles { /** The line's abort and the command's deadline, for a walk `treeWalk` answers. */ readonly signal?: AbortSignal; /** The entry at `path` itself, as lstat answers. */ readonly stat: (path: PathSpec) => Promise<FileStat>; readonly exists: (path: PathSpec) => Promise<boolean>; readonly readdir: (path: PathSpec, nofollow?: boolean, limit?: number) => Promise<string[]>; /** * `read(path, offset?, size?)`: the stored bytes or a window of them, * opened at the first pull; `materialize` collects them. */ readonly read: (path: PathSpec, offset?: number, size?: number | null) => AsyncIterable<Uint8Array>; /** * Whether the read cache holds a copy a read would be served from; it * fetches neither the copy nor anything from the backend. */ readonly isWarm: (path: PathSpec) => Promise<boolean>; readonly write: (path: PathSpec, data: ByteSource) => Promise<void>; readonly append: (path: PathSpec, data: ByteSource) => Promise<void>; readonly pwrite: (path: PathSpec, data: Uint8Array, offset: number) => Promise<void>; readonly mkdir: (path: PathSpec, parents?: boolean) => Promise<void>; readonly unlink: (path: PathSpec) => Promise<void>; readonly rmdir: (path: PathSpec) => Promise<void>; readonly rmR: (path: PathSpec) => Promise<void>; readonly rename: (src: PathSpec, dst: PathSpec) => Promise<void>; readonly copy: (src: PathSpec, dst: PathSpec) => Promise<void>; readonly dirCopy: (src: PathSpec, dst: PathSpec) => Promise<void>; /** noCreate requires an atomic existence precondition, or ENOTSUP before writing. */ readonly truncate: (path: PathSpec, length: number, noCreate?: boolean) => Promise<void>; readonly setAttrs: (path: PathSpec, fields: SetAttrFields) => Promise<Record<string, number | string>>; /** The first `n` lines; null when the file is to be read, as it is when the read cache holds it. */ readonly firstLines: (path: PathSpec, n: number) => Promise<Uint8Array | null>; /** The last `n` lines; null when the file is to be read. */ readonly lastLines: (path: PathSpec, n: number) => Promise<Uint8Array | null>; /** How many lines the file holds; null when it is to be read. */ readonly lineCount: (path: PathSpec) => Promise<number | null>; /** The digest the backend keeps; null when the file is to be read and hashed. */ readonly checksum: (path: PathSpec, algorithm: string) => Promise<string | null>; /** The last `n` lines, then the lines added; null when the file is to be polled. */ readonly follow: (path: PathSpec, n: number, interval: number) => Promise<AsyncIterable<Uint8Array> | null>; /** Every entry under `path`, keyed below it; null when the directory is to be walked. */ readonly readTree: ReadTree; /** The files under `path` that may contain `text`; null when the search cannot answer for every file. */ readonly filesContaining: (path: PathSpec, text: string, opts: { wholeWord: boolean; ignoreCase: boolean; }) => Promise<PathSpec[] | null>; /** The lines of `path` that may contain `text`; null when the search cannot answer for this file. */ readonly linesContaining: (path: PathSpec, text: string, opts: { ignoreCase: boolean; }) => Promise<ByteSource | null>; /** Expand glob operands, a typed word as bash would. */ readonly resolveGlob: ResolveGlobFn; /** * Whether the mount the command runs on, whatever routed it there (an * operand, an option, the working directory), answers `name` itself, as * the mount view says: where it does not, a shortcut is not taken. */ readonly answers: (name: string) => boolean; } /** * What the factory says of one invocation of a generic builder: whether an * operand's read is cut into pieces (a pass-through builder keeps the * producer's chunks), and what streams an operand in place of the dispatched * read (null keeps it). */ export interface Reading { readonly split: boolean; readonly stream: ((ctx: Context, path: PathSpec) => AsyncIterable<Uint8Array> | null) | null; } /** * `ctx` as the factory hands it to a builder, its files read as `reading` * says and under the command tier's guards. Mirrors Python's `with_reading`. */ export declare function withReading(ctx: Context, reading: Reading): Context; /** * What a command's whole read of an operand awaits first, and what learns the * operands its globs resolve to. * * A command reads its operands in turn, each whole once the one before is * done. On a whole read of one, the next READ_AHEAD go to the dispatcher as * one `read_many`, which keeps the small files among them in the read cache * (one request where the store reads a batch), and the batch after goes once * half of that one is read. A read whose file is still being warmed waits for * it, so it is served warm instead of fetching the file again. No bytes are * held here: a write in between drops the cached copy as it drops any. A * path that is not an operand warms nothing. Mirrors Python's `read_ahead`. */ export declare function readAhead(dispatch: DispatchFn, paths: readonly PathSpec[]): [(path: PathSpec) => Promise<void>, (found: PathSpec[]) => void]; /** * Send one call to the dispatcher and hand back its answer: the one way a * command's file call, a cross-mount run and a relayed command reach a mount. * Mirrors Python's `dispatched_call`. */ export declare function dispatchedCall(dispatch: DispatchFn, name: string, path: PathSpec, args?: readonly unknown[], kwargs?: Record<string, unknown>): Promise<unknown>; /** * The `readTree` a walk through the dispatcher takes, asked only where the * mount a subtree lands on answers one; elsewhere null, and the walk lists * instead. * * The serving mount's answer is no guide: a walk through the dispatcher also * reaches the mounts below it and those a link above the subtree leads to, so * a subtree on one that answers no read_tree (a disk mount under the root RAM) * is walked rather than refused with ENOTSUP. A walk across mounts * (`crossing`) asks only where no mount sits below the subtree either: the * serving backend's answer holds keys a mount there shadows, and lacks the * mount's own. A subtree read stops at a link at the final name (ENOTDIR), so * a walk that follows links lists them directory by directory instead. One * level (`level`) is the listing a `readdir` answers with each child's stat, * which every mount that keeps listings answers, asked only where no mount * sits below, whose root the listing would name. * `mounts` is the boundary view a walk that does not cross them sees * (`mountView(files)`), undefined to take no shortcut; `landing` where a path lands, every link above its * final name followed (`linkLocation`, `NamespaceLinks.followParent`), null * for a walk whose paths name their own place. Mirrors Python's * `dispatched_read_tree`. */ export declare function dispatchedReadTree(dispatch: DispatchFn, mounts: MountView | undefined, landing: ((virtual: string) => string) | null, crossing: boolean): ReadTree; export declare function dirAwareStat(files: CommandFiles, ctx: Context): (p: PathSpec) => Promise<FileStat>; /** * A regular file's size from its stat, for a command whose answer is the size * alone (`wc -c`), as GNU takes it from `st_size`. Null reads the file * instead: for a file the read cache holds, which is read from there as the * read it replaces would be; for anything but a regular file (a device or a * pipe has no size to trust); for a size the stat does not know; for a path * pinned to an older revision, whose size the stat does not give; and for a * store that keeps permission bits (a disk, SFTP), where a file the stat sees * may still refuse its read, and a disk's named pipe stats as a file. A store * without them grants the read with the stat. A directory fails with EISDIR * as `dirAwareStat` has it. A listing the index holds can be up to its TTL * old, so the size is checked against a stat that asks the backend now * (`fresh`), and a file whose size moved since the listing is read: whether * that read serves the listed version (a GitHub blob the listing named) or the * current one, it is what the command counts. Mirrors Python's `file_size`. */ export declare function fileSize(files: CommandFiles, ctx: Context, opened?: boolean): (p: PathSpec) => Promise<number | null>; export declare function dirAwareStream(files: CommandFiles, ctx: Context): (p: PathSpec) => AsyncIterable<Uint8Array>; /** * Expand glob operands, or [] when there is nothing to resolve: no operand * resolves to no paths, which the generics read as stdin mode. Operand * semantics (report-and-continue, directory refusal) live in the generics * via `splitReadable`; this is wiring only. Mirrors Python's * `resolve_or_empty`. */ export declare function resolveOrEmpty(files: CommandFiles, paths: readonly PathSpec[]): Promise<PathSpec[]>; /** Refuse a relocation that would surface a hidden path. * * A rename or a native directory copy re-anchors everything below its * source, and a hide's coverage does not move with the content, so * hidden bytes would land at paths the session can see. EACCES on the * source, which mv and cp render in GNU's permission-denied voice. * Only a directory has anything below it to re-anchor, so callers * check this for a source they know is a directory and skip it for a * file. Mirrors Python's `refuse_reveal`. */ export declare function refuseReveal(src: PathSpec, dst: PathSpec, access?: SessionAccess): void; /** * The files of the command `ctx` runs, its operands `paths`. * * Every call goes to the dispatcher through `ctx.vfs`, which refuses a read * of a directory and a write to a slash-terminated name and fills a stat's * unknown size from the read cache. A command on one mount reads the stored * bytes (`filetype: null`), its whole reads warmed ahead of themselves * (`readAhead`), and, as the factory hands it on (`withReading`), under the * command tier's guards: no call started once the line's signal has fired, * each operand's read cut into pieces under its abort and deadline. A run * across mounts (`crossesMounts`) reads each operand as the * command that asked for it, through the path's renderer, after a stat that * refuses a directory, and lists with no boundary to stop at. Throws without * `ctx.vfs`: a builder outside a workspace has no files to read. Mirrors * Python's `command_files`. */ export declare function commandFiles(ctx: Context, paths?: readonly PathSpec[]): CommandFiles; //# sourceMappingURL=files.d.ts.map