@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
98 lines • 4.14 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 { rawPathOf } from "../../utils/key_prefix.js";
import { rechunk, sliceStream } from "../../io/stream.js";
import { materialize } from "../../io/types.js";
import { recordStream, revisionFor } from "../../observe/context.js";
import { VFSName } from "../../types.js";
import { createS3Client, isDenied, isNotFound, loadS3Module, s3Key } from "./client.js";
import { fpRevFromS3Response, read } from "./read.js";
import { eacces, enoent } from "../../errors/fs.js";
const DEFAULT_CHUNK_SIZE = 8192;
export async function* readStream(accessor, path, onVersion) {
const virtual = path.virtual;
const rawPath = rawPathOf(path);
const { config } = accessor;
const { GetObjectCommand } = await loadS3Module(config);
const client = await createS3Client(config);
const send = client.send.bind(client);
const pinnedRevision = revisionFor(virtual);
const input = { Bucket: config.bucket, Key: s3Key(rawPath, config) };
if (pinnedRevision !== null)
input.VersionId = pinnedRevision;
try {
const resp = (await send(new GetObjectCommand(input)));
const rec = recordStream('read', virtual, VFSName.S3);
const { fingerprint, revision } = fpRevFromS3Response(resp);
onVersion?.(fingerprint);
if (rec !== null) {
rec.fingerprint = fingerprint;
rec.revision = revision;
}
const body = resp.Body;
if (body === undefined) {
if (rec !== null)
rec.complete = true;
return;
}
if (typeof body[Symbol.asyncIterator] === 'function') {
for await (const piece of rechunk(body, DEFAULT_CHUNK_SIZE)) {
if (rec !== null)
rec.bytes += piece.byteLength;
yield piece;
}
}
if (rec !== null)
rec.complete = true;
}
catch (err) {
if (isNotFound(err)) {
throw enoent(path);
}
if (isDenied(err))
throw eacces(path);
throw err;
}
finally {
;
client.destroy?.();
}
}
/**
* A byte window, taken the cheapest way the configured client allows.
*
* The SDK path asks for one ranged GET, which is the whole point on an object
* store. The browser path cannot: `createBrowserS3Client` fetches a presigned
* URL, and a presigned GET carries no `Range` — adding the header would make
* the request non-simple and trip a CORS preflight that presigned deployments
* generally do not allow. Its shim therefore refuses a `Range` input outright,
* so asking for one there would fail rather than quietly return the whole
* object as if it were the window.
*
* Streaming is the honest substitute: `bodyFromResponse` hands back a reader
* over the live body, so stopping at `offset + size` cancels the download
* instead of buffering the rest of the object and throwing it away.
*
* @param accessor the S3 accessor
* @param path the object path
* @param offset first byte to read
* @param size how many bytes, or null for the rest of the object
*/
export async function readRange(accessor, path, offset, size) {
if (accessor.config.presignedUrlProvider === undefined) {
return read(accessor, path, size === null ? { offset } : { offset, size });
}
return materialize(sliceStream(readStream(accessor, path), offset, size));
}
//# sourceMappingURL=stream.js.map