UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

110 lines • 4.59 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 { nowIso } from "../../utils/dates.js"; import { norm } from "../../utils/path.js"; import { rstripSlash } from "../../utils/slash.js"; import { fsError } from "../../errors/fs.js"; import { activePipe } from "../dev/device.js"; import { checkDestParents, checkRename, lookupError } from "./dest.js"; function moveAttrs(accessor, src, dst) { const attrs = accessor.store.attrs.get(src); if (attrs !== undefined) { accessor.store.attrs.delete(src); accessor.store.attrs.set(dst, attrs); } } // Re-key every descendant of a renamed directory. A synthetic-directory // store keeps subdirectories as entries of their own, so moving only the // files leaves those behind. The phantom tree the orphans imply makes the // old name reappear in its parent's listing and then stat as missing -- // the same shape checkDestParents refuses on the way in. function moveSubtree(accessor, s, d) { const srcPrefix = `${rstripSlash(s)}/`; const dstPrefix = `${rstripSlash(d)}/`; const { store } = accessor; for (const key of [...store.dirs]) { if (!key.startsWith(srcPrefix)) continue; store.dirs.delete(key); store.dirs.add(dstPrefix + key.slice(srcPrefix.length)); } for (const key of [...store.files.keys()]) { if (!key.startsWith(srcPrefix)) continue; const data = store.files.get(key); if (data !== undefined) { store.files.set(dstPrefix + key.slice(srcPrefix.length), data); store.files.delete(key); } } for (const key of [...store.modified.keys()]) { if (!key.startsWith(srcPrefix)) continue; const mod = store.modified.get(key); if (mod !== undefined) { store.modified.set(dstPrefix + key.slice(srcPrefix.length), mod); store.modified.delete(key); } } for (const key of [...store.attrs.keys()]) { if (!key.startsWith(srcPrefix)) continue; moveAttrs(accessor, key, dstPrefix + key.slice(srcPrefix.length)); } } /** * Move one key, a directory with its subtree. A live descriptor at either * end is the shell's to release, so moving one or over one is EPERM (GNU: * `mv /dev/fd/63 /dev/fd/62` is "Operation not permitted"). Mirrors * Python's rename. */ export async function rename(accessor, src, dst) { const s = norm(src.mountPath); const d = norm(dst.mountPath); if (activePipe(accessor, s) !== null) throw fsError(src, 'EPERM'); if (activePipe(accessor, d) !== null) throw fsError(dst, 'EPERM'); const now = nowIso(); checkDestParents(accessor, dst, d); // rename(2) onto its own name succeeds and changes nothing; moving the // key onto itself would delete it, and a directory's whole subtree. if (s === d) { if (accessor.store.files.has(s) || accessor.store.dirs.has(s)) return; throw lookupError(accessor, src, s); } checkRename(accessor, src, s, dst, d); const srcFile = accessor.store.files.get(s); if (srcFile !== undefined) { accessor.store.files.set(d, srcFile); accessor.store.files.delete(s); accessor.store.modified.set(d, accessor.store.modified.get(s) ?? now); accessor.store.modified.delete(s); moveAttrs(accessor, s, d); // A file has nothing beneath it: both ends take the unlink flavor. return Promise.resolve(); } if (accessor.store.dirs.has(s)) { accessor.store.dirs.delete(s); accessor.store.dirs.add(d); accessor.store.modified.set(d, accessor.store.modified.get(s) ?? now); accessor.store.modified.delete(s); moveAttrs(accessor, s, d); moveSubtree(accessor, s, d); return Promise.resolve(); } throw lookupError(accessor, src, s); } //# sourceMappingURL=rename.js.map