Skip to content

Release notes

Eugene Lazutkin edited this page May 22, 2026 · 4 revisions

Release notes

2.0.0 — multi-primitive surface, ESM, Web Streams

Bundled major rewrite. Replaces the single 1.x Fork class with three functional primitives (fork, route, filter) and a picker helper layer, ships the whole package as ESM, and adds a parallel Web Streams flavor at stream-fork/web. None of the 2.x work-in-progress was published before the rewrite landed, so this is the first 2.x on npm.

Behavior — primitives and picker helpers

  • The package exports three primitives: fork (broadcast, the default export), route (per-chunk single-target dispatch), and filter (per-chunk predicate-per-output subset). All three are sinks (Writable on the Node side, WritableStream on the Web side) and gate upstream backpressure on the receiving downstreams' write completions — for fork every live output, for route the single picked one, for filter the predicate-matched subset.
  • All primitives expose .outputs (read-only snapshot of the live downstreams) and .isEmpty() (true once every downstream has failed). Dead downstreams are filtered out of subsequent writes automatically.
  • All primitives accept options.ignoreErrors: when truthy, downstream errors are silently swallowed and the failing stream is dropped from the live set; when falsy (default), the first error per round is surfaced upstream (Node: 'error' event; Web: upstream pipeTo rejects).
  • New picker helpers under stream-fork/utils/:
    • pickRoundRobin(count) — load-balance.
    • pickByHash(keyFn, count) — stable shard via djb2 (numeric keys used directly modulo count).
    • pickByKey(keyFn, table) — explicit key→index lookup (plain object or Map).
    • pickFirstMatch(predicates) — first-true predicate's index; append () => true for catch-all. Picker helpers are pure functions, shared between the Node and Web trees.

Behavior — Node and Web flavors

  • Node Streams (stream-fork, default entry) wraps Writable. The three primitives are built on a shared internal makeStreamPusher(stream) (src/stream-pusher.js) that wraps stream.write / stream.end in a Promise-based interface and installs its own 'error' listener so Node never crashes on otherwise-unhandled downstream errors. Mirrors stream-join's makeStreamPuller substrate.
  • Web Streams (stream-fork/web) wraps WritableStream with the same options surface. The Web fork is a backpressure-preserving generalization of ReadableStream.tee() to N outputs — unlike tee, it does not buffer per branch (a slow branch slows upstream rather than ballooning a per-branch queue). Built on a parallel internal makeWebStreamPusher that acquires a writer, awaits writer.ready before each writer.write, and listens on writer.closed for async errors.

Breaking changes

  • Functional API. new Fork(outputs, options) from 1.x becomes fork(outputs, options) — no new, no class. The static Fork.fork(...) factory is gone (the function IS the factory).
  • Node 22+ required. 1.x supported Node 6+ via the old CI matrix.
  • ESM-only ("type": "module"). 1.x consumers using require() must switch to import (or await import() from a CommonJS host).
    // before (1.x, CommonJS)
    const Fork = require('stream-fork');
    // after (2.x, ESM)
    import fork from 'stream-fork';
  • Subpath imports for non-default primitives. route and filter import as stream-fork/route.js and stream-fork/filter.js; picker helpers as stream-fork/utils/<name>.js; the Web flavor at stream-fork/web (or per-primitive stream-fork/web/route.js etc.). The default export remains fork (import fork from 'stream-fork' or import fork from 'stream-fork/web') for back-compat with 1.x callers who imported the constructor under the name Fork.

Fleet-standard adoption

  • src/ layout with .js source and hand-written .d.ts sidecar per file (// @ts-self-types="./<name>.d.ts" directive at the top of each .js).
  • Default-export + named mirror applied across every public module: every export default X is paired with export {X} so CJS hosts can destructure via await import (const {fork} = await import('stream-fork')).
  • Added: AGENTS.md, CLAUDE.md, .github/COPILOT-INSTRUCTIONS.md, byte-identical .windsurfrules / .cursorrules / .clinerules, llms.txt, llms-full.txt, ARCHITECTURE.md, LICENSE.
  • Paired .claude/commands/ + .windsurf/workflows/ for ai-docs-update and release-check.
  • Tests migrated from heya-unit to tape-six, restructured into tests/node/ + tests/web/. Browser test workflow via tape-six-playwright (npm run test:browser).
  • CI: Node 22, 24, 26 on ubuntu-latest, windows-latest, macos-latest; actions/checkout@v6 + actions/setup-node@v6; runs ts-check + js-check in addition to tests.
  • Added .github/dependabot.yml (npm + github-actions, weekly, grouped, npm versioning-strategy: increase-if-necessary).
  • Added .github/FUNDING.yml.
  • Wiki content refreshed and the wiki repository is mounted as a submodule in the package repo.

1.0.5 — technical release

No functional changes; metadata only. Safe to skip.

1.0.4 — bugfix

  • Errors from downstream streams are now forwarded correctly. Thanks to dbubovych.

1.0.3 — technical release

Republished to support Node 14.

1.0.2 — Node 6 workaround

Switched from _final() to the 'finish' event so the package would work on Node 6.

1.0.1 — improved documentation

Documentation polish; no behavior change.

1.0.0 — initial release

First public release. A single Fork class — a Writable that broadcast every chunk to every output, with proper backpressure handling.

Clone this wiki locally