Skip to main content
Forst generates either Promise-based calls or Effect values. Both modes use the same package subpaths and $-prefixed handles.
Install @forst/cli first. Add a postinstall script that runs forst generate .. Run generate once before you import @forst/gen. Details are on the Installation page.
Promise mode is the default. To generate Effect values, install effect and enable Effect mode in ftconfig.json:

Promise mode

No extra dependency or generate setting is required.

Effect mode

Point the client at a server:
Call the same generated function in either style:
Import the package handle from @forst/gen/<package> (for example $auth from @forst/gen/auth) and call through it in both modes. See Generate a TypeScript client and Effect mode.

Quick start

1

Write a public Forst function

Only exported functions (capitalized names) are callable from JavaScript.
2

Install and generate

Follow Installation § Generated TypeScript client.Promise mode is enabled by default. For Effect return types, set "generate": { "effect": true } in ftconfig.json before generating.
3

Run a server

Development — hot reload while you edit .ft files:
Production — linked program binary for slim container images. Enable embedded invoke in ftconfig.json before building:
With bridge.hostMode, the same binary runs your Forst entry and starts the Node app as a child. Set FORST_SKIP_NODE_HOST=1 on the same binary when Node runs separately and the binary should not spawn the Node host.For Go source inspection or hand go build, emit sources with generate.go flags:
4

Set the URL and call

Repository example: examples/in/rfc/embedded-invoke.

Configure the client

Promise mode creates a configured client object. Effect mode creates a layer that provides the generated package services:

Custom HTTP headers

Pass default headers on the client config. They merge into every invoke and stream request. Per-call headers in the second argument override defaults for that request only.
Content-Type: application/json is always set. The invoke authentication headers are reserved and cannot be replaced by your defaults or per-call headers. See Invoke security. Connect-mode workers need the invoke HMAC secret from FORST_INVOKE_TOKEN (base64url). The forst dev process sets this when it starts. Sidecar spawn mode uses an inherited FORST_INVOKE_AUTH_FD handoff instead. See Invoke security.

How the client picks a server

Production never spawns a server. Set FORST_BASE_URL (or FORST_INVOKE_URL / FORST_DEV_URL) explicitly. Local spawn of forst dev is opt-in for development only. The forst dev HTTP contract (/invoke, /version, …) is documented in Dev server. Saving a .ft file regenerates the client so the editor stays in step. See Dev server § Generated client.

Failures

Every invoke failure has a stable _tag. Promise mode throws the tagged value. Effect mode exposes the same tags through its typed error channel:
Import invoke helpers from @forst/errors in Promise mode or @forst/errors/effect in Effect mode (InvokeRejected, isInvokeFailure, …). Class names stay short. Built-in invoke, harness, and unknown-failure _tag strings use the @forst/errors/ prefix. Domain error tags are namespaced with your npm package name (for example @forst/tictactoe/CellTaken). Optional domain namespaces are also available from @forst/gen/$errors (for example errors.auth.NotFound).

Domain errors

When a Forst function raises a nominal error X { ... }, the server includes structured errorValue on the invoke envelope (HTTP contract version "2"). The generated client decodes it into a tagged class with the same name as the Forst error type.
In Effect mode the function error channel includes each inferred domain error, ForstUnknownFailure, and transport failures:
Unmapped server failures (generic Go errors, panics) surface as ForstUnknownFailure.

.safe()

Promise mode offers .safe(). Effect mode can move the typed failure into an Either value:

Per-call options

In Effect mode, fiber interruption cancels the HTTP request. Use Effect.timeout and Effect.retry for timeouts and retry schedules.

Runtime support

The generated client targets Node.js 20.19+ server-side use (SSR, API routes, scripts). It assumes fetch, process.env, and optional AbortSignal. For edge deployments, keep Forst behind a Node or Go invoke server and call it from the edge with fetch and your own thin wrapper if needed.

Built-in HTTP server

Your compiled Go binary starts a local invoke server. On Unix it defaults to a Unix domain socket at .forst/invoke.sock. On Windows, or with FORST_INVOKE_TRANSPORT=tcp, it uses loopback TCP (default port 6321). Enable with server.embedded in ftconfig.json:
Then forst build -o <dir> links a native invoke binary (requires server.embedded). It writes .forst/invoke.ready and a separate local token file. HMAC auth is on by default. Use the Forst client, sidecar, or CLI helpers for POST /invoke, because authenticated RPC needs a fresh challenge and HMAC proof. For a manual smoke test over TCP:
See Invoke security for the local transport, token storage, reserved headers, and the test-only auth escape hatch.

Environment variables

When the built-in server starts, Forst writes .forst/invoke.ready with socketPath (Unix default) or url (TCP) for tooling auto-discovery.

Host mode (Remix and similar)

Incremental migration often runs three channels in one deployment: Enable both server.embedded and bridge.hostMode in ftconfig.json. Remix loaders call generated client functions over :6321; Forst main calls legacy JS over the nodert socket. Full combined demo: examples/in/rfc/bridge-interop/remix-serve. Bridgert setup is covered in Run in host mode.

Export rules

Only public functions with no unsatisfied Providers appear in the generated client. Functions that need wired dependencies belong in Forst main startup, not in /invoke from Node.

Troubleshooting

Follow Installation § Generated TypeScript client. Fresh checkouts need postinstall because .forst/client is gitignored and npm ci deletes the node_modules link.
The binary or forst dev is not listening. Check FORST_BASE_URL and the port in ftconfig.json (server.port, default 6321 for embedded).
Set FORST_BASE_URL. Production never auto-spawns forst dev.
Check the POST /invoke body: package must match the Forst package name, and function must match the exported name exactly. List available functions with GET /functions.
Re-run npx forst generate after changing .ft files. In development, regenerate-on-save keeps @forst/gen aligned. See Dev server.
Wire providers in Go/main before the server starts. Provider-dependent functions are excluded from the generated client surface.

Caveats

Generated client invoke and the built-in HTTP server are experimental. Pin compiler versions in production.

Check contractVersion

After upgrades, verify GET /version contractVersion matches what your client expects.

Spawn is local dev only

Auto-spawn of forst dev is for local development. Production must set FORST_BASE_URL and use the built-in HTTP server or another explicit connect target.

Exported functions only

Only capitalized function names appear in discovery and the invoke registry.

Providers before expose

Wire with blocks in Go main before the server starts. See Providers § Caveats.

Dev vs production URL

Executor profile (forst dev without embedded/host mode) often listens on 6320. Runtime profile (embedded or host mode) uses server.port (default 6321). A wrong FORST_BASE_URL looks like “works in dev, fails in prod”.

Installation

@forst/cli, postinstall, and first generate.

Generate a TypeScript client

Subpaths, types, and @forst/gen layout.

Testing

Stub calls with withForstTestScope.

Effect mode

Tagged errors and generate.effect.

Dev server

forst dev HTTP contract and regenerate on save.

CLI reference

Full generate configuration table.