@wtf/platform-node
Documentation / @wtf/platform-node
Run a program on @wtf/platform in Node: nodePlatform({ fetch, storage }) builds Node’s
platform from Node’s own objects, and runMain runs your program with it, stopping on SIGINT or
SIGTERM. Like Effect’s NodeContext, it uses Node’s objects where a web standard defines the
member, and Node’s versions of the other members otherwise, spawn among them. It is the only
package in the repo that imports node: modules.
| Subpath | What you get |
|---|---|
. | NodePlatformOptions, NodeConsoleOptions, NodeTerminalOptions, OutputStream |
./platform | nodePlatform({ fetch, storage, ...members }): every member; any you pass replaces Node’s |
./locks | nodeLocks(): locks from node:worker_threads, releasing the lock when a callback throws |
./worker | NodeWorker: a node:worker_threads thread that behaves like HTML’s Worker; parentScope(), a script’s self |
./console | nodeConsole({ stdout, stderr, format, level, color }): the Console Standard over stdout and stderr |
./env | nodeEnv({ snapshot }): process.env, live or a snapshot |
./path | nodePath({ flavour }): node:path, with file URLs from node:url |
./dirs | nodeDirs({ env }), nodeFacts(): from node:os and node:process, cwd read when asked |
./terminal | nodeTerminal({ stdin, stdout, env }): reads lines from stdin with node:readline |
./open | nodeOpen({ env }), spawnOpen({ spawn, platform, browser }), openerOf: BROWSER, else the platform’s app |
./process | nodeSpawn({ env }): spawn from node:child_process, its pipes Web Streams by Readable.toWeb |
./runtime | runMain(program, { fetch, storage }), processInterrupts({ signals }); processExitCode |
The standard members are Node’s own: setTimeout and clearTimeout from node:timers,
performance from node:perf_hooks, webcrypto from node:crypto, the console from
node:console, and BroadcastChannel and MessageChannel from node:worker_threads. Node has no
navigator.storage, so your entry point passes one, such as nodeStorage() from
@wtf/platform-fs-node/storage, whose root is /; this package depends on no file system. Node
has fetch only as a global, which nothing reads, so your entry point passes that too. dirs.cwd is
process.cwd(), read each time, so a chdir shows. Where Node differs from the standard, the
package adapts it: nodeLocks releases the lock when a callback throws, which Node 24 keeps held,
and NodeWorker makes a thread behave like a Worker, an EventTarget whose script is a URL.
open starts the program named by BROWSER in the platform’s env, with the URL as its one
argument, as CLIs do. BROWSER=none opens nothing. Otherwise it starts the platform’s own app:
open on macOS, rundll32 on Windows, or xdg-open. It reads BROWSER from the env it is given
each time it opens a URL, so a test sets it there:
nodePlatform({ fetch, storage, env: { BROWSER: 'none' } }).
spawn starts a process with spawn from node:child_process, with no shell, and turns its pipes
into Web Streams with Readable.toWeb and Writable.toWeb. A process gets the platform’s env,
read as it starts, unless you give it an env of its own, and an aborted signal ends it with
SIGTERM. A command that is not there rejects its status with a SystemError of NotFound.
import type { Platform } from '@wtf/platform'import { exec } from '@wtf/platform/process'import { runMain } from '@wtf/platform-node/runtime'import { nodeStorage } from '@wtf/platform-fs-node/storage'
declare const serve: (platform: Platform, options: { signal: AbortSignal }) => Promise<void>
await runMain( async ({ platform, signal }) => { const { stdout } = await exec(platform.spawn, 'git', ['rev-parse', '--short', 'HEAD']) platform.console.log('Serving %s at %s', stdout.trim(), platform.env['PORT'] ?? '3000') await serve(platform, { signal }) }, { fetch, storage: nodeStorage() })runMain runs your program with nodePlatform({ fetch, storage }), or with the platform you
give it. It aborts the program’s signal at the first SIGINT or SIGTERM; a second one ends the
process, as Node does by default. It sets process.exitCode from how the program ended: 0, or the
exitCode of what it threw, or 1.
It uses Node’s own modules and runs only in Node. The file system is for your entry point to pass
in, from @wtf/platform-fs-node.
