@lyleunderwood/filereader-polyfill
Version:
W3C File API specification compliant FileReader polyfill for Node.js environments
8 lines (7 loc) • 15.2 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../src/File.ts", "../src/FileReader.ts", "../src/index.ts"],
"sourcesContent": ["import { statSync } from 'fs';\nimport { basename } from 'path';\nimport { BlobPart, FilePropertyBag, IFile, NodeFile } from './types';\n\nexport class File extends Blob implements NodeFile {\n public readonly name: string;\n public readonly lastModified: number;\n private _path?: string;\n\n constructor(fileBits: BlobPart[], fileName: string, options?: FilePropertyBag);\n constructor(filePath: string);\n constructor(\n fileBitsOrPath: BlobPart[] | string,\n fileName?: string,\n options?: FilePropertyBag\n ) {\n if (typeof fileBitsOrPath === 'string') {\n // File path constructor - defer file system access\n const filePath = fileBitsOrPath;\n \n super([], { type: '' }); // Will be overridden by file content\n this.name = basename(filePath);\n this.lastModified = Date.now(); // Will be updated when file is accessed\n this._path = filePath;\n } else {\n // Standard Web API constructor\n super(fileBitsOrPath as any, options);\n this.name = fileName || '';\n this.lastModified = options?.lastModified ?? Date.now();\n }\n }\n\n get path(): string | undefined {\n return this._path;\n }\n\n // Helper method to get file stats and update lastModified\n private async _getFileStats(): Promise<any> {\n if (!this._path) return null;\n \n try {\n const { stat } = await import('fs/promises');\n const stats = await stat(this._path);\n // Update lastModified with actual file time (mutating readonly property)\n (this as any).lastModified = stats.mtime.getTime();\n return stats;\n } catch (error) {\n throw new DOMException(`Failed to read file: ${error}`, 'NotReadableError');\n }\n }\n\n // Override arrayBuffer to read from file if path is available\n async arrayBuffer(): Promise<ArrayBuffer> {\n if (this._path) {\n await this._getFileStats(); // This will throw DOMException if file doesn't exist\n try {\n const { readFile } = await import('fs/promises');\n const buffer = await readFile(this._path);\n return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);\n } catch (error) {\n throw new DOMException(`Failed to read file: ${error}`, 'NotReadableError');\n }\n }\n return super.arrayBuffer();\n }\n\n // Override stream to read from file if path is available\n stream(): ReadableStream<Uint8Array> {\n if (this._path) {\n const filePath = this._path;\n const self = this;\n \n return new ReadableStream({\n async start(controller) {\n try {\n await self._getFileStats(); // This will throw DOMException if file doesn't exist\n const { createReadStream } = require('fs');\n const nodeStream = createReadStream(filePath);\n \n nodeStream.on('data', (chunk: Buffer) => {\n controller.enqueue(new Uint8Array(chunk));\n });\n \n nodeStream.on('end', () => {\n controller.close();\n });\n \n nodeStream.on('error', (error: any) => {\n controller.error(new DOMException(`Failed to read file: ${error}`, 'NotReadableError'));\n });\n } catch (error) {\n controller.error(error);\n }\n },\n \n cancel() {\n // Stream will be cleaned up automatically\n }\n });\n }\n return super.stream();\n }\n\n // Override text to read from file if path is available\n async text(): Promise<string> {\n if (this._path) {\n await this._getFileStats(); // This will throw DOMException if file doesn't exist\n try {\n const { readFile } = await import('fs/promises');\n return readFile(this._path, 'utf8');\n } catch (error) {\n throw new DOMException(`Failed to read file: ${error}`, 'NotReadableError');\n }\n }\n return super.text();\n }\n}", "import { IFileReader, FileReaderState, EventHandler, ProgressEvent } from './types';\n\nexport class FileReader extends EventTarget implements IFileReader {\n // ReadyState constants (W3C spec compliant)\n static readonly EMPTY = 0 as const;\n static readonly LOADING = 1 as const;\n static readonly DONE = 2 as const;\n\n // Instance constants (required by W3C spec)\n public readonly EMPTY = 0 as const;\n public readonly LOADING = 1 as const;\n public readonly DONE = 2 as const;\n\n // Instance properties (readonly per W3C spec, but mutable internally)\n public error: DOMException | null = null;\n public readyState: FileReaderState = FileReader.EMPTY;\n public result: string | ArrayBuffer | null = null;\n\n // Event handlers (W3C spec compliant)\n public onabort: EventHandler = null;\n public onerror: EventHandler = null;\n public onload: EventHandler = null;\n public onloadend: EventHandler = null;\n public onloadstart: EventHandler = null;\n public onprogress: EventHandler = null;\n\n private _abortController: AbortController | null = null;\n\n constructor() {\n super();\n }\n\n // Read methods\n readAsArrayBuffer(blob: Blob): void {\n this._read(blob, 'arrayBuffer');\n }\n\n readAsText(blob: Blob, encoding = 'utf-8'): void {\n this._read(blob, 'text', encoding);\n }\n\n readAsDataURL(blob: Blob): void {\n this._read(blob, 'dataURL');\n }\n\n readAsBinaryString(blob: Blob): void {\n this._read(blob, 'binaryString');\n }\n\n abort(): void {\n if (this.readyState !== FileReader.LOADING) {\n return;\n }\n\n this.readyState = FileReader.DONE;\n this.result = null;\n this.error = new DOMException('The operation was aborted.', 'AbortError');\n\n if (this._abortController) {\n this._abortController.abort();\n }\n\n this._fireEvent('abort');\n this._fireEvent('loadend');\n }\n\n private _read(blob: Blob, format: string, encoding?: string): void {\n if (this.readyState === FileReader.LOADING) {\n throw new DOMException('The FileReader is already loading.', 'InvalidStateError');\n }\n\n this._performRead(blob, format, encoding);\n }\n\n private async _performRead(blob: Blob, format: string, encoding?: string): Promise<void> {\n\n this.readyState = FileReader.LOADING;\n this.result = null;\n this.error = null;\n this._abortController = new AbortController();\n\n this._fireEvent('loadstart');\n\n try {\n let totalSize = blob.size;\n let loadedSize = 0;\n\n // Use stream-based reading for progress events\n const reader = blob.stream().getReader();\n const chunks: Uint8Array[] = [];\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n \n if (this._abortController.signal.aborted) {\n return; // Abort was called\n }\n\n if (done) break;\n\n chunks.push(value);\n loadedSize += value.length;\n\n // Fire progress event (W3C spec compliant)\n const progressEvent = new Event('progress') as ProgressEvent;\n (progressEvent as any).lengthComputable = totalSize > 0; \n (progressEvent as any).loaded = loadedSize;\n (progressEvent as any).total = totalSize;\n this._fireProgressEvent(progressEvent);\n }\n } finally {\n reader.releaseLock();\n }\n\n if (this._abortController.signal.aborted) {\n return; // Abort was called during reading\n }\n\n // Combine all chunks\n const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);\n const combined = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n combined.set(chunk, offset);\n offset += chunk.length;\n }\n\n // Format the result based on the requested format\n switch (format) {\n case 'arrayBuffer':\n this.result = combined.buffer.slice(combined.byteOffset, combined.byteOffset + combined.byteLength);\n break;\n case 'text':\n this.result = new TextDecoder(encoding).decode(combined);\n break;\n case 'dataURL':\n const base64 = Buffer.from(combined).toString('base64');\n this.result = `data:${blob.type || 'application/octet-stream'};base64,${base64}`;\n break;\n case 'binaryString':\n this.result = Buffer.from(combined).toString('latin1');\n break;\n }\n\n this.readyState = FileReader.DONE;\n this._fireEvent('load');\n this._fireEvent('loadend');\n\n } catch (error) {\n if (this._abortController.signal.aborted) {\n return; // Abort was called\n }\n\n this.readyState = FileReader.DONE;\n this.error = error instanceof DOMException ? error : new DOMException(String(error), 'NotReadableError');\n this.result = null;\n\n this._fireEvent('error');\n this._fireEvent('loadend');\n }\n }\n\n private _fireEvent(type: string): void {\n const event = new Event(type);\n this.dispatchEvent(event);\n\n // Also call the corresponding handler if set\n const handler = (this as any)[`on${type}`];\n if (typeof handler === 'function') {\n handler.call(this, event);\n }\n }\n\n private _fireProgressEvent(event: ProgressEvent): void {\n this.dispatchEvent(event);\n\n if (typeof this.onprogress === 'function') {\n this.onprogress.call(this, event);\n }\n }\n}", "import { File as FilePolyfill } from './File';\nimport { FileReader as FileReaderPolyfill } from './FileReader';\n\nexport { FilePolyfill as File, FileReaderPolyfill as FileReader };\n\n// Export W3C spec compliant types\nexport type {\n BlobPart,\n FilePropertyBag,\n EventHandler,\n ProgressEvent,\n IFileReader,\n IFile,\n NodeFile,\n FileReaderState\n} from './types';\n\n// Polyfill globals if they don't exist\nif (typeof global !== 'undefined') {\n if (!(global as any).FileReader) {\n (global as any).FileReader = FileReaderPolyfill;\n }\n if (!(global as any).File) {\n (global as any).File = FilePolyfill;\n }\n}"],
"mappings": "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",
"names": []
}