UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

121 lines • 5.14 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 { enoent, enotempty } from "../../errors/fs.js"; import { rstripSlash } from "../../utils/slash.js"; import { compareCodePoints } from "../../utils/sort.js"; import { nowIso } from "../../utils/dates.js"; import { norm } from "../../utils/path.js"; import { checkDestParents, checkRename, keepLeft, lookupError } from "./dest.js"; // Re-key every descendant of a renamed directory. A synthetic-directory // store keeps subdirectories as members of their own set, 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. The whole new // tree is written before any old key is deleted: a full Redis refuses // writes but still runs deletes, so a refused write must find the old tree // intact. Returns the directories removed, whose metadata the caller drops // once it knows which ones a late key keeps. async function moveSubtree(store, s, d) { const prefix = rstripSlash(s) + '/'; const dPrefix = rstripSlash(d) + '/'; const dirs = [...(await store.listDirs())] .sort(compareCodePoints) .filter((k) => k.startsWith(prefix)); const moved = []; for (const key of dirs) { const newKey = dPrefix + key.slice(prefix.length); const mod = await store.getModified(key); const attrs = await store.getAttrs(key); await store.addDir(newKey); if (mod !== null) await store.setModified(newKey, mod); if (Object.keys(attrs).length > 0) await store.setAttrs(newKey, attrs); } for (const key of await store.listFiles()) { if (!key.startsWith(prefix)) continue; const newKey = dPrefix + key.slice(prefix.length); const data = await store.getFile(key); if (data === null) continue; const mod = await store.getModified(key); const attrs = await store.getAttrs(key); await store.setFile(newKey, data); if (mod !== null) await store.setModified(newKey, mod); if (Object.keys(attrs).length > 0) await store.setAttrs(newKey, attrs); moved.push(key); } for (const key of moved) { await store.delFile(key); await store.delModified(key); await store.delAttrs(key); } for (const key of dirs) await store.removeDir(key); return dirs; } export async function rename(accessor, src, dst) { const s = norm(src.mountPath); const d = norm(dst.mountPath); const now = nowIso(); const store = accessor.store; await checkDestParents(store, dst, d); await checkRename(store, src, s, dst, d); // Onto itself is a no-op: the write-then-delete below would erase it. if (s === d && ((await store.hasFile(s)) || (await store.hasDir(s)))) return; if (await store.hasFile(s)) { const data = await store.getFile(s); const mod = await store.getModified(s); const attrs = await store.getAttrs(s); if (data === null) throw enoent(src); await store.setFile(d, data); await store.setModified(d, mod ?? now); if (Object.keys(attrs).length > 0) await store.setAttrs(d, attrs); await store.delFile(s); await store.delModified(s); await store.delAttrs(s); // A file has nothing beneath it: both ends take the unlink flavor. return; } if (await store.hasDir(s)) { const mod = await store.getModified(s); const attrs = await store.getAttrs(s); await store.addDir(d); await store.setModified(d, mod ?? now); if (Object.keys(attrs).length > 0) await store.setAttrs(d, attrs); const removed = await moveSubtree(store, s, d); await store.removeDir(s); // A key written under the source while the subtree moved stayed, and the // directories that hold it keep their metadata. const kept = await keepLeft(store, s); for (const key of [...removed, s]) { if (kept.has(key)) continue; await store.delModified(key); await store.delAttrs(key); } if (kept.size > 0) throw enotempty(src); return; } throw await lookupError(store, src, s); } //# sourceMappingURL=rename.js.map