@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
197 lines • 12.1 kB
TypeScript
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