UNPKG

@alepha/file

Version:

Helpers for creating and managing file-like objects seamlessly.

205 lines (202 loc) 7 kB
import { createReadStream } from "node:fs"; import { readFile } from "node:fs/promises"; import { PassThrough, Readable } from "node:stream"; import { ReadableStream as ReadableStream$1 } from "node:stream/web"; import { fileURLToPath } from "node:url"; //#region src/helpers/getContentType.ts /** * Can be used to get the content type of file based on its extension. * * Feel free to add more mime types in your project! */ const mimeMap = { json: "application/json", txt: "text/plain", html: "text/html", htm: "text/html", xml: "application/xml", csv: "text/csv", pdf: "application/pdf", md: "text/markdown", markdown: "text/markdown", rtf: "application/rtf", css: "text/css", js: "application/javascript", mjs: "application/javascript", ts: "application/typescript", jsx: "text/jsx", tsx: "text/tsx", zip: "application/zip", rar: "application/vnd.rar", "7z": "application/x-7z-compressed", tar: "application/x-tar", gz: "application/gzip", tgz: "application/gzip", png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif", webp: "image/webp", svg: "image/svg+xml", bmp: "image/bmp", ico: "image/x-icon", tiff: "image/tiff", tif: "image/tiff", mp3: "audio/mpeg", wav: "audio/wav", ogg: "audio/ogg", m4a: "audio/mp4", aac: "audio/aac", flac: "audio/flac", mp4: "video/mp4", webm: "video/webm", avi: "video/x-msvideo", mov: "video/quicktime", wmv: "video/x-ms-wmv", flv: "video/x-flv", mkv: "video/x-matroska", 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", woff: "font/woff", woff2: "font/woff2", ttf: "font/ttf", otf: "font/otf", eot: "application/vnd.ms-fontobject" }; /** * Returns the content type of file based on its filename. * @see {mimeMap} */ const getContentType = (filename) => { const ext = filename.toLowerCase().split(".").pop() || ""; return mimeMap[ext] || "application/octet-stream"; }; //#endregion //#region src/helpers/createFile.ts const createFile = (source, options = {}) => { if (source instanceof File) return createFileFromWebFile(source, options); if (typeof source === "string" && (source.startsWith("file://") || source.startsWith("http://") || source.startsWith("https://"))) return createFileFromUrl(source, options); if (source instanceof ReadableStream || source instanceof ReadableStream$1) return createFileFromStream(Readable.from(source), options); if (isReadableStream(source) || source instanceof Readable) return createFileFromStream(source, options); return createFileFromBuffer(Buffer.isBuffer(source) ? source : typeof source === "string" ? Buffer.from(source, "utf-8") : Buffer.from(source), options); }; const createFileFromWebFile = (source, options = {}) => { return { name: source.name, type: source.type || options.type || getContentType(source.name), size: source.size || options.size || 0, lastModified: source.lastModified || Date.now(), stream: () => source.stream(), arrayBuffer: async () => { return await source.arrayBuffer(); }, text: async () => { return await source.text(); } }; }; const createFileFromBuffer = (source, options = {}) => { const name = options.name ?? "file"; return { name, type: options.type ?? getContentType(options.name ?? name), size: source.byteLength, lastModified: Date.now(), stream: () => Readable.from(source), arrayBuffer: async () => { return bufferToArrayBuffer(source); }, text: async () => { return source.toString("utf-8"); } }; }; const createFileFromStream = (source, options = {}) => { let buffer = null; return { name: options.name ?? "file", type: options.type ?? getContentType(options.name ?? "file"), size: options.size ?? 0, lastModified: Date.now(), stream: () => source, _buffer: null, async arrayBuffer() { buffer ??= await streamToBuffer(source); return bufferToArrayBuffer(buffer); }, async text() { buffer ??= await streamToBuffer(source); return buffer.toString("utf-8"); } }; }; const createFileFromUrl = (url, options = {}) => { const parsedUrl = new URL(url); const filename = options.name || parsedUrl.pathname.split("/").pop() || "file"; let buffer = null; return { name: filename, type: options.type ?? getContentType(filename), size: 0, lastModified: Date.now(), stream: () => createStreamFromUrl(url), async arrayBuffer() { buffer ??= await loadFromUrl(url); return bufferToArrayBuffer(buffer); }, async text() { buffer ??= await loadFromUrl(url); return buffer.toString("utf-8"); }, filepath: url }; }; const getStreamingResponse = (url) => { const stream = new PassThrough(); fetch(url).then((res) => Readable.fromWeb(res.body).pipe(stream)).catch((err) => stream.destroy(err)); return stream; }; const loadFromUrl = async (url) => { const parsedUrl = new URL(url); if (parsedUrl.protocol === "file:") { const filePath = fileURLToPath(url); return await readFile(filePath); } else if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") { const response = await fetch(url); if (!response.ok) throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`); const arrayBuffer = await response.arrayBuffer(); return Buffer.from(arrayBuffer); } else throw new Error(`Unsupported protocol: ${parsedUrl.protocol}`); }; const createStreamFromUrl = (url) => { const parsedUrl = new URL(url); if (parsedUrl.protocol === "file:") return createReadStream(fileURLToPath(url)); else if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") return getStreamingResponse(url); else throw new Error(`Unsupported protocol: ${parsedUrl.protocol}`); }; /** * Converts a stream-like object to a Buffer. */ const streamToBuffer = async (streamLike) => { const stream = streamLike instanceof Readable ? streamLike : Readable.fromWeb(streamLike); return new Promise((resolve, reject) => { const buffer = []; stream.on("data", (chunk) => buffer.push(Buffer.from(chunk))); stream.on("end", () => resolve(Buffer.concat(buffer))); stream.on("error", (err) => reject(new Error("Error converting stream", { cause: err }))); }); }; /** * Converts a Node.js Buffer to an ArrayBuffer. */ const bufferToArrayBuffer = (buffer) => { return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength); }; const isReadableStream = (obj) => obj instanceof Readable || obj != null && typeof obj === "object" && typeof obj.pipe === "function" && typeof obj.read === "function"; //#endregion export { bufferToArrayBuffer, createFile, createFileFromBuffer, createFileFromStream, createFileFromUrl, createFileFromWebFile, getContentType, isReadableStream, mimeMap, streamToBuffer }; //# sourceMappingURL=index.js.map