globalThis.process ??= {};
globalThis.process.env ??= {};
//#region ../../node_modules/.pnpm/aws4fetch@1.0.20/node_modules/aws4fetch/dist/aws4fetch.esm.mjs
/**
* @license MIT <https://opensource.org/licenses/MIT>
* @copyright Michael Hart 2024
*/
var encoder = new TextEncoder();
var HOST_SERVICES = {
	appstream2: "appstream",
	cloudhsmv2: "cloudhsm",
	email: "ses",
	marketplace: "aws-marketplace",
	mobile: "AWSMobileHubService",
	pinpoint: "mobiletargeting",
	queue: "sqs",
	"git-codecommit": "codecommit",
	"mturk-requester-sandbox": "mturk-requester",
	"personalize-runtime": "personalize"
};
var UNSIGNABLE_HEADERS = /* @__PURE__ */ new Set([
	"authorization",
	"content-type",
	"content-length",
	"user-agent",
	"presigned-expires",
	"expect",
	"x-amzn-trace-id",
	"range",
	"connection"
]);
var AwsClient = class {
	constructor({ accessKeyId, secretAccessKey, sessionToken, service, region, cache, retries, initRetryMs }) {
		if (accessKeyId == null) throw new TypeError("accessKeyId is a required option");
		if (secretAccessKey == null) throw new TypeError("secretAccessKey is a required option");
		this.accessKeyId = accessKeyId;
		this.secretAccessKey = secretAccessKey;
		this.sessionToken = sessionToken;
		this.service = service;
		this.region = region;
		this.cache = cache || /* @__PURE__ */ new Map();
		this.retries = retries != null ? retries : 10;
		this.initRetryMs = initRetryMs || 50;
	}
	async sign(input, init) {
		if (input instanceof Request) {
			const { method, url, headers, body } = input;
			init = Object.assign({
				method,
				url,
				headers
			}, init);
			if (init.body == null && headers.has("Content-Type")) init.body = body != null && headers.has("X-Amz-Content-Sha256") ? body : await input.clone().arrayBuffer();
			input = url;
		}
		const signer = new AwsV4Signer(Object.assign({ url: input.toString() }, init, this, init && init.aws));
		const signed = Object.assign({}, init, await signer.sign());
		delete signed.aws;
		try {
			return new Request(signed.url.toString(), signed);
		} catch (e) {
			if (e instanceof TypeError) return new Request(signed.url.toString(), Object.assign({ duplex: "half" }, signed));
			throw e;
		}
	}
	async fetch(input, init) {
		for (let i = 0; i <= this.retries; i++) {
			const fetched = fetch(await this.sign(input, init));
			if (i === this.retries) return fetched;
			const res = await fetched;
			if (res.status < 500 && res.status !== 429) return res;
			await new Promise((resolve) => setTimeout(resolve, Math.random() * this.initRetryMs * Math.pow(2, i)));
		}
		throw new Error("An unknown error occurred, ensure retries is not negative");
	}
};
var AwsV4Signer = class {
	constructor({ method, url, headers, body, accessKeyId, secretAccessKey, sessionToken, service, region, cache, datetime, signQuery, appendSessionToken, allHeaders, singleEncode }) {
		if (url == null) throw new TypeError("url is a required option");
		if (accessKeyId == null) throw new TypeError("accessKeyId is a required option");
		if (secretAccessKey == null) throw new TypeError("secretAccessKey is a required option");
		this.method = method || (body ? "POST" : "GET");
		this.url = new URL(url);
		this.headers = new Headers(headers || {});
		this.body = body;
		this.accessKeyId = accessKeyId;
		this.secretAccessKey = secretAccessKey;
		this.sessionToken = sessionToken;
		let guessedService, guessedRegion;
		if (!service || !region) [guessedService, guessedRegion] = guessServiceRegion(this.url, this.headers);
		this.service = service || guessedService || "";
		this.region = region || guessedRegion || "us-east-1";
		this.cache = cache || /* @__PURE__ */ new Map();
		this.datetime = datetime || (/* @__PURE__ */ new Date()).toISOString().replace(/[:-]|\.\d{3}/g, "");
		this.signQuery = signQuery;
		this.appendSessionToken = appendSessionToken || this.service === "iotdevicegateway";
		this.headers.delete("Host");
		if (this.service === "s3" && !this.signQuery && !this.headers.has("X-Amz-Content-Sha256")) this.headers.set("X-Amz-Content-Sha256", "UNSIGNED-PAYLOAD");
		const params = this.signQuery ? this.url.searchParams : this.headers;
		params.set("X-Amz-Date", this.datetime);
		if (this.sessionToken && !this.appendSessionToken) params.set("X-Amz-Security-Token", this.sessionToken);
		this.signableHeaders = ["host", ...this.headers.keys()].filter((header) => allHeaders || !UNSIGNABLE_HEADERS.has(header)).sort();
		this.signedHeaders = this.signableHeaders.join(";");
		this.canonicalHeaders = this.signableHeaders.map((header) => header + ":" + (header === "host" ? this.url.host : (this.headers.get(header) || "").replace(/\s+/g, " "))).join("\n");
		this.credentialString = [
			this.datetime.slice(0, 8),
			this.region,
			this.service,
			"aws4_request"
		].join("/");
		if (this.signQuery) {
			if (this.service === "s3" && !params.has("X-Amz-Expires")) params.set("X-Amz-Expires", "86400");
			params.set("X-Amz-Algorithm", "AWS4-HMAC-SHA256");
			params.set("X-Amz-Credential", this.accessKeyId + "/" + this.credentialString);
			params.set("X-Amz-SignedHeaders", this.signedHeaders);
		}
		if (this.service === "s3") try {
			this.encodedPath = decodeURIComponent(this.url.pathname.replace(/\+/g, " "));
		} catch (e) {
			this.encodedPath = this.url.pathname;
		}
		else this.encodedPath = this.url.pathname.replace(/\/+/g, "/");
		if (!singleEncode) this.encodedPath = encodeURIComponent(this.encodedPath).replace(/%2F/g, "/");
		this.encodedPath = encodeRfc3986(this.encodedPath);
		const seenKeys = /* @__PURE__ */ new Set();
		this.encodedSearch = [...this.url.searchParams].filter(([k]) => {
			if (!k) return false;
			if (this.service === "s3") {
				if (seenKeys.has(k)) return false;
				seenKeys.add(k);
			}
			return true;
		}).map((pair) => pair.map((p) => encodeRfc3986(encodeURIComponent(p)))).sort(([k1, v1], [k2, v2]) => k1 < k2 ? -1 : k1 > k2 ? 1 : v1 < v2 ? -1 : v1 > v2 ? 1 : 0).map((pair) => pair.join("=")).join("&");
	}
	async sign() {
		if (this.signQuery) {
			this.url.searchParams.set("X-Amz-Signature", await this.signature());
			if (this.sessionToken && this.appendSessionToken) this.url.searchParams.set("X-Amz-Security-Token", this.sessionToken);
		} else this.headers.set("Authorization", await this.authHeader());
		return {
			method: this.method,
			url: this.url,
			headers: this.headers,
			body: this.body
		};
	}
	async authHeader() {
		return [
			"AWS4-HMAC-SHA256 Credential=" + this.accessKeyId + "/" + this.credentialString,
			"SignedHeaders=" + this.signedHeaders,
			"Signature=" + await this.signature()
		].join(", ");
	}
	async signature() {
		const date = this.datetime.slice(0, 8);
		const cacheKey = [
			this.secretAccessKey,
			date,
			this.region,
			this.service
		].join();
		let kCredentials = this.cache.get(cacheKey);
		if (!kCredentials) {
			kCredentials = await hmac(await hmac(await hmac(await hmac("AWS4" + this.secretAccessKey, date), this.region), this.service), "aws4_request");
			this.cache.set(cacheKey, kCredentials);
		}
		return buf2hex(await hmac(kCredentials, await this.stringToSign()));
	}
	async stringToSign() {
		return [
			"AWS4-HMAC-SHA256",
			this.datetime,
			this.credentialString,
			buf2hex(await hash(await this.canonicalString()))
		].join("\n");
	}
	async canonicalString() {
		return [
			this.method.toUpperCase(),
			this.encodedPath,
			this.encodedSearch,
			this.canonicalHeaders + "\n",
			this.signedHeaders,
			await this.hexBodyHash()
		].join("\n");
	}
	async hexBodyHash() {
		let hashHeader = this.headers.get("X-Amz-Content-Sha256") || (this.service === "s3" && this.signQuery ? "UNSIGNED-PAYLOAD" : null);
		if (hashHeader == null) {
			if (this.body && typeof this.body !== "string" && !("byteLength" in this.body)) throw new Error("body must be a string, ArrayBuffer or ArrayBufferView, unless you include the X-Amz-Content-Sha256 header");
			hashHeader = buf2hex(await hash(this.body || ""));
		}
		return hashHeader;
	}
};
async function hmac(key, string) {
	const cryptoKey = await crypto.subtle.importKey("raw", typeof key === "string" ? encoder.encode(key) : key, {
		name: "HMAC",
		hash: { name: "SHA-256" }
	}, false, ["sign"]);
	return crypto.subtle.sign("HMAC", cryptoKey, encoder.encode(string));
}
async function hash(content) {
	return crypto.subtle.digest("SHA-256", typeof content === "string" ? encoder.encode(content) : content);
}
var HEX_CHARS = [
	"0",
	"1",
	"2",
	"3",
	"4",
	"5",
	"6",
	"7",
	"8",
	"9",
	"a",
	"b",
	"c",
	"d",
	"e",
	"f"
];
function buf2hex(arrayBuffer) {
	const buffer = new Uint8Array(arrayBuffer);
	let out = "";
	for (let idx = 0; idx < buffer.length; idx++) {
		const n = buffer[idx];
		out += HEX_CHARS[n >>> 4 & 15];
		out += HEX_CHARS[n & 15];
	}
	return out;
}
function encodeRfc3986(urlEncodedStr) {
	return urlEncodedStr.replace(/[!'()*]/g, (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase());
}
function guessServiceRegion(url, headers) {
	const { hostname, pathname } = url;
	if (hostname.endsWith(".on.aws")) {
		const match = hostname.match(/^[^.]{1,63}\.lambda-url\.([^.]{1,63})\.on\.aws$/);
		return match != null ? ["lambda", match[1] || ""] : ["", ""];
	}
	if (hostname.endsWith(".r2.cloudflarestorage.com")) return ["s3", "auto"];
	if (hostname.endsWith(".backblazeb2.com")) {
		const match = hostname.match(/^(?:[^.]{1,63}\.)?s3\.([^.]{1,63})\.backblazeb2\.com$/);
		return match != null ? ["s3", match[1] || ""] : ["", ""];
	}
	const match = hostname.replace("dualstack.", "").match(/([^.]{1,63})\.(?:([^.]{0,63})\.)?amazonaws\.com(?:\.cn)?$/);
	let service = match && match[1] || "";
	let region = match && match[2];
	if (region === "us-gov") region = "us-gov-west-1";
	else if (region === "s3" || region === "s3-accelerate") {
		region = "us-east-1";
		service = "s3";
	} else if (service === "iot") {
		if (hostname.startsWith("iot.")) service = "execute-api";
		else if (hostname.startsWith("data.jobs.iot.")) service = "iot-jobs-data";
		else service = pathname === "/mqtt" ? "iotdevicegateway" : "iotdata";
	} else if (service === "autoscaling") {
		const targetPrefix = (headers.get("X-Amz-Target") || "").split(".")[0];
		if (targetPrefix === "AnyScaleFrontendService") service = "application-autoscaling";
		else if (targetPrefix === "AnyScaleScalingPlannerFrontendService") service = "autoscaling-plans";
	} else if (region == null && service.startsWith("s3-")) {
		region = service.slice(3).replace(/^fips-|^external-1/, "");
		service = "s3";
	} else if (service.endsWith("-fips")) service = service.slice(0, -5);
	else if (region && /-\d$/.test(service) && !/-\d$/.test(region)) [service, region] = [region, service];
	return [HOST_SERVICES[service] || service, region || ""];
}
//#endregion
//#region ../../packages/uploads/dist/presign-s3compat.js
var InvalidPresignKeyError = class extends Error {
	name = "InvalidPresignKeyError";
	constructor(key) {
		super(`Presign key must be one exact object key, not a prefix or wildcard: ${key}`);
	}
};
function assertExactKey(key) {
	if (!key || key.includes("*") || key.includes("?") || key.endsWith("/")) throw new InvalidPresignKeyError(key);
}
function joinEndpoint(endpoint, key) {
	return `${endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint}/${key.split("/").map((segment) => encodeURIComponent(segment)).join("/")}`;
}
/**
* Mint a browser-direct PUT URL for one exact R2 object key.
*
* Authz floor: single exact key (never prefix/wildcard), PUT-only, required bounded
* `expiresIn`, Content-Type bound in `X-Amz-SignedHeaders`.
*
* **Direct-PUT bypass contract (two control points):**
* - **Pre-issue:** host MUST call `checkStorageQuota` before minting — bytes hit R2 on PUT.
* - **Post-upload:** host MUST HEAD+range-GET first bytes → `detectMimeFromMagicBytes` + size,
*   or use proxy/direct-POST where the worker sees bytes. Content-Type binding pins the declared
*   type only — NOT spoof-protection.
*/
async function presignR2Put(creds, key, contentType, expiresIn) {
	assertExactKey(key);
	if (!Number.isFinite(expiresIn) || expiresIn <= 0) throw new RangeError("expiresIn must be a positive finite number of seconds");
	const client = new AwsClient({
		accessKeyId: creds.accessKeyId,
		secretAccessKey: creds.secretAccessKey,
		region: creds.region ?? "auto",
		service: "s3"
	});
	const objectUrl = `${joinEndpoint(creds.endpoint, key)}?X-Amz-Expires=${expiresIn}`;
	const signed = await client.sign(new Request(objectUrl, {
		method: "PUT",
		headers: { "Content-Type": contentType }
	}), { aws: {
		signQuery: true,
		allHeaders: true
	} });
	const headers = {};
	signed.headers.forEach((value, name) => {
		headers[name.toLowerCase()] = value;
	});
	return {
		url: signed.url,
		method: "PUT",
		headers
	};
}
//#endregion
//#region ../../packages/uploads/dist/r2-binding.js
/** Thin adapter over the native Workers `R2Bucket.put` binding. */
async function putObject(bucket, key, body, opts) {
	return bucket.put(key, body, opts);
}
//#endregion
//#region ../../packages/uploads/dist/storage.js
var StorageError = class extends Error {
	isStorageError = true;
	code;
	constructor(code, message) {
		super(message);
		this.code = code;
		this.name = "StorageError";
	}
};
/**
* Shared exact-key guard. Throws StorageError('invalid_key') on prefix/wildcard/empty/trailing-slash.
* Both adapters call this so presignPut enforces the single-exact-key floor identically.
*/
function assertExactStorageKey(key) {
	if (!key || key.includes("*") || key.includes("?") || key.endsWith("/")) throw new StorageError("invalid_key", `storage key must be one exact object key, not a prefix or wildcard: ${key}`);
}
/** Shared expiry guard. Throws RangeError when expiresIn is not a positive finite number of seconds. */
function assertPresignExpiry(expiresIn) {
	if (!Number.isFinite(expiresIn) || expiresIn <= 0) throw new RangeError("expiresIn must be a positive finite number of seconds");
}
//#endregion
//#region ../../packages/uploads/dist/storage-r2.js
function headOf(o) {
	return {
		key: o.key,
		size: o.size,
		etag: o.etag,
		contentType: o.httpMetadata?.contentType,
		customMetadata: o.customMetadata,
		uploadedAt: o.uploaded?.getTime()
	};
}
/** CF R2 StorageAdapter — the mod-cms production backend. Wraps the existing putObject/presignR2Put leaves. */
function createR2Storage(options) {
	const { bucket, presign } = options;
	return {
		async put(key, body, opts) {
			const result = await putObject(bucket, key, body, {
				httpMetadata: {
					contentType: opts?.contentType,
					cacheControl: opts?.cacheControl
				},
				customMetadata: opts?.customMetadata
			});
			return {
				key: result.key,
				size: result.size,
				etag: result.etag
			};
		},
		async get(key) {
			const o = await bucket.get(key);
			if (!o) return null;
			return {
				...headOf(o),
				body: o.body
			};
		},
		async head(key) {
			const o = await bucket.head(key);
			return o ? headOf(o) : null;
		},
		async delete(key) {
			await bucket.delete(key);
		},
		async list(opts) {
			const res = await bucket.list({
				prefix: opts?.prefix,
				cursor: opts?.cursor,
				limit: opts?.limit
			});
			return {
				objects: res.objects.map(headOf),
				cursor: res.truncated ? res.cursor : void 0
			};
		},
		async presignPut(key, opts) {
			if (!presign) throw new StorageError("backend", "presignPut requires S3-API creds (createR2Storage was given no `presign`)");
			assertExactStorageKey(key);
			assertPresignExpiry(opts.expiresIn);
			try {
				const signed = await presignR2Put(presign, key, opts.contentType ?? "application/octet-stream", opts.expiresIn);
				return {
					url: signed.url,
					method: "PUT",
					headers: signed.headers,
					expiresIn: opts.expiresIn
				};
			} catch (e) {
				if (e.name === "InvalidPresignKeyError") throw new StorageError("invalid_key", e.message);
				throw e;
			}
		}
	};
}
//#endregion
//#region src/lib/storage.ts
var MissingMediaBucketError = class extends Error {
	name = "MissingMediaBucketError";
	constructor() {
		super("MEDIA_BUCKET R2 binding is required but was not present in the Cloudflare runtime environment");
	}
};
var InvalidMediaBaseUrlError = class extends Error {
	name = "InvalidMediaBaseUrlError";
	constructor(value) {
		super(`MEDIA_PUBLIC_BASE_URL must be an https:// URL (the content sanitizer allows only https image src); got: ${value}`);
	}
};
/** Validated public display origin. Trailing slash stripped. https-only — see seam spec §4.2/§5. */
function publicBaseUrl(env) {
	const raw = env.MEDIA_PUBLIC_BASE_URL;
	let parsed;
	try {
		parsed = new URL(raw);
	} catch {
		throw new InvalidMediaBaseUrlError(String(raw));
	}
	if (parsed.protocol !== "https:" || !parsed.hostname) throw new InvalidMediaBaseUrlError(String(raw));
	return raw.replace(/\/$/, "");
}
/** One wiring file (adapter-minimalism). Public-bucket default → no presign creds. */
function getStorage(env) {
	if (!env.MEDIA_BUCKET) throw new MissingMediaBucketError();
	return createR2Storage({ bucket: env.MEDIA_BUCKET });
}
/** Image size ceiling — MUST stay well under the CF Workers ~128 MB memory limit (route buffers the file). Default 10 MB. */
function maxBytes(env) {
	const n = Number(env.MEDIA_MAX_BYTES);
	return Number.isFinite(n) && n > 0 ? n : 10485760;
}
/** Per-site media quota. Default 1 GB. */
function quotaBytes(env) {
	const n = Number(env.MEDIA_QUOTA_BYTES);
	return Number.isFinite(n) && n > 0 ? n : 1073741824;
}
//#endregion
export { quotaBytes as i, maxBytes as n, publicBaseUrl as r, getStorage as t };
