-
Notifications
You must be signed in to change notification settings - Fork 10
feat(examples,docs): migrate reference hubs to initHub; Nitro & Hono examples, Bun smoke, framework guides #172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
90930f8
feat(examples,docs): migrate the reference hubs to initHub; add Nitro…
antfubot d465079
fix(hub-ui): read import.meta.url through a variable so Vite doesn't …
antfubot d54199c
feat(initiate)!: require `base` and expose it on the instance
antfubot 493307b
refactor(examples)!: rename hub examples to the hub-* prefix and add …
antfubot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,9 @@ | |
| *.tsbuildinfo | ||
| coverage | ||
| dist | ||
| .next | ||
| .nitro | ||
| .output | ||
| lib-cov | ||
| logs | ||
| node_modules | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| # Initiate (standard middleware) | ||
|
|
||
| Serve a devframe from inside any app that can mount a catch-all route: `initDevframe(def, { base })` 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. | ||
|
|
||
| ```ts | ||
| import { initDevframe } from 'devframe/initiate' | ||
| import myDevframe from './devframe' | ||
|
|
||
| const devtools = initDevframe(myDevframe, { base: '/__my-tool/', key: 'my-tool' }) | ||
| // devtools.base, devtools.handler, devtools.nodeMiddleware, devtools.websocket, | ||
| // devtools.ready, devtools.context, devtools.connectionMeta(), devtools.close() | ||
| ``` | ||
|
|
||
| `base` is required, so the mount path is explicit at the call site — pass the conventional `resolveBasePath(def, 'hosted')` (i.e. `def.basePath ?? /__<id>/`) if you don't want to pick one. The instance echoes the normalized value back as `devtools.base`, so route guards and middleware reference it instead of repeating the string. The factory is synchronous and initializes eagerly; `handler`/`nodeMiddleware` await readiness internally, so hosts never race the boot. | ||
|
|
||
| ## Mount the handler | ||
|
|
||
| ::: code-group | ||
|
|
||
| ```ts [Vite] | ||
| import { initDevframe } from 'devframe/initiate' | ||
| // vite.config.ts — connect-style middleware + Vite's own server for the socket | ||
| import { defineConfig } from 'vite' | ||
| import myDevframe from './devframe' | ||
|
|
||
| export default defineConfig({ | ||
| plugins: [{ | ||
| name: 'my-tool', | ||
| apply: 'serve', | ||
| configureServer(server) { | ||
| const devtools = initDevframe(myDevframe, { | ||
| base: '/__my-tool/', | ||
| key: 'my-tool', | ||
| server: server.httpServer ?? undefined, | ||
| }) | ||
| server.middlewares.use(devtools.nodeMiddleware) | ||
| }, | ||
| }], | ||
| }) | ||
| ``` | ||
|
|
||
| ```ts [Nitro] | ||
| // routes/__my-tool/[...path].ts — plus routes/__my-tool/index.ts (same body) | ||
| // for the namespace root, since a catch-all doesn't match its own empty path. | ||
| import { defineHandler } from 'nitro' | ||
| import { devtools } from '../../devtools' | ||
|
|
||
| export default defineHandler(event => devtools.handler(event.req)) | ||
| ``` | ||
|
|
||
| ```ts [Hono] | ||
| // server.ts — the same file runs on Node and Bun | ||
| import { Hono } from 'hono' | ||
| import { devtools } from './devtools' | ||
|
|
||
| const app = new Hono() | ||
| app.all('/__my-tool/*', c => devtools.handler(c.req.raw, c.env)) | ||
| ``` | ||
|
|
||
| ```ts [Next.js] | ||
| import { initDevframe } from 'devframe/initiate' | ||
| // app/%5F_my-tool/[[...path]]/route.ts — Next reserves `_`-prefixed | ||
| // folders, so the segment is URL-encoded (`%5F_` decodes to `__`). | ||
| import myDevframe from '@/devframe' | ||
|
|
||
| export const runtime = 'nodejs' | ||
| export const dynamic = 'force-dynamic' | ||
|
|
||
| const devtools = initDevframe(myDevframe, { base: '/__my-tool/', key: 'my-tool' }) | ||
| export const GET = devtools.handler | ||
| ``` | ||
|
|
||
| ```ts [Nuxt] | ||
| // server/middleware/devtools.ts | ||
| import { devtools } from '../devtools' | ||
|
|
||
| export default defineEventHandler((event) => { | ||
| const { pathname } = new URL(toWebRequest(event).url) | ||
| // `devtools.base` is the normalized mount base — no repeated string. | ||
| if (pathname.startsWith(devtools.base) || pathname === devtools.base.slice(0, -1)) | ||
| return devtools.handler(toWebRequest(event)) | ||
| }) | ||
| ``` | ||
|
|
||
| ```ts [SvelteKit] | ||
| // src/routes/%5F_my-tool/[...path]/+server.ts | ||
| import myDevframe from '$lib/devframe' | ||
| import { initDevframe } from 'devframe/initiate' | ||
|
|
||
| const devtools = initDevframe(myDevframe, { base: '/__my-tool/', key: 'my-tool' }) | ||
| export const GET = ({ request }) => devtools.handler(request) | ||
| ``` | ||
|
|
||
| ::: | ||
|
|
||
| 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). | ||
|
|
||
| ## The WebSocket binding | ||
|
|
||
| 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: | ||
|
|
||
| 1. **`ws.port`** — an explicit side-car port. | ||
| 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. | ||
| 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. | ||
| 4. **Bun** — same-origin fetch upgrades: pass the `Bun.serve` server as `handler`'s second argument and wire `Bun.serve({ websocket: devtools.websocket })`. | ||
| 5. **Default** — an eager side-car on a free port, started at init so the meta is stable from the first request. | ||
|
|
||
| ## Auth | ||
|
|
||
| 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. | ||
|
|
||
| ## Relation to the other adapters | ||
|
|
||
| `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). | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| --- | ||
| outline: deep | ||
| --- | ||
|
|
||
| # DF8004: Devframe Id Is Not a Mountable URL Segment | ||
|
|
||
| ## Message | ||
|
|
||
| > Devframe id "`{id}`" is not a mountable URL segment — the hub mounts each frame at `<base><id>/`. | ||
|
|
||
| ## Cause | ||
|
|
||
| `initHub` derives each frame's mount base from its id (`/__devframes/<id>/`), and that segment is routed by h3 — where `:` and `*` are route-pattern markers and `/` ends the segment. An id carrying those characters either crashes route registration or matches the wrong paths. | ||
|
|
||
| ## Example | ||
|
|
||
| ```ts | ||
| import { initHub } from '@devframes/hub/initiate' | ||
|
|
||
| initHub({ | ||
| base: '/__devframes/', | ||
| devframes: [defineDevframe({ id: 'devframes:plugin:my-tool', /* … */ })], // ✗ throws DF8004 | ||
| }) | ||
|
|
||
| // ✓ Good — route-safe id (letters, digits, `_`, `-`, `.`): | ||
| defineDevframe({ id: 'devframes_plugin_my-tool', /* … */ }) | ||
| ``` | ||
|
|
||
| ## Fix | ||
|
|
||
| Set a route-safe `id` on the definition — letters, digits, `_`, `-`, and `.` only. Plugins that accept an `id` option can be re-instantiated with a safe one; RPC function ids (the colon-namespaced `devframes:plugin:<slug>:<fn>` convention) are unaffected — this constraint applies to the devframe id alone. | ||
|
|
||
| ## Source | ||
|
|
||
| - [`packages/hub/src/node/initiate.ts`](https://github.com/devframes/devframe/blob/main/packages/hub/src/node/initiate.ts) — `initHub` throws this while mounting the `devframes` list. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| --- | ||
| outline: deep | ||
| --- | ||
|
|
||
| # hub-hono-minimal | ||
|
|
||
| The minimal [Hono](https://hono.dev) host for [`@devframes/hub`](/guide/hub): one `initHub()` call behind a catch-all route, running on **Node and Bun** from the same app file, the UI supplied by `@devframes/hub-ui`. | ||
|
|
||
| Package: `hub-hono-minimal` · framework: **Hono** | ||
|
|
||
| ## What it shows | ||
|
|
||
| - `initHub({ base, devframes: [inspect, messages], ui: createUi() })` in `src/app.ts` plus `app.all(\`${hub.base}*\`, c => hub.handler(c.req.raw, c.env))`. | ||
| - On Node (`@hono/node-server`), the RPC WebSocket runs on an eager side-car port. | ||
| - On Bun (`Bun.serve({ fetch, websocket: hub.websocket })`), WebSocket upgrades complete through `hub.handler(request, server)` on the app's own origin — no side-car. The repo's `scripts/smoke-bun.ts` exercises this path end to end. | ||
|
|
||
| ## Run it | ||
|
|
||
| ```sh | ||
| pnpm install | ||
| pnpm --filter hub-hono-minimal dev # Node | ||
| pnpm --filter hub-hono-minimal dev:bun # Bun | ||
| ``` | ||
|
|
||
| ## Source | ||
|
|
||
| [`examples/hub-hono-minimal`](https://github.com/devframes/devframe/tree/main/examples/hub-hono-minimal) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| --- | ||
| outline: deep | ||
| --- | ||
|
|
||
| # hub-next-minimal | ||
|
|
||
| The minimal [Next.js](https://nextjs.org) host for [`@devframes/hub`](/guide/hub): one `initHub()` call on an App Router catch-all route, the UI supplied by `@devframes/hub-ui`. | ||
|
|
||
| Package: `hub-next-minimal` · framework: **React (Next.js)** | ||
|
|
||
| ## What it shows | ||
|
|
||
| - `initHub({ base, devframes: [inspect, messages], ui: createUi() })` behind one route (`app/%5F_devframes/[[...path]]/route.ts`) delegating to `hub.handler(request)`. | ||
| - The plugins and `@devframes/hub-ui` load via a bundler-ignored dynamic `import()`, so Next resolves their published `dist` at runtime (their `import.meta.url` asset lookups don't survive static bundling). | ||
| - Next route handlers can't accept WebSocket upgrades, so the instance runs its eager side-car WS server, advertised through `<base>__connection.json`. | ||
|
|
||
| ## Run it | ||
|
|
||
| ```sh | ||
| pnpm install | ||
| pnpm --filter hub-next-minimal dev | ||
| ``` | ||
|
|
||
| Open the printed URL for the host page with the floating dock, or `/__devframes/` for the standalone viewer. | ||
|
|
||
| ## Source | ||
|
|
||
| [`examples/hub-next-minimal`](https://github.com/devframes/devframe/tree/main/examples/hub-next-minimal) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| --- | ||
| outline: deep | ||
| --- | ||
|
|
||
| # hub-next | ||
|
|
||
| The same hub protocol as the [Vite host](./hub-vite), hosted from a **Next.js** App Router app with a hand-built React viewer — proof that the hub is host-runtime-agnostic. | ||
|
|
||
| Package: `hub-next` · framework: **React (Next.js)** | ||
|
|
||
| ## What it proves | ||
|
|
||
| - `initHub({ base, devframes, configure })` boots the whole hub from one call; a single App Router catch-all route (`app/%5F_devframes/[[...path]]/route.ts`) delegates to `hub.handler(request)`. | ||
| - Next route handlers can't accept WebSocket upgrades, so the instance starts its eager side-car WS server, advertised through `<base>__connection.json`. | ||
| - The [JSON-render](/guide/json-render) hub integration with **registry replacement**: the React client renders the server-authored view with a small in-example React registry (rather than the Vue `@devframes/json-render-ui`) — the path a non-Vue host uses. | ||
| - [Client-only docks](/guide/client-context#client-only-docks) the page registers itself with `context.docks.register()`. | ||
|
|
||
| For the minimal counterpart — the hub UI supplied by `@devframes/hub-ui` instead of a hand-built viewer — see [hub-next-minimal](./hub-next-minimal). | ||
|
|
||
| ## Run it | ||
|
|
||
| ```sh | ||
| pnpm install | ||
| pnpm --filter hub-next dev | ||
| ``` | ||
|
|
||
| Open the printed URL to see the docks, commands, messages, and terminals the hub exposes. | ||
|
|
||
| ## Source | ||
|
|
||
| [`examples/hub-next`](https://github.com/devframes/devframe/tree/main/examples/hub-next) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| --- | ||
| outline: deep | ||
| --- | ||
|
|
||
| # hub-nitro-minimal | ||
|
|
||
| The minimal [Nitro](https://nitro.build) host for [`@devframes/hub`](/guide/hub): one `initHub()` call behind a catch-all route, the UI supplied by `@devframes/hub-ui`. | ||
|
|
||
| Package: `hub-nitro-minimal` · framework: **Nitro** | ||
|
|
||
| ## What it shows | ||
|
|
||
| - `initHub({ base, devframes: [inspect, messages], ui: createUi() })` in `hub.ts`, delegated to by a catch-all route (`routes/__devframes/[...path].ts`, plus its `index.ts` sibling for the namespace root) via `hub.handler(event.req)`. | ||
| - `nitro.config.ts` keeps the devframe packages external so their prebuilt client assets resolve from the packages themselves rather than Nitro's build output. | ||
| - The RPC WebSocket runs on an eager side-car port, advertised through `<base>__connection.json`. | ||
|
|
||
| ## Run it | ||
|
|
||
| ```sh | ||
| pnpm install | ||
| pnpm --filter hub-nitro-minimal dev | ||
| ``` | ||
|
|
||
| Open the printed URL for the host page with the floating dock, or `/__devframes/` for the standalone viewer. | ||
|
|
||
| ## Source | ||
|
|
||
| [`examples/hub-nitro-minimal`](https://github.com/devframes/devframe/tree/main/examples/hub-nitro-minimal) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another solution is to create a server route, see: https://content.comark.dev/integrations/nitro#mount-the-handler