@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
106 lines • 4.77 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 MIRAGE_RS_WASM from "../generated/mirage_rs.js";
import { decodeBase64 } from "../utils/base64.js";
import { StreamRefusal } from "./errors.js";
const encoder = new TextEncoder();
const decoder = new TextDecoder();
/**
* The mirage-rs NFSv3 server, which does no I/O of its own: the host feeds
* it each connection's bytes, runs the file operations `requests` names,
* and hands each result back through `complete`. It runs on a wasm
* instance of its own, so a long-lived server never holds the one streams
* share. Mirrors Python's NFSEngine.
*/
export class NFSEngine {
exports;
handle;
constructor(exports, handle) {
this.exports = exports;
this.handle = handle;
}
/**
* Open a server: `exportPath` is the path a client mounts, `generation` is
* stamped into every file handle (one from another generation is stale),
* and `port` is the port the host listens on.
*/
static async open(exportPath, generation, port) {
const module = await WebAssembly.compile(decodeBase64(MIRAGE_RS_WASM));
const exports = new WebAssembly.Instance(module, {}).exports;
const config = encoder.encode(JSON.stringify({ export: exportPath, generation, port }));
const ptr = exports.mirage_rs_args(config.length);
new Uint8Array(exports.memory.buffer, ptr, config.length).set(config);
const handle = exports.mirage_rs_nfs_open(config.length);
if (handle === 0) {
const ptr = exports.mirage_rs_error(0);
const message = decoder.decode(new Uint8Array(exports.memory.buffer, ptr, exports.mirage_rs_error_len(0)));
throw new StreamRefusal(message, new Uint8Array());
}
return new NFSEngine(exports, handle);
}
/** Take what connection `conn` sent; returns the replies it answers at once. */
feed(conn, data) {
this.put([data]);
if (this.exports.mirage_rs_nfs_feed(this.handle, conn, data.length) !== 0)
throw this.refusal();
return this.output();
}
/** The file operations calls wait on, each with its `id`. */
requests() {
this.exports.mirage_rs_nfs_requests(this.handle);
return JSON.parse(decoder.decode(this.output()));
}
/**
* Hand back one request's result: `{error: 'ENOENT'}`, or what it asked for
* (`attrs`, `entries`, `target`), and the bytes a read returned. Returns the
* call's connection (null once it has closed) and its reply once it has one.
*/
complete(id, reply, data = new Uint8Array()) {
const json = encoder.encode(JSON.stringify(reply));
this.put([json, data]);
const conn = this.exports.mirage_rs_nfs_complete(this.handle, id, json.length, data.length);
if (conn === -2)
throw this.refusal();
return [conn === -1 ? null : conn, this.output()];
}
/** Forget connection `conn` and the calls it has out. */
close(conn) {
this.exports.mirage_rs_nfs_disconnect(this.handle, conn);
}
/** Free the server. */
dispose() {
this.exports.mirage_rs_nfs_close(this.handle);
}
put(parts) {
const len = parts.reduce((sum, part) => sum + part.length, 0);
const ptr = this.exports.mirage_rs_nfs_buffer(this.handle, len);
const view = new Uint8Array(this.exports.memory.buffer, ptr, len);
let at = 0;
for (const part of parts) {
view.set(part, at);
at += part.length;
}
}
output() {
const ptr = this.exports.mirage_rs_nfs_output(this.handle);
const len = this.exports.mirage_rs_nfs_output_len(this.handle);
return new Uint8Array(this.exports.memory.buffer, ptr, len).slice();
}
refusal() {
const ptr = this.exports.mirage_rs_nfs_error(this.handle);
const len = this.exports.mirage_rs_nfs_error_len(this.handle);
return new StreamRefusal(decoder.decode(new Uint8Array(this.exports.memory.buffer, ptr, len)), new Uint8Array());
}
}
//# sourceMappingURL=nfs.js.map