UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

118 lines • 5.28 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 { walkFind } from "../../../core/generic/find.js"; import { FileType } from "../../../types.js"; import { isEnotdir, isMissingPath } from "../../../errors/fs.js"; import { rstripSlash } from "../../../utils/slash.js"; import { readdirOp, statOp } from "../generic/crossmount/utils.js"; import { beforeFullWalk, treeWalk } from "../utils/tree.js"; import { callerSignal } from "../../../context/session_context.js"; function walkWith(readdir, stat, dispatch, command) { return async (p, findType) => { if (findType === 'f') await beforeFullWalk(dispatch, p, command); const unreadable = []; const paths = await walkFind(p, { unreadable, readdir, stat }, { type: findType }); return { paths, unreadable }; }; } /** * The subtree listing tar and zip both walk with. * * Reuses find's walk so an archiver classifies an entry exactly the way find * does (through stat, never by name). The two calls a directory operand makes * share one readdir cache, so the second is answered from the index instead of * the backend. * * walkFind stands in for a backend's native find op, so it answers in * mount-relative keys; both archivers name members from virtual paths, so they * are lifted back here the way findGeneric lifts them. A directory the walk * could not open rides along (as the virtual path the walk recorded), so the * archiver can report it the way GNU does instead of silently leaving its * contents out. The files listed are every file the archiver reads, so the * mount that owns the operand may refuse the walk before they are listed. */ export function walkOf(files, dispatch, command) { const [readdir, stat] = treeWalk(files.readTree, files.readdir, files.stat, files.answers('read_tree'), files.signal); return walkWith((spec) => readdir(spec), stat, dispatch, command); } /** * The directory probe tar's `-C` check uses. * * Two channels, because a stat miss alone is not absence: on a prefix store * a directory is the set of keys under it and nothing answers stat for it, * so a readdir that returns anything is the second and deciding opinion. */ function isDirWith(stat, readdir) { return async (p) => { try { return (await stat(p)).type === FileType.DIRECTORY; } catch (err) { if (!isMissingPath(err) && !isEnotdir(err)) throw err; console.debug(`archive: ${p.virtual} does not stat; asking readdir`); } try { return (await readdir(p)).length > 0; } catch (err) { if (!isMissingPath(err) && !isEnotdir(err)) throw err; console.debug(`archive: ${p.virtual} is not a directory on either channel: ${String(err)}`); return false; } }; } export function isDirOf(files) { return isDirWith(files.stat, (p) => files.readdir(p)); } /** * One directory as its own backend lists it, through the dispatcher. * * The dispatcher adds the names the namespace owes a directory (nested mount roots * and symlinks) to every listing. The archive scan adds those itself, from * the same tables, and a walk that followed one would descend into a link's * target under the link's name or into a mount the scan must not cross, so * they are left to the scan. */ function ownListing(readdir, owed) { return async (p) => { const entries = await readdir(p); if (owed === undefined) return entries; const names = new Set(owed(p.virtual)); return entries.filter((entry) => { const trimmed = rstripSlash(entry); return !names.has(trimmed.slice(trimmed.lastIndexOf('/') + 1)); }); }; } /** * The subtree listing tar and zip walk with when a line spans mounts. * * Every directory is listed on the mount that owns it, so operands from * several mounts go into one archive, and find's walk classifies what it * lists exactly as it does on one backend. `readTree` is the walk's subtree * read (`dispatchedReadTree`); undefined lists every directory. */ export function relayWalkOf(dispatch, owed, command, readTree) { const [readdir, stat] = treeWalk(readTree, readdirOp(dispatch), statOp(dispatch), true, callerSignal()); return walkWith(ownListing(readdir, owed), stat, dispatch, command); } // The directory probe tar's `-C` check uses when a line spans mounts. export function relayIsDirOf(dispatch) { return isDirWith(statOp(dispatch), readdirOp(dispatch)); } //# sourceMappingURL=archive_io.js.map