@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
144 lines • 6.03 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 { 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