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
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 tiers resolve by precedence: ws.url (external, advertise-only)
> ws.port (explicit side-car) > server (shared upgrade at <base>__ws) >
Bun fetch-upgrade > eager auto side-car. A key option memoizes the
handler on globalThis so HMR module re-evaluation can't leak side-cars.
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.
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):
# DF0052: Conflicting WebSocket Bindings on createHandler
6
+
7
+
## Message
8
+
9
+
> createHandler("`{id}`") received \`ws.url\` alongside \`server\`/\`ws.port\` — the external URL wins and no local WebSocket transport is started.
10
+
11
+
## Cause
12
+
13
+
`createHandler` resolves its WebSocket tier in precedence order — `ws.url` (advertise an external endpoint verbatim) > `ws.port` (explicit side-car port) > `server` (shared upgrade on the host's HTTP server) > the eager auto side-car. Passing `ws.url` together with `server` or `ws.port` is contradictory: the external URL is advertised, and the other bindings are ignored — the handler starts no transport of its own in that tier.
14
+
15
+
## Example
16
+
17
+
```ts
18
+
import { createHandler } from'devframe/handler'
19
+
20
+
// ✗ Bad — the server is never used for devframe's socket:
Pass exactly one WebSocket binding: `ws.url` when a server you run yourself owns the RPC endpoint (wire the handler's `context` into it via `startHttpAndWs`), `ws.port` for an explicit side-car port, or `server` to share the host HTTP server's port. Drop the extras.
34
+
35
+
## Source
36
+
37
+
-[`packages/devframe/src/adapters/handler.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/adapters/handler.ts) — `createHandler`'s WebSocket tier resolution warns this when `ws.url` shadows another binding.
> createHandler("`{id}`") replaced the live handler memoized under key "`{key}`": its options changed since the previous call.
10
+
11
+
## Cause
12
+
13
+
`createHandler` was called with a `key` that already maps to a live handler, but the option fingerprint differs from the memoized instance's. Dev servers that re-evaluate modules on the fly (Next.js, Nitro, SvelteKit HMR) re-run `createHandler` 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 handlers distinct keys.
30
+
31
+
## Source
32
+
33
+
-[`packages/devframe/src/adapters/handler.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/adapters/handler.ts) — `createHandler` warns this before closing and replacing a memoized instance whose options fingerprint changed.
> connectionMeta() was called before createHandler("`{id}`") finished initializing.
10
+
11
+
## Cause
12
+
13
+
`createHandler` 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 { createHandler } from'devframe/handler'
19
+
20
+
const handler =createHandler(def)
21
+
handler.connectionMeta() // ✗ throws DF0054 — init is still in flight
Await `handler.ready` (or any `handler.fetch` call — it awaits readiness internally) before reading `connectionMeta()`.
30
+
31
+
## Source
32
+
33
+
-[`packages/devframe/src/adapters/handler.ts`](https://github.com/devframes/devframe/blob/main/packages/devframe/src/adapters/handler.ts) — `createHandler`'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