UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

176 lines • 6.9 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 { ByteSlice, MEMORY_LIMIT } from "../../io/spool.js"; import { concat } from "../../utils/bytes.js"; import { spliceWindow } from "../../utils/ranges.js"; /** * Buffered (offset, payload) writes as the fewest pwrites that leave a file * as the writes did, in arrival order. * * The kernel adapters buffer each write on its handle and owe the mount the * lot at flush. A write that starts inside the last run, or right at its * end, folds into it, so a sequential stream is one run. Any other starts a * run of its own; the runs apply in order, so a later run still overwrites * what it overlaps of an earlier one. * * Args: * writes: the buffered writes, in arrival order. */ export function writeRuns(writes) { const runs = []; for (const [offset, chunk] of writes) { const last = runs.at(-1); if (last !== undefined && last.start <= offset && offset <= last.start + last.length) { const at = offset - last.start; if (at === last.length) { last.parts.push(chunk.slice()); last.length += chunk.byteLength; continue; } const merged = spliceWindow(concat(last.parts), at, chunk); last.parts = [merged]; last.length = merged.byteLength; continue; } runs.push({ start: offset, parts: [chunk.slice()], length: chunk.byteLength }); } return runs.map((run) => [run.start, concat(run.parts)]); } /** * A read window with a handle's buffered writes laid over it. * * The kernel adapters keep a handle's writes until flush; a read through that * handle sees them, as a read after write(2) does. They apply in arrival * order, so a later write wins where it overlaps, and a gap a write grew the * file across reads as zeros. Mirrors Python's `overlaid`. * * Args: * stored: the stored bytes from `offset`, short where the stored file ends. * offset: where the window starts. * size: the window's length. * writes: the buffered writes, in arrival order. */ export function overlaid(stored, offset, size, writes) { let reach = offset + stored.byteLength; for (const [start, data] of writes) reach = Math.max(reach, start + data.byteLength); const end = Math.min(offset + size, reach); if (end <= offset) return new Uint8Array(); const out = new Uint8Array(end - offset); out.set(stored.subarray(0, end - offset)); for (const [start, data] of writes) { const low = Math.max(start, offset); const high = Math.min(start + data.byteLength, end); if (low < high) out.set(data.subarray(low - start, high - start), low - offset); } return out; } /** * Buffered writes in the groups `writeRuns` folds into one run each, * unfolded, for a buffer some of whose writes wait in a spool. Mirrors * Python's `run_groups`. * * Args: * writes: the buffered writes, in arrival order. */ export function runGroups(writes) { const groups = []; let start = 0; let end = 0; for (const [offset, chunk] of writes) { const last = groups.at(-1); if (last !== undefined && start <= offset && offset <= end) { last.push([offset, chunk]); end = Math.max(end, offset + chunk.byteLength); continue; } groups.push([[offset, chunk]]); start = offset; end = offset + chunk.byteLength; } return groups; } /** * Buffered writes as `overlaid` lays them over a read window: a write * waiting in a spool is read for the part of the window it covers, and past * that keeps only where it ends, so the read still reaches as far as the * file does. Each spool stays open until the window is read, so a handle * that lands and goes meanwhile still shows what it held to a read that took * its writes. Mirrors Python's `window_writes`. * * Args: * writes: the buffered writes, in arrival order. * offset: where the window starts. * size: the window's length. */ export async function windowWrites(writes, offset, size) { const spools = new Set(); for (const [, run] of writes) if (run instanceof ByteSlice) spools.add(run.source); for (const spool of spools) spool.hold(); try { const out = []; for (const [at, run] of writes) { if (!(run instanceof ByteSlice)) { out.push([at, run]); continue; } const low = Math.max(at, offset); const high = Math.min(at + run.size, offset + size); if (low < high) out.push([low, await run.source.read(run.offset + low - at, high - low)]); out.push([at + run.size, new Uint8Array()]); } return out; } finally { for (const spool of spools) await spool.unhold(); } } /** * The bytes one group of buffered writes leaves, from where it starts to * where it reaches, a window at a time. Each window lays only the writes * that reach it, so a flush costs the writes plus the windows, however many * small edits a large file has. Mirrors Python's `run_stream`. * * Args: * group: one of `runGroups`. */ export async function* runStream(group) { const first = group[0]; if (first === undefined) return; const start = first[0]; let end = start; for (const [at, run] of group) end = Math.max(end, at + run.byteLength); const windows = Array.from({ length: Math.ceil((end - start) / MEMORY_LIMIT) }, () => []); for (const write of group) { const [at, run] = write; const last = Math.floor((at + run.byteLength - 1 - start) / MEMORY_LIMIT); for (let index = Math.floor((at - start) / MEMORY_LIMIT); index <= last; index++) windows[index]?.push(write); } for (const [index, writes] of windows.entries()) { const offset = start + index * MEMORY_LIMIT; const size = Math.min(MEMORY_LIMIT, end - offset); yield overlaid(new Uint8Array(), offset, size, await windowWrites(writes, offset, size)); } } //# sourceMappingURL=file_handle.js.map