Files
WRNexusJS/packages/uploader/src/mime.ts
T
2026-07-12 15:55:18 +05:30

82 lines
2.6 KiB
TypeScript

/**
* Minimal extension ↔ MIME mapping + `accept` matching. Zero-dep: just a table
* big enough for the common upload types (images, docs, media, archives).
*/
const BY_EXT: Record<string, string> = {
// images
png: "image/png",
jpg: "image/jpeg",
jpeg: "image/jpeg",
gif: "image/gif",
webp: "image/webp",
avif: "image/avif",
svg: "image/svg+xml",
ico: "image/x-icon",
bmp: "image/bmp",
// documents
pdf: "application/pdf",
txt: "text/plain",
html: "text/html",
htm: "text/html",
csv: "text/csv",
json: "application/json",
xml: "application/xml",
doc: "application/msword",
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
xls: "application/vnd.ms-excel",
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
ppt: "application/vnd.ms-powerpoint",
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
// media
mp3: "audio/mpeg",
wav: "audio/wav",
ogg: "audio/ogg",
mp4: "video/mp4",
webm: "video/webm",
mov: "video/quicktime",
// archives / misc
zip: "application/zip",
gz: "application/gzip",
tar: "application/x-tar",
};
/** Lowercased extension WITHOUT the dot (e.g. "png"), or "" if none. */
export function extOf(name: string): string {
const clean = name.split(/[?#]/)[0] ?? "";
const dot = clean.lastIndexOf(".");
return dot >= 0 ? clean.slice(dot + 1).toLowerCase() : "";
}
/** MIME type for a filename/key by its extension, or a safe default. */
export function contentTypeOf(name: string, fallback = "application/octet-stream"): string {
return BY_EXT[extOf(name)] ?? fallback;
}
/** The conventional extension for a MIME type, or "" (used to name S3 keys). */
export function extForType(type: string): string {
const t = type.split(";")[0]!.trim().toLowerCase();
for (const [ext, mime] of Object.entries(BY_EXT)) if (mime === t) return ext;
return "";
}
/**
* Does `file` (its MIME `type` + `name`) satisfy an `accept` list? Each accept
* entry is a MIME type (`"image/png"`), a wildcard MIME (`"image/*"`), or a
* dotted extension (`".pdf"`). An empty/omitted list accepts everything.
*/
export function accepts(
accept: string[] | undefined,
file: { type: string; name: string },
): boolean {
if (!accept || accept.length === 0) return true;
const type = (file.type || contentTypeOf(file.name)).toLowerCase();
const ext = "." + extOf(file.name);
return accept.some((raw) => {
const rule = raw.trim().toLowerCase();
if (rule.startsWith(".")) return rule === ext;
if (rule.endsWith("/*")) return type.startsWith(rule.slice(0, -1)); // "image/" prefix
return rule === type;
});
}