UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

104 lines • 4.86 kB
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= import { eisdir, enoent } from "../../errors/fs.js"; import { sliceWindow } from "../../utils/ranges.js"; import { stripSlash } from "../../utils/slash.js"; import { ROOT } from "./scope.js"; /** * Build a call only some file kinds answer: classify, guard, dispatch, null * for the rest. * * The line calls (`firstLines`, `lastLines`, `lineCount`, `follow`) are * answered for a backend's data files (a table's rows, a collection's * documents) and not for its metadata files (a schema, a database.json), * which the caller reads the usual way. The table names the data kinds once, * so no answer repeats the check. Each answer is called as * `answer(accessor, match, path, ...args)`, after `guard`. Mirrors * `per_kind` in `mirage/core/hierarchy/read.py`. */ export function perKind(detect, answers, options = {}) { return async (accessor, path, ...args) => { const match = detect(path); const answer = answers[match.kind]; if (answer === undefined) return null; if (options.guard !== undefined) await options.guard(accessor, match, path.virtual); return answer(accessor, match, path, ...args); }; } /** * Build a hierarchy read: classify, dispatch, refuse the rest. * * Readers own their fetches, guards and rendering, handed what is at the path * (`entry`) and at its parent (`parent`) as the listings name them; the kit * owns the classification and the ENOENT funnel for every non-file shape. * `readers` holds one reader per leaf kind. Mirrors `make_read` in * `mirage/core/hierarchy/read.py`. */ export function makeRead(detect, readers, options = {}) { const windowed = options.windowed ?? {}; const parented = options.parented === true; return async function read(accessor, path, entry = null, parent = null, window = {}) { const match = detect(path); const windowReader = windowed[match.kind]; const reader = readers[match.kind]; if (parented && (windowReader !== undefined || reader !== undefined) && parent === null && stripSlash(path.vfsPath).includes('/')) { // Named as the file the caller asked for, the way GNU reports // `cat missing/f` against the operand rather than its directory. throw enoent(path.virtual); } if (windowReader !== undefined) { return windowReader(accessor, match, path, entry, parent, window); } if (reader === undefined) { // A directory that exists by construction (the root, or a // probed=false scope) read as a file is EISDIR. Everything else is // reported absent: a matched shape alone is no proof the node // exists, and GNU says "No such file" for a missing name, "Is a // directory" only for a real one. if (match.kind === ROOT || (match.scope !== null && !match.scope.leaf && !match.scope.probed)) { throw eisdir(path); } throw enoent(path); } return reader(accessor, match, path, entry, parent); }; } /** * Build a byte-ranged read over a hierarchy read. * * A rendered file has no remote range to ask for — its bytes do not exist * until the read renders them — so the window is sliced after the fact. A * stored blob does (discord and slack attachments serve HTTP range * requests), and downloading the whole file to keep a slice would defeat the * ranged read; those kinds name a ranged reader in `ranged` and push the * byte window to the source. */ export function makeReadRange(detect, read, ranged) { return async function readRange(accessor, path, entry = null, parent = null, options) { const offset = options?.offset ?? 0; const size = options?.size ?? null; const match = detect(path); const fn = ranged[match.kind]; if (fn !== undefined) return fn(accessor, match, path, entry, parent, offset, size); return sliceWindow(await read(accessor, path, entry, parent), offset, size); }; } //# sourceMappingURL=read.js.map