# Node.js library

Serve a local page, check its response, and stop the preview from a Node.js program.

## Install and prepare a page

Use Node.js 22.23 or later on a [supported platform](/installation/index.md#requirements). This example needs no global CLI, MCP registration, or Docker.
Create an example directory:

```sh
mkdir previewhost-library
cd previewhost-library
npm init -y
npm install previewhost
mkdir site
printf '<h1>Hello from Previewhost.</h1>\n' > site/index.html
```

## Start a preview

Save this as `preview.mjs` beside the `site` directory:

```js
import { resolve } from 'node:path';
import { createPreviewRuntime } from 'previewhost';

const runtime = await createPreviewRuntime({
  allowedRoots: [process.cwd()],
});

try {
  const started = await runtime.start({
    name: 'site',
    type: 'static',
    directory: resolve('site'),
  });
  const ready = await runtime.wait('site', started.candidate.id);
  if (ready.state !== 'ready') throw new Error(JSON.stringify(ready.error));

  const response = await fetch(ready.url);
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  console.log(await response.text());
} finally {
  await runtime.close();
}
```

Run the program from that directory:

```sh
node preview.mjs
```

The program prints `<h1>Hello from Previewhost.</h1>` and stops the preview.
`start` returns before startup finishes. `wait` follows the returned attempt ID until startup succeeds or fails.
If the request fails, the `finally` block still closes the runtime.
For a longer-lived preview, keep the runtime open until your application finishes with it.

## Run your application

The runtime accepts the same [spec types](/reference/index.md#specs) as the CLI and MCP: `static`, `command`, `attach`, and `environment`.
Direct specs require absolute source paths within `allowedRoots`.
To reuse a file, import `loadPreviewSpec` from `previewhost` and pass its result to `runtime.start`:

```js
const spec = await loadPreviewSpec('./preview.yaml');
```

File paths resolve relative to the configuration file. See [Write preview.yaml](/configuration/index.md) for commands, readiness checks, and service connections.

Native commands and managed databases require an `authorize` callback in `createPreviewRuntime`.
The callback must decide whether to allow the requested operation. Without it, these operations fail with `EXECUTION_DENIED`.
Managed databases also require `dataDirectory`, a private directory outside the source tree, and the [database prerequisites](/databases/index.md#prepare-docker).
See [runtime configuration](/reference/index.md#runtime-and-client) for callback arguments and selected inputs.

For stored secrets, select the exact reference names in `secretIds` when you create the runtime.
Stored secrets and managed databases also require an unlocked `runtime.keystore` session.
`runtime.keystore.status()` reports `new`, `locked`, or `unlocked` and attempts remembered automatic unlock on macOS.
For an existing locked keystore, collect the password through your host's private input.
Call `runtime.keystore.unlock({ password })` before startup.

Use [keystore setup](/secrets/index.md) to create storage before this workflow. Unlocking the CLI or dashboard does not unlock this runtime.
`runtime.close()` also closes its keystore session.

## Embed in a desktop host

The host creates one runtime per profile. `stateDirectory`, `dataDirectory`, and `keystoreDirectory` must use private directories outside application sources.
`stateDirectory` retains definitions and native ownership records. Startup reconciles those records without executing application commands again.
Recovery signals a process group only after its recorded process identity matches. Unresolved ownership remains visible as cleanup debt.
The host must keep source folders until cleanup succeeds.

Packaged Electron hosts supply `supervisor: { executable, module, env }`.
The executable uses Electron's Node mode, and the module points to the packaged `supervisor.js` outside ASAR.
The host supplies `ELECTRON_RUN_AS_NODE: "1"` in that supervisor environment.
Application environment variables still pass through Previewhost's restricted environment construction.

To run the repository's commands as another user, supply [`applicationProcesses`](/reference/index.md#application-processes).
The host then starts and stops their supervisor and recovers them after a crash.

`resolvePreviewFile(project)` selects `preview.yaml` or `preview.yml` and rejects ambiguous defaults.
`configureBindings` edits the retained definition without exposing concealed values.
`describe` and log reads expose redacted results. The host never needs a plaintext secret-read endpoint.

For exact-source Design verification, `prepareCandidate` holds a candidate before public promotion.
`candidateUrl` returns its verification endpoint. `promote` accepts the exact attempt and an optional settlement callback.
A rejected callback restores the old route, or clears it if no application was serving.
A successful callback commits promotion before retiring the old process.
The host owns source capture, verification, and its transaction. Previewhost owns serving and process cleanup.

The runtime retains up to eight attempts in memory. Logs expire after eviction or owner restart.
A normal shutdown joins resource cleanup. A client window closing does not independently stop an environment.

## Connect to an existing daemon

Use `connectPreviewDaemon` to control a daemon that already runs. It implements the same `PreviewApi` interface as the runtime.

```js
import { connectPreviewDaemon } from 'previewhost';

const client = connectPreviewDaemon({
  endpoint: 'http://127.0.0.1:9400',
  tokenFile: '/absolute/private-directory/token',
});

try {
  console.log(await client.list());
} finally {
  await client.close();
}
```

Replace the endpoint and token path with those of your daemon.
`client.close()` ends client requests and leaves previews active. `client.shutdown()` stops the daemon and its previews.
