@wtf/platform-node
v2.0.0
Published
Node's platform: nodePlatform({ fetch, storage }) builds it from Node's own modules, with spawn over node:child_process, and runMain runs your program with it, stopping on SIGINT or SIGTERM.
Readme
@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; kill(signal) sends it one, as SIGKILL to a process that ignores SIGTERM. A command
that is not there rejects its status with a SystemError of NotFound; any other process Node
cannot start rejects it too, as of a link that loops or of arguments too long, where Node's own
spawn throws. Its stdin takes bytes alone, where Writable.toWeb takes text too: a chunk that
is no Uint8Array errors it, and closes the pipe.
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.
