@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
121 lines • 5 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 { eexist, einval, eisdir, enoent, enotdir } from "../../errors/fs.js";
import { mountedPath } from "../../utils/key_prefix.js";
import { ancestors } from "../../utils/path.js";
import { rstripSlash } from "../../utils/slash.js";
import { statEntries } from "./client.js";
import { backendPath } from "./path.js";
/**
* What each mount-local path is, in one fs:batch-stat. drive9 creates missing
* parents on a write, a mkdir and a file rename, and keeps a file and a
* directory of one name side by side, so the errno open(2) or rename(2) would
* answer has to be worked out here from what the paths are before the call.
* Each path is asked bare, which answers the file of a name that is both, and
* with a trailing slash, kept under `key + '/'`, which answers only a
* directory. The mount root is always a directory and is not asked.
*/
export async function kindsOf(accessor, keys) {
const asked = [...new Set(keys)].filter((key) => key !== '/');
const remote = asked.map((key) => backendPath(accessor.config, key));
const found = await statEntries(accessor, [...remote, ...remote.map((path) => `${path}/`)]);
const kinds = new Map(asked.map((key, index) => [key, found[index] ?? null]));
asked.forEach((key, index) => kinds.set(`${key}/`, found[asked.length + index] ?? null));
kinds.set('/', { name: '/', isDir: true, size: 0, mtime: null });
return kinds;
}
/**
* What `key` is as a folder on a longer path: the directory of a name that is
* also a file, as a listing shows it, else what is there.
*/
export function folderOf(kinds, key) {
return kinds.get(`${rstripSlash(key)}/`) ?? kinds.get(key) ?? null;
}
/**
* ENOTDIR for a file above `key`, ENOENT for a missing directory above it,
* the first the walk meets, as the kernel resolves a path.
*/
export function brokenParent(kinds, spec, key) {
for (const ancestor of ancestors(key)) {
const entry = folderOf(kinds, ancestor);
if (entry === null)
return enoent(spec);
if (!entry.isDir)
return enotdir(spec);
}
return null;
}
export function lookupError(kinds, spec, key) {
return brokenParent(kinds, spec, key) ?? enoent(spec);
}
export function checkWriteTarget(kinds, spec, key) {
const broken = brokenParent(kinds, spec, key);
if (broken !== null)
throw broken;
if (kinds.get(key)?.isDir === true)
throw eisdir(spec);
}
/**
* Refuse a mkdir GNU refuses; true when `-p` finds it done. Plain mkdir is
* EEXIST for anything at the target and owes the parent chain the walk a
* write owes it. `-p` names the component it trips on: a file above the
* target is ENOTDIR, the target as a file EEXIST, and the target as a
* directory is success.
*/
export function checkMkdirTarget(kinds, spec, key, parents) {
const entry = kinds.get(key) ?? null;
if (!parents) {
if (entry !== null)
throw eexist(spec);
const broken = brokenParent(kinds, spec, key);
if (broken !== null)
throw broken;
return false;
}
for (const component of ancestors(key)) {
if (folderOf(kinds, component)?.isDir === false)
throw enotdir(mountedPath(spec, component));
}
if (entry !== null && !entry.isDir)
throw eexist(spec);
return entry !== null;
}
/**
* Refuse a rename rename(2) refuses once both ends resolve. A missing
* destination parent is ENOENT and a file above it ENOTDIR; a file onto a
* directory is EISDIR, a directory onto a file ENOTDIR, and into its own
* subtree EINVAL. A directory onto one that holds entries is left to drive9,
* which answers it with a conflict.
*/
export function checkRename(kinds, src, s, dst, d) {
const broken = brokenParent(kinds, dst, d);
if (broken !== null)
throw broken;
const source = kinds.get(s) ?? null;
if (source === null)
throw lookupError(kinds, src, s);
if (s === d)
return;
const target = kinds.get(d) ?? null;
if (!source.isDir) {
if (target?.isDir === true)
throw eisdir(dst);
return;
}
if (d.startsWith(`${rstripSlash(s)}/`))
throw einval(src);
if (target !== null && !target.isDir)
throw enotdir(dst);
}
//# sourceMappingURL=dest.js.map