Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 26 additions & 39 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,44 @@ function listErrorCodes(prefix: string): string[] {
function guideGroups(prefix: string) {
return [
{
text: 'Fundamentals',
text: 'Introduction',
items: [
{ text: 'Introduction', link: `${prefix}/guide/` },
],
},
{
text: 'Define your tool',
items: [
{ text: 'Devframe Definition', link: `${prefix}/guide/devframe-definition` },
{ text: 'RPC', link: `${prefix}/guide/rpc` },
{ text: 'Shared State', link: `${prefix}/guide/shared-state` },
{ text: 'Client Assets', link: `${prefix}/guide/client-assets` },
{ text: 'Structured Diagnostics', link: `${prefix}/guide/diagnostics` },
{ text: 'Agent-Native', link: `${prefix}/guide/agent-native` },
{ text: 'JSON-Render', link: `${prefix}/guide/json-render` },
{ text: 'Streaming', link: `${prefix}/guide/streaming` },
{ text: 'Client Assets', link: `${prefix}/guide/client-assets` },
{ text: 'Scoped Context', link: `${prefix}/guide/scoped-context` },
{ text: 'Standalone CLI', link: `${prefix}/guide/standalone-cli` },
{ text: 'JSON-Render', link: `${prefix}/guide/json-render` },
{ text: 'Structured Diagnostics', link: `${prefix}/guide/diagnostics` },
{ text: 'When Clauses', link: `${prefix}/guide/when-clauses` },
],
},
{
text: 'Client & Security',
text: 'Mount anywhere',
items: [
{ text: 'The Standard Handler', link: `${prefix}/adapters/initiate` },
{ text: 'Adapters', link: `${prefix}/adapters/` },
{ text: 'Standalone CLI', link: `${prefix}/guide/standalone-cli` },
{ text: 'Client', link: `${prefix}/guide/client` },
{ text: 'Transports', link: `${prefix}/guide/transports` },
{ text: 'Security', link: `${prefix}/guide/security` },
],
},
{
text: 'Hub',
text: 'Agentic',
items: [
{ text: 'Agent-Native', link: `${prefix}/guide/agent-native` },
],
},
{
text: 'Compose a hub',
items: [
{ text: 'Hub', link: `${prefix}/guide/hub` },
{ text: 'Client Scripts & Context', link: `${prefix}/guide/client-context` },
Expand All @@ -54,17 +67,16 @@ function guideGroups(prefix: string) {
],
},
{
text: 'Customization',
text: 'Customize the UI',
items: [
{ text: 'Build Your Own JSON-Render Frontend', link: `${prefix}/guide/build-your-own-json-render-frontend` },
{ text: 'Build Your Own Hub UI', link: `${prefix}/guide/build-your-own-hub-ui` },
],
},
{
text: 'References',
text: 'Ecosystem',
items: [
{ text: 'When Clauses', link: `${prefix}/guide/when-clauses` },
{ text: 'Examples', link: `${prefix}/examples/` },
{ text: 'Built with Devframe', link: `${prefix}/guide/built-with` },
],
},
] satisfies { text: string, items: DefaultTheme.NavItemWithLink[] }[]
Expand All @@ -73,9 +85,9 @@ function guideGroups(prefix: string) {
function adaptersItems(prefix: string) {
return [
{ text: 'Overview', link: `${prefix}/adapters/` },
{ text: 'Initiate (middleware)', link: `${prefix}/adapters/initiate` },
{ text: 'Dev', link: `${prefix}/adapters/dev` },
{ text: 'The Standard Handler', link: `${prefix}/adapters/initiate` },
{ text: 'CLI', link: `${prefix}/adapters/cac` },
{ text: 'Dev', link: `${prefix}/adapters/dev` },
{ text: 'Build', link: `${prefix}/adapters/build` },
{ text: 'Vite DevTools', link: `${prefix}/adapters/vite` },
{ text: 'Embedded', link: `${prefix}/adapters/embedded` },
Expand Down Expand Up @@ -115,27 +127,6 @@ function pluginsItems(prefix: string) {
] satisfies DefaultTheme.NavItemWithLink[]
}

function examplesItems(prefix: string) {
return [
{ text: 'Overview', link: `${prefix}/examples/` },
{ text: 'Built with Devframe', link: `${prefix}/examples/built-with` },
{ text: 'files-inspector', link: `${prefix}/examples/files-inspector` },
{ text: 'json-render', link: `${prefix}/examples/json-render` },
{ text: 'streaming-chat', link: `${prefix}/examples/streaming-chat` },
{ text: 'next-runtime-snapshot', link: `${prefix}/examples/next-runtime-snapshot` },
{ text: 'hub-vite', link: `${prefix}/examples/hub-vite` },
{ text: 'hub-next', link: `${prefix}/examples/hub-next` },
{ text: 'hub-vite-minimal', link: `${prefix}/examples/hub-vite-minimal` },
{ text: 'hub-next-minimal', link: `${prefix}/examples/hub-next-minimal` },
{ text: 'hub-nitro-minimal', link: `${prefix}/examples/hub-nitro-minimal` },
{ text: 'hub-hono-minimal', link: `${prefix}/examples/hub-hono-minimal` },
{ text: 'hub-fastify-minimal', link: `${prefix}/examples/hub-fastify-minimal` },
{ text: 'hub-sveltekit-minimal', link: `${prefix}/examples/hub-sveltekit-minimal` },
{ text: 'hub-deno-minimal', link: `${prefix}/examples/hub-deno-minimal` },
{ text: 'hub-rsbuild-minimal', link: `${prefix}/examples/hub-rsbuild-minimal` },
] satisfies DefaultTheme.NavItemWithLink[]
}

export function devframeSidebar(prefix = ''): DefaultTheme.SidebarItem[] {
return [
{
Expand All @@ -159,10 +150,6 @@ export function devframeSidebar(prefix = ''): DefaultTheme.SidebarItem[] {
text: 'Plugins',
items: pluginsItems(prefix),
},
{
text: 'Examples',
items: examplesItems(prefix),
},
]
}

Expand Down
18 changes: 8 additions & 10 deletions docs/adapters/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ outline: deep

# Build

Produces a self-contained static deploy of a devframe:
Produces a static deploy:

1. Copies the author's SPA dist (`clientAssets` or `options.distDir`) into `<outDir>`.
1. Copies the SPA dist into `<outDir>`.
2. Runs `setup(ctx)` with `mode: 'build'`.
3. Collects RPC dumps for every `'static'` function and any `'query'` function with `dump.inputs` / `snapshot: true`.
4. Writes `<outDir>/__connection.json` (`{ backend: 'static' }`) and sharded dump files under `<outDir>/__rpc-dump/` — both at the SPA root so the deployed client discovers them via relative paths from `document.baseURI`.
3. Collects RPC dumps for every `'static'` and `'query'` with `dump.inputs` / `snapshot: true`.
4. Writes `__connection.json` (`{ backend: 'static' }`) and sharded dumps under `__rpc-dump/`.

```ts
import { createBuild } from 'devframe/adapters/build'
Expand All @@ -22,10 +22,8 @@ await createBuild(devframe, {

| Option | Default | Description |
|--------|---------|-------------|
| `outDir` | `dist-static` | Output directory. Cleared on each build. |
| `distDir` | `def.clientAssets` (falls back to deprecated `def.cli?.distDir`) | Override the SPA dist directory (a local path or a [remote assets](/guide/client-assets) package, materialized in full at build time). |
| `pretty` | `false` | Pretty-print dump JSON (larger on disk). |
| `outDir` | `dist-static` | Output directory (cleared). |
| `distDir` | `def.clientAssets` | SPA dist override (or [remote assets](/guide/client-assets)). |
| `pretty` | `false` | Pretty-print dump JSON. |

The resulting directory hosts on any static web server (`serve`, nginx, GitHub Pages, …). The client auto-detects `static` mode by resolving `./__connection.json` against `document.baseURI` and runs in read-only form.

`createBuild` copies the SPA verbatim, so deploying under a custom URL base just means building the SPA with relative asset paths (`vite.base: './'`) — the client discovers the effective base at runtime.
The client runs read-only. For a custom URL base, build with relative asset paths (`vite.base: './'`).
38 changes: 18 additions & 20 deletions docs/adapters/cac.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ outline: deep

# CLI (cac)

The cac adapter wraps a `DevframeDefinition` in a [`cac`](https://github.com/cacjs/cac)-powered command-line interface. From one entry it spins up an `h3` dev server with WebSocket RPC, builds static snapshots, or starts an MCP server.
Wraps a `DevframeDefinition` in a [`cac`](https://github.com/cacjs/cac)-powered CLI with `dev`, `build`, and `mcp` commands.

`cac` is an optional peer dependency, pulled in only through this adapter — install it alongside `devframe` to opt into `createCac`:
`cac` is an optional peer of this adapter:

```sh
npm install devframe cac
```

Tools that assemble their own command-line shell from the [lower-level factories](#use-your-own-cli-framework) never import this adapter, so they run without `cac`.
Tools using the [lower-level factories](#use-your-own-cli-framework) need no `cac`.

```ts
import { defineDevframe } from 'devframe'
Expand All @@ -28,7 +28,7 @@ const devframe = defineDevframe({
await createCac(devframe).parse()
```

Running the resulting binary:
Running the binary:

```sh
my-devframe # dev server at http://localhost:9999/
Expand All @@ -38,17 +38,17 @@ my-devframe build --out-dir dist-static --base /devframe/
my-devframe mcp # stdio MCP server
```

Standalone CLI serves the SPA at `/` by default. The `/__devframe/` prefix is for *hosted* adapters where devframe mounts alongside an existing app — see [Mount paths](./#mount-paths).
The SPA serves at `/` standalone, `/__devframe/` when hosted ([Mount paths](./#mount-paths)).

## Options

`createCac(def, options?)` accepts:
`createCac(def, options?)`:

| Option | Default | Description |
|--------|---------|-------------|
| `defaultPort` | `9999` (or `def.cli?.port`) | Port used by the dev command when `--port` isn't provided. |
| `configureCli` | — | `(cli: CAC) => void` — final hook to add commands/flags at the assembly stage, after the definition's `cli.configure` runs. |
| `onReady` | — | `(info: { origin, port, app }) => void \| Promise<void>` — called once the dev server is listening. Use this to print your own startup banner. |
| `defaultPort` | `9999` (or `def.cli?.port`) | Dev port if `--port` unset. |
| `configureCli` | — | `(cli: CAC) => void` — add commands/flags post-`cli.configure`. |
| `onReady` | — | `(info: { origin, port, app }) => void \| Promise<void>` — once listening. |

`createCac` returns a `CacHandle`:

Expand All @@ -59,7 +59,7 @@ interface CacHandle {
}
```

The `cli` property lets the caller add ad-hoc commands and flags right before `parse()` when a `configureCli` callback is inconvenient.
Add commands/flags via `cli` before `parse()`.

## Definition-level `cli` fields

Expand Down Expand Up @@ -87,11 +87,11 @@ defineDevframe({
})
```

The top-level [`clientAssets`](/guide/client-assets) supplies the SPA the dev/build commands serve; everything under `cli` has sensible defaults. The `configure` hook runs *before* the `configureCli` option passed to `createCac`, so the final tool author always has the last word on flags.
`configure` runs *before* `createCac`'s `configureCli`.

## Headless logging

Devframe leaves startup output to the application. Wire `onReady` to print your own banner:
Wire `onReady` to print a banner:

```ts
await createCac(devframe, {
Expand All @@ -101,17 +101,15 @@ await createCac(devframe, {
}).parse()
```

Structured diagnostics (via `nostics`) continue to surface through their normal reporters.

## Use your own CLI framework

To integrate devframe into an existing commander / yargs program or to expose a different command structure than `createCac`'s `dev` / `build` / `mcp` triplet — drop down to the peer factories. Same `DevframeDefinition`, different shell:
Drop to the peer factories for a commander/yargs program or other structure:

| Building block | Entry | Purpose |
|----------------|-------|---------|
| [`createDevServer(def, opts?)`](./dev) | `devframe/adapters/dev` | h3 + WebSocket RPC + SPA mount |
| [`createBuild(def, opts?)`](./build) | `devframe/adapters/build` | Static deploy |
| [`createMcpServer(def, opts?)`](./mcp) | `devframe/adapters/mcp` | stdio MCP server |
| `parseCliFlags(schema, raw)` | `devframe/adapters/cac` | Validate a flag bag against a `CliFlagsSchema` |
| [`createDevServer()`](./dev) | `devframe/adapters/dev` | h3 + WebSocket RPC + SPA mount |
| [`createBuild()`](./build) | `devframe/adapters/build` | Static deploy |
| [`createMcpServer()`](./mcp) | `devframe/adapters/mcp` | stdio MCP server |
| `parseCliFlags(schema, raw)` | `devframe/adapters/cac` | Validate flags (`CliFlagsSchema`) |

See the [Standalone CLI guide](/guide/standalone-cli#use-your-own-cli-framework) for a worked commander example.
See the [Standalone CLI guide](/guide/standalone-cli#use-your-own-cli-framework).
49 changes: 18 additions & 31 deletions docs/adapters/dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ outline: deep

# Dev

The `dev` adapter is the building block `createCac` uses internally — h3 + WebSocket RPC + the author's SPA mounted at the resolved base path. Reach for it directly to mount the dev server inside an existing CLI program (commander, yargs, hand-rolled CAC) or to attach custom middleware to the underlying h3 app.
`createCac`'s building block: h3 + WebSocket RPC + the SPA at the resolved base path. Use it in a custom CLI or with middleware.

```ts
import { createDevServer } from 'devframe/adapters/dev'
Expand All @@ -19,48 +19,35 @@ const handle = await createDevServer(devframe, {
process.on('SIGINT', () => handle.close().then(() => process.exit(0)))
```

`createDevServer` returns the underlying `StartedServer` (origin, port, h3 app, WS server, RPC group, `close()`) so callers can integrate it into their own process lifecycle.
Returns the `StartedServer` (origin, port, h3 app, WS server, RPC group, `close()`).

| Option | Default | Description |
|--------|---------|-------------|
| `host` | `def.cli?.host ?? 'localhost'` | Bind host. |
| `port` | resolved via `resolveDevServerPort` | Port to listen on. |
| `flags` | `{}` | Parsed flag bag forwarded to `setup(ctx, { flags })`. |
| `distDir` | `def.clientAssets` (falls back to deprecated `def.cli?.distDir`) | SPA dist override. When unset the server runs in bridge mode (meta + WS only). |
| `basePath` | `resolveBasePath(def, 'standalone')` | Mount path override. |
| `app` | fresh h3 app | Pre-configured h3 app to mount onto (custom middleware, auth, extra static assets). |
| `openBrowser` | resolves from `flags.open` / `def.cli?.open` | Explicit on/off override. `false` disables; a string opens that relative path. |
| `ws` | `def.cli?.ws` | How the browser reaches the RPC WebSocket — see below. |
| `onReady` | — | Callback when the WS server is bound. |
| `port` | resolved via `resolveDevServerPort` | Listen port. |
| `flags` | `{}` | Passed to `setup(ctx, { flags })`. |
| `distDir` | `def.clientAssets` (falls back to deprecated `def.cli?.distDir`) | SPA dist; unset = bridge mode. |
| `basePath` | `resolveBasePath(def, 'standalone')` | Mount override. |
| `app` | fresh h3 app | Mount onto. |
| `openBrowser` | resolves from `flags.open` / `def.cli?.open` | `false` off; string opens a path. |
| `ws` | `def.cli?.ws` | RPC WebSocket — see below. |
| `onReady` | — | WS-bind callback. |

## WebSocket endpoint

By default the RPC socket shares the HTTP server's port and binds to the `__ws` route next to `__connection.json`. The descriptor advertises a *relative* path, so the client connects to its own origin — the link follows the page through a reverse proxy that rewrites the domain, port, or subpath. Configure the three connection scenarios via `def.cli.ws` (or the `ws` call-site option):

```ts
defineDevframe({
// 1. Same server, a custom route (default route is `__ws`):
cli: { ws: { route: '__sockets' } },

// 2. A dedicated port on the same host:
cli: { ws: { port: 9788 } },

// 3. A remote, fully-qualified endpoint (e.g. a tunnel/relay):
cli: { ws: { url: 'wss://devtools.example.com/relay/__ws' } },
})
```
The RPC socket shares the HTTP port on `__ws`, advertised *relative* so the client dials its own origin through a reverse proxy. Configure `def.cli.ws`:

| Field | Scenario | Advertised `websocket` |
|-------|----------|------------------------|
| `route` | same server, different route | `{ path: <route> }` (same origin) |
| `port` | different port | `{ port, path: <route> }` (page host) |
| `url` | remote, different origin | the URL string, used verbatim |
| `route` | same server, other route | `{ path: <route> }` |
| `port` | different port | `{ port, path: <route> }` |
| `url` | remote origin | URL verbatim |

Precedence is `url` > `port` > `route`. In the remote case the dev server still hosts the socket locally on `route`; point your tunnel at it.
Precedence `url` > `port` > `route`; for `url` the socket stays local on `route`point your tunnel there.

## Port resolution

`resolveDevServerPort(def, opts?)` resolves a port up-front (to print or log it) before the server starts:
`resolveDevServerPort(def, opts?)` resolves a port before start:

```ts
import { resolveDevServerPort } from 'devframe/adapters/dev'
Expand All @@ -71,5 +58,5 @@ const port = await resolveDevServerPort(devframe, { host: '127.0.0.1' })

| Option | Default | Description |
|--------|---------|-------------|
| `host` | `def.cli?.host ?? 'localhost'` | Bind host (passed to `get-port-please` for in-use detection). |
| `defaultPort` | `def.cli?.port ?? 9999` | Override the preferred port. |
| `host` | `def.cli?.host ?? 'localhost'` | Bind host (`get-port-please` detection). |
| `defaultPort` | `def.cli?.port ?? 9999` | Preferred-port override. |
6 changes: 2 additions & 4 deletions docs/adapters/embedded.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ outline: deep

# Embedded

Register a devframe into an already-running context at runtime. Mirrors the [`vite`](./vite) adapter's plugin-scan, but for callers that need dynamic, post-startup registration. The host decides the mount path; `embedded` is a hosted adapter and inherits the `/__<id>/` default when one is needed.
Register a devframe into an already-running context at runtime — dynamic, post-startup registration (unlike [`vite`](./vite)'s plugin-scan). Inherits the hosted `/__<id>/` default.

```ts
import { createEmbedded } from 'devframe/adapters/embedded'
Expand All @@ -15,6 +15,4 @@ await createEmbedded(devframe, { ctx: existingCtx })

| Option | Required | Description |
|--------|----------|-------------|
| `ctx` | ✓ | Target `DevframeNodeContext` the devframe is registered into. |

Useful when a host loads devframes based on runtime conditions (feature flags, user opt-in, dynamic discovery) rather than static config.
| `ctx` | ✓ | Target `DevframeNodeContext` to register into. |
Loading
Loading