UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

131 lines • 5.64 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 { READ_CHUNK } from "./constants.js"; /** * A read-only handle that fetches its file a chunk at a time. * * The read-side twin of `FileHandle`, for a read-only open of a file larger * than one chunk; a smaller file is read whole, since whole is what the file * cache keeps. Nothing moves at open: a read fetches the chunk it lands in * through the file adapter's ranged read, and that chunk is kept so a sequential * read of small pieces costs one request per chunk. The file ends where a * fetch comes back short, not at the size the open saw: a rendering need * not be as long as the stored bytes a stat measured, so that size answers * only until the end has been seen. Mirrors Python's `ChunkedHandle`. */ export class ChunkedHandle { path; size; pos = 0; fetch; start = 0; kept = new Uint8Array(); end = null; generation = 0; constructor(path, size, fetch) { this.path = path; this.size = size; this.fetch = fetch; } /** Read at an explicit offset without moving the position. */ async pread(offset, size) { if (size <= 0 || this.atEnd(offset)) return new Uint8Array(); if (this.covers(offset, size)) return this.slice(offset, size); return (await this.load(offset, size)).slice(0, size); } /** Whether the kept bytes or known EOF answer without fetching. */ holds(offset, size) { return size <= 0 || this.atEnd(offset) || this.covers(offset, size); } /** Whether reading `size` bytes at the position (a negative size: the rest) needs a fetch first. */ lacks(size) { if (size === 0 || this.atEnd(this.pos)) return false; if (!this.covers(this.pos, Math.max(size, 1))) return true; return size < 0 && !this.keptToEnd(); } /** Fetch what a read of `size` bytes at the position lacks; any other size, one more chunk. */ async fill(size) { await this.load(this.pos, Math.max(size, 0)); } /** The kept bytes from `offset`, at most `size` of them; empty when none are kept there. */ peek(offset, size) { const keptEnd = this.start + this.kept.length; if (size <= 0 || offset < this.start || offset >= keptEnd) return new Uint8Array(); return this.slice(offset, Math.min(size, keptEnd - offset)); } /** True once the position sits at or past where the file is known to end. */ get eof() { return this.atEnd(this.pos); } /** Forget the kept bytes: the next read fetches the file anew. */ drop() { this.generation += 1; this.kept = new Uint8Array(); this.end = null; } atEnd(offset) { return this.end !== null && offset >= this.end; } keptToEnd() { return this.end !== null && this.start + this.kept.length >= this.end; } covers(offset, size) { const keptEnd = this.start + this.kept.length; return (this.start <= offset && offset < keptEnd && (offset + size <= keptEnd || keptEnd === this.end)); } slice(offset, size) { const low = offset - this.start; return this.kept.slice(low, low + Math.max(0, size)); } /** * Fetch so a read at `offset` of `size` is answerable, and answer the * bytes from `offset` on. A fetch that continues the kept bytes is joined * onto what they hold from `offset` on, so a line can run across two * chunks. Reads may overlap (FUSE issues them concurrently), so the state * is read before the fetch and each read answers from its own bytes; a * `drop` while the fetch was out keeps them from being installed. */ async load(offset, size) { const { start, kept, generation } = this; const keptEnd = start + kept.length; const from = kept.length > 0 && start <= offset && offset <= keptEnd ? keptEnd : offset; const asked = Math.max(offset + size - from, READ_CHUNK); const bytes = await this.fetch(from, asked); let joined = bytes; if (from !== offset) { const held = kept.subarray(offset - start); joined = new Uint8Array(held.length + bytes.length); joined.set(held); joined.set(bytes, held.length); } if (generation === this.generation) { if (bytes.length < asked) { // An empty fetch past the bytes seen so far says only that the // file ends at or before it, so it caps the size without setting it. const seen = from === 0 || (kept.length > 0 && from === keptEnd); this.end = from + bytes.length; this.size = bytes.length > 0 || seen ? this.end : Math.min(this.size, this.end); } this.start = offset; this.kept = joined; } return joined; } } //# sourceMappingURL=chunked.js.map