UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

112 lines • 4.58 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 { conditioned, settleAfter, heldVersions, keepRefused } from "../../cache/context.js"; import { liftLost, lostCount, record, startOp } from "../../observe/context.js"; import { enoent, enotempty } from "../../errors/fs.js"; import { childSpec } from "../../utils/key_prefix.js"; import { DropboxApiError } from "./client.js"; import { deletePath, listFolder, lookup } from "./api.js"; import { GONE_SUMMARIES, LOST_SUMMARIES } from "./constants.js"; import { liveOf } from "./fingerprint.js"; import { checkPath, dropboxPathOf } from "./paths.js"; import { deleteResolved } from "./unlink.js"; /** * Delete a folder file by file, each held to the version read or listed. A file * that changed stays, with the folders above it; a folder goes once a listing * shows it empty. Returns whether the folder itself was deleted. */ async function deleteTree(accessor, path, lost) { const tm = accessor.tokenManager; const apiPath = dropboxPathOf(accessor, path); const entries = await listFolder(tm, apiPath); const walk = entries.map((entry) => [entry, childSpec(path, entry.name)]); const held = await heldVersions(walk.map(([, spec]) => spec)); let emptied = true; for (const [i, [entry, spec]] of walk.entries()) { if (entry['.tag'] === 'folder') { emptied = (await deleteTree(accessor, spec, lost)) && emptied; continue; } const live = liveOf(entry); const want = held[i] ?? live?.content ?? null; if (want !== live?.content) { lost.push([spec, want]); emptied = false; continue; } try { await deletePath(tm, dropboxPathOf(accessor, spec), want !== null ? live.native : null); } catch (err) { if (!(err instanceof DropboxApiError)) throw err; if (GONE_SUMMARIES.some((s) => err.summary.startsWith(s))) { console.debug(`${spec.virtual} already gone: ${String(err)}`); continue; } if (!LOST_SUMMARIES.some((s) => err.summary.startsWith(s))) throw err; lost.push([spec, want]); emptied = false; } } if (!emptied) return false; if ((await listFolder(tm, apiPath, { limit: 1 })).length > 0) throw enotempty(path); await deletePath(tm, apiPath); return true; } /** Delete a looked-up entry: a file alone, a folder walked. Mirrors Python's `_remove`. */ async function remove(accessor, path, entry, lost) { if (entry['.tag'] === 'folder') await deleteTree(accessor, path, lost); else await deleteResolved(accessor, path, entry); return true; } /** * Remove a file or folder; a conditional mount walks it file by file. Mirrors * Python's `rm_r`. */ export async function rmR(accessor, path) { await checkPath(accessor, path); const apiPath = dropboxPathOf(accessor, path); const timer = startOp(); if (!conditioned(path, 'delete')) { try { await deletePath(accessor.tokenManager, apiPath); } catch (err) { if (err instanceof DropboxApiError && err.status === 409) throw enoent(path.virtual); throw err; } record('rm_r', path.virtual, 'dropbox', 0, timer); return; } const entry = await lookup(accessor.tokenManager, apiPath); if (entry === null) throw enoent(path.virtual); const upto = lostCount(); const lost = []; await settleAfter(() => remove(accessor, path, entry, lost), async (done) => { record('rm_r', path.virtual, 'dropbox', 0, timer); const refusal = await keepRefused(lost); if (done === true && refusal !== null) throw refusal; }); liftLost(path, upto, true); } //# sourceMappingURL=rm.js.map