@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
125 lines • 5.08 kB
JavaScript
// ========= 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