Skip to content

Node streams

Eugene Lazutkin edited this page Jun 2, 2026 · 3 revisions

A Node-streams facade for dollar-shell. It exposes the same API as the main entry, except the standard streams are Node streams instead of web streams.

Reach for it when you want to pipe a process straight into Node's ecosystem (fs, zlib, readline, …) without a web↔Node adapter, or to skip the web-stream conversion entirely.

Usage

import {$, $$, $sh, shell, sh, spawn} from 'dollar-shell/node';

$ is the default export, so import $ from 'dollar-shell/node' works too.

The entry always spawns through the Node backend (node:child_process). On Bun and Deno it runs through their Node compatibility layer, so it works there as well — process spawning still needs the usual permissions (e.g., Deno's --allow-run). Only the spawn mechanism is Node's: the runtime that executes your scripts and how dollar-shell re-launches it stay native (a child of Bun/Deno is still bun run … / deno run …), exactly as with the DSH_FORCE_NODE flag.

Docs

Everything is identical to the main dollar-shell API — $, $$, $sh, shell / sh, spawn(), and all the utilities — with $/$$/$sh/shell carrying the same .from, .to, .through/.io helpers.

The single difference is the stream type on the sub-process object:

import type {Readable, Writable, Duplex} from 'node:stream';

interface Subprocess {
  readonly command: string[];
  readonly options: SpawnOptions | undefined;
  readonly exited: Promise<number>;
  readonly finished: boolean;
  readonly killed: boolean;
  readonly exitCode: number | null;
  readonly signalCode: string | null;
  readonly stdin: Writable | null; // a Node Writable (was a WritableStream)
  readonly stdout: Readable | null; // a Node Readable (was a ReadableStream)
  readonly stderr: Readable | null; // a Node Readable (was a ReadableStream)
  readonly asDuplex: Duplex;
  kill(): void;
}

Correspondingly, $.from / $sh.from return a Node Readable, $.to / $sh.to return a Node Writable, and $.io/$.through (and the $sh equivalents) return a Node Duplex — so a process drops straight into a .pipe() chain or stream.pipeline() as a transform step. (On the main, web-streams entry these return a {readable, writable} pair, the shape ReadableStream.pipeThrough() consumes.)

SpawnOptions, ShellOptions, DollarResult, and the rest of the non-stream types are exactly the same as the main entry. The TypeScript declarations live in src/node/index.d.ts.

Examples

import {spawn} from 'dollar-shell/node';
import {createWriteStream} from 'node:fs';

// Pipe a process straight into a file with Node streams — no adapter.
const sp = spawn(['ls', '-l'], {stdout: 'pipe'});
sp.stdout.pipe(createWriteStream('listing.txt'));
await sp.exited;
import {$} from 'dollar-shell/node';

// `$.from` is a Node Readable here.
const lines = $.from`ls -l`;
lines.setEncoding('utf8');
for await (const chunk of lines) process.stdout.write(chunk);
import {$} from 'dollar-shell/node';
import {pipeline} from 'node:stream/promises';
import {createReadStream, createWriteStream} from 'node:fs';

// `$.io` is a Node Duplex, so it slots into a pipeline as a transform step.
await pipeline(
  createReadStream('in.txt'),
  $.io`grep error`,
  createWriteStream('out.txt')
);

See also

  • Cross-runtime notes — how Node, Bun, and Deno differ, and the related DSH_FORCE_NODE flag, which forces the Node backend but keeps the web-streams API. (This page is the other half: always Node backend, Node streams.)

Clone this wiki locally