You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* docs: plan for /__devframes/ standard middleware handlers
* refactor(rpc): extract transport-agnostic rpc core and reusable ws peer hooks
createContextRpcServer owns everything about serving RPC that is
independent of how peers connect (auth wiring, session resolver,
auto-trust shim); createWsRpcPeerHooks shapes the per-peer lifecycle for
any crossws adapter. startHttpAndWs behavior is unchanged — it now
composes the two, so other transports (fetch-upgrade runtimes) can reuse
the same wiring.
* feat(handler): add devframe/handler — framework-agnostic web-standard middleware
createHandler(def) serves a devframe's whole surface — SPA,
__connection.json discovery, WebSocket RPC, auth gate (on by default),
and the optional MCP route — through one fetch handler mountable on any
framework's catch-all route, plus a connect-style nodeMiddleware and Bun
fetch-upgrade websocket hooks.
WebSocket binding resolves by precedence: ws.port (explicit side-car) >
server (shared upgrade at <base>__ws) > ws.url alone (no local
transport; external server owns it) > Bun fetch-upgrade > eager auto
side-car. ws.url always overrides the advertised endpoint (the tunnel
pattern: bind locally, advertise the relay). A key option memoizes the
handler on globalThis so HMR module re-evaluation can't leak side-cars
(DF0053 on option changes; DF0054 for connectionMeta before ready).
BREAKING CHANGE: the WS route unifies on `__ws` (was `__devframe_ws`)
across every adapter, and the unused DEVFRAME_MOUNT_PATH /
DEVFRAME_DIRNAME constants are removed.
* feat(handler)!: rename to initDevframe from devframe/initiate, returning a DevframeInstance
The factory is named for the instance it initiates (define → init
pairing with defineDevframe), reached from the devframe/initiate
subpath, and the web-standard request handler is a property —
initDevframe(def).handler — matching the content.handler mounting
model, so future capabilities extend the instance object instead of
overloading a handler-named factory.
- subpath: devframe/handler → devframe/initiate (src/adapters/initiate.ts)
- createHandler → initDevframe; CreateHandlerOptions → InitDevframeOptions
- DevframeHandler → DevframeInstance; fetch → handler
- diagnostics/docs updated (DF0053/DF0054 wording)
By default the RPC socket shares the HTTP server's port and binds to the `__devframe_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):
38
+
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):
39
39
40
40
```ts
41
41
defineDevframe({
42
-
// 1. Same server, a custom route (default route is `__devframe_ws`):
42
+
// 1. Same server, a custom route (default route is `__ws`):
43
43
cli: { ws: { route: '__sockets' } },
44
44
45
45
// 2. A dedicated port on the same host:
46
46
cli: { ws: { port: 9788 } },
47
47
48
48
// 3. A remote, fully-qualified endpoint (e.g. a tunnel/relay):
> initDevframe("`{id}`") replaced the live instance memoized under key "`{key}`": its options changed since the previous call.
10
+
11
+
## Cause
12
+
13
+
`initDevframe` was called with a `key` that already maps to a live instance, but the option fingerprint differs from the memoized one's. Dev servers that re-evaluate modules on the fly (Next.js, Nitro, SvelteKit HMR) re-run `initDevframe` on every reload; the `key` memoization normally returns the live instance, but when the options genuinely changed the old instance — including its side-car WebSocket server — is closed and a fresh one starts.
This is informational when you edited the options on purpose — the replacement is the intended behavior. If it fires without an intentional change, make the options stable across reloads (module-level constants rather than values recomputed per evaluation), or give genuinely different instances distinct keys.
30
+
31
+
## Source
32
+
33
+
-[`packages/devframe/src/adapters/initiate.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/adapters/initiate.ts) — `initDevframe` warns this before closing and replacing a memoized instance whose options fingerprint changed.
> connectionMeta() was called before initDevframe("`{id}`") finished initializing.
10
+
11
+
## Cause
12
+
13
+
`initDevframe` is a synchronous factory that kicks off asynchronous initialization eagerly — running `def.setup`, binding the WebSocket tier, and mounting the routes. `connectionMeta()` describes the WebSocket binding, which only exists once that initialization completes; calling it earlier has nothing correct to return.
14
+
15
+
## Example
16
+
17
+
```ts
18
+
import { initDevframe } from'devframe/initiate'
19
+
20
+
const devtools =initDevframe(def)
21
+
devtools.connectionMeta() // ✗ throws DF0054 — init is still in flight
Await `instance.ready` (or any request through `instance.handler` — it awaits readiness internally) before reading `connectionMeta()`.
30
+
31
+
## Source
32
+
33
+
-[`packages/devframe/src/adapters/initiate.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/adapters/initiate.ts) — `initDevframe`'s `connectionMeta()` throws this while initialization is still pending.
Copy file name to clipboardExpand all lines: docs/guide/client.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -229,12 +229,12 @@ With caching on, `query` / `static` function responses are memoized per argument
229
229
230
230
## Discovery (`__connection.json`)
231
231
232
-
Devframe writes a JSON descriptor at `<base>/__connection.json` so the client knows where to connect. The dev server shares one port for HTTP and the WebSocket — the socket is bound to a route (`<base>__devframe_ws`) next to the meta file — and advertises it as a relative path:
232
+
Devframe writes a JSON descriptor at `<base>/__connection.json` so the client knows where to connect. The dev server shares one port for HTTP and the WebSocket — the socket is bound to a route (`<base>__ws`) next to the meta file — and advertises it as a relative path:
Copy file name to clipboardExpand all lines: docs/helpers/vite-bridge.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -21,7 +21,7 @@ export default defineConfig({
21
21
## Modes
22
22
23
23
-**Static mount** (default) — mounts `def.cli.distDir` at `options.base` (`/__<id>/` by default). No RPC server. Useful when you only need the SPA bundle served from a known path.
24
-
-**Bridge mode** (`devMiddleware: true | {…}`) — skips the static mount; the host app owns the SPA. Devframe spawns a separate RPC + WS server and registers Vite middleware at `<base>__connection.json` so the host-served SPA can discover the WS endpoint. The side-car listens on its own port, so the descriptor carries that port alongside the `/__devframe_ws` route.
24
+
-**Bridge mode** (`devMiddleware: true | {…}`) — skips the static mount; the host app owns the SPA. Devframe spawns a separate RPC + WS server and registers Vite middleware at `<base>__connection.json` so the host-served SPA can discover the WS endpoint. The side-car listens on its own port, so the descriptor carries that port alongside the `/__ws` route.
25
25
26
26
To mount the RPC socket onto the Vite server's own port instead of a side-car — so it shares the origin with the app and rides through a proxy — pass an existing HTTP server and a route to [`startHttpAndWs`](/adapters/dev) via its `server` and `path` options. Devframe routes only that upgrade path and leaves the rest (Vite's HMR socket included) untouched.
0 commit comments