UNPKG

@lyleunderwood/filereader-polyfill

Version:

W3C File API specification compliant FileReader polyfill for Node.js environments

285 lines (280 loc) 9.15 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { File: () => File, FileReader: () => FileReader }); module.exports = __toCommonJS(index_exports); // src/File.ts var import_path = require("path"); var File = class extends Blob { constructor(fileBitsOrPath, fileName, options) { if (typeof fileBitsOrPath === "string") { const filePath = fileBitsOrPath; super([], { type: "" }); this.name = (0, import_path.basename)(filePath); this.lastModified = Date.now(); this._path = filePath; } else { super(fileBitsOrPath, options); this.name = fileName || ""; this.lastModified = (options == null ? void 0 : options.lastModified) ?? Date.now(); } } get path() { return this._path; } // Helper method to get file stats and update lastModified async _getFileStats() { if (!this._path) return null; try { const { stat } = await import("fs/promises"); const stats = await stat(this._path); this.lastModified = stats.mtime.getTime(); return stats; } catch (error) { throw new DOMException(`Failed to read file: ${error}`, "NotReadableError"); } } // Override arrayBuffer to read from file if path is available async arrayBuffer() { if (this._path) { await this._getFileStats(); try { const { readFile } = await import("fs/promises"); const buffer = await readFile(this._path); return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength); } catch (error) { throw new DOMException(`Failed to read file: ${error}`, "NotReadableError"); } } return super.arrayBuffer(); } // Override stream to read from file if path is available stream() { if (this._path) { const filePath = this._path; const self = this; return new ReadableStream({ async start(controller) { try { await self._getFileStats(); const { createReadStream } = require("fs"); const nodeStream = createReadStream(filePath); nodeStream.on("data", (chunk) => { controller.enqueue(new Uint8Array(chunk)); }); nodeStream.on("end", () => { controller.close(); }); nodeStream.on("error", (error) => { controller.error(new DOMException(`Failed to read file: ${error}`, "NotReadableError")); }); } catch (error) { controller.error(error); } }, cancel() { } }); } return super.stream(); } // Override text to read from file if path is available async text() { if (this._path) { await this._getFileStats(); try { const { readFile } = await import("fs/promises"); return readFile(this._path, "utf8"); } catch (error) { throw new DOMException(`Failed to read file: ${error}`, "NotReadableError"); } } return super.text(); } }; // src/FileReader.ts var _FileReader = class _FileReader extends EventTarget { constructor() { super(); // Instance constants (required by W3C spec) this.EMPTY = 0; this.LOADING = 1; this.DONE = 2; // Instance properties (readonly per W3C spec, but mutable internally) this.error = null; this.readyState = _FileReader.EMPTY; this.result = null; // Event handlers (W3C spec compliant) this.onabort = null; this.onerror = null; this.onload = null; this.onloadend = null; this.onloadstart = null; this.onprogress = null; this._abortController = null; } // Read methods readAsArrayBuffer(blob) { this._read(blob, "arrayBuffer"); } readAsText(blob, encoding = "utf-8") { this._read(blob, "text", encoding); } readAsDataURL(blob) { this._read(blob, "dataURL"); } readAsBinaryString(blob) { this._read(blob, "binaryString"); } abort() { if (this.readyState !== _FileReader.LOADING) { return; } this.readyState = _FileReader.DONE; this.result = null; this.error = new DOMException("The operation was aborted.", "AbortError"); if (this._abortController) { this._abortController.abort(); } this._fireEvent("abort"); this._fireEvent("loadend"); } _read(blob, format, encoding) { if (this.readyState === _FileReader.LOADING) { throw new DOMException("The FileReader is already loading.", "InvalidStateError"); } this._performRead(blob, format, encoding); } async _performRead(blob, format, encoding) { this.readyState = _FileReader.LOADING; this.result = null; this.error = null; this._abortController = new AbortController(); this._fireEvent("loadstart"); try { let totalSize = blob.size; let loadedSize = 0; const reader = blob.stream().getReader(); const chunks = []; try { while (true) { const { done, value } = await reader.read(); if (this._abortController.signal.aborted) { return; } if (done) break; chunks.push(value); loadedSize += value.length; const progressEvent = new Event("progress"); progressEvent.lengthComputable = totalSize > 0; progressEvent.loaded = loadedSize; progressEvent.total = totalSize; this._fireProgressEvent(progressEvent); } } finally { reader.releaseLock(); } if (this._abortController.signal.aborted) { return; } const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0); const combined = new Uint8Array(totalLength); let offset = 0; for (const chunk of chunks) { combined.set(chunk, offset); offset += chunk.length; } switch (format) { case "arrayBuffer": this.result = combined.buffer.slice(combined.byteOffset, combined.byteOffset + combined.byteLength); break; case "text": this.result = new TextDecoder(encoding).decode(combined); break; case "dataURL": const base64 = Buffer.from(combined).toString("base64"); this.result = `data:${blob.type || "application/octet-stream"};base64,${base64}`; break; case "binaryString": this.result = Buffer.from(combined).toString("latin1"); break; } this.readyState = _FileReader.DONE; this._fireEvent("load"); this._fireEvent("loadend"); } catch (error) { if (this._abortController.signal.aborted) { return; } this.readyState = _FileReader.DONE; this.error = error instanceof DOMException ? error : new DOMException(String(error), "NotReadableError"); this.result = null; this._fireEvent("error"); this._fireEvent("loadend"); } } _fireEvent(type) { const event = new Event(type); this.dispatchEvent(event); const handler = this[`on${type}`]; if (typeof handler === "function") { handler.call(this, event); } } _fireProgressEvent(event) { this.dispatchEvent(event); if (typeof this.onprogress === "function") { this.onprogress.call(this, event); } } }; // ReadyState constants (W3C spec compliant) _FileReader.EMPTY = 0; _FileReader.LOADING = 1; _FileReader.DONE = 2; var FileReader = _FileReader; // src/index.ts if (typeof global !== "undefined") { if (!global.FileReader) { global.FileReader = FileReader; } if (!global.File) { global.File = File; } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { File, FileReader }); //# sourceMappingURL=index.js.map