|
| 1 | +# Initiate (standard middleware) |
| 2 | + |
| 3 | +Serve a devframe from inside any app that can mount a catch-all route: `initDevframe(def)` returns a live instance whose `.handler` — a web-standard `(request: Request) => Promise<Response>` — carries the whole surface (the SPA, `__connection.json` discovery, the WebSocket RPC endpoint, the auth gate, and the optional MCP route) under one mount base. |
| 4 | + |
| 5 | +```ts |
| 6 | +import { initDevframe } from 'devframe/initiate' |
| 7 | +import myDevframe from './devframe' |
| 8 | + |
| 9 | +const devtools = initDevframe(myDevframe, { key: 'my-tool' }) |
| 10 | +// devtools.handler, devtools.nodeMiddleware, devtools.websocket, |
| 11 | +// devtools.ready, devtools.context, devtools.connectionMeta(), devtools.close() |
| 12 | +``` |
| 13 | + |
| 14 | +The factory is synchronous and initializes eagerly; `handler`/`nodeMiddleware` await readiness internally, so hosts never race the boot. The default base is the hosted rule — `def.basePath` or `/__<id>/`. |
| 15 | + |
| 16 | +## Mount the handler |
| 17 | + |
| 18 | +::: code-group |
| 19 | + |
| 20 | +```ts [Vite] |
| 21 | +import { initDevframe } from 'devframe/initiate' |
| 22 | +// vite.config.ts — connect-style middleware + Vite's own server for the socket |
| 23 | +import { defineConfig } from 'vite' |
| 24 | +import myDevframe from './devframe' |
| 25 | + |
| 26 | +export default defineConfig({ |
| 27 | + plugins: [{ |
| 28 | + name: 'my-tool', |
| 29 | + apply: 'serve', |
| 30 | + configureServer(server) { |
| 31 | + const devtools = initDevframe(myDevframe, { |
| 32 | + key: 'my-tool', |
| 33 | + server: server.httpServer ?? undefined, |
| 34 | + }) |
| 35 | + server.middlewares.use(devtools.nodeMiddleware) |
| 36 | + }, |
| 37 | + }], |
| 38 | +}) |
| 39 | +``` |
| 40 | + |
| 41 | +```ts [Nitro] |
| 42 | +// middleware/devtools.ts |
| 43 | +import { defineHandler } from 'h3' |
| 44 | +import { devtools } from '../devtools' |
| 45 | + |
| 46 | +export default defineHandler((event) => { |
| 47 | + const { pathname } = new URL(event.req.url) |
| 48 | + if (pathname === '/__my-tool' || pathname.startsWith('/__my-tool/')) |
| 49 | + return devtools.handler(event.req) |
| 50 | +}) |
| 51 | +``` |
| 52 | + |
| 53 | +```ts [Hono] |
| 54 | +// server.ts — the same file runs on Node and Bun |
| 55 | +import { Hono } from 'hono' |
| 56 | +import { devtools } from './devtools' |
| 57 | + |
| 58 | +const app = new Hono() |
| 59 | +app.all('/__my-tool/*', c => devtools.handler(c.req.raw, c.env)) |
| 60 | +``` |
| 61 | + |
| 62 | +```ts [Next.js] |
| 63 | +import { initDevframe } from 'devframe/initiate' |
| 64 | +// app/%5F_my-tool/[[...path]]/route.ts — Next reserves `_`-prefixed |
| 65 | +// folders, so the segment is URL-encoded (`%5F_` decodes to `__`). |
| 66 | +import myDevframe from '@/devframe' |
| 67 | + |
| 68 | +export const runtime = 'nodejs' |
| 69 | +export const dynamic = 'force-dynamic' |
| 70 | + |
| 71 | +const devtools = initDevframe(myDevframe, { key: 'my-tool' }) |
| 72 | +export const GET = devtools.handler |
| 73 | +``` |
| 74 | + |
| 75 | +```ts [Nuxt] |
| 76 | +// server/middleware/devtools.ts |
| 77 | +import { devtools } from '../devtools' |
| 78 | + |
| 79 | +export default defineEventHandler((event) => { |
| 80 | + const { pathname } = new URL(toWebRequest(event).url) |
| 81 | + if (pathname === '/__my-tool' || pathname.startsWith('/__my-tool/')) |
| 82 | + return devtools.handler(toWebRequest(event)) |
| 83 | +}) |
| 84 | +``` |
| 85 | + |
| 86 | +```ts [SvelteKit] |
| 87 | +// src/routes/%5F_my-tool/[...path]/+server.ts |
| 88 | +import myDevframe from '$lib/devframe' |
| 89 | +import { initDevframe } from 'devframe/initiate' |
| 90 | + |
| 91 | +const devtools = initDevframe(myDevframe, { key: 'my-tool' }) |
| 92 | +export const GET = ({ request }) => devtools.handler(request) |
| 93 | +``` |
| 94 | + |
| 95 | +::: |
| 96 | + |
| 97 | +For frameworks with dev-time module reloading (Next, Nitro, SvelteKit), always set `key` — a re-evaluation returns the live instance instead of leaking WebSocket servers (`DF0053` reports an intentional replacement when the options changed). |
| 98 | + |
| 99 | +## The WebSocket binding |
| 100 | + |
| 101 | +Fetch handlers hand over `Request`s, so the RPC socket needs its own binding. The instance resolves it in precedence order and advertises the result in `__connection.json` — the browser client follows whatever is advertised: |
| 102 | + |
| 103 | +1. **`ws.port`** — an explicit side-car port. |
| 104 | +2. **`server`** — share the host's `node:http` server; the upgrade binds at `<base>__ws`. Zero extra ports, and the socket follows the app through proxies and HTTPS. |
| 105 | +3. **`ws.url` alone** — advertise an external endpoint verbatim; the server behind that URL owns the transport (wire the instance's `context` into your own server with `startHttpAndWs`). Combined with `server`/`ws.port`, `ws.url` overrides only the advertisement — the tunnel pattern. |
| 106 | +4. **Bun** — same-origin fetch upgrades: pass the `Bun.serve` server as `handler`'s second argument and wire `Bun.serve({ websocket: devtools.websocket })`. |
| 107 | +5. **Default** — an eager side-car on a free port, started at init so the meta is stable from the first request. |
| 108 | + |
| 109 | +## Auth |
| 110 | + |
| 111 | +The instance **gates by default** — a handler mounted inside an app server is reachable by anything that can open its socket. Devframe's interactive OTP handler is wired automatically and prints its code/magic-link banner once the public origin is known (derived from the first request, or the `origin` option). Pass `auth: false` for a single-user localhost setup, or a `DevframeAuthHandler` for a custom scheme. |
| 112 | + |
| 113 | +## Relation to the other adapters |
| 114 | + |
| 115 | +`createDevServer`, `viteDevBridge`, and `@devframes/next` are assembled from this instance internally — the handler is the one wiring underneath every serving path. To host **many** devframes behind one namespace with shared transport and docks, use the hub's counterpart: [`initHub`](../guide/hub-initiate). |
0 commit comments