#!/usr/bin/env node
import { D as getConfigDirectoryPath, c as logger } from "./logger-4ntIRWmf.js";
import { g as getDb, n as blobReferencesTable, t as blobAssetsTable } from "./tables-BD_diFhY.js";
import * as path$1 from "node:path";
import * as fsPromises$1 from "node:fs/promises";
import { createHash, randomUUID } from "node:crypto";
import * as fs$1 from "node:fs";
import { and, eq } from "drizzle-orm";
//#region src/blobs/constants.ts
const BLOB_MIN_SIZE = 1024;
const BLOB_MAX_SIZE = 52428800;
const BLOB_SCHEME = "promptfoo://blob/";
const DEFAULT_FILESYSTEM_SUBDIR = "blobs";
//#endregion
//#region src/blobs/filesystemProvider.ts
const BLOB_HASH_REGEX = /^[a-f0-9]{64}$/i;
function computeHash(data) {
	return createHash("sha256").update(data).digest("hex");
}
function buildUri(hash) {
	return `${BLOB_SCHEME}${hash}`;
}
var FilesystemBlobStorageProvider = class {
	providerId = "filesystem";
	basePath;
	constructor(config) {
		const defaultBase = path$1.join(getConfigDirectoryPath(true), DEFAULT_FILESYSTEM_SUBDIR);
		this.basePath = path$1.resolve(config?.basePath || defaultBase);
		this.ensureDirectory();
	}
	ensureDirectory() {
		if (!fs$1.existsSync(this.basePath)) {
			fs$1.mkdirSync(this.basePath, { recursive: true });
			logger.debug("[BlobFS] Created blob directory", { basePath: this.basePath });
		}
	}
	assertValidHash(hash) {
		if (!BLOB_HASH_REGEX.test(hash)) throw new Error(`[BlobFS] Invalid blob hash: "${hash}"`);
	}
	resolvePathInBase(unsafePath) {
		const targetPath = path$1.isAbsolute(unsafePath) ? path$1.resolve(unsafePath) : path$1.resolve(this.basePath, unsafePath);
		const safeBase = path$1.resolve(this.basePath) + path$1.sep;
		if (!targetPath.startsWith(safeBase)) throw new Error("[BlobFS] Path traversal attempt detected");
		return targetPath;
	}
	hashToPath(hash) {
		this.assertValidHash(hash);
		const dirRelative = path$1.join(hash.slice(0, 2), hash.slice(2, 4));
		const fileRelative = path$1.join(dirRelative, hash);
		return this.resolvePathInBase(fileRelative);
	}
	async ensureHashDir(hash) {
		this.assertValidHash(hash);
		const dirRelative = path$1.join(hash.slice(0, 2), hash.slice(2, 4));
		const dirPath = this.resolvePathInBase(dirRelative);
		await fsPromises$1.mkdir(dirPath, { recursive: true });
	}
	metadataPath(filePath) {
		return `${filePath}.meta.json`;
	}
	async store(data, mimeType) {
		const hash = computeHash(data);
		await this.ensureHashDir(hash);
		const filePath = this.hashToPath(hash);
		try {
			await fsPromises$1.access(filePath);
			const meta = await this.readMetadata(filePath);
			return {
				ref: this.buildRef(hash, meta?.mimeType ?? mimeType, meta?.sizeBytes ?? data.length, meta?.provider ?? this.providerId),
				deduplicated: true
			};
		} catch {}
		await fsPromises$1.writeFile(filePath, data);
		const metadata = {
			mimeType,
			sizeBytes: data.length,
			createdAt: (/* @__PURE__ */ new Date()).toISOString(),
			provider: this.providerId,
			key: filePath
		};
		await fsPromises$1.writeFile(this.metadataPath(filePath), JSON.stringify(metadata, null, 2));
		return {
			ref: this.buildRef(hash, mimeType, data.length, this.providerId),
			deduplicated: false
		};
	}
	async getByHash(hash) {
		const filePath = this.hashToPath(hash);
		let data;
		try {
			data = await fsPromises$1.readFile(filePath);
		} catch (error) {
			if (error.code === "ENOENT") throw new Error(`Blob not found: ${hash}`);
			throw error;
		}
		const metadata = await this.readMetadata(filePath) || {
			mimeType: "application/octet-stream",
			sizeBytes: data.length,
			createdAt: (/* @__PURE__ */ new Date()).toISOString(),
			provider: this.providerId,
			key: filePath
		};
		return {
			data,
			metadata
		};
	}
	async exists(hash) {
		try {
			const filePath = this.hashToPath(hash);
			await fsPromises$1.access(filePath);
			return true;
		} catch {
			return false;
		}
	}
	async deleteByHash(hash) {
		try {
			const filePath = this.hashToPath(hash);
			const metaPath = this.metadataPath(filePath);
			try {
				await fsPromises$1.unlink(filePath);
			} catch (error) {
				if (error.code !== "ENOENT") throw error;
			}
			try {
				await fsPromises$1.unlink(metaPath);
			} catch (error) {
				if (error.code !== "ENOENT") throw error;
			}
		} catch {}
	}
	async getUrl(_hash, _expiresInSeconds) {
		return null;
	}
	buildRef(hash, mimeType, sizeBytes, provider) {
		return {
			uri: buildUri(hash),
			hash,
			mimeType,
			sizeBytes,
			provider
		};
	}
	async readMetadata(filePath) {
		const safeFilePath = this.resolvePathInBase(filePath);
		const metaPath = this.metadataPath(safeFilePath);
		try {
			const raw = await fsPromises$1.readFile(metaPath, "utf8");
			return JSON.parse(raw);
		} catch (error) {
			if (error.code === "ENOENT") return null;
			logger.warn("[BlobFS] Failed to read metadata", { error });
			return null;
		}
	}
};
//#endregion
//#region src/blobs/index.ts
let defaultProvider = null;
function createDefaultProvider() {
	return new FilesystemBlobStorageProvider();
}
function getBlobStorageProvider() {
	if (!defaultProvider) {
		defaultProvider = createDefaultProvider();
		logger.debug("[BlobStorage] Initialized provider", { provider: defaultProvider.providerId });
	}
	return defaultProvider;
}
async function storeBlob(data, mimeType, refContext) {
	const provider = getBlobStorageProvider();
	const result = await provider.store(data, mimeType);
	const db = await getDb();
	try {
		await db.transaction(async (tx) => {
			await tx.insert(blobAssetsTable).values({
				hash: result.ref.hash,
				sizeBytes: result.ref.sizeBytes,
				mimeType: result.ref.mimeType,
				provider: result.ref.provider
			}).onConflictDoNothing().run();
			if (refContext?.evalId) await tx.insert(blobReferencesTable).values({
				id: randomUUID(),
				blobHash: result.ref.hash,
				evalId: refContext.evalId,
				testIdx: refContext.testIdx,
				promptIdx: refContext.promptIdx,
				location: refContext.location,
				kind: refContext.kind
			}).onConflictDoNothing().run();
		});
	} catch (error) {
		try {
			await provider.deleteByHash(result.ref.hash);
		} catch (cleanupError) {
			logger.warn("[BlobStorage] Failed to rollback blob after DB error", {
				error: cleanupError,
				hash: result.ref.hash
			});
		}
		throw error;
	}
	return result;
}
async function getBlobByHash(hash) {
	return getBlobStorageProvider().getByHash(hash);
}
async function getBlobUrl(hash, expiresInSeconds) {
	return getBlobStorageProvider().getUrl(hash, expiresInSeconds);
}
async function recordBlobReference(hash, refContext) {
	if (!refContext.evalId) return;
	if (!await getBlobStorageProvider().exists(hash).catch(() => false)) {
		logger.debug("[BlobStorage] Attempted to record reference for missing blob", {
			hash,
			evalId: refContext.evalId,
			location: refContext.location
		});
		return;
	}
	const db = await getDb();
	if (await db.select({ id: blobReferencesTable.id }).from(blobReferencesTable).where(and(eq(blobReferencesTable.blobHash, hash), eq(blobReferencesTable.evalId, refContext.evalId))).get()) return;
	await db.insert(blobReferencesTable).values({
		id: randomUUID(),
		blobHash: hash,
		evalId: refContext.evalId,
		testIdx: refContext.testIdx,
		promptIdx: refContext.promptIdx,
		location: refContext.location,
		kind: refContext.kind
	}).run();
}
//#endregion
export { storeBlob as a, BLOB_SCHEME as c, recordBlobReference as i, getBlobStorageProvider as n, BLOB_MAX_SIZE as o, getBlobUrl as r, BLOB_MIN_SIZE as s, getBlobByHash as t };

//# sourceMappingURL=blobs-Bkgz8-5S.js.map