Skip to content

Release notes

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

Release notes

3.0.0 — ESM + Web Streams

Two big changes that ship together because they share the same migration burden for consumers. ESM unlocks the Web variant — Web Streams aren't usable from CommonJS without await import() gymnastics — and the new src/web/ tree lives alongside the renamed-and-converted Node tree under a single ESM-only package.

Behavior

  • Same three primitives, two flavors. The Node Streams API (fork, route, filter wrapping Writable) is unchanged in shape; a parallel Web Streams API at stream-fork/web ships the same three primitives wrapping WritableStream with the same options surface (predicates, pick, ignoreErrors).
  • Web fork replaces ReadableStream.tee() for the common N-output case. Unlike tee, the Web fork is backpressure-preserving — a slow branch slows upstream rather than ballooning a per-branch infinite buffer.
  • Picker helpers (stream-fork/utils/*) are shared between flavors. Pure functions, no runtime imports; the same pickRoundRobin / pickByHash / pickByKey / pickFirstMatch work in both trees.
  • New internal makeWebStreamPusher(stream) mirrors makeStreamPusher: acquires a writer, awaits writer.ready before each write, listens on writer.closed for async errors, swallows write/close rejections into Error | null so the primitives can resume after a downstream fails.
  • Web-side introspection mirrors Node. Both flavors expose .outputs (live snapshot) and .isEmpty() (true once every downstream has failed).

Breaking changes

  • ESM-only. "type": "module" in package.json. require() is no longer supported; consumers must use import (or await import() from a CommonJS host).

    // before (2.x, CommonJS)
    const fork = require('stream-fork');
    
    // after (3.x, ESM)
    import fork from 'stream-fork';
  • Source layout split. The Node primitives still live at src/{fork,route,filter}.js, but the new Web primitives live at src/web/{fork,route,filter}.js. The src/utils/ picker helpers are unchanged in location but are now shared between both trees.

  • Subpath imports (stream-fork/web, stream-fork/web/fork.js, etc.) added; the existing stream-fork/{fork,route,filter}.js and stream-fork/utils/<name>.js continue to work.

  • Tests restructured. tests/test-*.mjs becomes tests/node/test-*.js (Node) + tests/web/test-*.js (Web + pure picker tests). Helpers split into tests/helpers.js (Node-only) + tests/web-helpers.js (Web Streams). The browser-runnable set runs under tape-six-playwright via npm run test:browser.

Fleet-standard adoption

  • All .js sources now follow the esm-default-export-with-named-mirror rule: every export default X is paired with export {X} so CJS hosts can destructure via await import (const {fork} = await import('stream-fork')).
  • tape-six-playwright added as a devDependency for the browser test workflow.
  • .windsurfrules / .cursorrules / .clinerules now byte-identical to AGENTS.md per fleet slice 1 (previously a shorter directive form, now full mirror).
  • package.json#keywords adds web-streams.

2.0.0 — multi-primitive surface

Functional rewrite around three primitives plus a picker helper layer. Adopts fleet conventions (source layout, AI-agent docs, CI matrix).

Behavior

  • The package now 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 Writables and gate upstream backpressure on the receiving downstreams' write callbacks — 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 forwarded to the primitive's own 'error' event.
  • 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.
  • All 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.

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.
  • 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 default export (require('stream-fork') or import fork from 'stream-fork') remains fork() for back-compat with 1.x callers.

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).
  • 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 (.mjs test files).
  • 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 (this page + per-primitive references) 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