UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

134 lines • 5.94 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 { KeyLock } from "../../cache/lock.js"; import { runWithSession } from "../../context/session_context.js"; import { DEFAULT_READ_SPEC, FileType, MountMode, PathSpec, WritePolicy } from "../../types.js"; import { eexist, enoent, enotdir, isEnoent } from "../../errors/fs.js"; import { pathVisible } from "../../utils/hidden.js"; import { norm, parent } from "../../utils/path.js"; import { DocumentVFS } from "../../vfs/document/document.js"; import { applyProfile } from "../session/resolve.js"; import { SessionState } from "../session/session.js"; import * as render from "./render.js"; export class Documents { registry; files; manager; session; profile; ensureLoaded; unmount; followParent; views = new Map(); lock = new KeyLock(); constructor(registry, files, manager, session, profile, ensureLoaded, unmount, followParent) { this.registry = registry; this.files = files; this.manager = manager; this.session = session; this.profile = profile; this.ensureLoaded = ensureLoaded; this.unmount = unmount; this.followParent = followParent; } render(kind) { return (kind === 'vfs' ? render.vfsMd : render.skillMd)(this.registry, this.session(null)); } /** Drop every binding; a snapshot load restores none. */ async clear() { await this.lock.withLock('documents', async () => { for (const path of [...this.views.keys()]) await this.unmount(path); }); } async releaseSession(sessionId) { await this.lock.withLock('documents', async () => { for (const [path, view] of this.views) { view.sessions.delete(sessionId); if (!view.globalView && view.sessions.size === 0) await this.unmount(path); } }); } async get(kind, path, profile, sessionId) { if (profile !== undefined && (path !== undefined || sessionId !== undefined)) { throw new Error('profile is only valid for generation without a path or session'); } await this.ensureLoaded(); let session; if (profile !== undefined) { session = new SessionState({ sessionId: '' }); applyProfile(session, this.profile(profile)); } else session = sessionId === undefined ? this.session(null) : this.manager.get(sessionId); return runWithSession(session, async () => { if (path !== undefined) { const virtual = path instanceof PathSpec ? path.virtual : path; if (norm(virtual) !== virtual || virtual.includes('\0') || virtual .slice(1) .split('/') .some((part) => ['', '.', '..'].includes(part))) { throw new Error('document path must be an absolute, normalized file path'); } // Bound where a later read lands: every link above the name is // followed, as the read's own walk follows it. const bound = this.followParent(virtual); if (![virtual, bound].every((p) => pathVisible(session.visibility, p))) throw enoent(virtual); await this.lock.withLock('documents', () => this.expose(kind, bound, sessionId === undefined ? null : session)); } return this.render(kind); }, { owner: this.manager }); } async expose(kind, path, session) { const directory = await this.files.stat(parent(path)); if (directory.type !== FileType.DIRECTORY) throw enotdir(parent(path)); let view = this.views.get(path); if (view !== undefined && view.kind !== kind) throw eexist(path); if (view === undefined) { // Collision checks are host-side: a hidden backend entry must not be // overwritten by a new view either. await runWithSession(new SessionState({ sessionId: '' }), async () => { try { await this.files.stat(path, undefined, { nofollow: true }); } catch (error) { if (isEnoent(error)) return; throw error; } throw eexist(path); }, { owner: this.manager }); view = new DocumentVFS(path.slice(path.lastIndexOf('/') + 1), () => this.render(kind), kind); const document = view; const mount = this.registry.mount(path, view, MountMode.READ, DEFAULT_READ_SPEC, { write: WritePolicy.UNCONDITIONAL, }); mount.visible = (sessionId) => document.globalView || document.sessions.get(this.session(sessionId).sessionId) === this.session(sessionId).createdAt; this.views.set(path, view); } if (session === null) view.globalView = true; else view.sessions.set(session.sessionId, session.createdAt); } } //# sourceMappingURL=documents.js.map