UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

144 lines • 6.03 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 { concat } from "../../utils/bytes.js"; import { DEFAULT_TIMEOUT } from "../api/constants.js"; import { loadOptionalPeer } from "../../utils/optional_peer.js"; import * as kp from "../../utils/key_prefix.js"; import { CONDITION_LOST_CODES, DENIED_CODES } from "./constants.js"; export function s3Key(path, config) { return kp.apply(config.keyPrefix ?? '', path); } export async function withClient(config, fn) { const client = (await createS3Client(config)); try { return await fn(client); } finally { client.destroy?.(); } } let cachedModule = null; /** * Load the S3 "command module" (GetObjectCommand etc.). When `config` * has a presignedUrlProvider — the browser runtime path — skip the AWS * SDK entirely and return a set of tagged data-only shim classes that * the browser-side client knows how to dispatch via fetch(). * * Mirrors Python's `async_session(config)` abstraction: one seam, * two implementations (boto3 vs. presigner+fetch), same call surface. */ export async function loadS3Module(config) { if (config?.presignedUrlProvider !== undefined) { const { BROWSER_S3_MODULE } = await import("./client_browser.js"); return BROWSER_S3_MODULE; } cachedModule ??= loadOptionalPeer(() => import('@aws-sdk/client-s3'), { feature: 'S3VFS', packageName: '@aws-sdk/client-s3', docsUrl: 'https://mirage.dev/typescript/install', }); return cachedModule; } export async function createS3Client(config) { if (config.presignedUrlProvider !== undefined) { const { createBrowserS3Client } = await import("./client_browser.js"); return createBrowserS3Client(config); } const mod = await loadS3Module(); const options = {}; if (config.region !== undefined) options.region = config.region; if (config.endpoint !== undefined) options.endpoint = config.endpoint; if (config.forcePathStyle === true) options.forcePathStyle = true; // The SDK treats `profile` as a per-client AWS_PROFILE: the default // credential chain resolves from that profile in the shared credentials // file. Explicit keys below still win, which is the precedence Python // gets from `aioboto3.Session(profile_name=...)` plus client kwargs. if (config.profile !== undefined) options.profile = config.profile; if (config.accessKeyId !== undefined && config.secretAccessKey !== undefined) { options.credentials = { accessKeyId: config.accessKeyId, secretAccessKey: config.secretAccessKey, ...(config.sessionToken !== undefined ? { sessionToken: config.sessionToken } : {}), }; } options.requestHandler = { connectionTimeout: (config.timeout ?? DEFAULT_TIMEOUT) * 1000, requestTimeout: (config.timeout ?? DEFAULT_TIMEOUT) * 1000, ...(config.httpAgentProvider !== undefined ? config.httpAgentProvider() : {}), }; return new mod.S3Client(options); } export function isNotFound(err) { if (err === null || typeof err !== 'object') return false; const e = err; if (e.name === 'NoSuchKey' || e.name === 'NotFound') return true; if (e.Code === 'NoSuchKey' || e.Code === '404') return true; return e.$metadata?.httpStatusCode === 404; } /** * Whether an S3 error means the key refuses this caller (403). Keyed on the * error's code; a bodiless reply (named Unknown) is judged on its status. A * HEAD carries no body, so a credential failure behind one reads as a * refusal, as does any 403 the browser client's presigned fetch answers. * Mirrors Python's `is_denied`. */ export function isDenied(err) { if (err === null || typeof err !== 'object') return false; const e = err; if (e.name !== undefined && DENIED_CODES.has(e.name)) return true; const status = e.$metadata?.httpStatusCode; return e.name === 'Unknown' && status !== undefined && DENIED_CODES.has(String(status)); } /** * Whether a conditional request lost: the object changed since the version * sent (412), or another conditional write is in flight (409). Keyed on the * error's code, so any other 409 or 412, and auth, missing-key and transport * failures, keep their own meaning; a bodiless reply (named Unknown) is * judged on its status. Mirrors Python's `is_condition_lost`. */ export function isConditionLost(err) { if (err === null || typeof err !== 'object') return false; const e = err; if (e.name !== undefined && CONDITION_LOST_CODES.has(e.name)) return true; const status = e.$metadata?.httpStatusCode; return e.name === 'Unknown' && status !== undefined && CONDITION_LOST_CODES.has(String(status)); } export async function streamToBuffer(stream) { if (stream === null || stream === undefined) return new Uint8Array(); const s = stream; if (typeof s.transformToByteArray === 'function') { return s.transformToByteArray(); } if (typeof s[Symbol.asyncIterator] === 'function') { const chunks = []; for await (const chunk of s) { chunks.push(chunk); } return concat(chunks); } throw new Error('S3 response Body is not a readable stream'); } //# sourceMappingURL=client.js.map