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
fix(handler): support the tunnel pattern — ws.url overrides advertisement, not binding
ws.url now controls only the advertised endpoint (a relay the browser
dials verbatim); the local binding still follows server/ws.port when
given, matching createDevServer's remote-origin scenario. Only when no
explicit binding accompanies ws.url does the handler start no transport.
Drops the now-meaningless conflict diagnostic (codes renumbered:
DF0052 = memoized handler replaced, DF0053 = connectionMeta before
ready).
Copy file name to clipboardExpand all lines: docs/errors/DF0052.md
+9-13Lines changed: 9 additions & 13 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,36 +2,32 @@
2
2
outline: deep
3
3
---
4
4
5
-
# DF0052: Conflicting WebSocket Bindings on createHandler
5
+
# DF0052: Memoized Handler Replaced
6
6
7
7
## Message
8
8
9
-
> createHandler("`{id}`") received \`ws.url\` alongside \`server\`/\`ws.port\` — the external URL wins and no local WebSocket transport is started.
9
+
> createHandler("`{id}`") replaced the live handler memoized under key "`{key}`": its options changed since the previous call.
10
10
11
11
## Cause
12
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.
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.
14
14
15
15
## Example
16
16
17
17
```ts
18
18
import { createHandler } from'devframe/handler'
19
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.
29
+
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.
34
30
35
31
## Source
36
32
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.
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.
Copy file name to clipboardExpand all lines: docs/errors/DF0053.md
+9-9Lines changed: 9 additions & 9 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,32 +2,32 @@
2
2
outline: deep
3
3
---
4
4
5
-
# DF0053: Memoized Handler Replaced
5
+
# DF0053: connectionMeta() Before Handler Ready
6
6
7
7
## Message
8
8
9
-
> createHandler("`{id}`") replaced the live handler memoized under key "`{key}`": its options changed since the previous call.
9
+
> connectionMeta() was called before createHandler("`{id}`") finished initializing.
10
10
11
11
## Cause
12
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.
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.
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.
29
+
Await `handler.ready` (or any `handler.fetch` call — it awaits readiness internally) before reading `connectionMeta()`.
30
30
31
31
## Source
32
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.
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.
fix: 'Restart the instance with the --mcp flag (or set `cli.mcp: true` on its definition) to expose its tools, then list instances again.',
114
114
},
115
115
DF0052: {
116
-
why: (p: {id: string})=>`createHandler("${p.id}") received \`ws.url\` alongside \`server\`/\`ws.port\` — the external URL wins and no local WebSocket transport is started.`,
117
-
fix: 'Pass exactly one WebSocket binding: `ws.url` (advertise an external endpoint you run yourself), `ws.port` (explicit side-car port), or `server` (share the host HTTP server). Drop the extras.',
118
-
},
119
-
DF0053: {
120
116
why: (p: {key: string,id: string})=>`createHandler("${p.id}") replaced the live handler memoized under key "${p.key}": its options changed since the previous call.`,
121
117
fix: 'A dev-time module reload re-ran createHandler with different options, so the old instance (and its side-car WebSocket server) was closed and a new one started. If this is unexpected, keep the options stable across reloads — or use distinct keys for genuinely different handlers.',
122
118
},
123
-
DF0054: {
119
+
DF0053: {
124
120
why: (p: {id: string})=>`connectionMeta() was called before createHandler("${p.id}") finished initializing.`,
125
121
fix: 'Await `handler.ready` (or any `handler.fetch` call) before reading `connectionMeta()` — the WebSocket binding it describes is only known once initialization completes.',
0 commit comments