devframe 0.9.8 → 0.9.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_shared-CCzf9Hub.d.mts +83 -0
- package/dist/_shared-U8D-xzrp.mjs +115 -0
- package/dist/adapters/build.d.mts +6 -6
- package/dist/adapters/build.mjs +33 -36
- package/dist/adapters/cac.d.mts +12 -1
- package/dist/adapters/cac.mjs +8 -8
- package/dist/adapters/dev.d.mts +17 -17
- package/dist/adapters/dev.mjs +2 -2
- package/dist/adapters/embedded.d.mts +1 -1
- package/dist/adapters/initiate.d.mts +31 -28
- package/dist/adapters/initiate.mjs +1 -1
- package/dist/adapters/mcp.d.mts +28 -14
- package/dist/adapters/mcp.mjs +76 -40
- package/dist/{agent-tool-name-EgfoFO8C.mjs → agent-tool-name-DGzeuWcO.mjs} +2 -2
- package/dist/cli/main.d.mts +1 -1
- package/dist/cli/main.mjs +41 -17
- package/dist/client/index.d.mts +36 -36
- package/dist/client/index.mjs +90 -52
- package/dist/{client-assets-C7SXgTWY.mjs → client-assets-DNdhagGm.mjs} +2 -2
- package/dist/constants.d.mts +8 -7
- package/dist/constants.mjs +7 -6
- package/dist/{context-omlma_1n.mjs → context-B29Oh0oA.mjs} +166 -93
- package/dist/{context-DftYyXaR.d.mts → context-BJiKJqyW.d.mts} +5 -5
- package/dist/{context-CLF4-8Pp.d.mts → context-CHW4BZOj.d.mts} +203 -134
- package/dist/{context-KUqAfawQ.mjs → context-jXO5vzjH.mjs} +3 -3
- package/dist/{crypto-token-XCqTSMg9.mjs → crypto-token-TVbGGFuj.mjs} +1 -1
- package/dist/{dev-CzkPCJwi.mjs → dev-CRYHx7iG.mjs} +73 -40
- package/dist/{diagnostics-BXWW3VK-.mjs → diagnostics-DZA6KQ_Z.mjs} +32 -19
- package/dist/{events-4daw0uK9.mjs → events-BV4Dj59a.mjs} +2 -2
- package/dist/{services-install-CqqQVls5.mjs → host-agent-CKqSTEX9.mjs} +15 -151
- package/dist/{host-h3-fRbF9yor.mjs → host-h3-BROI6SQ4.mjs} +2 -2
- package/dist/in-page-channel/index.d.mts +14 -14
- package/dist/in-page-channel/index.mjs +63 -54
- package/dist/{index-D3k0BIhe.d.mts → index-CNC390b_.d.mts} +4 -4
- package/dist/{index-BzyopqxV.d.mts → index-pIVt92jP.d.mts} +9 -9
- package/dist/index.d.mts +8 -8
- package/dist/index.mjs +2 -2
- package/dist/{instance-registry-BjlMQbFB.mjs → instance-registry-DCq762YJ.mjs} +9 -9
- package/dist/{instance-shell-Tcp5hGoq.d.mts → instance-shell-DZipaP9k.d.mts} +18 -18
- package/dist/{instance-shell-CpVW4zG0.mjs → instance-shell-DvYCwrDy.mjs} +223 -67
- package/dist/internal/index.d.mts +45 -26
- package/dist/internal/index.mjs +10 -10
- package/dist/node/auth.d.mts +2 -2
- package/dist/node/auth.mjs +2 -2
- package/dist/node/hub-internals.d.mts +2 -2
- package/dist/node/hub-internals.mjs +2 -2
- package/dist/node/index.d.mts +5 -5
- package/dist/node/index.mjs +2 -2
- package/dist/{nostics-SPGDgHEP.d.mts → nostics-0sKe5HIj.d.mts} +2 -2
- package/dist/{nostics-CzECRXpE.mjs → nostics-D0PvLZsn.mjs} +1 -1
- package/dist/origin-DGDH8BI9.d.mts +45 -0
- package/dist/origin-DZit9g29.mjs +80 -0
- package/dist/recipes/common-rpc-functions.d.mts +5 -5
- package/dist/recipes/common-rpc-functions.mjs +3 -3
- package/dist/recipes/interactive-auth.d.mts +6 -6
- package/dist/recipes/interactive-auth.mjs +6 -6
- package/dist/{remote-assets-DezQmPQU.mjs → remote-assets-DMFzLSoH.mjs} +27 -7
- package/dist/{remote-assets-B13_vRkJ.d.mts → remote-assets-eBvSKZMS.d.mts} +7 -7
- package/dist/{revoke-COLW0cOi.mjs → revoke-cqztiCX4.mjs} +1 -1
- package/dist/rpc/dump.d.mts +19 -12
- package/dist/rpc/dump.mjs +2 -2
- package/dist/rpc/index.d.mts +2 -2
- package/dist/rpc/index.mjs +2 -3
- package/dist/rpc/transports/sse-client.d.mts +1 -1
- package/dist/rpc/transports/sse-client.mjs +30 -14
- package/dist/rpc/transports/sse-server.d.mts +4 -4
- package/dist/rpc/transports/sse-server.mjs +7 -7
- package/dist/rpc/transports/ws-bun.d.mts +5 -5
- package/dist/rpc/transports/ws-bun.mjs +8 -2
- package/dist/rpc/transports/ws-client.d.mts +1 -1
- package/dist/rpc/transports/ws-client.mjs +2 -2
- package/dist/rpc/transports/ws-deno.d.mts +4 -4
- package/dist/rpc/transports/ws-deno.mjs +8 -2
- package/dist/rpc/transports/ws-server.d.mts +2 -1
- package/dist/rpc/transports/ws-server.mjs +232 -1
- package/dist/{rpc-core-DlHd276T.mjs → rpc-core-CvkU23bQ.mjs} +14 -2
- package/dist/{rpc-core-B3W3z9ff.d.mts → rpc-core-DsyDViQy.d.mts} +4 -4
- package/dist/serialization-DFg5sUt6.mjs +116 -0
- package/dist/services-install-Ben4yh52.mjs +154 -0
- package/dist/session-DPfj6zQG.mjs +16 -0
- package/dist/{shared-state--sduc6Xi.mjs → shared-state-CaxB1q0h.mjs} +2 -2
- package/dist/{simple-schema-DQPZrAaZ.mjs → simple-schema-Bi40l-P6.mjs} +2 -2
- package/dist/{sse-client-DwX021ST.d.mts → sse-client-BQ4jS3eO.d.mts} +4 -4
- package/dist/{state-CK9LjrnT.mjs → state-Mwt6--el.mjs} +17 -9
- package/dist/{storage-BXNVhKyR.mjs → storage-B70AXK64.mjs} +2 -2
- package/dist/types/index.d.mts +6 -6
- package/dist/{types-CMdPYhOx.d.mts → types-Dq4i156u.d.mts} +17 -17
- package/dist/utils/agent-tool-name.d.mts +2 -2
- package/dist/utils/agent-tool-name.mjs +2 -2
- package/dist/utils/colors.d.mts +1 -1
- package/dist/utils/crypto-token.d.mts +1 -1
- package/dist/utils/crypto-token.mjs +1 -1
- package/dist/utils/events.d.mts +1 -1
- package/dist/utils/nostics.d.mts +1 -1
- package/dist/utils/nostics.mjs +1 -1
- package/dist/utils/open.mjs +1 -1
- package/dist/utils/origin.d.mts +2 -0
- package/dist/utils/origin.mjs +80 -0
- package/dist/utils/remote-assets.d.mts +2 -2
- package/dist/utils/remote-assets.mjs +1 -1
- package/dist/utils/serve-static.d.mts +4 -4
- package/dist/utils/serve-static.mjs +56 -19
- package/dist/utils/shared-state.d.mts +1 -1
- package/dist/utils/shared-state.mjs +1 -1
- package/dist/utils/simple-schema.d.mts +5 -5
- package/dist/utils/simple-schema.mjs +2 -2
- package/dist/utils/streaming-channel.d.mts +1 -1
- package/dist/utils/streaming-channel.mjs +2 -2
- package/dist/utils/when.d.mts +5 -5
- package/dist/{validation-YrZH6atx.mjs → validation-CfV1s0SF.mjs} +2 -2
- package/dist/{wire-codec-0K-o5MYW.mjs → wire-codec-BHGt0sSs.mjs} +3 -3
- package/dist/{static-DV_qUSRS.mjs → write-BuLCrn9h.mjs} +153 -137
- package/dist/{ws-client-7_lZi-B9.d.mts → ws-client-Xpxt0oPA.d.mts} +3 -3
- package/dist/{ws-server-VyHlanzy.d.mts → ws-server-D2nv68Hg.d.mts} +13 -37
- package/package.json +3 -6
- package/skills/devframe/SKILL.md +5 -3
- package/dist/_shared-BM3PdYli.mjs +0 -68
- package/dist/_shared-mtXpcJIv.d.mts +0 -41
- package/dist/diagnostics-CD8nlgll.mjs +0 -39
- package/dist/serialization-BGzEwAdr.mjs +0 -77
- package/dist/ws-server-Y_AumUQH.mjs +0 -294
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import "./index-
|
|
1
|
+
import { c as DevframeNodeRpcSessionMeta, l as DevframeRpcConnection, n as WsOriginRegistry } from "./ws-server-D2nv68Hg.mjs";
|
|
2
|
+
import { Tt as DevframeAuthHandler, U as DevframeSseOptions, W as DevframeWsOptions, o as DevframeNodeContext, s as DevframeNodeRpcSession, t as ConnectionMeta, vt as DevframeRpcClientFunctions, yt as DevframeRpcServerFunctions } from "./context-CHW4BZOj.mjs";
|
|
3
|
+
import "./index-CNC390b_.mjs";
|
|
4
4
|
import { BirpcGroup } from "birpc";
|
|
5
5
|
import { NodeAdapter } from "crossws/adapters/node";
|
|
6
6
|
import { Buffer } from "node:buffer";
|
|
@@ -10,7 +10,7 @@ import { H3 } from "h3";
|
|
|
10
10
|
//#region src/node/instance-registry.d.ts
|
|
11
11
|
/**
|
|
12
12
|
* One running devframe instance, as recorded in the instance registry.
|
|
13
|
-
* Records are self-describing JSON
|
|
13
|
+
* Records are self-describing JSON, so additive fields are safe.
|
|
14
14
|
*/
|
|
15
15
|
interface DevframeInstanceRecord {
|
|
16
16
|
/** Process id of the dev server. */
|
|
@@ -58,7 +58,7 @@ interface DevframeInstanceRegistration {
|
|
|
58
58
|
* The record is written atomically to `<dir>/<pid>-<port>.json` and removed
|
|
59
59
|
* by {@link DevframeInstanceRegistration.unregister}. Records surviving a
|
|
60
60
|
* crash are pruned by readers whose liveness probe fails. Registration never
|
|
61
|
-
* throws
|
|
61
|
+
* throws; a write failure degrades to a coded warning (`DF0045`), since a
|
|
62
62
|
* dev server must not die over discovery metadata.
|
|
63
63
|
*/
|
|
64
64
|
declare function registerDevframeInstance(record: DevframeInstanceRecord, options?: {
|
|
@@ -85,7 +85,7 @@ declare function listLiveDevframeInstances(options?: {
|
|
|
85
85
|
//#endregion
|
|
86
86
|
//#region src/node/instance-shell.d.ts
|
|
87
87
|
/**
|
|
88
|
-
* The live handle for a bound HTTP + WebSocket RPC server
|
|
88
|
+
* The live handle for a bound HTTP + WebSocket RPC server: what the
|
|
89
89
|
* side-car / shared-server tiers produce and what {@link createDevServer}
|
|
90
90
|
* re-exposes through its own return contract.
|
|
91
91
|
*/
|
|
@@ -101,7 +101,7 @@ interface StartedServer {
|
|
|
101
101
|
ws?: NodeAdapter;
|
|
102
102
|
rpcGroup: BirpcGroup<DevframeRpcClientFunctions, DevframeRpcServerFunctions, false>;
|
|
103
103
|
/**
|
|
104
|
-
* The {@link ConnectionMeta} descriptor for this server
|
|
104
|
+
* The {@link ConnectionMeta} descriptor for this server, the same shape a
|
|
105
105
|
* `__connection.json` route should serve so a devframe client's
|
|
106
106
|
* `resolveWsUrl` can dial back in.
|
|
107
107
|
*/
|
|
@@ -111,15 +111,15 @@ interface StartedServer {
|
|
|
111
111
|
/**
|
|
112
112
|
* How the instance's RPC socket is bound:
|
|
113
113
|
*
|
|
114
|
-
* - `sidecar
|
|
114
|
+
* - `sidecar`: its own HTTP+WS server on a dedicated port (`ws.port` /
|
|
115
115
|
* `ws.sidecar`), advertised with that port.
|
|
116
|
-
* - `server
|
|
117
|
-
* - `external
|
|
116
|
+
* - `server`: a shared upgrade route on the host's `node:http` server.
|
|
117
|
+
* - `external`: no local transport; `ws.url` alone names a server that owns
|
|
118
118
|
* both the socket and its auth.
|
|
119
|
-
* - `unbound
|
|
119
|
+
* - `unbound`: the transport exists but nothing is bound to it yet; the host
|
|
120
120
|
* drives it through {@link InstanceShell.handleUpgrade} /
|
|
121
121
|
* {@link InstanceShell.attach}.
|
|
122
|
-
* - `disabled`
|
|
122
|
+
* - `disabled` (`ws: false`): no WebSocket at all; clients connect over the
|
|
123
123
|
* SSE endpoint instead (`backend: 'sse'`).
|
|
124
124
|
*/
|
|
125
125
|
type InstanceWsTier = 'sidecar' | 'server' | 'external' | 'unbound' | 'disabled';
|
|
@@ -154,9 +154,9 @@ interface CreateInstanceShellOptions<TContext extends DevframeNodeContext> {
|
|
|
154
154
|
auth?: boolean | DevframeAuthHandler;
|
|
155
155
|
/** Host `node:http` server to share the WS upgrade with. */
|
|
156
156
|
server?: Server;
|
|
157
|
-
/** Explicit WebSocket control
|
|
157
|
+
/** Explicit WebSocket control; see {@link DevframeWsOptions}. `false` disables the socket (SSE-only). */
|
|
158
158
|
ws?: DevframeWsOptions | false;
|
|
159
|
-
/** SSE endpoint control
|
|
159
|
+
/** SSE endpoint control, enabled by default; `false` disables, an object renames the route. */
|
|
160
160
|
sse?: boolean | DevframeSseOptions;
|
|
161
161
|
/** Bind host for a side-car WebSocket server. Default: `localhost`. */
|
|
162
162
|
host?: string;
|
|
@@ -177,7 +177,7 @@ interface CreateInstanceShellOptions<TContext extends DevframeNodeContext> {
|
|
|
177
177
|
resolveSidecarPort?: (host: string) => Promise<number>;
|
|
178
178
|
/**
|
|
179
179
|
* Publish this instance in the global registry (`~/.devframe/instances/`)
|
|
180
|
-
* once its public origin is known
|
|
180
|
+
* once its public origin is known, via a dynamic import so the registry code
|
|
181
181
|
* stays out of instances that opt out. Omit to skip registration.
|
|
182
182
|
*/
|
|
183
183
|
register?: InstanceRegisterConfig;
|
|
@@ -190,7 +190,7 @@ interface CreateInstanceShellOptions<TContext extends DevframeNodeContext> {
|
|
|
190
190
|
}
|
|
191
191
|
/**
|
|
192
192
|
* The identity a shell needs to publish itself in the global instance
|
|
193
|
-
* registry
|
|
193
|
+
* registry: the parts it can't derive on its own. The shell fills in
|
|
194
194
|
* `pid` / `origin` / `port` / `basePath` / `mcp` / `startedAt` once the
|
|
195
195
|
* origin resolves, then merges {@link InstanceRegisterConfig.overrides} last.
|
|
196
196
|
*/
|
|
@@ -240,8 +240,8 @@ declare function samePath(a: string, b: string): boolean;
|
|
|
240
240
|
* The shared machinery behind `initDevframe` and `initHub`: one mount base,
|
|
241
241
|
* one h3 app, one lazily-derived public origin (and the auth banner that waits
|
|
242
242
|
* for it), one WebSocket binding, and the fetch / connect-middleware pair that
|
|
243
|
-
* serves them. Each factory supplies only what makes it itself
|
|
244
|
-
* its routes, its diagnostics
|
|
243
|
+
* serves them. Each factory supplies only what makes it itself (its context,
|
|
244
|
+
* its routes, its diagnostics) through `init` / `mount`.
|
|
245
245
|
*
|
|
246
246
|
* Nothing here listens on a port unless a side-car was explicitly requested:
|
|
247
247
|
* the default tier leaves the socket `unbound`, so a host chains it onto its
|
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
import "./constants.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { t as getInternalContext } from "./context-
|
|
2
|
+
import { r as validateOriginCandidate } from "./origin-DZit9g29.mjs";
|
|
3
|
+
import { t as diagnostics } from "./diagnostics-DZA6KQ_Z.mjs";
|
|
4
|
+
import { t as getInternalContext } from "./context-jXO5vzjH.mjs";
|
|
5
5
|
import { createInteractiveAuth } from "./recipes/interactive-auth.mjs";
|
|
6
6
|
import { createServer } from "node:http";
|
|
7
7
|
import process from "node:process";
|
|
8
8
|
import { isIP } from "node:net";
|
|
9
9
|
import { joinURL, withLeadingSlash, withoutLeadingSlash, withoutTrailingSlash } from "ufo";
|
|
10
10
|
import { H3, defineHandler, toNodeHandler } from "h3";
|
|
11
|
-
//#region src/node/
|
|
11
|
+
//#region src/node/runtime.ts
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* Detect the current server runtime from its runtime-specific global. The
|
|
14
|
+
* WebSocket binding branches on this: Bun and Deno expose their sockets as
|
|
15
|
+
* `fetch` upgrades through `Bun.serve` / `Deno.serve` (crossws's Bun/Deno
|
|
16
|
+
* adapters), while the `node:http` upgrade event (and crossws's Node adapter,
|
|
17
|
+
* which refuses to run anywhere else) is Node-only.
|
|
18
18
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
function detectServerRuntime() {
|
|
20
|
+
const g = globalThis;
|
|
21
|
+
if (typeof g.Deno !== "undefined") return "deno";
|
|
22
|
+
if (typeof g.Bun !== "undefined") return "bun";
|
|
23
|
+
return "node";
|
|
21
24
|
}
|
|
22
25
|
//#endregion
|
|
23
26
|
//#region src/node/utils.ts
|
|
@@ -42,8 +45,17 @@ function normalizeHttpServerUrl(host, port) {
|
|
|
42
45
|
}
|
|
43
46
|
//#endregion
|
|
44
47
|
//#region src/node/instance-shell.ts
|
|
48
|
+
function websocketConnectionMeta(rpcHost, path) {
|
|
49
|
+
const jsonSerializableMethods = [];
|
|
50
|
+
for (const def of rpcHost.definitions.values()) if (def.jsonSerializable === true) jsonSerializableMethods.push(def.name);
|
|
51
|
+
return {
|
|
52
|
+
backend: "websocket",
|
|
53
|
+
websocket: { path },
|
|
54
|
+
jsonSerializableMethods
|
|
55
|
+
};
|
|
56
|
+
}
|
|
45
57
|
/**
|
|
46
|
-
* Compose an h3 + WebSocket RPC server for a devframe context
|
|
58
|
+
* Compose an h3 + WebSocket RPC server for a devframe context: the low-level
|
|
47
59
|
* "listen on a port (or share one) + attach the WS transport" binding the
|
|
48
60
|
* side-car and shared-server tiers below are built on. Owns and listens on a
|
|
49
61
|
* fresh `node:http` server unless `server` is supplied, in which case it only
|
|
@@ -97,22 +109,13 @@ async function bindHttpAndWs(options) {
|
|
|
97
109
|
const internal = getInternalContext(context);
|
|
98
110
|
const wsUrl = `ws://${formatHostForUrl(bindHost)}:${resolvedPort}${options.path ?? ""}`;
|
|
99
111
|
if (websocket) internal.setWsEndpoint({ url: wsUrl });
|
|
100
|
-
function connectionMeta() {
|
|
101
|
-
const jsonSerializableMethods = [];
|
|
102
|
-
for (const def of rpcHost.definitions.values()) if (def.jsonSerializable === true) jsonSerializableMethods.push(def.name);
|
|
103
|
-
return {
|
|
104
|
-
backend: "websocket",
|
|
105
|
-
websocket: { path: options.path },
|
|
106
|
-
jsonSerializableMethods
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
112
|
return {
|
|
110
113
|
origin,
|
|
111
114
|
port: resolvedPort,
|
|
112
115
|
app,
|
|
113
116
|
ws,
|
|
114
117
|
rpcGroup: core.rpcGroup,
|
|
115
|
-
connectionMeta,
|
|
118
|
+
connectionMeta: () => websocketConnectionMeta(rpcHost, options.path),
|
|
116
119
|
async close() {
|
|
117
120
|
await closeWs();
|
|
118
121
|
if (ownsHttpServer) await new Promise((r) => httpServer.close(() => r()));
|
|
@@ -121,6 +124,105 @@ async function bindHttpAndWs(options) {
|
|
|
121
124
|
};
|
|
122
125
|
}
|
|
123
126
|
/**
|
|
127
|
+
* Whether a request is a WebSocket upgrade aimed at `path`: the `fetch`-side
|
|
128
|
+
* equivalent of the Node transport's `upgrade`-event path filter, used by the
|
|
129
|
+
* native ({@link bindNativeHttpAndWs}) tiers to route only the RPC route to
|
|
130
|
+
* the socket and leave every other request to the h3 app.
|
|
131
|
+
*/
|
|
132
|
+
function isWsUpgradeRequest(request, path) {
|
|
133
|
+
if ((request.headers.get("upgrade") ?? "").toLowerCase() !== "websocket") return false;
|
|
134
|
+
if (!path) return true;
|
|
135
|
+
try {
|
|
136
|
+
return samePath(new URL(request.url).pathname, path);
|
|
137
|
+
} catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The Bun/Deno counterpart to {@link bindHttpAndWs}: own and listen on a
|
|
143
|
+
* native `fetch`-upgrade server (`Bun.serve` / `Deno.serve`) with crossws's
|
|
144
|
+
* Bun/Deno adapter driving the RPC socket, since crossws's Node adapter (and
|
|
145
|
+
* the `node:http` `upgrade` event it needs) refuses to run off Node. Only the
|
|
146
|
+
* owns-a-server tiers reach here; a shared foreign `node:http` server can't be
|
|
147
|
+
* re-hosted on a native runtime, so that path falls back to SSE instead.
|
|
148
|
+
*/
|
|
149
|
+
async function bindNativeHttpAndWs(runtime, options) {
|
|
150
|
+
const { context, port, core } = options;
|
|
151
|
+
const bindHost = options.host;
|
|
152
|
+
const app = new H3();
|
|
153
|
+
const rpcHost = context.rpc;
|
|
154
|
+
const wsPath = options.path;
|
|
155
|
+
const fail = async (closeWs, error) => {
|
|
156
|
+
await closeWs().catch(() => {});
|
|
157
|
+
throw diagnostics.DF0052({
|
|
158
|
+
host: bindHost,
|
|
159
|
+
port,
|
|
160
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
161
|
+
cause: error
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
let resolvedPort;
|
|
165
|
+
let closeWs;
|
|
166
|
+
let closeServer;
|
|
167
|
+
if (runtime === "bun") {
|
|
168
|
+
const { attachBunWsTransport } = await import("./rpc/transports/ws-bun.mjs");
|
|
169
|
+
const tier = await attachBunWsTransport(core, { allowedOrigins: options.allowedOrigins });
|
|
170
|
+
closeWs = tier.close;
|
|
171
|
+
const Bun = globalThis.Bun;
|
|
172
|
+
let server;
|
|
173
|
+
try {
|
|
174
|
+
server = Bun.serve({
|
|
175
|
+
port,
|
|
176
|
+
hostname: bindHost,
|
|
177
|
+
fetch: async (request, srv) => isWsUpgradeRequest(request, wsPath) ? await tier.handleUpgrade(request, srv) : await app.fetch(request),
|
|
178
|
+
websocket: tier.websocket
|
|
179
|
+
});
|
|
180
|
+
} catch (error) {
|
|
181
|
+
return await fail(closeWs, error);
|
|
182
|
+
}
|
|
183
|
+
resolvedPort = server.port;
|
|
184
|
+
closeServer = async () => {
|
|
185
|
+
await server.stop(true);
|
|
186
|
+
};
|
|
187
|
+
} else {
|
|
188
|
+
const { attachDenoWsTransport } = await import("./rpc/transports/ws-deno.mjs");
|
|
189
|
+
const tier = await attachDenoWsTransport(core, { allowedOrigins: options.allowedOrigins });
|
|
190
|
+
closeWs = tier.close;
|
|
191
|
+
const Deno = globalThis.Deno;
|
|
192
|
+
let server;
|
|
193
|
+
try {
|
|
194
|
+
server = Deno.serve({
|
|
195
|
+
port,
|
|
196
|
+
hostname: bindHost,
|
|
197
|
+
onListen: () => {}
|
|
198
|
+
}, async (request, info) => isWsUpgradeRequest(request, wsPath) ? await tier.handleUpgrade(request, info) : await app.fetch(request));
|
|
199
|
+
} catch (error) {
|
|
200
|
+
return await fail(closeWs, error);
|
|
201
|
+
}
|
|
202
|
+
resolvedPort = server.addr.port;
|
|
203
|
+
closeServer = async () => {
|
|
204
|
+
await server.shutdown();
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const origin = normalizeHttpServerUrl(bindHost, resolvedPort);
|
|
208
|
+
const internal = getInternalContext(context);
|
|
209
|
+
const wsUrl = `ws://${formatHostForUrl(bindHost)}:${resolvedPort}${options.path ?? ""}`;
|
|
210
|
+
internal.setWsEndpoint({ url: wsUrl });
|
|
211
|
+
return {
|
|
212
|
+
origin,
|
|
213
|
+
port: resolvedPort,
|
|
214
|
+
app,
|
|
215
|
+
ws: void 0,
|
|
216
|
+
rpcGroup: core.rpcGroup,
|
|
217
|
+
connectionMeta: () => websocketConnectionMeta(rpcHost, options.path),
|
|
218
|
+
async close() {
|
|
219
|
+
await closeServer();
|
|
220
|
+
await closeWs();
|
|
221
|
+
if (getInternalContext(context).wsEndpoint?.url === wsUrl) getInternalContext(context).setWsEndpoint(void 0);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
124
226
|
* Translate the public `register?: boolean | Partial<DevframeInstanceRecord>`
|
|
125
227
|
* option into a shell {@link InstanceRegisterConfig}, or `undefined` when
|
|
126
228
|
* registration is opted out. The object form supplies record overrides on top
|
|
@@ -141,8 +243,8 @@ function samePath(a, b) {
|
|
|
141
243
|
}
|
|
142
244
|
/**
|
|
143
245
|
* Copy a web `Response` from a fetch-style transport handler onto the h3
|
|
144
|
-
* event's response and return its body
|
|
145
|
-
* Returning the body (a `ReadableStream`, or `''` for an empty one
|
|
246
|
+
* event's response and return its body, mirroring the MCP route's bridge.
|
|
247
|
+
* Returning the body (a `ReadableStream`, or `''` for an empty one, since h3
|
|
146
248
|
* middleware only falls through on `undefined`) terminates the chain with
|
|
147
249
|
* the status/headers set here instead of continuing to the SPA catch-all.
|
|
148
250
|
*/
|
|
@@ -158,8 +260,8 @@ function respondWith(event, response) {
|
|
|
158
260
|
* The shared machinery behind `initDevframe` and `initHub`: one mount base,
|
|
159
261
|
* one h3 app, one lazily-derived public origin (and the auth banner that waits
|
|
160
262
|
* for it), one WebSocket binding, and the fetch / connect-middleware pair that
|
|
161
|
-
* serves them. Each factory supplies only what makes it itself
|
|
162
|
-
* its routes, its diagnostics
|
|
263
|
+
* serves them. Each factory supplies only what makes it itself (its context,
|
|
264
|
+
* its routes, its diagnostics) through `init` / `mount`.
|
|
163
265
|
*
|
|
164
266
|
* Nothing here listens on a port unless a side-car was explicitly requested:
|
|
165
267
|
* the default tier leaves the socket `unbound`, so a host chains it onto its
|
|
@@ -172,6 +274,8 @@ function createInstanceShell(options) {
|
|
|
172
274
|
const base = options.base;
|
|
173
275
|
const baseNoSlash = withoutTrailingSlash(base);
|
|
174
276
|
const app = options.app ?? new H3();
|
|
277
|
+
const runtime = detectServerRuntime();
|
|
278
|
+
const nativeRuntime = runtime !== "node";
|
|
175
279
|
const wsDisabled = options.ws === false;
|
|
176
280
|
const ws = options.ws === false ? {} : options.ws ?? {};
|
|
177
281
|
const route = withoutLeadingSlash(ws.route ?? "__ws");
|
|
@@ -186,8 +290,11 @@ function createInstanceShell(options) {
|
|
|
186
290
|
const sseRoutePath = joinURL(base, sseRoute);
|
|
187
291
|
const advertisedSsePath = options.absoluteWsPath ? sseRoutePath : sseRoute;
|
|
188
292
|
let derivedOrigin;
|
|
293
|
+
function explicitOrigin() {
|
|
294
|
+
return typeof options.origin === "function" ? options.origin() : options.origin;
|
|
295
|
+
}
|
|
189
296
|
function currentOrigin() {
|
|
190
|
-
return (
|
|
297
|
+
return explicitOrigin() || derivedOrigin;
|
|
191
298
|
}
|
|
192
299
|
let authHandler;
|
|
193
300
|
let bannerPrinted = false;
|
|
@@ -201,7 +308,7 @@ function createInstanceShell(options) {
|
|
|
201
308
|
let registerPromise;
|
|
202
309
|
/**
|
|
203
310
|
* Publish the instance in the global registry the moment both its origin
|
|
204
|
-
* and connection meta are known
|
|
311
|
+
* and connection meta are known: at init end for a pinned origin, or on
|
|
205
312
|
* the first request for a derived one. Registration never throws (the
|
|
206
313
|
* registry writer degrades to a coded warning), so failures never surface.
|
|
207
314
|
*/
|
|
@@ -210,7 +317,7 @@ function createInstanceShell(options) {
|
|
|
210
317
|
const origin = currentOrigin();
|
|
211
318
|
if (!cfg || registerPromise || !origin || !meta) return;
|
|
212
319
|
const resolvedMeta = meta;
|
|
213
|
-
registerPromise = import("./instance-registry-
|
|
320
|
+
registerPromise = import("./instance-registry-DCq762YJ.mjs").then((n) => n.t).then(({ registerDevframeInstance }) => {
|
|
214
321
|
let port = 0;
|
|
215
322
|
try {
|
|
216
323
|
const url = new URL(origin);
|
|
@@ -230,8 +337,20 @@ function createInstanceShell(options) {
|
|
|
230
337
|
});
|
|
231
338
|
}).catch(() => {});
|
|
232
339
|
}
|
|
233
|
-
|
|
234
|
-
|
|
340
|
+
/**
|
|
341
|
+
* Consider a request-derived origin candidate for the advertised public
|
|
342
|
+
* origin (which backs the OTP magic link). {@link validateOriginCandidate}
|
|
343
|
+
* adopts only a loopback host or an exact `allowedOrigins` match, so a raw
|
|
344
|
+
* inbound `Host`/URL authority never redirects the credential-bearing link.
|
|
345
|
+
* First-valid-origin wins: an invalid candidate leaves `derivedOrigin` unset
|
|
346
|
+
* (printing/registering nothing) so a later valid one can still be adopted.
|
|
347
|
+
*/
|
|
348
|
+
function noteOrigin(candidate) {
|
|
349
|
+
if (derivedOrigin === void 0 && !explicitOrigin()) {
|
|
350
|
+
const allowed = options.allowedOrigins;
|
|
351
|
+
const accepted = validateOriginCandidate(candidate, Array.isArray(allowed) ? allowed : void 0);
|
|
352
|
+
if (accepted !== void 0) derivedOrigin = accepted;
|
|
353
|
+
}
|
|
235
354
|
maybePrintBanner();
|
|
236
355
|
maybeRegister();
|
|
237
356
|
}
|
|
@@ -248,7 +367,7 @@ function createInstanceShell(options) {
|
|
|
248
367
|
/**
|
|
249
368
|
* Auth resolution: gate by default, `false` opts out, a handler object
|
|
250
369
|
* installs a custom scheme. The `external` tier has no local transport to
|
|
251
|
-
* gate
|
|
370
|
+
* gate (the server behind `ws.url` owns auth) so it resolves to nothing.
|
|
252
371
|
*/
|
|
253
372
|
function resolveAuth() {
|
|
254
373
|
if (options.auth === false) return false;
|
|
@@ -260,7 +379,7 @@ function createInstanceShell(options) {
|
|
|
260
379
|
return authHandler;
|
|
261
380
|
}
|
|
262
381
|
/**
|
|
263
|
-
* The context's RPC core (birpc group, session lifecycle, auth gate)
|
|
382
|
+
* The context's RPC core (birpc group, session lifecycle, auth gate) is
|
|
264
383
|
* one per instance, shared by every transport binding (WS and SSE), so a
|
|
265
384
|
* WS peer and an SSE session live in the same session/broadcast space.
|
|
266
385
|
* Built lazily: an `unbound` host that never wires a transport pays
|
|
@@ -270,7 +389,7 @@ function createInstanceShell(options) {
|
|
|
270
389
|
let resolvedAuth = false;
|
|
271
390
|
let corePromise;
|
|
272
391
|
function ensureCore() {
|
|
273
|
-
corePromise ??= import("./rpc-core-
|
|
392
|
+
corePromise ??= import("./rpc-core-CvkU23bQ.mjs").then((n) => n.n).then(({ createContextRpcServer }) => createContextRpcServer({
|
|
274
393
|
context: ctx,
|
|
275
394
|
auth: resolvedAuth,
|
|
276
395
|
onPeerConnect: options.onPeerConnect,
|
|
@@ -302,14 +421,17 @@ function createInstanceShell(options) {
|
|
|
302
421
|
*/
|
|
303
422
|
async function startSidecar(core) {
|
|
304
423
|
const sidecarHost = options.host ?? "localhost";
|
|
305
|
-
const start = (port) =>
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
424
|
+
const start = (port) => {
|
|
425
|
+
const bindOptions = {
|
|
426
|
+
context: ctx,
|
|
427
|
+
core,
|
|
428
|
+
host: sidecarHost,
|
|
429
|
+
port,
|
|
430
|
+
path: withLeadingSlash(route),
|
|
431
|
+
allowedOrigins: options.allowedOrigins
|
|
432
|
+
};
|
|
433
|
+
return nativeRuntime ? bindNativeHttpAndWs(runtime, bindOptions) : bindHttpAndWs(bindOptions);
|
|
434
|
+
};
|
|
313
435
|
if (ws.port != null) return await start(ws.port);
|
|
314
436
|
const { getPort } = await import("./dist-CZXfGEkd.mjs").then((n) => n.t);
|
|
315
437
|
let lastError;
|
|
@@ -326,37 +448,70 @@ function createInstanceShell(options) {
|
|
|
326
448
|
}
|
|
327
449
|
throw lastError;
|
|
328
450
|
}
|
|
451
|
+
async function startServerTier() {
|
|
452
|
+
if (nativeRuntime && !ws.url) {
|
|
453
|
+
const core = await ensureCore();
|
|
454
|
+
started = {
|
|
455
|
+
origin: currentOrigin() ?? "",
|
|
456
|
+
port: 0,
|
|
457
|
+
app,
|
|
458
|
+
ws: void 0,
|
|
459
|
+
rpcGroup: core.rpcGroup,
|
|
460
|
+
connectionMeta: () => meta ?? options.onMetaUnavailable(),
|
|
461
|
+
close: async () => {}
|
|
462
|
+
};
|
|
463
|
+
return { wsAvailable: false };
|
|
464
|
+
}
|
|
465
|
+
started = await bindHttpAndWs({
|
|
466
|
+
context: ctx,
|
|
467
|
+
core: await ensureCore(),
|
|
468
|
+
host: options.host ?? "localhost",
|
|
469
|
+
port: 0,
|
|
470
|
+
server: options.server,
|
|
471
|
+
path: routePath,
|
|
472
|
+
allowedOrigins: options.allowedOrigins,
|
|
473
|
+
destroyUnmatched: options.destroyUnmatchedUpgrades
|
|
474
|
+
});
|
|
475
|
+
return {
|
|
476
|
+
websocketMeta: { path: advertisedPath },
|
|
477
|
+
wsAvailable: !wsDisabled
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
async function resolveWebsocketBinding() {
|
|
481
|
+
if (tier === "sidecar") {
|
|
482
|
+
started = await startSidecar(await ensureCore());
|
|
483
|
+
return {
|
|
484
|
+
websocketMeta: {
|
|
485
|
+
port: started.port,
|
|
486
|
+
path: route
|
|
487
|
+
},
|
|
488
|
+
wsAvailable: !wsDisabled
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
if (tier === "server") return startServerTier();
|
|
492
|
+
if (tier === "external") return {
|
|
493
|
+
websocketMeta: ws.url,
|
|
494
|
+
wsAvailable: !wsDisabled
|
|
495
|
+
};
|
|
496
|
+
return {
|
|
497
|
+
websocketMeta: { path: advertisedPath },
|
|
498
|
+
wsAvailable: !wsDisabled
|
|
499
|
+
};
|
|
500
|
+
}
|
|
329
501
|
async function init() {
|
|
330
502
|
const result = await options.init(api);
|
|
331
503
|
ctx = result.context;
|
|
332
504
|
dispose = result.dispose;
|
|
333
505
|
resolvedAuth = tier === "external" ? false : resolveAuth();
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
websocketMeta = {
|
|
338
|
-
port: started.port,
|
|
339
|
-
path: route
|
|
340
|
-
};
|
|
341
|
-
} else if (tier === "server") {
|
|
342
|
-
started = await bindHttpAndWs({
|
|
343
|
-
context: ctx,
|
|
344
|
-
core: await ensureCore(),
|
|
345
|
-
host: options.host ?? "localhost",
|
|
346
|
-
port: 0,
|
|
347
|
-
server: options.server,
|
|
348
|
-
path: routePath,
|
|
349
|
-
allowedOrigins: options.allowedOrigins,
|
|
350
|
-
destroyUnmatched: options.destroyUnmatchedUpgrades
|
|
351
|
-
});
|
|
352
|
-
websocketMeta = { path: advertisedPath };
|
|
353
|
-
} else if (tier === "external") websocketMeta = ws.url;
|
|
354
|
-
else if (tier === "unbound") websocketMeta = { path: advertisedPath };
|
|
506
|
+
const binding = await resolveWebsocketBinding();
|
|
507
|
+
const wsAvailable = binding.wsAvailable;
|
|
508
|
+
let websocketMeta = binding.websocketMeta;
|
|
355
509
|
if (!wsDisabled && ws.url) websocketMeta = ws.url;
|
|
356
510
|
if (sseEnabled) app.use(sseRoutePath, defineHandler(async (event) => respondWith(event, await (await ensureSse()).handler(event.req))));
|
|
511
|
+
if (!wsDisabled && !wsAvailable && !sseEnabled) diagnostics.DF0075({ runtime }, { method: "warn" });
|
|
357
512
|
meta = {
|
|
358
|
-
backend: wsDisabled
|
|
359
|
-
...websocketMeta !== void 0 ? { websocket: websocketMeta } : {},
|
|
513
|
+
backend: !wsDisabled && wsAvailable ? "websocket" : sseEnabled ? "sse" : "none",
|
|
514
|
+
...wsAvailable && websocketMeta !== void 0 ? { websocket: websocketMeta } : {},
|
|
360
515
|
...sseEnabled ? { sse: { path: advertisedSsePath } } : {},
|
|
361
516
|
...result.mcp ? { mcp: result.mcp } : {}
|
|
362
517
|
};
|
|
@@ -371,7 +526,7 @@ function createInstanceShell(options) {
|
|
|
371
526
|
contextPromise.catch(() => {});
|
|
372
527
|
/**
|
|
373
528
|
* The `unbound` tier: the RPC core and its crossws adapter, bound to
|
|
374
|
-
* nothing. Built on the first `attach` / `handleUpgrade
|
|
529
|
+
* nothing. Built on the first `attach` / `handleUpgrade`; a host that
|
|
375
530
|
* never wires the socket (or whose runtime brings its own WS transport)
|
|
376
531
|
* pays nothing for it, not even the adapter's imports.
|
|
377
532
|
*/
|
|
@@ -437,6 +592,7 @@ function createInstanceShell(options) {
|
|
|
437
592
|
if (tier === "disabled") throw diagnostics.DF0057();
|
|
438
593
|
if (tier === "external") throw diagnostics.DF0056({ url: ws.url });
|
|
439
594
|
if (tier !== "unbound") throw diagnostics.DF0055({ tier });
|
|
595
|
+
if (nativeRuntime) throw diagnostics.DF0076({ runtime });
|
|
440
596
|
}
|
|
441
597
|
/**
|
|
442
598
|
* Publish the socket's absolute URL on the context, so surfaces that hand
|
|
@@ -496,4 +652,4 @@ function createInstanceShell(options) {
|
|
|
496
652
|
};
|
|
497
653
|
}
|
|
498
654
|
//#endregion
|
|
499
|
-
export {
|
|
655
|
+
export { normalizeHttpServerUrl as i, resolveInstanceRegister as n, samePath as r, createInstanceShell as t };
|