WHATWG Encoding

Elide implements the WHATWG [Encoding Standard][spec] globals TextEncoder and TextDecoder, available on globalThis and mirrored at require('node:util').TextEncoder / require('node:util').TextDecoder.

[spec]: https://encoding.spec.whatwg.org/

TextEncoder

js
new TextEncoder()
MemberTypeNotes
encodingstringReadonly; always "utf-8".
encode(input = "")Uint8ArrayReturns a fresh Uint8Array containing the UTF-8 bytes.
encodeInto(source, destination){ read, written }Writes into destination; never writes a partial code point.
Unpaired surrogate code units in the input are replaced with U+FFFD, producing EF BF BD in the output.

TextDecoder

js
new TextDecoder(label = "utf-8", options = {})
// options: { fatal: boolean = false, ignoreBOM: boolean = false }
MemberTypeNotes
encodingstringReadonly; canonical lowercase encoding name (e.g. "utf-8").
fatalbooleanReadonly; true → malformed input throws TypeError.
ignoreBOMbooleanReadonly; when false and encoding is utf-8/utf-16le/utf-16be, a leading BOM is stripped.
decode(input?, options?)stringoptions.stream: boolean = false. Partial trailing bytes are retained for the next call only when stream is true.

Supported labels

All labels defined by the [WHATWG Encoding names-and-labels table][labels] that map to a JVM-available Charset. This covers:

[labels]: https://encoding.spec.whatwg.org/#names-and-labels

  • Unicode: utf-8 (and aliases utf8, unicode-1-1-utf-8), utf-16le (aliases utf-16, unicode, ucs-2, etc.), utf-16be.
  • Western: windows-1252 (default for ascii, iso-8859-1, latin1), iso-8859-2iso-8859-16, windows-1250windows-1258, macintosh, x-mac-cyrillic.
  • Cyrillic: ibm866, iso-8859-5, koi8-r, koi8-u, windows-1251.
  • Greek / Arabic / Hebrew / Thai: iso-8859-6/7/8/8-i, windows-874.
  • CJK: gbk, gb18030, big5, euc-jp, iso-2022-jp, shift_jis, euc-kr.

Label normalization: ASCII whitespace (tab, LF, FF, CR, space) is stripped from each end and the remainder is lowercased. Unknown labels throw RangeError.

Call vs new

Both constructors must be invoked with new; calling TextEncoder(...) or TextDecoder(...) as plain functions throws TypeError.

Streaming: TextEncoderStream / TextDecoderStream

TextEncoderStream and TextDecoderStream are installed on globalThis as TransformStreams built over TextEncoder / TextDecoder. TextEncoderStream turns a stream of strings into UTF-8 Uint8Array chunks; TextDecoderStream turns a stream of byte chunks into strings, carrying partial multi-byte sequences across chunk boundaries.
  • new TextDecoderStream([label][, options])options.fatal and options.ignoreBOM are honoured; instances expose encoding, fatal, ignoreBOM, and the readable / writable pair.
  • new TextEncoderStream() — always UTF-8; exposes encoding and the readable / writable pair.
js
const lines = byteSource.pipeThrough(new TextDecoderStream());
for await (const chunk of lines) {
  // chunk: decoded string
}

Example

js
const enc = new TextEncoder();
const bytes = enc.encode("Hello, мир! 🌍");
// bytes = Uint8Array([72,101,108,108,111,44,32,208,188,208,184,209,128,33,32,240,159,140,141])

const dec = new TextDecoder("utf-8");
dec.decode(bytes); // → "Hello, мир! 🌍"

// Streaming across partial multi-byte sequences:
const streamer = new TextDecoder();
streamer.decode(new Uint8Array([0xc3]), { stream: true }); // → ""
streamer.decode(new Uint8Array([0xa9]));                    // → "é"

// fatal mode raises TypeError on invalid input instead of replacing with U+FFFD:
const strict = new TextDecoder("utf-8", { fatal: true });
strict.decode(new Uint8Array([0xff])); // throws TypeError

Notes on the implementation

  • The fix that routes plain-call TextEncoder(...) / TextDecoder(...) to TypeError lives in the generated JSTextEncoderClassBase.java and JSTextDecoderClassBase.java under packages/generated/. Upstream elide-dev/bindgen has the corresponding template (JSConstructorCodegen.kt); the next regenerate will emit the same branch automatically.
  • TextDecoder instance creation explicitly reinstalls the intrinsic prototype via JSObjectUtil.setPrototypeImpl after JSObjectFactory.initProto — in single-context mode the factory's initProto only swaps the prototype when it differs from the cached default, and our subclass-with-extra-fields path doesn't always hit the shape-embedded prototype correctly. The explicit call guarantees instance instanceof TextDecoder holds.