blob

js/globals/blob.ts

Blob and File globals (WHATWG File API).

Blob is an immutable byte sequence with an associated MIME type. It is the standard way to carry binary data in web APIs: fetch Request/Response bodies, FormData values, FileReader, and FileReaderSync all work in terms of Blobs. File extends Blob with a name and lastModified timestamp.

Alongside the two globals this module implements the reader interfaces from the same spec: FileReader (asynchronous, event-based), FileReaderSync (the worker-only synchronous variant), and the array-like FileList. The WebIDL surface details WPT checks for — enumerable prototype members, constructor length values, readonly FileReader state constants, and string tags — are patched onto the classes at the bottom of the file.

WHATWG File API: https://w3c.github.io/FileAPI/

Internal byte storage

All Blob content is eagerly concatenated into a single Uint8Array on construction. String parts honor endings: 'native' by normalizing CRLF, CR, and LF sequences to the runtime's native newline before UTF-8 encoding; non-string parts remain byte-preserving. The WHATWG spec allows lazy concatenation (Blob objects can reference other Blobs by ref), but eager flattening is simpler and correct for our use case where Blobs are typically small-to-medium in-memory values rather than multi-GB file handles.

The BYTES_INIT sentinel

slice() needs to construct a Blob directly from raw bytes, bypassing the normal parts iteration and normalization logic. Rather than adding a public constructor overload or a static factory, we use a module-private Symbol('bytes-init') as a sentinel first argument. When the constructor sees parts === BYTES_INIT, it treats the second argument as { bytes: Uint8Array, type: string } and uses those values directly. This keeps the fast path clean and invisible to external callers.

The _blobBytes WeakMap

_normalizePart() needs to read the internal bytes of a Blob (or File) that appears as a part in another Blob's constructor. Since #bytes is a private field only accessible within the class body, module-level helpers can't reach it directly. The _blobBytes WeakMap is populated in the constructor (_blobBytes.set(this, this.#bytes)) to give module-level code read access to internal bytes without exposing a public getter.

arrayBuffer() returns a copy

blob.arrayBuffer() returns a copy of the internal buffer (via buffer.slice()), not a view into it. This prevents callers from mutating the Blob's internal state through the returned ArrayBuffer. bytes() similarly returns a fresh new Uint8Array(this.#bytes) copy. The stream() method returns a byte-oriented ReadableStream that enqueues one copied chunk and closes, and textStream() does the same with the decoded UTF-8 text — sufficient for piping Blob bodies without a chunking layer.

slice() semantics

slice(start, end, contentType) follows the WHATWG spec: negative indices are resolved relative to the blob size, values are clamped to [0, size, and end < start produces an empty Blob. The content type of the resulting Blob is the provided contentType (lowercased), or empty string if omitted.

// Blob and File are available via globalThis

const blob = new Blob(['hello, ', 'world'], { type: 'text/plain' });
blob.size;                // 12
await blob.text();        // 'hello, world'
await blob.bytes();       // Uint8Array
await blob.arrayBuffer(); // ArrayBuffer
blob.slice(0, 5);         // new Blob containing 'hello'

const file = new File([blob], 'hello.txt', { type: 'text/plain' });
file.name;                // 'hello.txt'
file.lastModified;        // number (ms since epoch)

const syncReader = new FileReaderSync();
syncReader.readAsText(blob); // 'hello, world'

Types

type BlobPart = string | ArrayBuffer | ArrayBufferView | Blob

Value accepted by the Blob and File constructors as one input part.

Strings are UTF-8 encoded after optional line-ending normalization. ArrayBuffer and ArrayBufferView values are copied as bytes, and Blob/File parts contribute their immutable byte contents.

type FileReaderHandler = ((event: Event) => void) | null

Event handler attribute type used by FileReader.

Assigning a function installs the handler for the corresponding event. Assigning null or a non-function value clears it, matching WebIDL event handler attribute behavior.

Interfaces

interface BlobOptions {

Options accepted by the Blob constructor.

type is lowercased and must contain only printable ASCII characters or it is normalized to the empty string. endings controls only string parts: 'transparent' preserves line endings and 'native' normalizes CRLF, CR, and LF sequences to the runtime's native newline before UTF-8 encoding.

Properties

type?: string
endings?: EndingType

interface FileOptions extends BlobOptions {

Options accepted by the File constructor.

In addition to Blob options, lastModified supplies the millisecond Unix timestamp exposed by the File instance. When omitted, construction captures Date.now().

Properties

lastModified?: number

interface FileList {

Array-like list of File objects.

FileList objects are produced by web APIs such as file inputs and drag/drop. Fino exposes the interface for File API compatibility; there is no public constructor, matching browsers.

try { new FileList(); } catch (err) { console.log(err instanceof TypeError); }

Readonly Properties

readonly length: number

Number of File objects in the list.

Methods

item(index: number): File | null

Return the File at the given index, or null when out of range.

The index is coerced to an unsigned 32-bit integer, matching WebIDL. Throws a TypeError when called with no arguments.

Classes

class Blob {

Web Blob facade backed by an immutable in-memory byte store.

Blob parts are eagerly normalized and concatenated during construction. MIME types are lowercased and rejected to the empty string when they contain non-ASCII printable characters. Methods that expose bytes return copies.

const blob = new Blob(['hello'], { type: 'TEXT/PLAIN' });
blob.type; // "text/plain"
await blob.text(); // "hello"

Constructors

constructor(parts?: Iterable<BlobPart> | null, options?: BlobOptions | null)

Create a Blob from strings, buffers, views, or other Blobs.

Omitted parts create an empty Blob. Non-iterable parts, including null, throw a TypeError. endings: 'native' normalizes string-part line endings before encoding; the default transparent preserves them. The internal BYTES_INIT path is private to this module and is used by slice() to avoid re-normalizing already-owned bytes.

const bytes = new Uint8Array([104, 105]);
const blob = new Blob(['prefix-', bytes], { type: 'text/plain' });
blob.size; // 9
constructor( parts?: Iterable<BlobPart> | typeof BYTES_INIT | null, options?: BlobOptions | InternalBlobOptions | null )

Getters

get size()

Number of bytes in the Blob.

The value is computed from the immutable internal byte store.

new Blob(['abc']).size; // 3
get type()

Normalized MIME type for the Blob.

The value is lowercased. Invalid type strings containing characters outside U+0020 through U+007E become the empty string.

new Blob([], { type: 'Application/JSON' }).type; // "application/json"

Methods

slice(start?: number, end?: number, contentType?: string): Blob

Return a new Blob containing a byte range from this Blob.

Negative indexes are resolved from the end, bounds are clamped to the Blob size, and end values before start produce an empty Blob. The contentType argument becomes the returned Blob type after the same normalization rules.

const blob = new Blob(['abcdef']);
await blob.slice(1, 4).text(); // "bcd"
async text(): Promise<string>

Decode the Blob bytes as UTF-8 text.

Invalid UTF-8 sequences follow the internal decoder's replacement behavior. The method resolves to a string and does not mutate the Blob.

const text = await new Blob(['hello']).text();
textStream(): ReadableStream<string>

Create a ReadableStream that emits the Blob contents as UTF-8 text.

The MIME type charset parameter is ignored, matching the File API text decoding rules used by text(). Empty blobs close without emitting chunks.

const chunks = [];
for await (const chunk of new Blob(['hi']).textStream()) chunks.push(chunk);
async arrayBuffer(): Promise<ArrayBuffer>

Copy the Blob bytes into a new ArrayBuffer.

The returned buffer is detached from Blob storage, so modifications to a view over it cannot change the Blob.

const buffer = await new Blob(['hi']).arrayBuffer();
new Uint8Array(buffer).byteLength; // 2
async bytes(): Promise<Uint8Array>

Copy the Blob bytes into a new Uint8Array.

This is a convenience over arrayBuffer() when callers want a typed array. The returned array is safe to mutate.

const bytes = await new Blob(['hi']).bytes();
bytes[0]; // 104
stream(): ReadableStream

Create a ReadableStream for the Blob contents.

The current implementation emits one copied Uint8Array chunk and then closes. It is suitable for piping Blob bodies without exposing storage.

const stream = new Blob(['hi']).stream();
const reader = stream.getReader();
await reader.read();

class File extends Blob {

File extends Blob with a filename and lastModified timestamp.

File contents use the same eager in-memory byte store as Blob. The name is string-coerced and lastModified defaults to Date.now() when omitted.

const file = new File(['hello'], 'hello.txt', { type: 'text/plain' });
file.name; // "hello.txt"

Constructors

constructor(parts: Iterable<BlobPart> | null, name: string, options?: FileOptions | null)

Create a File from Blob parts and metadata.

The options type and endings are passed through Blob normalization. lastModified is truncated to an integer millisecond timestamp; when absent, Date.now() is captured at construction time.

const file = new File(['data'], 'data.bin', { lastModified: 0 });
file.lastModified; // 0

Getters

get name()

File name supplied at construction.

The name is not path-normalized or sanitized; callers should validate it before using it on a filesystem.

new File([], 'avatar.png').name; // "avatar.png"
get lastModified()

Last modified time in Unix epoch milliseconds.

The value is an integer and defaults to the construction time.

new File([], 'x', { lastModified: 123 }).lastModified; // 123

class FileReader extends EventTarget {

Asynchronous Blob reader from the File API.

FileReader reads an in-memory Blob or File as text, an ArrayBuffer, a binary string, or a data URL. Reads transition through EMPTY, LOADING, and DONE, dispatching the standard loadstart, progress, load, abort, error, and loadend events. Fino supports Blob-backed reads and does not install filesystem-backed browser file handles.

const reader = new FileReader();
reader.onload = () => console.log(reader.result);
reader.readAsText(new Blob(['hello']));

Static Properties

static EMPTY

No read has started.

new FileReader().readyState === FileReader.EMPTY; // true
static LOADING

A read is currently in progress.

FileReader.LOADING; // 1
static DONE

The read completed, failed, or was aborted.

FileReader.DONE; // 2

Getters

get onloadstart()

Event handler invoked when a read begins (the loadstart event).

Assigning a non-function clears the handler to null, matching WebIDL event handler attributes. Handlers run in addition to any listeners added via addEventListener.

get onprogress()

Event handler for progress events.

Fino dispatches a single progress event per read, and only for non-empty Blobs — reads are in-memory so there is no incremental progress to report.

get onload()

Event handler invoked when a read completes successfully (load).

By the time this fires, result holds the decoded value and readyState is DONE.

const reader = new FileReader();
reader.onload = () => console.log(reader.result);
reader.readAsText(new Blob(['hi']));
get onabort()

Event handler invoked when an in-progress read is cancelled via abort().

get onerror()

Event handler invoked when a read fails (error).

The failure is available on the error property as a DOMException; result is null.

get onloadend()

Event handler for loadend, fired after load, error, or abort.

Use this for cleanup that must run regardless of how the read finished.

get EMPTY()

Instance mirror of FileReader.EMPTY, required by WebIDL.

get LOADING()

Instance mirror of FileReader.LOADING, required by WebIDL.

get DONE()

Instance mirror of FileReader.DONE, required by WebIDL.

get readyState()

Current read state: EMPTY (0), LOADING (1), or DONE (2).

A new reader starts at EMPTY, moves to LOADING when a readAs* method is called, and settles at DONE after load, error, or abort.

get result()

Value produced by the last completed read, or null.

The type depends on the read method: an ArrayBuffer for readAsArrayBuffer, otherwise a string. The value stays null while a read is in flight and is reset to null when a new read starts, on failure, and on abort.

get error()

DOMException describing the last read failure, or null.

Non-DOMException failures are wrapped in a NotReadableError DOMException.

Setters

set onloadstart(value: FileReaderHandler)
set onprogress(value: FileReaderHandler)
set onload(value: FileReaderHandler)
set onabort(value: FileReaderHandler)
set onerror(value: FileReaderHandler)
set onloadend(value: FileReaderHandler)

Methods

dispatchEvent(event: Event): boolean

Dispatch an event to listeners and the matching on* handler.

After regular EventTarget dispatch, the corresponding event handler property (onload for a load event, and so on) is invoked with the same event. Exceptions thrown by that handler are swallowed so a faulty handler cannot break the read state machine.

readAsArrayBuffer(blob: Blob): void

Start reading the Blob; result becomes a copied ArrayBuffer on load.

Throws a TypeError if the argument is not a Blob, and an InvalidStateError DOMException if a read is already in progress. Events fire asynchronously in the order loadstart, progress (non-empty Blobs only), load or error, loadend.

const reader = new FileReader();
reader.onload = () => new Uint8Array(reader.result as ArrayBuffer);
reader.readAsArrayBuffer(new Blob([new Uint8Array([1, 2, 3])]));
readAsBinaryString(blob: Blob): void

Start reading the Blob; result becomes a binary string on load.

Each byte maps to one code unit with the same numeric value. Error conditions and event sequencing match readAsArrayBuffer.

readAsDataURL(blob: Blob): void

Start reading the Blob; result becomes a base64 data URL on load.

A Blob with an empty type is labeled application/octet-stream in the URL. Error conditions and event sequencing match readAsArrayBuffer.

const reader = new FileReader();
reader.onload = () => reader.result; // "data:text/plain;base64,aGk="
reader.readAsDataURL(new Blob(['hi'], { type: 'text/plain' }));
readAsText(blob: Blob, encoding?: string): void

Start reading the Blob; result becomes decoded text on load.

The encoding is chosen in order of precedence: the explicit encoding label, a charset parameter in the Blob type, a UTF-16 byte order mark, and finally UTF-8. Error conditions and event sequencing match readAsArrayBuffer.

const reader = new FileReader();
reader.onload = () => reader.result; // "héllo"
reader.readAsText(new Blob(['héllo']), 'utf-8');
abort(): void

Cancel an in-progress read.

When a read is LOADING, the pending result is discarded, readyState becomes DONE, and abort then loadend events fire; the read's remaining load/error events are suppressed. When no read is in progress the call only clears result, without dispatching events.

class FileReaderSync {

Synchronous Blob reader from the File API.

FileReaderSync exposes the worker-only synchronous read methods for Blob-backed data. Fino does not install it on the normal global object; the WPT worker harness installs it only for worker tests. Like FileReader, reads are limited to in-memory Blob and File objects.

const reader = new FileReaderSync();
reader.readAsText(new Blob(['hello'])); // "hello"

Methods

readAsArrayBuffer(blob: Blob): ArrayBuffer

Read Blob bytes into a new ArrayBuffer.

The returned buffer is detached from Blob storage.

readAsBinaryString(blob: Blob): string

Read Blob bytes as a binary string.

Each byte becomes one code unit with the same numeric value.

readAsDataURL(blob: Blob): string

Read Blob bytes as a data URL.

Empty Blob types use application/octet-stream, matching FileReader.

new FileReaderSync().readAsDataURL(new Blob(['hi'], { type: 'text/plain' }));
// "data:text/plain;base64,aGk="
readAsText(blob: Blob, encoding?: string): string

Read Blob bytes as text.

The optional encoding label follows the same decoding rules as FileReader.readAsText: explicit label, then Blob type charset, then a UTF-16 byte order mark, then UTF-8.

new FileReaderSync().readAsText(new Blob(['hello'])); // "hello"

Constants

const FileList

FileList constructor global — always throws, matching browsers.

FileList has no public constructor; new FileList() throws a TypeError with the standard "Illegal constructor" message. Instances are created only by the runtime (via _createFileList) for web API integrations. The global is still installed so instanceof checks and prototype inspection work.

try {
  new FileList();
} catch (err) {
  err instanceof TypeError; // true
}