UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

93 lines • 4.43 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 { enotsup, fsError } from "../../errors/fs.js"; import { WritePolicy } from "../../types.js"; import { stripMount, underPath } from "../../utils/key_prefix.js"; import { rstripSlash } from "../../utils/slash.js"; import { writeConditions } from "./write_policy.js"; // A driver that knows where its bytes live (a disk root, a bucket and key // prefix) says so through `storageLocation`; one that does not is its own // location, keyed by identity. Mirrors Python's `vfs_storage_location`. const OWN_LOCATION = new WeakMap(); let ownLocations = 0; export function vfsStorageLocation(vfs) { const location = vfs.storageLocation(); if (location !== null) return location; let own = OWN_LOCATION.get(vfs); if (own === undefined) { ownLocations += 1; own = `${vfs.name}:${String(ownLocations)}`; OWN_LOCATION.set(vfs, own); } return own; } /** * Build the transfer generics' identity function for a mount set. * * `cp` and `mv` compare two operands to decide whether they name the same * file. Within one mount the mount-relative path answers that, but across * mounts it does not: two prefixes can address one store, and there a move * would copy an object over itself and then unlink the source. * * The VFS's storage id and the mount-relative path are joined into * one path-like string rather than kept as separate components, so nested * backings collapse onto the same key. Two disk mounts rooted at * `/srv/data` and `/srv/data/sub` make `/a/sub/x` and `/b/x` the same * file, and both render as `disk:/srv/data/sub/x`. A delimiter between * the two parts would keep them apart and let the move through. * * The mount-relative path keeps its leading slash so the generics' * `startsWith(key + '/')` containment test still marks a directory as an * ancestor of its children, and only within one storage. */ export function makeStorageKey(registry) { return (path) => { const entry = registry.tryMountFor(path.virtual); if (entry === null) { // Outside every mount there is no storage to name, so fall back to // the path itself; such an operand fails on its own when the // command tries to read it. return rstripSlash(path.virtual); } const rel = rstripSlash(stripMount(path.virtual, rstripSlash(entry.prefix))); return vfsStorageLocation(entry.vfs) + rel; }; } /** * Refuse up front a delete a mount under the path could not condition: a * move across mounts copies before it deletes, and on a conditional mount * whose backend ignores delete conditions the delete would be refused after * the copy landed. The owning mount counts, and so does any mount nested * under the path, which the walk would cross; a mount above the owner is * never touched. A live pipe at or below the source cannot be deleted either, * and copying it would drain it, so it is refused with EPERM, as `rm` refuses * it; it is looked up as the moving command's own session sees /dev. Mirrors * Python's `make_check_unlink`. */ export function makeCheckUnlink(registry, access) { return (path) => { const owner = registry.tryMountFor(path.virtual); if (owner?.holdsLivePipe(path.virtual, access) === true) throw fsError(path, 'EPERM'); for (const entry of registry.allMounts()) { if (entry.write === WritePolicy.CONDITIONAL && (entry === owner || underPath(entry.prefix, path.virtual)) && !writeConditions(entry.vfs).includes('delete')) { throw enotsup(entry.vfs.name, 'conditional delete', path); } } }; } //# sourceMappingURL=storage.js.map