@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
153 lines • 6.75 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. =========
/**
* The window a reader was asked for, or undefined when it wants all of it.
*
* @param offset first byte to read
* @param size how many bytes, or null for the rest
*/
export function windowFor(offset, size) {
if (offset === 0 && size === null)
return undefined;
return { offset, size };
}
/**
* An HTTP `Range` value for a byte window, or null for the whole file.
*
* Every HTTP-backed store spells a partial read the same way, so the spelling
* lives here rather than once per backend. `null` means the caller wants
* everything and should send no header at all.
*
* A zero-length window has no HTTP spelling: `bytes=N--1` is malformed and an
* absent header means the opposite of what was asked. It is refused here so a
* caller that forgot to short-circuit finds out rather than silently
* downloading the whole object.
*
* @param offset first byte to read
* @param size how many bytes, or null for the rest of the file
*/
export function rangeHeader(offset, size) {
if (offset < 0)
throw new RangeError(`range offset must be non-negative: ${String(offset)}`);
if (size !== null && size < 0)
throw new RangeError(`range size must be non-negative: ${String(size)}`);
if (size === 0)
throw new RangeError('a zero-length range has no HTTP spelling');
if (offset === 0 && size === null)
return null;
const end = size === null ? '' : String(offset + size - 1);
return `bytes=${String(offset)}-${end}`;
}
/**
* The requested window out of bytes already in hand.
*
* The answer when nothing remote can serve a range: a store that renders its
* content, or one whose reader has no range support.
*
* @param data the whole content
* @param offset first byte to keep
* @param size how many bytes, or null for the rest
*/
export function sliceWindow(data, offset, size) {
return data.slice(offset, size === null ? undefined : offset + size);
}
/**
* Bytes in hand with `payload` written at `offset`, as pwrite(2) leaves a
* file. The answer when nothing remote can write a range: what lies before
* and after the window stays, and a gap past the end reads as zeros.
* Writing nothing changes nothing, even past the end.
*
* @param data the whole content
* @param offset first byte the payload replaces
* @param payload the bytes written there
*/
export function spliceWindow(data, offset, payload) {
if (payload.byteLength === 0)
return data;
const out = new Uint8Array(Math.max(data.byteLength, offset + payload.byteLength));
out.set(data);
out.set(payload, offset);
return out;
}
// HTTP 416, and the spellings the stores put on it. S3 and its clones answer
// `InvalidRange`, Azure/OneDrive `InvalidRange` too, and a bare WebDAV or
// static host only sets the status line.
const UNSATISFIABLE_CODES = new Set(['InvalidRange', 'RequestedRangeNotSatisfiable']);
function errorFields(err) {
if (typeof err !== 'object' || err === null)
return { status: undefined, code: undefined, message: String(err) };
const e = err;
const status = e.$metadata?.httpStatusCode ?? e.statusCode ?? e.status;
return {
status: typeof status === 'number' ? status : undefined,
code: e.Code ?? e.code ?? e.name,
message: e.message ?? '',
};
}
/**
* Whether an error means "that window runs past the end of the object".
*
* OpenDAL's node binding takes an exact byte range and refuses to return
* fewer bytes than asked for, where a POSIX read simply comes back short.
* Python is not affected: its binding opens a file object, and `f.read(n)`
* stops at EOF like any file. The two OpenDAL-backed node backends (hf and
* nextcloud) therefore retry the read unbounded and take the window
* themselves; the predicate lives here beside the 416 one rather than in
* both of them.
*
* @param err whatever the reader threw
*/
export function isShortRangeRefusal(err) {
const { message } = errorFields(err);
return /got too little data/i.test(message) || /reader got too little/i.test(message);
}
/**
* Whether an error means "that byte window starts past the end of the object".
*
* A POSIX read at or past EOF returns zero bytes; an HTTP store answers 416
* instead, and every backend spells the refusal differently. The predicate
* lives here so the ops factory can turn all of them into the empty read the
* caller expects, rather than each backend re-deciding.
*
* @param err whatever the backend reader threw
*/
export function isUnsatisfiableRange(err) {
const { status, code, message } = errorFields(err);
if (status === 416)
return true;
if (code !== undefined && UNSATISFIABLE_CODES.has(code))
return true;
if (/range not satisfiable/i.test(message))
return true;
// OpenDAL reports the store's whole response as message text rather than
// fields, so a WebDAV 416 arrives as SabreDAV's exception name and wording
// with the status only readable inside the string. Neither the code nor the
// spaced spelling above can see it.
if (/RequestedRangeNotSatisfiable/i.test(message))
return true;
if (/exceeded the size of the entity/i.test(message))
return true;
// Asked for a window past the end, huggingface echoes a Content-Range whose
// end precedes its start (`bytes 99-2/3` for a 3-byte file) and OpenDAL
// refuses to parse it rather than reporting a status. Both halves are
// matched so a genuinely malformed header from anywhere else still raises.
if (/content range is invalid/i.test(message) && /end is less than start/i.test(message))
return true;
// A reader that seeks rather than sending a header (OpenDAL's file object,
// which hf and nextcloud both open) raises from the seek itself instead of
// surfacing a status. Same condition, so it is matched here rather than
// guarded in each of those backends.
return /seek/i.test(message) && /beyond the end/i.test(message);
}
//# sourceMappingURL=ranges.js.map