UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

138 lines • 6.05 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 { compareCodePoints } from "../utils/sort.js"; import { Effect, Target } from "./types.js"; const MARK = Symbol.for('mirage.vfsCall'); // The functions whose TypeScript method name is not the name Python gives // them: each answers to Python's name at the dispatcher, so a policy sees // one name for it on both hosts. const DISPATCH_NAMES = new Map([ ['rmR', 'rm_r'], ['dirCopy', 'dir_copy'], ['firstLines', 'first_lines'], ['lastLines', 'last_lines'], ['lineCount', 'line_count'], ['readTree', 'read_tree'], ['filesContaining', 'files_containing'], ['linesContaining', 'lines_containing'], ['beforeFullWalk', 'before_full_walk'], ]); const METHOD_NAMES = new Map([...DISPATCH_NAMES].map(([method, name]) => [name, method])); /** The method that answers the dispatcher's `name`. */ export function methodName(name) { return METHOD_NAMES.get(name) ?? name; } // Removing, moving or copying a name is what these calls do, and the // dispatcher's link, overlay and cache bookkeeping for it is keyed on their // names. const NAMED = { [Effect.REMOVE]: ['unlink', 'rmdir', 'rm_r'], [Effect.RENAME]: ['rename'], [Effect.COPY]: ['copy', 'dir_copy'], }; /** * Make a VFS method callable by name through the dispatcher. * * `ws.dispatch('search_abc', path)` reaches a method marked here through * every check the dispatcher runs: hidden paths, path rules, the mount's mode and * admission policies. The effect tells the dispatcher what the call does to the * mount, so a read-only mount refuses a write and a policy judges it as one. * A subclass overriding a marked method keeps the mark, so a backend writes * its `read` without repeating it. * * The built-in functions' marks are where the dispatcher's op classes come from: * which ops follow a link, create a name, run one at a time per path or stamp * an mtime is read off what they declare here. REMOVE belongs to `unlink`, * `rmdir` and `rm_r`, RENAME to `rename` and COPY to `copy` and `dir_copy`: * what the dispatcher does around them (a link removed rather than followed, a * rename refused when it would bring hidden entries into view, the links and * cache below a moved, removed or copied directory) is keyed on those names, * so another function declaring one is refused. A VFS that deletes, moves or * copies defines those functions. `target` is the kind of entry the path names * (any when omitted), `creates` marks a WRITE that makes a missing file, as * open(2) with O_CREAT, and `subtree` a call that reaches everything below its * paths. Mirrors Python's `vfs_call`. */ export function vfsCall(options) { const mark = { effect: options.effect, target: options.target ?? Target.ANY, creates: options.creates ?? false, subtree: options.subtree ?? false, }; return function apply(method, context) { const name = DISPATCH_NAMES.get(String(context.name)) ?? String(context.name); const names = NAMED[mark.effect] ?? [name]; if (!names.includes(name)) { throw new TypeError(`${name}: only ${names.join(', ')} may declare ${mark.effect.toUpperCase()}`); } ; method[MARK] = { name, ...mark }; }; } /** * What `cls` declares for `name`, null when not dispatchable. The first class * up the prototype chain that marks `name` answers, so an unmarked override * inherits its base's mark. Mirrors Python's `declared`. */ export function declared(cls, name) { const method = methodName(name); let proto = cls.prototype; while (typeof proto === 'object' && proto !== null) { const fn = Object.getOwnPropertyDescriptor(proto, method)?.value; const mark = typeof fn === 'function' ? fn[MARK] : undefined; if (mark !== undefined) return mark; proto = Object.getPrototypeOf(proto); } return null; } /** Every name `cls` marks dispatchable, to what it declares. Mirrors Python's `declared_calls`. */ export function declaredCalls(cls) { const names = new Set(); let proto = cls.prototype; while (typeof proto === 'object' && proto !== null) { for (const name of Object.getOwnPropertyNames(proto)) names.add(name); proto = Object.getPrototypeOf(proto); } const calls = new Map(); const dispatched = [...names].map((name) => DISPATCH_NAMES.get(name) ?? name); for (const name of dispatched.sort(compareCodePoints)) { const mark = declared(cls, name); if (mark !== null) calls.set(name, mark); } return calls; } /** * The names in `calls` whose declaration matches every filter: the effects * and targets to keep (all when omitted), and `creates` to keep only * creating (true) or only non-creating (false) calls. Mirrors Python's * `call_names`. */ export function callNames(calls, filter = {}) { const names = new Set(); for (const [name, mark] of calls) { if (filter.effects !== undefined && !filter.effects.includes(mark.effect)) continue; if (filter.targets !== undefined && !filter.targets.includes(mark.target)) continue; if (filter.creates !== undefined && mark.creates !== filter.creates) continue; names.add(name); } return names; } //# sourceMappingURL=call.js.map