@forst/node-runtime
v0.3.1
Published
Node.js runtime bootstrap and TypeScript indexer for Forst Node interop
Maintainers
Readme
@forst/node-runtime
Node runtime for Forst → TypeScript interop. Compiled Go binaries call your legacy .ts and .js modules over a closed RPC channel. The Forst compiler uses this package at build time to index TypeScript exports.
Status: experimental. Pin this package and verify with the examples before production use.
Full guide → Call JavaScript from Forst
Install
npm install @forst/node-runtimenpx jsr add @forst/node-runtimeRequires Node.js 18+. When your Forst program uses import node, you also need tsx on the path for TypeScript loading.
| Registry | Package | | --- | --- | | npm | @forst/node-runtime | | JSR | @forst/node-runtime |
What you get
The runtime is built on Effect: structured logs via Effect.log* and Effect.fn programs, with ForstNodeRuntimeLayer (stderr pretty logging + FORST_NODE_LOG_LEVEL) provided at process boundaries via NodeRuntime.runMain or Effect.runPromise.
Custom Effect runtime
Default entrypoints (bootstrap.js, @forst/node-runtime/host) use ForstNodeRuntimeLayer. To bring your own logging, tracing, or services, build a setup and pass it at the process boundary:
import { NodeRuntime } from "@effect/platform-node";
import { Effect, Layer, Logger } from "effect";
import {
bootstrapMain,
bootstrapFatal,
createNodeRuntimeSetup,
makeForstNodeRuntimeLayer,
startRpcServer,
startForstNodeHost,
} from "@forst/node-runtime";
const myLayer = makeForstNodeRuntimeLayer();
const { layer, runtime } = createNodeRuntimeSetup(myLayer);
// Bootstrap child (stdin/stdout RPC). disablePrettyLogger keeps stdout for frames only.
NodeRuntime.runMain(
bootstrapMain({ runtime }).pipe(
Effect.catchAllDefect((cause) => bootstrapFatal(cause)),
Effect.provide(layer)
),
{ disablePrettyLogger: true }
);
// Or wire RPC directly
NodeRuntime.runMain(
startRpcServer(process.stdin, process.stdout, {
exitProcessOnShutdown: true,
runtime,
}).pipe(Effect.provide(layer)),
{ disablePrettyLogger: true }
);
// Host mode: pass runtimeLayer so RPC forks use the same setup
await Effect.runPromise(
startForstNodeHost({ runtimeLayer: layer }).pipe(Effect.provide(layer))
);Use the same layer for Effect.provide and the matching runtime for async RPC dispatch sites (startRpcServer, host connection forks).
| Piece | Role |
| --- | --- |
| bootstrap.js | RPC server process Go spawns in bootstrap mode |
| @forst/node-runtime/host | In process RPC when your app runs the Node child |
| forst-node-index | CLI the compiler invokes to read TypeScript exports |
| Schema types | forst-node-manifest-v1 and forst-index-v1 validation |
Project setup
Enable node interop in ftconfig.json:
{
"files": {
"include": ["**/*.ft", "**/*.ts"]
},
"node": {
"enabled": true,
"runtimeEnabled": true
}
}Use opt in imports in Forst source:
import node "./legacy/payment"
func main() {
result := payment.create(100.0, "USD")
}Build and run with the Forst compiler (@forst/cli):
npx forst build -root . ./main.ft
npx forst run -root . ./main.ftRuntime modes
Bootstrap (default): Go starts a dedicated Node child that runs dist/bootstrap.js. Isolated process. Logs on stderr. Stdout is RPC only.
Host: Go starts your app (node.binary + node.args). RPC listens on a local socket inside that process so module cache and globals stay shared. Import from @forst/node-runtime/host and call signalForstAppReady() when your app is ready.
See runtime modes in the docs.
CLI
forst-node-index --root . --format forst-index-v1 --files legacy/payment.tsThe compiler calls this during type checking. You rarely run it yourself.
Environment variables
Bootstrap and logging
| Variable | Purpose |
| --- | --- |
| FORST_NODE_LOG_LEVEL | Log verbosity: debug, info, warn, or error (default info). Per-RPC trace (rpc_recv, call, module_cache_hit, …) requires debug. |
| FORST_NODE_LOG_FORMAT | Log format: pretty (default) or json for structured stderr lines. |
| FORST_NODE_BOOTSTRAP | Absolute path to bootstrap.js (bootstrap mode spawn planning) |
Host mode (set by Go on the direct app-shim child)
| Variable | Purpose |
| --- | --- |
| FORST_NODE_HOST | When "1", enables in-process host RPC (startForstNodeHost). Unset in bootstrap mode. |
| FORST_NODE_HOST_LEADER | When "1", marks the Go-spawned leader process. Required together with register.mjs in process.execArgv; workers skip binding. |
| FORST_NODE_SOCKET | Absolute Unix socket path (TCP URL on Windows) for Go↔Node RPC in host mode. |
| FORST_NODE_HOST_READY | Absolute path to JSON readiness file ({socket}.ready); Go waits for phase: "app". |
| FORST_NODE_APP_READY_MODULE | Optional path to a module loaded before app readiness when node.hostAppReadyModule is configured. |
See Call JavaScript from Forst — host mode environment variables for spawn layout, readiness phases, and troubleshooting.
Development
From the monorepo:
cd packages/node-runtime
bun run build
bun testPublishing
Release Please tags node-runtime-v* bump package.json and jsr.json. CI publishes to npm and JSR via .github/workflows/publish-packages.yml.
Manual publish from packages/node-runtime:
bun run build
npm publish --access public
npx jsr publishDry run:
bun run pack:dry
npx jsr publish --dry-runLicense
MIT. See LICENSE.
