API reference

Streams

9/9Coverage3/9Strict (zero caveats)

Two stream worlds live here. The WHATWG classes (ReadableStream/WritableStream/TransformStream, plus Compression/Decompression) run on the promise/microtask runtime alone — no fibers or libcurl for in-memory pipelines. Node's stream module (Readable/Writable/Duplex/Transform/PassThrough, pipeline/finished, duplexPair) is Node v24's own implementation ported to TypeScript and compiled with the program, so its event order follows Node's. The recurring divergences: a synchronously-throwing pull/start/write of a web stream propagates to the caller instead of erroring the stream, an array/tuple-chunk closed-stream read still yields a zero-shaped value rather than undefined, an omitted error argument to a Node stream callback arrives as null rather than undefined, and fs/http hand out their own stream handles rather than instances of the Node classes.

Exact Works, with caveats Not supported

ReadableStream

Works, with caveats
new ReadableStream<T>(source?, strategy?): ReadableStream<T>

A pull/push source with backpressure: enqueue via a controller in start/pull, consume with for await, .getReader().read(), or by piping. ReadableStream.from() builds one from an array. Runs on the promise/microtask runtime alone — no fibers or libcurl for in-memory pipelines.

Returns

ReadableStream<T> — A readable stream.

let n = 0
const numbers = new ReadableStream<number>({
  pull: (controller) => {
    n = n + 1
    if (n > 5) { controller.close() } else { controller.enqueue(n * n) }
  }
}, { highWaterMark: 1 })
for await (const square of numbers) { console.log("square:", square) }
See it in a full example →
⚠ Differences from standard
  • No BYOB readers / byte controllers (type: 'bytes' rejected at compile time) — byte data flows as Uint8Array chunks through default readers
  • ReadableStream.from() accepts arrays only, not async iterables
  • Source callbacks must be arrow/function-expression/method-shorthand literals; a cancel callback can't take a reason parameter
  • read() on a closed stream whose chunk type is an **array/tuple** still yields the chunk's zero-shaped value, not undefined (an array slot has no spare absent state); a scalar/string/object chunk reads a real undefined, and controller.desiredSize is null once errored
  • releaseLock() leaves queued pending reads pending instead of rejecting them
  • values() returns a reader usable in for await only (no standalone .next())
  • In the no-fiber tier, await of an already-settled read() skips the microtask drain, so a pending pull can run one tick later than real JS
  • A synchronously-throwing pull/start propagates to the caller instead of erroring the stream
  • tee() forwards the last cancelling branch's reason instead of composing both; a signal abort of pipeTo takes effect at the next chunk boundary

WritableStream

Works, with caveats
new WritableStream<T>(sink?, strategy?): WritableStream<T>

A backpressured sink: .write() queues a chunk (each write carries its own promise), .close()/.abort() finish it, and ready re-arms on backpressure flips. The sink's write/close/abort callbacks drive delivery.

Returns

WritableStream<T> — A writable stream.

const printed: string[] = []
const sink = new WritableStream<string>({
  write: (chunk) => { printed.push(chunk) }
})
await source.pipeThrough(upper).pipeTo(sink)
See it in a full example →
⚠ Differences from standard
  • releaseLock() doesn't reject a pending-write view
  • A synchronously-throwing sink write propagates instead of erroring the stream (an async rejection is handled per spec)
  • A sink abort callback can't take a reason parameter; no transformer/sink start promise-vs-write interlock beyond the started flag

TransformStream

Works, with caveats
new TransformStream<I, O>(transformer?): TransformStream<I, O>

A writable-in / readable-out pair whose transform callback maps chunks from one side to the other, running when the readable side has capacity (else parking one chunk). Compose it with pipeThrough. Writable HWM defaults to 1, readable HWM to 0.

Returns

TransformStream<I, O> — A transform stream.

const upper = new TransformStream<string, string>({
  transform: (chunk, controller) => { controller.enqueue(chunk.toUpperCase()) }
})
await source.pipeThrough(upper).pipeTo(sink)
See it in a full example →
⚠ Differences from standard
  • No transformer start callback
  • Cancelling the readable side doesn't error the writable side
  • An identity transform (no callback) requires matching I/O chunk representations

CompressionStream / DecompressionStream

Works, with caveats
new CompressionStream(format): TransformStream; new DecompressionStream(format): TransformStream

TransformStreams backed by native zlib: gzip/deflate/deflate-raw compress and decompress a Uint8Array chunk stream. Inflate auto-detects gzip/zlib wrappers, and -lz is linked only when one of these is actually constructed.

Returns

TransformStream — A zlib-backed transform stream.

const [rawBranch, decodeBranch] = source.pipeThrough(new CompressionStream("gzip")).tee()
for await (const chunk of decodeBranch.pipeThrough(new DecompressionStream("gzip"))) {
  out = out + dec.decode(chunk)
}
See it in a full example →
⚠ Differences from standard
  • The format argument must be a string literal ("gzip", "deflate", "deflate-raw") — a runtime-computed format string is a compile error
  • Chunks are Uint8Array only (no ArrayBuffer/DataView BufferSource variants)

Blob.stream()

Works, with caveats
blob.stream(): ReadableStream<Uint8Array>

Returns a ReadableStream<Uint8Array> over the blob's bytes — one owned-copy chunk then closed (an empty blob yields no chunk). Consumable via for await or .getReader().

Returns

ReadableStream<Uint8Array> — A readable stream of the blob's bytes.

⚠ Differences from standard
  • Delivers the blob's bytes as a **single** Uint8Array chunk (the whole blob), not incrementally chunked as a real byte source would be

stream.Readable / .Writable / .Duplex / .Transform / .PassThrough

Works, with caveats
new Readable({ read() { this.push(chunk) } }); class X extends Writable { _write(chunk, encoding, callback) {} }

Node's stream classes, ported from Node v24's own implementation to TypeScript and compiled with the program: the same states, event order and process.nextTick deferrals. The options form binds this to the stream, subclasses override _read/_write/_transform/_final/_destroy, chunks are Buffers (or strings with setEncoding, or any value in object mode), and Readable.from/.toWeb/.fromWeb bridge to iterables, async iterables and web streams. The readable operators (map with concurrency, filter, flatMap, drop, take, forEach, toArray, some, every, find, reduce), the signal option and addAbortSignal, Duplex.from and compose are Node's own.

Returns

Readable | Writable | Transform | PassThrough — A Node stream.

class Greeter extends Readable {
  words: string[] = ["kalimera", "kosme", "apo", "thessaloniki"]
  i: number = 0
  constructor() { super({ objectMode: true }) }
  _read() {
    if (this.i >= this.words.length) { this.push(null) }
    else { this.push(this.words[this.i]); this.i = this.i + 1 }
  }
}
See it in a full example →
⚠ Differences from standard
  • An omitted error argument reaches a callback declared (err?: Error | null) as null; Node passes undefined
  • The fs streams and the HTTP server's req/res are separate runtime handles, not instances of these classes
  • Readable.toWeb has no pull backpressure; Duplex.toWeb/fromWeb are missing
  • Not ported: Readable.wrap, isReadable/isErrored/setDefaultHighWaterMark
  • A method replaced on an instance (this._read = fn) is not supported; pass it in the options

stream.pipeline() / .finished()

Exact
pipeline(src, ...stages, cb); finished(stream, cb)

The callback forms: pipeline(a, ...b, cb) pipes each stream into the next and calls cb once the last has finished, or with the first error, destroying every stream; a stage may be an async generator function, the source any iterable, and the last stage may return a promise, with { signal, end } options; finished(s, [options,] cb) calls cb when the stream has ended, finished and closed, and returns a function removing its listeners. Their promise twins live in stream/promises.

Returns

Stream | () => void — pipeline returns the last stream; finished returns its cleanup function.

finished(echo, (err) => { console.log("echo finished, clean:", !err) })
pipeline(src, new PassThrough(), sink, (err) => { console.log("pipeline done", err ? err.message : "ok") })
See it in a full example →

stream/promises

Exact
import { pipeline, finished } from 'stream/promises'

The promise-returning pipeline and finished, over the callback forms: pipeline resolves once the last stream has finished, finished once the stream has ended and closed; either rejects with the stream's error.

Returns

Promise<void> — Resolves on completion, rejects on error.

import { pipeline, finished } from 'stream/promises'
await pipeline(numbers, square, sink)
await finished(words)
See it in a full example →

stream.duplexPair()

Exact
duplexPair(): [Duplex, Duplex]

Two Duplex instances wired to each other: what one side writes, the other reads. A write completes when the peer reads it, and end() finishes once the peer has ended, as Node's DuplexSide does. Returned as a two-element array.

Returns

[Duplex, Duplex] — A pair of cross-wired Duplex streams.

const [clientSide, serverSide] = duplexPair()
serverSide.on("data", (d) => { console.log("server saw:", d.toString()) })
clientSide.end("hello over the pair")
See it in a full example →