UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

98 lines • 4.14 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 { 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