@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
188 lines • 7.26 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 { enoent } from "../../errors/fs.js";
import { stripSlash } from "../../utils/slash.js";
import { compareCodePoints } from "../../utils/sort.js";
import { graphList } from "../msgraph/client.js";
import { graphApi } from "../msgraph/config.js";
import { DriveLoc } from "../msgraph/drive.js";
import { driveRefPath, itemUrl } from "./client.js";
function resolvedPath(level, siteId = null, driveId = null, itemPath = null) {
return { level, siteId, driveId, itemPath };
}
function scopedItemPath(keyPrefix, raw) {
if (keyPrefix !== '' && raw !== '')
return `${keyPrefix}/${raw}`;
return keyPrefix || raw;
}
function driveKey(siteId, name) {
return `${siteId}\0${name}`;
}
function text(value) {
return typeof value === 'string' ? value : '';
}
// A site whose webUrl is not on the configured tenant host is another
// tenant's, and a site name is only unique within one.
function onTenant(site, tenantHost) {
const webUrl = text(site.webUrl);
return URL.canParse(webUrl) && new URL(webUrl).host === tenantHost.toLowerCase();
}
async function siteItems(accessor) {
const config = accessor.config;
const sites = await graphList(config, `${graphApi(config)}/sites`, {
search: config.siteFilter ?? '*',
$select: 'id,displayName,name,webUrl',
});
const tenantHost = config.tenantHost;
return tenantHost === null ? sites : sites.filter((site) => onTenant(site, tenantHost));
}
async function driveItems(accessor, siteId) {
const url = `${graphApi(accessor.config)}/sites/${encodeURIComponent(siteId)}/drives`;
return graphList(accessor.config, url, { $select: 'id,name' });
}
/**
* Every accessible site as `[display name, id]`, sorted by name.
*
* A site with no id or no name is skipped: nothing could address it.
* Unscoped paths use only the listed name. Internal names also resolve a
* configured site when the mount is scoped to one document library.
*
* Args:
* accessor: the mount's accessor.
*/
export async function siteEntries(accessor) {
const cache = accessor.siteCache;
const entries = [];
for (const site of await siteItems(accessor)) {
const id = text(site.id);
const name = text(site.name);
const display = text(site.displayName) || name;
if (id === '' || display === '')
continue;
entries.push([display, id]);
cache.set(display, id);
if (name !== '' && accessor.config.site !== null && accessor.config.drive !== null)
cache.set(name, id);
}
return entries.sort((left, right) => compareCodePoints(left[0], right[0]));
}
export async function listSites(accessor) {
return (await siteEntries(accessor)).map((entry) => entry[0]);
}
/**
* A site's document libraries as `[name, id]`, sorted by name.
*
* Args:
* accessor: the mount's accessor.
* siteId: the site whose drives to list.
*/
export async function driveEntries(accessor, siteId) {
const cache = accessor.driveCache;
const entries = [];
for (const drive of await driveItems(accessor, siteId)) {
const id = text(drive.id);
const name = text(drive.name);
if (id === '' || name === '')
continue;
entries.push([name, id]);
cache.set(driveKey(siteId, name), id);
}
return entries.sort((left, right) => compareCodePoints(left[0], right[0]));
}
export async function listDrives(accessor, siteId) {
return (await driveEntries(accessor, siteId)).map((entry) => entry[0]);
}
async function resolveSiteId(accessor, name) {
const cache = accessor.siteCache;
if (!cache.has(name))
await siteEntries(accessor);
return cache.get(name) ?? null;
}
async function resolveDriveId(accessor, siteId, name) {
const key = driveKey(siteId, name);
const cache = accessor.driveCache;
if (!cache.has(key))
await driveEntries(accessor, siteId);
return cache.get(key) ?? null;
}
/**
* Resolve a mount path to its site, drive and drive-relative item path.
*
* A mount scoped to one site and drive (both configured) lives inside that
* drive, so its paths are drive-relative and the namespace levels vanish.
* An unscoped mount exposes `/<site>/<library>/...`.
*
* Args:
* accessor: the mount's accessor.
* path: the path to resolve.
*/
export async function resolve(accessor, path) {
const raw = stripSlash(path.vfsPath);
const config = accessor.config;
if (config.site !== null && config.drive !== null) {
const siteId = await resolveSiteId(accessor, config.site);
if (siteId === null)
return resolvedPath('site');
const driveId = await resolveDriveId(accessor, siteId, config.drive);
if (driveId === null)
return resolvedPath('drive', siteId);
const itemPath = scopedItemPath(config.keyPrefix, raw);
if (itemPath === '')
return resolvedPath('drive', siteId, driveId);
return resolvedPath('item', siteId, driveId, itemPath);
}
if (raw === '')
return resolvedPath('root');
const parts = raw.split('/');
const siteId = await resolveSiteId(accessor, parts[0] ?? '');
if (siteId === null)
return resolvedPath('site');
if (parts.length === 1)
return resolvedPath('site', siteId);
const driveId = await resolveDriveId(accessor, siteId, parts[1] ?? '');
if (driveId === null)
return resolvedPath('drive', siteId);
if (parts.length === 2)
return resolvedPath('drive', siteId, driveId);
return resolvedPath('item', siteId, driveId, parts.slice(2).join('/'));
}
/**
* Raise ENOENT unless `resolved` names a drive item.
*
* Args:
* path: the path `resolved` came from, named by the error.
* resolved: its resolution.
*/
export function requireItem(path, resolved) {
if (resolved.driveId === null || resolved.itemPath === null)
throw enoent(path);
}
export async function resolveItem(accessor, path) {
const resolved = await resolve(accessor, path);
requireItem(path, resolved);
return resolved;
}
export function driveLoc(config, resolved, virtual) {
const driveId = resolved.driveId;
if (driveId === null)
throw new Error('SharePoint path has no drive');
return new DriveLoc({
drive: driveId,
path: resolved.itemPath ?? '',
virtual: stripSlash(virtual),
url: (item, action) => itemUrl(config, driveId, item, action),
ref: (folder) => driveRefPath(driveId, folder),
});
}
//# sourceMappingURL=resolve.js.map