UNPKG

@lyleunderwood/filereader-polyfill

Version:

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

254 lines (250 loc) 7.86 kB
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); // src/File.ts import { basename } from "path"; var File = class extends Blob { constructor(fileBitsOrPath, fileName, options) { if (typeof fileBitsOrPath === "string") { const filePath = fileBitsOrPath; super([], { type: "" }); this.name = 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; } } export { File, FileReader }; //# sourceMappingURL=index.mjs.map