export const RELEASE_PREFIXES = new Set(["canvas", "cards", "docs", "fonts", "icons", "lang", "nue", "scripts", "sounds", "toolclips", "tours", "ui"]); const DATA_PREFIXES = new Set(["assets", "modules", "systems", "worlds"]); const MEDIA_EXTENSIONS = { image: new Set(["avif", "gif", "jpeg", "jpg", "png", "svg", "webp"]), video: new Set(["m4v", "mkv", "mov", "mp4", "mpeg", "mpg", "webm"]), audio: new Set(["flac", "m4a", "mid", "midi", "mp3", "ogg", "opus", "wav", "webm"]) }; export function normalizePath(path) { const normalized = String(path ?? "") .replace(/\\/g, "/") .replace(/^[./]+/, "") .replace(/^\/+/, "") .replace(/\/{2,}/g, "/") .trim(); try { return decodeURI(normalized); } catch { return normalized; } } export function splitResourceSuffix(path) { const [withoutHash] = String(path ?? "").split("#", 1); const [cleanPath] = withoutHash.split("?", 1); return cleanPath; } export function parseStoragePath(rawValue) { if (typeof rawValue !== "string") return null; const trimmed = rawValue.trim(); if (!trimmed) return null; if (/^(?:https?:|data:|blob:)/i.test(trimmed)) return null; const clean = normalizePath(splitResourceSuffix(trimmed)); if (!clean) return null; const first = clean.split("/", 1)[0]; let storage = null; if (DATA_PREFIXES.has(first)) storage = "data"; else if (RELEASE_PREFIXES.has(first)) storage = "public"; if (!storage) return null; return { storage, path: clean, locator: `${storage}:${clean}` }; } export function getExtension(path) { const clean = normalizePath(path); const index = clean.lastIndexOf("."); return index === -1 ? "" : clean.slice(index + 1).toLowerCase(); } export function detectMediaKind(path) { const ext = getExtension(path); for (const [kind, extensions] of Object.entries(MEDIA_EXTENSIONS)) { if (extensions.has(ext)) return kind; } return "other"; } export function isMediaPath(path) { return detectMediaKind(path) !== "other"; } export function classifyRisk(locator) { const parts = locator.path.split("/"); if (locator.storage === "public") return "release-public"; if (parts[0] === "modules") return "package-module"; if (parts[0] === "systems") return "package-system"; if (parts[0] === "assets") return "stable-user"; if (parts[0] === "worlds") return "stable-world"; return locator.storage === "data" ? "stable-user" : "unknown"; } export function inferOwnerHint(locator) { const parts = locator.path.split("/"); if (locator.storage === "public") { return { ownerType: "public", ownerId: parts[0] ?? null }; } if (parts[0] === "assets") { return { ownerType: "user", ownerId: null }; } if ((parts[0] === "worlds") && parts[1]) { return { ownerType: "world", ownerId: parts[1] }; } if ((parts[0] === "modules") && parts[1]) { return { ownerType: "module", ownerId: parts[1] }; } if ((parts[0] === "systems") && parts[1]) { return { ownerType: "system", ownerId: parts[1] }; } if (locator.storage === "data") { return { ownerType: "user", ownerId: parts[0] ?? null }; } return { ownerType: "unknown", ownerId: null }; } export function isStorageAreaPath(locator) { const parts = locator.path.split("/"); return ((parts[0] === "modules") || (parts[0] === "systems")) && (parts[2] === "storage"); } export function isCorePublicPath(locator) { if (locator.storage !== "public") return false; const parts = locator.path.split("/"); return RELEASE_PREFIXES.has(parts[0] ?? ""); } export function createFileLocator(storage, path) { const cleanPath = normalizePath(path); return { storage, path: cleanPath, locator: `${storage}:${cleanPath}` }; }