UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

125 lines • 5.08 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 { conditionVersion, nativeCondition, stale, writeCondition } from "../../cache/context.js"; import { liftLost, lostCount, record, startOp } from "../../observe/context.js"; import { enoent } from "../../errors/fs.js"; import { DropboxApiError } from "./client.js"; import { copyPath, deletePath, getMetadata, listFolder, lookup, refused, } from "./api.js"; import { MISSING_SOURCE, TAKEN, TAKEN_BY_FOLDER } from "./constants.js"; import { liveOf } from "./fingerprint.js"; import { checkPath, dropboxPathOf } from "./paths.js"; /** * The refusal for a copy or move whose cleared destination came back. The * condition the op sent stays held, whatever took the name. Mirrors Python's * `_retaken`. */ async function retaken(err, cond, dst) { if (cond === null || !(err instanceof DropboxApiError) || !err.summary.startsWith(TAKEN)) { return null; } const held = conditionVersion(cond); return stale(dst, { version: held === '' ? null : held }); } /** * Run a copy or move, replacing a file that holds the destination name. The op * goes first, since Dropbox answers a missing source before a taken name. A * held destination is measured before the op, cleared with its rev on a taken * name, and refused as gone when a folder took it. `emptyFolder` lets an empty * folder there give way too. Returns the op's reply and the destination it * replaced when that was looked up. Mirrors Python's `replace_onto`. */ export async function replaceOnto(tm, src, dst, to, op, emptyFolder) { const cond = await writeCondition(dst, 'copy'); let rev = null; if (cond?.kind === 'match' && cond.version !== '') { rev = await nativeCondition(dst, cond, liveOf(await lookup(tm, to)), 'copy'); } try { return [await op(), null]; } catch (err) { if (!(err instanceof DropboxApiError)) throw err; if (err.summary.startsWith(MISSING_SOURCE)) throw enoent(src.virtual); if (!err.summary.startsWith(TAKEN)) throw err; let existing = null; if (rev === null) { existing = await getMetadata(tm, to); const blocks = existing['.tag'] === 'folder' && !(emptyFolder && (await listFolder(tm, to, { limit: 1 })).length === 0); if (blocks) throw err; } else if (err.summary.startsWith(TAKEN_BY_FOLDER)) { throw await stale(dst); } try { await deletePath(tm, to, rev); } catch (cleared) { throw (await refused(dst, cleared, cond, rev)) ?? cleared; } try { return [await op(), existing]; } catch (again) { if (again instanceof DropboxApiError && again.summary.startsWith(MISSING_SOURCE)) { throw enoent(src.virtual); } throw (await retaken(again, cond, dst)) ?? again; } } } /** * copy_v2 copies files and folder subtrees server-side; an existing * destination FILE is replaced, as cp does (see `replaceOnto`). */ export async function copy(accessor, src, dst) { await checkPath(accessor, src); await checkPath(accessor, dst); const from = dropboxPathOf(accessor, src); const to = dropboxPathOf(accessor, dst); const tm = accessor.tokenManager; const upto = lostCount(); const timer = startOp(); await replaceOnto(tm, src, dst, to, () => copyPath(tm, from, to), false); record('copy', dst.virtual, 'dropbox', 0, timer); liftLost(dst, upto); } /** * Copy a folder tree onto a fresh name in one copy_v2. `cp -r` asks this * only when nothing holds `dst`, so a taken name is Dropbox's conflict, * never a merge. */ export async function dirCopy(accessor, src, dst) { await checkPath(accessor, src); await checkPath(accessor, dst); const tm = accessor.tokenManager; const upto = lostCount(); const timer = startOp(); try { await copyPath(tm, dropboxPathOf(accessor, src), dropboxPathOf(accessor, dst)); } catch (err) { if (err instanceof DropboxApiError && err.summary.startsWith(MISSING_SOURCE)) { throw enoent(src.virtual); } throw err; } record('copy_prefix', dst.virtual, 'dropbox', 0, timer); liftLost(dst, upto, true); } //# sourceMappingURL=copy.js.map