@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
77 lines • 3.44 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 { keyStats } from "./stat.js";
import { readdirError } from "../../errors/fs.js";
import { rstripSlash } from "../../utils/slash.js";
import { compareCodePoints } from "../../utils/sort.js";
import { norm } from "../../utils/path.js";
export async function readdir(accessor, path) {
// A pattern spec addresses the directory whose entries the glob filters,
// and the rest of this function works in mount-relative space, so the
// directory has to be read off `dir` rather than off the virtual
// `directory` string (python strips the prefix by hand for the same
// reason).
const target = path.pattern !== null ? path.dir : path;
const store = accessor.store;
const p = norm(target.mountPath);
if (!(await store.hasDir(p))) {
throw await readdirError(path, p, (k) => store.hasFile(k), (k) => store.hasDir(k));
}
const dirPrefix = p === '/' ? '/' : `${p}/`;
const seen = new Set();
const files = await store.listFiles();
const dirs = await store.listDirs();
for (const key of [...files, ...dirs]) {
if (key === p)
continue;
if (!key.startsWith(dirPrefix))
continue;
const remainder = key.slice(dirPrefix.length);
const first = remainder.split('/')[0];
if (first !== undefined && first.length > 0)
seen.add(dirPrefix + first);
}
const fileSet = new Set(files);
return keyStats(store, [...seen].sort(compareCodePoints).map((key) => [key, !fileSet.has(key)]));
}
/**
* Every key under a directory from one scan, each as `stat` answers it,
* fetched in one pipeline; null when `path` is no directory, or when a key's
* parent is none (a file another client left without its folder), so the
* walk's readdir and stat report what is there. Mirrors Python's `tree`.
*/
export async function tree(accessor, path) {
const store = accessor.store;
const p = norm(path.mountPath);
if (!(await store.hasDir(p)))
return null;
const base = rstripSlash(p) + '/';
const dirs = [...(await store.listDirs())].sort(compareCodePoints);
const keys = [
...(await store.listFiles(base)).map((key) => [key, false]),
...dirs.filter((key) => key.startsWith(base)).map((key) => [key, true]),
].filter(([key]) => key !== p);
const known = new Set(dirs);
if (keys.some(([key]) => !known.has(key.slice(0, key.lastIndexOf('/')) || '/')))
return null;
const stats = await keyStats(store, keys);
const found = new Map();
stats.forEach((st, i) => {
const key = keys[i]?.[0];
if (key !== undefined)
found.set(key.slice(base.length), st);
});
return found;
}
//# sourceMappingURL=readdir.js.map