Skip to content

Commit 842fde0

Browse files
authored
feat: add Fastify, SvelteKit, and Deno hub examples (#262)
1 parent 02d5c42 commit 842fde0

37 files changed

Lines changed: 1373 additions & 23 deletions

alias.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const alias = {
1111
'devframe/rpc/transports/sse-client': r('devframe/src/rpc/transports/sse-client.ts'),
1212
'devframe/rpc/transports/sse-server': r('devframe/src/rpc/transports/sse-server.ts'),
1313
'devframe/rpc/transports/ws-bun': r('devframe/src/rpc/transports/ws-bun.ts'),
14+
'devframe/rpc/transports/ws-deno': r('devframe/src/rpc/transports/ws-deno.ts'),
1415
'devframe/rpc/transports/ws-server': r('devframe/src/rpc/transports/ws-server.ts'),
1516
'devframe/rpc/transports/ws-client': r('devframe/src/rpc/transports/ws-client.ts'),
1617
'devframe/rpc/client': r('devframe/src/rpc/client.ts'),

docs/.vitepress/config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ function examplesItems(prefix: string) {
129129
{ text: 'hub-next-minimal', link: `${prefix}/examples/hub-next-minimal` },
130130
{ text: 'hub-nitro-minimal', link: `${prefix}/examples/hub-nitro-minimal` },
131131
{ text: 'hub-hono-minimal', link: `${prefix}/examples/hub-hono-minimal` },
132+
{ text: 'hub-fastify-minimal', link: `${prefix}/examples/hub-fastify-minimal` },
133+
{ text: 'hub-sveltekit-minimal', link: `${prefix}/examples/hub-sveltekit-minimal` },
134+
{ text: 'hub-deno-minimal', link: `${prefix}/examples/hub-deno-minimal` },
132135
{ text: 'hub-rsbuild-minimal', link: `${prefix}/examples/hub-rsbuild-minimal` },
133136
] satisfies DefaultTheme.NavItemWithLink[]
134137
}

docs/examples/hub-deno-minimal.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# hub-deno-minimal
6+
7+
The minimal [Deno](https://deno.com) host for [`@devframes/hub`](/guide/hub): one `initHub()` call served through `Deno.serve`, the UI supplied by `@devframes/hub-ui`.
8+
9+
Package: `hub-deno-minimal` · framework: **Deno**
10+
11+
## What it shows
12+
13+
- `initHub({ base, devframes, ui: createUi() })` in `src/hub.ts`, memoized on `globalThis`. No transport option, so the entry wires the socket itself.
14+
- `Deno.serve(options, handler)` serves HTTP (web `Request``Response`), and the whole namespace flows through `hub.handler(request)`.
15+
- WebSockets arrive as fetch upgrades, so `src/server.ts` binds Deno's transport with `createContextRpcServer` + `attachDenoWsTransport` (crossws' Deno adapter) and answers `${hub.base}__ws` on the app's own origin. crossws attaches the socket to the `Response` its `handleUpgrade` returns, so there is no separate `websocket` handler object.
16+
17+
## Run it
18+
19+
```sh
20+
pnpm install
21+
pnpm --filter hub-deno-minimal dev
22+
```
23+
24+
## Source
25+
26+
[`examples/hub-deno-minimal`](https://github.com/devframes/devframe/tree/main/examples/hub-deno-minimal)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# hub-fastify-minimal
6+
7+
The minimal [Fastify](https://fastify.dev) host for [`@devframes/hub`](/guide/hub): one `initHub()` call mounted through Fastify's connect-middleware layer, the UI supplied by `@devframes/hub-ui`.
8+
9+
Package: `hub-fastify-minimal` · framework: **Fastify**
10+
11+
## What it shows
12+
13+
- `initHub({ base, devframes, ui: createUi() })` in `src/hub.ts`, memoized on `globalThis`. No transport option, so the socket rides Fastify's own server.
14+
- Fastify is the `nodeMiddleware` host: `src/server.ts` registers `hub.nodeMiddleware` — the same `(req, res, next)` shape Vite's dev server consumes — through [`@fastify/middie`](https://github.com/fastify/middie). Requests under `${hub.base}` are served by the hub; the rest fall through `next()` to Fastify's routes.
15+
- `hub.attach(fastify.server)` routes the HTTP server's upgrade events to the RPC socket at `${hub.base}__ws`, on the app's own origin — no side-car port.
16+
17+
## Run it
18+
19+
```sh
20+
pnpm install
21+
pnpm --filter hub-fastify-minimal dev
22+
```
23+
24+
## Source
25+
26+
[`examples/hub-fastify-minimal`](https://github.com/devframes/devframe/tree/main/examples/hub-fastify-minimal)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
outline: deep
3+
---
4+
5+
# hub-sveltekit-minimal
6+
7+
The minimal [SvelteKit](https://svelte.dev/docs/kit) host for [`@devframes/hub`](/guide/hub): one `initHub()` call behind a single catch-all endpoint, the UI supplied by `@devframes/hub-ui`.
8+
9+
Package: `hub-sveltekit-minimal` · framework: **SvelteKit**
10+
11+
## What it shows
12+
13+
- `initHub({ base, devframes, ui: createUi() })` in `src/hub.ts`, memoized on `globalThis`. The RPC socket runs on a side-car port (`ws: { sidecar: true }`) advertised via `__connection.json` — SvelteKit's `+server.ts` handlers hand over `Request`s and never see WebSocket upgrades, so the hub takes a socket of its own.
14+
- `src/routes/__devframes/[...path]/+server.ts` mounts the whole namespace: `fallback` answers every method with `hub.handler(event.request)`, and the `[...path]` rest param matches the namespace root as well as everything beneath it.
15+
- The endpoint exports `trailingSlash = 'ignore'` so SvelteKit serves the hub's trailing-slash URLs (the standalone viewer and each frame SPA) verbatim instead of 308-redirecting them, and `src/app.html` injects `${hub.base}embedded.js` to mount the floating dock.
16+
17+
## Run it
18+
19+
```sh
20+
pnpm install
21+
pnpm --filter hub-sveltekit-minimal dev
22+
```
23+
24+
## Source
25+
26+
[`examples/hub-sveltekit-minimal`](https://github.com/devframes/devframe/tree/main/examples/hub-sveltekit-minimal)

docs/examples/index.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ The **minimal** family instead mounts one `initHub({ ui: createUi() })` handler
2323
| [hub-next-minimal](./hub-next-minimal) | Next.js | The hub handler on an App Router catch-all route. |
2424
| [hub-nitro-minimal](./hub-nitro-minimal) | Nitro | The hub handler on a Nitro catch-all route. |
2525
| [hub-hono-minimal](./hub-hono-minimal) | Hono | The hub handler on Hono, running on Node and Bun. |
26+
| [hub-fastify-minimal](./hub-fastify-minimal) | Fastify | The hub handler on Fastify via `nodeMiddleware`. |
27+
| [hub-sveltekit-minimal](./hub-sveltekit-minimal) | SvelteKit | The hub handler on a SvelteKit catch-all endpoint. |
28+
| [hub-deno-minimal](./hub-deno-minimal) | Deno | The hub handler on `Deno.serve`, with a Deno fetch-upgrade socket. |
2629
| [hub-rsbuild-minimal](./hub-rsbuild-minimal) | Rsbuild | The hub handler on Rsbuild's dev middleware. |
2730

2831
## Run any example

docs/guide/hub-initiate.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export const hub = initHub({
2222

2323
## The shared socket
2424

25-
One transport serves the whole namespace, and the hub binds nothing on its own — the same four choices `initDevframe` offers, in the same precedence: `ws.port` pins a side-car, `server` shares the host's `node:http` upgrade at `<base>__ws`, `ws: { sidecar: true }` takes a free port (for Next.js, Nitro and Rsbuild hosts, whose handlers never see upgrades), and passing none of them leaves the socket to the host:
25+
One transport serves the whole namespace, and the hub binds nothing on its own — the same four choices `initDevframe` offers, in the same precedence: `ws.port` pins a side-car, `server` shares the host's `node:http` upgrade at `<base>__ws`, `ws: { sidecar: true }` takes a free port (for Next.js, Nitro, SvelteKit and Rsbuild hosts, whose handlers never see upgrades), and passing none of them leaves the socket to the host — a Node host hands over its server with `hub.attach(server)`, and Bun and Deno hosts complete the upgrade on their own origin with `attachBunWsTransport` / `attachDenoWsTransport`:
2626

2727
```ts
2828
import { serve } from '@hono/node-server'
@@ -125,4 +125,4 @@ Hosts that assemble `createHubContext` + `ctx.install` themselves (with their ow
125125
const hub = initHub({ base: DEVFRAMES_HUB_BASE, context: ctx })
126126
```
127127

128-
The instance then serves the hub-level endpoints and transport only; serve each frame's meta from `hub.connectionMeta()` yourself. The two reference examples — `examples/hub-vite` and `examples/hub-next` — use the declarative mode with their own hand-built viewer UIs, while the `hub-*-minimal` family (`hub-vite-minimal`, `hub-next-minimal`, `hub-nitro-minimal`, `hub-hono-minimal`, `hub-rsbuild-minimal`) shows the minimal `createUi()` mount across frameworks (the Hono one on Node and Bun).
128+
The instance then serves the hub-level endpoints and transport only; serve each frame's meta from `hub.connectionMeta()` yourself. The two reference examples — `examples/hub-vite` and `examples/hub-next` — use the declarative mode with their own hand-built viewer UIs, while the `hub-*-minimal` family (`hub-vite-minimal`, `hub-next-minimal`, `hub-nitro-minimal`, `hub-hono-minimal`, `hub-fastify-minimal`, `hub-sveltekit-minimal`, `hub-deno-minimal`, `hub-rsbuild-minimal`) shows the minimal `createUi()` mount across frameworks (the Hono one on Node and Bun, the Deno one on `Deno.serve`).
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# hub-deno-minimal
2+
3+
The minimal [Deno](https://deno.com) host for `@devframes/hub` — one `initHub()` call served through `Deno.serve`, with the RPC socket riding a same-origin fetch upgrade.
4+
5+
```sh
6+
pnpm --filter hub-deno-minimal dev
7+
```
8+
9+
Open <http://localhost:5182> — the host page carries the floating dock via one script tag — or <http://localhost:5182/__devframes/> for the standalone viewer.
10+
11+
## How it works
12+
13+
- [`src/hub.ts`](./src/hub.ts)`initHub({ devframes, ui: createUi({ branding }) })` (rebranded to Deno's own green, `#70ffaf`), memoized on `globalThis` so a dev-time reload reuses the live hub. No `ws` option is passed, so the entry wires the socket itself.
14+
- [`src/server.ts`](./src/server.ts) — Deno serves HTTP through `Deno.serve(options, handler)` (web `Request``Response`), and the whole namespace flows through `hub.handler(request)`. WebSockets arrive as fetch upgrades rather than `node:http` `upgrade` events, so the entry binds Deno's own transport to the hub context with `createContextRpcServer` + `attachDenoWsTransport` (crossws' Deno adapter) and answers `/__devframes/__ws` itself — the socket rides the app's own origin with no side-car port. crossws attaches the socket to the `Response` its `handleUpgrade` returns, so there is no separate `websocket` handler object to register.
15+
16+
The same `initHub` instance mounts identically on Vite, Hono, Nitro, Fastify, and Next.js — see the sibling examples.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "hub-deno-minimal",
3+
"type": "module",
4+
"version": "0.9.1",
5+
"private": true,
6+
"description": "Minimal Deno host that serves the devframe hub over a fetch-upgrade WebSocket.",
7+
"scripts": {
8+
"dev": "deno run --allow-net --allow-read --allow-env --node-modules-dir src/server.ts",
9+
"typecheck": "tsc --noEmit"
10+
},
11+
"dependencies": {
12+
"@devframes/hub": "workspace:*",
13+
"@devframes/hub-ui": "workspace:*",
14+
"@devframes/plugin-a11y": "workspace:*",
15+
"@devframes/plugin-assets": "workspace:*",
16+
"@devframes/plugin-code-server": "workspace:*",
17+
"@devframes/plugin-data-inspector": "workspace:*",
18+
"@devframes/plugin-git": "workspace:*",
19+
"@devframes/plugin-inspect": "workspace:*",
20+
"@devframes/plugin-messages": "workspace:*",
21+
"@devframes/plugin-og": "workspace:*",
22+
"@devframes/plugin-terminals": "workspace:*",
23+
"devframe": "workspace:*"
24+
},
25+
"devDependencies": {
26+
"@types/node": "catalog:types"
27+
}
28+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import type { HubInstance } from '@devframes/hub/initiate'
2+
import { createUi } from '@devframes/hub-ui'
3+
import { DEVFRAMES_HUB_BASE, initHub } from '@devframes/hub/initiate'
4+
import { createA11yDevframe } from '@devframes/plugin-a11y'
5+
import { createAssetsDevframe } from '@devframes/plugin-assets'
6+
import { createCodeServerDevframe } from '@devframes/plugin-code-server'
7+
import { createDataInspectorDevframe } from '@devframes/plugin-data-inspector'
8+
import { createGitDevframe } from '@devframes/plugin-git'
9+
import { createInspectDevframe } from '@devframes/plugin-inspect'
10+
import { createMessagesDevframe } from '@devframes/plugin-messages'
11+
import { createOgDevframe } from '@devframes/plugin-og'
12+
import { createTerminalsDevframe } from '@devframes/plugin-terminals'
13+
14+
// One `initHub` call, memoized on globalThis so a dev-time reload returns the
15+
// live hub instead of leaking transports. No `ws` option is passed: on Deno,
16+
// WebSockets arrive as fetch upgrades rather than `node:http` `upgrade`
17+
// events, so `src/server.ts` binds Deno's own transport to the hub context
18+
// and answers the upgrade route itself — the socket rides the app's own
19+
// origin with no side-car port.
20+
const globalRef = globalThis as { __hubDenoMinimal?: HubInstance }
21+
22+
export const hub: HubInstance = globalRef.__hubDenoMinimal ??= initHub({
23+
base: DEVFRAMES_HUB_BASE,
24+
// Every built-in plugin, dogfooded end to end through the hub mount path.
25+
// `data-inspector`'s default id carries `:` (a route-param marker), so it
26+
// gets a colon-free id override to be a valid `<base><id>/` segment; the
27+
// assets watcher is off since this host demonstrates mounting, not authoring.
28+
devframes: [
29+
createGitDevframe(),
30+
createTerminalsDevframe(),
31+
createCodeServerDevframe(),
32+
createInspectDevframe(),
33+
createDataInspectorDevframe({ id: 'devframes_plugin_data-inspector' }),
34+
createA11yDevframe(),
35+
createMessagesDevframe(),
36+
createOgDevframe(),
37+
createAssetsDevframe({ watch: false }),
38+
],
39+
// Rebrand the reference UI to Deno's own navy — one field, no CSS:
40+
// `createUi`'s `branding` option publishes `ConnectionMeta.configs.ui.branding`,
41+
// which the dock reads at connect time and feeds into `--devframe-primary`
42+
// (see `@devframes/hub-ui`'s `primary-ramp.css`).
43+
ui: createUi({ branding: { primaryColor: '#70ffaf', productName: 'Devframes on Deno' } }),
44+
// Gate with devframe's interactive OTP (the default). The hub prints a
45+
// 6-digit code + magic link on startup, and the reference UI's authorization
46+
// view exchanges it for a bearer token. See docs/guide/security.md.
47+
configure(ctx) {
48+
ctx.commands.register({
49+
id: 'example:hub-deno-minimal:ping',
50+
title: 'Deno Hub · Ping',
51+
icon: 'ph:bell-duotone',
52+
category: 'kit',
53+
handler: () => 'pong',
54+
})
55+
ctx.rpc.register({
56+
name: 'example:hub-deno-minimal:probe',
57+
type: 'query',
58+
jsonSerializable: true,
59+
handler: () => 'pong',
60+
})
61+
},
62+
})
63+
64+
/** The host page — one script tag turns any page into a devtools host. */
65+
export const hostPage = `<!doctype html>
66+
<html lang="en">
67+
<head>
68+
<meta charset="UTF-8" />
69+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
70+
<title>Deno Devframe Hub</title>
71+
</head>
72+
<body style="font-family: system-ui; padding: 2rem">
73+
<h1>Deno Devframe Hub</h1>
74+
<p>This page is the host app. The devtools ride along:</p>
75+
<ul>
76+
<li>the floating dock (bottom of this page) is <code>/__devframes/embedded.js</code></li>
77+
<li>the standalone viewer lives at <a href="/__devframes/">/__devframes/</a></li>
78+
<li>discovery: <a href="/__devframes/__index.json">__index.json</a> · <a href="/__devframes/__connection.json">__connection.json</a></li>
79+
</ul>
80+
<script type="module" src="/__devframes/embedded.js"></script>
81+
</body>
82+
</html>`

0 commit comments

Comments
 (0)