devframe 0.8.2 → 0.9.0-beta.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.
Files changed (127) hide show
  1. package/dist/_shared-BPmX9fqK.d.mts +41 -0
  2. package/dist/_shared-CbZ_yhbz.mjs +68 -0
  3. package/dist/adapters/build.d.mts +10 -12
  4. package/dist/adapters/build.mjs +26 -29
  5. package/dist/adapters/cac.d.mts +1 -1
  6. package/dist/adapters/cac.mjs +156 -1
  7. package/dist/adapters/dev.d.mts +38 -46
  8. package/dist/adapters/dev.mjs +2 -1
  9. package/dist/adapters/embedded.d.mts +1 -1
  10. package/dist/adapters/initiate.d.mts +224 -0
  11. package/dist/adapters/initiate.mjs +2 -0
  12. package/dist/adapters/mcp.d.mts +24 -7
  13. package/dist/adapters/mcp.mjs +2 -2
  14. package/dist/{agent-tool-name-C3b5vEwJ.mjs → agent-tool-name-EgfoFO8C.mjs} +0 -3
  15. package/dist/cli/main.d.mts +0 -2
  16. package/dist/cli/main.mjs +4 -8
  17. package/dist/client/index.d.mts +71 -7
  18. package/dist/client/index.mjs +720 -1540
  19. package/dist/constants.d.mts +28 -7
  20. package/dist/constants.mjs +28 -7
  21. package/dist/{context-DoSozfvx.d.mts → context-B3YUu_95.d.mts} +19 -1
  22. package/dist/{host-h3-D2hYZvDK.mjs → context-CEe4PBz1.mjs} +25 -557
  23. package/dist/{context-CBg8iRbQ.mjs → context-xdynGSLP.mjs} +10 -1
  24. package/dist/dev--rAPzbvO.mjs +239 -0
  25. package/dist/{devframe-mQ4qZAuo.d.mts → devframe-DJMg0WLe.d.mts} +181 -134
  26. package/dist/{diagnostics-hwjXp_UV.mjs → diagnostics-CD8nlgll.mjs} +1 -3
  27. package/dist/{diagnostics-D7wM2cwM.mjs → diagnostics-DOQPnQmX.mjs} +51 -5
  28. package/dist/dist-eNI9qM_I.mjs +228 -0
  29. package/dist/host-agent-xLCSe65D.mjs +264 -0
  30. package/dist/host-h3-Crkzm42q.mjs +26 -0
  31. package/dist/{fetch-BKaD0Vb3.mjs → http-DYpK0cUi.mjs} +42 -11
  32. package/dist/{index-b64Uuhy2.d.mts → index-CB-fSUyn.d.mts} +2 -2
  33. package/dist/{index-Dbw1p5ch.d.mts → index-tdrk1bzV.d.mts} +3 -32
  34. package/dist/index.d.mts +12 -5
  35. package/dist/index.mjs +8 -1
  36. package/dist/{instance-registry-BnnA2Eoh.mjs → instance-registry-WvQkt42E.mjs} +9 -8
  37. package/dist/instance-shell-CMFyszWJ.mjs +486 -0
  38. package/dist/instance-shell-CljiKq1c.d.mts +255 -0
  39. package/dist/internal/index.d.mts +397 -0
  40. package/dist/internal/index.mjs +9 -0
  41. package/dist/node/auth.d.mts +2 -2
  42. package/dist/node/auth.mjs +1 -1
  43. package/dist/node/hub-internals.d.mts +2 -14
  44. package/dist/node/hub-internals.mjs +2 -2
  45. package/dist/node/index.d.mts +4 -268
  46. package/dist/node/index.mjs +3 -5
  47. package/dist/nostics-CzECRXpE.mjs +59 -0
  48. package/dist/nostics-SPGDgHEP.d.mts +33 -0
  49. package/dist/recipes/common-rpc-functions.d.mts +4 -4
  50. package/dist/recipes/interactive-auth.d.mts +3 -3
  51. package/dist/recipes/interactive-auth.mjs +8 -8
  52. package/dist/remote-assets-Bg4gCUZ_.d.mts +123 -0
  53. package/dist/remote-assets-nTTxY2MU.mjs +636 -0
  54. package/dist/rolldown-runtime-JspESFgx.mjs +35 -0
  55. package/dist/rpc/dump.d.mts +77 -1
  56. package/dist/rpc/dump.mjs +1 -1
  57. package/dist/rpc/index.d.mts +3 -3
  58. package/dist/rpc/index.mjs +4 -18
  59. package/dist/rpc/transports/sse-client.d.mts +2 -0
  60. package/dist/rpc/transports/sse-client.mjs +142 -0
  61. package/dist/rpc/transports/sse-server.d.mts +61 -0
  62. package/dist/rpc/transports/sse-server.mjs +209 -0
  63. package/dist/rpc/transports/ws-bun.d.mts +35 -0
  64. package/dist/rpc/transports/ws-bun.mjs +31 -0
  65. package/dist/rpc/transports/ws-client.d.mts +1 -1
  66. package/dist/rpc/transports/ws-client.mjs +10 -18
  67. package/dist/rpc/transports/ws-server.d.mts +2 -2
  68. package/dist/rpc/transports/ws-server.mjs +2 -235
  69. package/dist/rpc-core-Cc4lucI0.d.mts +48 -0
  70. package/dist/rpc-core-DMTe-hLo.mjs +81 -0
  71. package/dist/{serialization-C8Mnw9hK.mjs → serialization-BGzEwAdr.mjs} +2 -2
  72. package/dist/{shared-state-6kHolYZy.mjs → shared-state-fRRbRUtD.mjs} +1 -1
  73. package/dist/sse-client-CFBOIaUi.d.mts +37 -0
  74. package/dist/state-Cc1IeSmQ.mjs +1291 -0
  75. package/dist/{dump-CgZShDRB.mjs → static-BPZ1_OPA.mjs} +3 -266
  76. package/dist/storage-CYye7P7L.mjs +137 -0
  77. package/dist/types/index.d.mts +6 -4
  78. package/dist/types/index.mjs +1 -6
  79. package/dist/{types-CnJSgRVa.d.mts → types-Djq7CBXh.d.mts} +0 -7
  80. package/dist/utils/agent-tool-name.d.mts +0 -3
  81. package/dist/utils/agent-tool-name.mjs +0 -3
  82. package/dist/utils/events.d.mts +1 -1
  83. package/dist/utils/get-port.d.mts +17 -0
  84. package/dist/utils/get-port.mjs +2 -0
  85. package/dist/utils/launch-editor.mjs +1 -25
  86. package/dist/utils/nostics.d.mts +2 -0
  87. package/dist/utils/nostics.mjs +2 -0
  88. package/dist/utils/open.d.mts +2 -10
  89. package/dist/utils/open.mjs +56 -1
  90. package/dist/utils/remote-assets.d.mts +15 -0
  91. package/dist/utils/remote-assets.mjs +2 -0
  92. package/dist/utils/serve-static.d.mts +13 -5
  93. package/dist/utils/serve-static.mjs +119 -9
  94. package/dist/utils/shared-state.d.mts +1 -1
  95. package/dist/utils/shared-state.mjs +1 -1
  96. package/dist/utils/simple-schema.d.mts +101 -1
  97. package/dist/utils/streaming-channel.d.mts +1 -1
  98. package/dist/validation-CpXFB6Dz.mjs +266 -0
  99. package/dist/wire-codec-0K-o5MYW.mjs +56 -0
  100. package/dist/{ws-client-B6Tnr0ic.d.mts → ws-client-DhjNyQo3.d.mts} +7 -2
  101. package/dist/ws-server-BdSLrhxE.mjs +267 -0
  102. package/dist/{ws-server-uKQcClcJ.d.mts → ws-server-D1d3QM9f.d.mts} +90 -9
  103. package/package.json +13 -12
  104. package/skills/devframe/SKILL.md +91 -93
  105. package/dist/_shared-bWRzeSa0.mjs +0 -19
  106. package/dist/adapters/cli.d.mts +0 -11
  107. package/dist/adapters/cli.mjs +0 -6
  108. package/dist/cac-D4_9ok0z.mjs +0 -157
  109. package/dist/dev-Bc49u3qg.mjs +0 -439
  110. package/dist/diagnostics-reporter-CsIG85Q5.mjs +0 -38
  111. package/dist/helpers/vite.d.mts +0 -100
  112. package/dist/helpers/vite.mjs +0 -105
  113. package/dist/http-C4T68YV6.mjs +0 -38
  114. package/dist/index-vAX6bYuc.d.mts +0 -78
  115. package/dist/open-Deb5xmIT.mjs +0 -547
  116. package/dist/recipes/open-helpers.d.mts +0 -6
  117. package/dist/recipes/open-helpers.mjs +0 -6
  118. package/dist/server-CD8XmBXl.mjs +0 -167
  119. package/dist/server-UHWae43Q.d.mts +0 -149
  120. package/dist/simple-schema-BDzLeJDk.d.mts +0 -102
  121. package/dist/state-BeUHDDjk.mjs +0 -108
  122. package/dist/storage-CTNVbuFB.mjs +0 -1122
  123. package/dist/utils/promise.d.mts +0 -8
  124. package/dist/utils/promise.mjs +0 -15
  125. package/dist/utils/scope.d.mts +0 -11
  126. package/dist/utils/scope.mjs +0 -15
  127. package/skills/devframe/templates/spa-devframe.ts +0 -38
@@ -0,0 +1,56 @@
1
+ import { n as strictJsonStringify } from "./serialization-BGzEwAdr.mjs";
2
+ import { n as structuredCloneStringify, t as structuredCloneParse } from "./structured-clone-CbAV5rFI.mjs";
3
+ //#region src/rpc/wire-codec.ts
4
+ const EMPTY_WIRE_DEFS = /* @__PURE__ */ new Map();
5
+ /**
6
+ * Build the per-connection wire codec every live transport (WS server, WS
7
+ * client, SSE server, SSE client) shares: per-method dispatch between strict
8
+ * JSON (methods declared `jsonSerializable: true`) and `s:`-prefixed
9
+ * structured-clone (everything else, including all error envelopes), with a
10
+ * request-id → method map so a response independently picks the same
11
+ * encoder as its request. One codec per connection — request-id spaces
12
+ * don't collide across connections.
13
+ *
14
+ * @internal
15
+ */
16
+ function createRpcWireCodec(definitions = EMPTY_WIRE_DEFS) {
17
+ const pendingRequestMethods = /* @__PURE__ */ new Map();
18
+ return {
19
+ serialize: (msg) => {
20
+ let method;
21
+ if (msg.t === "q") method = msg.m;
22
+ else {
23
+ method = pendingRequestMethods.get(msg.i);
24
+ pendingRequestMethods.delete(msg.i);
25
+ }
26
+ if (!(msg.t === "s" && "e" in msg) && !!method && definitions.get(method)?.jsonSerializable === true) return strictJsonStringify(msg, method ?? "");
27
+ return `s:${structuredCloneStringify(msg)}`;
28
+ },
29
+ deserialize: (raw) => {
30
+ const msg = raw.startsWith("s:") ? structuredCloneParse(raw.slice(2)) : JSON.parse(raw);
31
+ if (msg.t === "q" && msg.i && msg.m) pendingRequestMethods.set(msg.i, msg.m);
32
+ return msg;
33
+ }
34
+ };
35
+ }
36
+ /**
37
+ * Peek at a wire frame's birpc envelope without engaging a codec's
38
+ * request-id bookkeeping — used by the SSE transport to route a frame
39
+ * (park a POST for its response / answer with a bare 202) before it is
40
+ * handed to birpc proper.
41
+ *
42
+ * @internal
43
+ */
44
+ function peekRpcWireFrame(raw) {
45
+ try {
46
+ const msg = raw.startsWith("s:") ? structuredCloneParse(raw.slice(2)) : JSON.parse(raw);
47
+ return {
48
+ t: msg?.t,
49
+ i: msg?.i
50
+ };
51
+ } catch {
52
+ return {};
53
+ }
54
+ }
55
+ //#endregion
56
+ export { peekRpcWireFrame as n, createRpcWireCodec as t };
@@ -1,4 +1,4 @@
1
- import { v as RpcFunctionDefinitionAny } from "./types-CnJSgRVa.mjs";
1
+ import { v as RpcFunctionDefinitionAny } from "./types-Djq7CBXh.mjs";
2
2
  import { ChannelOptions } from "birpc";
3
3
  //#region src/rpc/transports/ws-client.d.ts
4
4
  interface WsRpcChannelOptions {
@@ -19,7 +19,12 @@ interface WsRpcChannelOptions {
19
19
  /**
20
20
  * Build a birpc `ChannelOptions` object backed by a browser `WebSocket`.
21
21
  * Pass the result straight to `createRpcClient`'s `channel` option.
22
+ *
23
+ * Also returns `close()`, closing the underlying socket — mirroring the server transport's
24
+ * existing `WsRpcTransport.close()`. `birpc`'s own `ChannelOptions` has no teardown of its own.
22
25
  */
23
- declare function createWsRpcChannel(options: WsRpcChannelOptions): ChannelOptions;
26
+ declare function createWsRpcChannel(options: WsRpcChannelOptions): ChannelOptions & {
27
+ close: () => void;
28
+ };
24
29
  //#endregion
25
30
  export { createWsRpcChannel as n, WsRpcChannelOptions as t };
@@ -0,0 +1,267 @@
1
+ import "./constants.mjs";
2
+ import { t as createRpcWireCodec } from "./wire-codec-0K-o5MYW.mjs";
3
+ import { n as randomToken, r as timingSafeEqual } from "./crypto-token-XCqTSMg9.mjs";
4
+ import { createServer } from "node:http";
5
+ import { createServer as createServer$1 } from "node:https";
6
+ import crossws from "crossws/adapters/node";
7
+ //#region src/rpc/transports/session.ts
8
+ let sessionId = 0;
9
+ /**
10
+ * Mint the per-connection session meta every transport binding shares —
11
+ * one id space across transports, so session bookkeeping (streaming
12
+ * subscriptions, shared-state sync, auth trust) never collides between a
13
+ * WS peer and an SSE session on the same server.
14
+ */
15
+ function createRpcSessionMeta() {
16
+ return {
17
+ id: sessionId++,
18
+ subscribedStates: /* @__PURE__ */ new Set()
19
+ };
20
+ }
21
+ //#endregion
22
+ //#region src/rpc/transports/ws-server.ts
23
+ /**
24
+ * Create a live, token-protected origin allowlist for external browser
25
+ * viewers. Pass it to {@link WsRpcTransportOptions.allowedOrigins}, then use
26
+ * `registerFromUrl()` in the connection metadata handler to authorize a
27
+ * viewer without sharing a mutable array or disabling DNS-rebinding protection.
28
+ */
29
+ function createWsOriginRegistry(options = {}) {
30
+ const token = randomToken();
31
+ const origins = new Set(options.allowedOrigins ?? []);
32
+ function normalizeOrigin(origin) {
33
+ if (!origin) return;
34
+ try {
35
+ const url = new URL(origin);
36
+ const normalized = url.origin === "null" ? `${url.protocol}//${url.host}` : url.origin;
37
+ return origin === normalized ? normalized : void 0;
38
+ } catch {}
39
+ }
40
+ function registerOrigin(origin, candidateToken) {
41
+ const normalized = normalizeOrigin(origin);
42
+ if (!normalized || !candidateToken || !timingSafeEqual(token, candidateToken)) return false;
43
+ if (options.validateOrigin && !options.validateOrigin(normalized)) return false;
44
+ origins.add(normalized);
45
+ return true;
46
+ }
47
+ return {
48
+ token,
49
+ registerFromUrl(url) {
50
+ let parsed;
51
+ try {
52
+ parsed = new URL(url, "http://localhost");
53
+ } catch {
54
+ return;
55
+ }
56
+ const origin = parsed.searchParams.get("devframe_viewer_origin") ?? void 0;
57
+ return registerOrigin(origin, parsed.searchParams.get("devframe_viewer_origin_token") ?? void 0) ? origin : void 0;
58
+ },
59
+ isAllowed(origin) {
60
+ return isAllowedOrigin(origin, [...origins]);
61
+ }
62
+ };
63
+ }
64
+ const EMPTY_DEFS = /* @__PURE__ */ new Map();
65
+ function NOOP() {}
66
+ function listen(server, port, host) {
67
+ return new Promise((resolve, reject) => {
68
+ const onError = (error) => reject(error);
69
+ server.once("error", onError);
70
+ try {
71
+ server.listen(port, host, () => {
72
+ server.off("error", onError);
73
+ resolve();
74
+ });
75
+ } catch (error) {
76
+ server.off("error", onError);
77
+ reject(error);
78
+ }
79
+ });
80
+ }
81
+ /** Compare two URL paths ignoring a trailing slash. */
82
+ function pathMatches(a, b) {
83
+ const strip = (p) => p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
84
+ return strip(a) === strip(b);
85
+ }
86
+ function isLoopbackHostname(hostname) {
87
+ const h = hostname.replace(/^\[|\]$/g, "");
88
+ return h === "localhost" || h === "127.0.0.1" || h === "::1" || h.endsWith(".localhost") || h.startsWith("127.");
89
+ }
90
+ /**
91
+ * Default origin policy for a localhost dev tool: allow requests with no
92
+ * `Origin` header (native, non-browser clients), allow any loopback origin
93
+ * (so cross-port localhost dev setups keep working), and allow explicitly
94
+ * configured origins. Everything else — a real remote page in the dev's
95
+ * browser — is rejected.
96
+ */
97
+ function isAllowedOrigin(origin, allowedOrigins) {
98
+ if (!origin) return true;
99
+ if (allowedOrigins.includes(origin)) return true;
100
+ try {
101
+ return isLoopbackHostname(new URL(origin).hostname);
102
+ } catch {
103
+ return false;
104
+ }
105
+ }
106
+ function isWsOriginRegistry(value) {
107
+ return !!value && !Array.isArray(value);
108
+ }
109
+ /**
110
+ * Build the `upgrade` listener that hands a request to the crossws adapter,
111
+ * optionally filtered to a single `path`. Non-matching requests are left
112
+ * untouched so other upgrade listeners (e.g. a Vite dev server's HMR socket)
113
+ * can claim them, unless `destroyUnmatched` is set.
114
+ */
115
+ function createUpgradeListener(ws, path, destroyUnmatched, allowedOrigins) {
116
+ return (req, socket, head) => {
117
+ socket.on("error", () => {});
118
+ if (path) {
119
+ let pathname = req.url ?? "/";
120
+ try {
121
+ pathname = new URL(req.url ?? "/", "http://localhost").pathname;
122
+ } catch {}
123
+ if (!pathMatches(pathname, path)) {
124
+ if (destroyUnmatched) {
125
+ socket.write("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
126
+ socket.destroy();
127
+ }
128
+ return;
129
+ }
130
+ }
131
+ const originAllowed = isWsOriginRegistry(allowedOrigins) ? allowedOrigins.isAllowed(req.headers.origin) : isAllowedOrigin(req.headers.origin, allowedOrigins || []);
132
+ if (allowedOrigins !== false && !originAllowed) {
133
+ socket.write("HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n");
134
+ socket.destroy();
135
+ return;
136
+ }
137
+ ws.handleUpgrade(req, socket, head);
138
+ };
139
+ }
140
+ /**
141
+ * The per-peer lifecycle hooks driving a devframe RPC WebSocket, shaped for
142
+ * any [crossws](https://crossws.h3.dev) adapter. {@link attachWsRpcTransport}
143
+ * feeds them to the Node adapter; runtime-specific attachments (e.g. Bun's
144
+ * fetch-upgrade adapter) reuse the same hooks so every transport speaks the
145
+ * identical wire protocol — one birpc channel per peer, per-method
146
+ * `jsonSerializable` dispatch between strict JSON and structured-clone.
147
+ */
148
+ function createWsRpcPeerHooks(rpcGroup, options = {}) {
149
+ const { onConnected = NOOP, onDisconnected = NOOP, definitions = EMPTY_DEFS, serialize: serializeOverride, deserialize: deserializeOverride } = options;
150
+ const states = /* @__PURE__ */ new WeakMap();
151
+ return {
152
+ open: (peer) => {
153
+ const meta = createRpcSessionMeta();
154
+ meta.peer = peer;
155
+ const connection = {
156
+ id: meta.id,
157
+ transport: "websocket",
158
+ request: peer.request,
159
+ send: (data) => peer.send(data),
160
+ close: (code, reason) => peer.close(code, reason),
161
+ peer
162
+ };
163
+ const codec = createRpcWireCodec(definitions);
164
+ const state = {
165
+ meta,
166
+ connection,
167
+ channel: void 0
168
+ };
169
+ const channel = {
170
+ post: (data) => {
171
+ peer.send(data);
172
+ },
173
+ on: (fn) => {
174
+ state.onMessage = fn;
175
+ },
176
+ serialize: serializeOverride ?? codec.serialize,
177
+ deserialize: deserializeOverride ?? codec.deserialize,
178
+ meta
179
+ };
180
+ state.channel = channel;
181
+ states.set(peer, state);
182
+ rpcGroup.updateChannels((channels) => {
183
+ channels.push(channel);
184
+ });
185
+ onConnected(connection, meta);
186
+ },
187
+ message: (peer, message) => {
188
+ states.get(peer)?.onMessage?.(message.text());
189
+ },
190
+ close: (peer) => {
191
+ const state = states.get(peer);
192
+ if (!state) return;
193
+ states.delete(peer);
194
+ rpcGroup.updateChannels((channels) => {
195
+ const index = channels.indexOf(state.channel);
196
+ if (index >= 0) channels.splice(index, 1);
197
+ });
198
+ onDisconnected(state.connection, state.meta);
199
+ }
200
+ };
201
+ }
202
+ /**
203
+ * Attach a WebSocket transport to an existing RPC group, powered by
204
+ * [crossws](https://crossws.h3.dev). Either attach to an existing HTTP(S)
205
+ * `server` (sharing its port, optionally scoped to a `path`), or let this
206
+ * helper create a standalone server from `port` / `host` / `https`.
207
+ *
208
+ * Returns the crossws node adapter, standalone-server readiness/address
209
+ * accessors, `detach` (remove the upgrade listener from a shared `server`),
210
+ * and `close` (full deterministic teardown).
211
+ */
212
+ function attachWsRpcTransport(rpcGroup, options = {}) {
213
+ const { server, port, host = "localhost", path, destroyUnmatched = false, unbound, https, allowedOrigins } = options;
214
+ const ws = crossws({ hooks: createWsRpcPeerHooks(rpcGroup, options) });
215
+ const sharedUpgradeListener = createUpgradeListener(ws, path, destroyUnmatched, allowedOrigins);
216
+ const ownedUpgradeListener = createUpgradeListener(ws, path, true, allowedOrigins);
217
+ /** Bind a server's `upgrade` events, tracked so `close()` detaches them. */
218
+ const attachments = /* @__PURE__ */ new Set();
219
+ function attachTo(target, listener) {
220
+ target.on("upgrade", listener);
221
+ const detachOne = () => {
222
+ target.off("upgrade", listener);
223
+ attachments.delete(detachOne);
224
+ };
225
+ attachments.add(detachOne);
226
+ return detachOne;
227
+ }
228
+ let ready = Promise.resolve();
229
+ let ownedServer;
230
+ if (unbound) {} else if (server) attachTo(server, sharedUpgradeListener);
231
+ else if (https) {
232
+ ownedServer = createServer$1(https);
233
+ attachTo(ownedServer, ownedUpgradeListener);
234
+ ready = listen(ownedServer, port ?? 0, host);
235
+ } else {
236
+ ownedServer = createServer((_req, res) => {
237
+ res.writeHead(426, { "content-type": "text/plain" });
238
+ res.end("Upgrade Required");
239
+ });
240
+ attachTo(ownedServer, ownedUpgradeListener);
241
+ ready = listen(ownedServer, port ?? 0, host);
242
+ }
243
+ const activeServer = server ?? ownedServer;
244
+ function detachAll() {
245
+ for (const detachOne of [...attachments]) detachOne();
246
+ }
247
+ return {
248
+ ws,
249
+ ready,
250
+ address: () => activeServer?.address() ?? null,
251
+ handleUpgrade: sharedUpgradeListener,
252
+ attach: (target) => attachTo(target, sharedUpgradeListener),
253
+ detach: detachAll,
254
+ async close() {
255
+ detachAll();
256
+ ws.closeAll(void 0, void 0, true);
257
+ if (ownedServer) {
258
+ const srv = ownedServer;
259
+ await ready.catch(() => {});
260
+ if (!srv.listening) return;
261
+ await new Promise((r) => srv.close(() => r()));
262
+ }
263
+ }
264
+ };
265
+ }
266
+ //#endregion
267
+ export { isLoopbackHostname as a, isAllowedOrigin as i, createWsOriginRegistry as n, createRpcSessionMeta as o, createWsRpcPeerHooks as r, attachWsRpcTransport as t };
@@ -1,14 +1,62 @@
1
- import { v as RpcFunctionDefinitionAny } from "./types-CnJSgRVa.mjs";
2
- import { Peer } from "crossws";
1
+ import { v as RpcFunctionDefinitionAny } from "./types-Djq7CBXh.mjs";
3
2
  import { BirpcGroup, ChannelOptions } from "birpc";
3
+ import { Hooks, Peer } from "crossws";
4
4
  import { NodeAdapter } from "crossws/adapters/node";
5
- import { Server } from "node:http";
5
+ import { Buffer } from "node:buffer";
6
+ import { IncomingMessage, Server } from "node:http";
6
7
  import { Server as Server$1, ServerOptions } from "node:https";
7
8
  import { AddressInfo } from "node:net";
8
- //#region src/rpc/transports/ws-server.d.ts
9
+ import { Duplex } from "node:stream";
10
+ //#region src/rpc/transports/session.d.ts
11
+ /**
12
+ * Which wire transport produced an RPC connection. Every transport speaks
13
+ * the same birpc channel protocol; the kind only matters to code that needs
14
+ * transport-specific behavior (e.g. reaching the WS escape hatch on
15
+ * {@link DevframeRpcConnection.peer}).
16
+ */
17
+ type DevframeRpcTransportKind = 'websocket' | 'sse';
18
+ /**
19
+ * Structural view of the connect-time HTTP request behind an RPC connection
20
+ * — the WS upgrade request, or the request opening an SSE stream. Shaped to
21
+ * match both the web `Request` a crossws peer exposes and a plain
22
+ * `node:http` request wrapper, so auth hooks can read the bearer-token
23
+ * query param and the `Origin` header without caring which transport (or
24
+ * runtime) produced the connection.
25
+ */
26
+ interface DevframeRpcConnectionRequest {
27
+ /** Request URL (may be path-only, e.g. `/__ws?devframe_auth_token=…`). */
28
+ url?: string;
29
+ /** Header lookup, `Headers`-style. */
30
+ headers?: {
31
+ get: (name: string) => string | null | undefined;
32
+ };
33
+ }
34
+ /**
35
+ * A live RPC connection, independent of the transport that carries it. One
36
+ * exists per connected client; transport bindings construct it alongside the
37
+ * session meta and hand both to the connect/disconnect hooks
38
+ * (`onPeerConnect` / `onPeerDisconnect`, {@link DevframeAuthHandler.onConnect}).
39
+ */
40
+ interface DevframeRpcConnection {
41
+ /** Session id — the same value as the session meta's `id`. */
42
+ id: number;
43
+ /** The transport carrying this connection. */
44
+ transport: DevframeRpcTransportKind;
45
+ /** The connect-time HTTP request (upgrade request / stream request). */
46
+ request?: DevframeRpcConnectionRequest;
47
+ /** Send a raw wire frame to this client. Prefer the birpc channel. */
48
+ send?: (data: string) => void;
49
+ /** Terminate the connection from the server side. */
50
+ close?: (code?: number, reason?: string) => void;
51
+ /**
52
+ * The crossws peer backing a `websocket` connection — the WS-specific
53
+ * escape hatch (pub/sub, raw socket access). Absent on other transports.
54
+ */
55
+ peer?: Peer;
56
+ }
9
57
  interface DevframeNodeRpcSessionMeta {
10
58
  id: number;
11
- /** The crossws peer backing this session's socket. */
59
+ /** The crossws peer backing this session's socket (WS transport only). */
12
60
  peer?: Peer;
13
61
  clientAuthToken?: string;
14
62
  isTrusted?: boolean;
@@ -26,6 +74,8 @@ interface DevframeNodeRpcSessionMeta {
26
74
  */
27
75
  uploadingStreams?: Set<string>;
28
76
  }
77
+ //#endregion
78
+ //#region src/rpc/transports/ws-server.d.ts
29
79
  interface WsRpcTransportOptions {
30
80
  /**
31
81
  * Attach to an existing HTTP(S) server, sharing its port. Combine with
@@ -43,12 +93,20 @@ interface WsRpcTransportOptions {
43
93
  /** Host for the standalone WebSocket server. Defaults to `localhost`. */
44
94
  host?: string;
45
95
  /**
46
- * Restrict the WS endpoint to a single upgrade route (e.g. `/__devframe_ws`). When
96
+ * Restrict the WS endpoint to a single upgrade route (e.g. `/__ws`). When
47
97
  * sharing a `server`, non-matching upgrade requests are left untouched for
48
98
  * other listeners to handle, so devframe's socket can sit alongside
49
99
  * framework sockets (Vite HMR, etc.).
50
100
  */
51
101
  path?: string;
102
+ /**
103
+ * Create the adapter without binding it to anything: no server is created,
104
+ * no port is bound, no `upgrade` listener is installed. The caller drives
105
+ * the socket itself through {@link WsRpcTransport.handleUpgrade} (from its
106
+ * own `upgrade` listener) or {@link WsRpcTransport.attach} (to bind a
107
+ * server later). Takes precedence over `server` / `port` / `https`.
108
+ */
109
+ unbound?: boolean;
52
110
  /**
53
111
  * Destroy upgrade requests that don't match `path` instead of leaving them
54
112
  * for other listeners. Enable this when devframe owns the shared server
@@ -76,8 +134,8 @@ interface WsRpcTransportOptions {
76
134
  * loses dev-time validation for `jsonSerializable: true` declarations.
77
135
  */
78
136
  definitions?: ReadonlyMap<string, Pick<RpcFunctionDefinitionAny, 'jsonSerializable'>>;
79
- onConnected?: (peer: Peer, meta: DevframeNodeRpcSessionMeta) => void;
80
- onDisconnected?: (peer: Peer, meta: DevframeNodeRpcSessionMeta) => void;
137
+ onConnected?: (connection: DevframeRpcConnection, meta: DevframeNodeRpcSessionMeta) => void;
138
+ onDisconnected?: (connection: DevframeRpcConnection, meta: DevframeNodeRpcSessionMeta) => void;
81
139
  /** Override the default per-call serializer. Most callers should leave this unset. */
82
140
  serialize?: ChannelOptions['serialize'];
83
141
  /** Override the default per-call deserializer. Most callers should leave this unset. */
@@ -114,6 +172,20 @@ interface WsRpcTransport {
114
172
  ready: Promise<void>;
115
173
  /** Returns the bound address, or `null` when the server is not listening. */
116
174
  address: () => AddressInfo | string | null;
175
+ /**
176
+ * Complete a `node:http` `upgrade` event on this transport, applying the
177
+ * same `path` filter and origin gate the transport's own listener uses.
178
+ * Wire it into a host server directly — `server.on('upgrade',
179
+ * transport.handleUpgrade)` — or call it from an existing listener.
180
+ */
181
+ handleUpgrade: (req: IncomingMessage, socket: Duplex, head: Buffer) => void;
182
+ /**
183
+ * Route a server's `upgrade` events to this transport, returning a detach
184
+ * function. Use it to bind an `unbound` transport once the host server
185
+ * exists; {@link WsRpcTransport.close} detaches every server attached this
186
+ * way (without closing them — the caller owns their lifecycle).
187
+ */
188
+ attach: (server: Server | Server$1) => () => void;
117
189
  /** Remove the upgrade listener from a shared `server` (a no-op otherwise). */
118
190
  detach: () => void;
119
191
  /**
@@ -132,6 +204,15 @@ declare function isLoopbackHostname(hostname: string): boolean;
132
204
  * browser — is rejected.
133
205
  */
134
206
  declare function isAllowedOrigin(origin: string | undefined, allowedOrigins: readonly string[]): boolean;
207
+ /**
208
+ * The per-peer lifecycle hooks driving a devframe RPC WebSocket, shaped for
209
+ * any [crossws](https://crossws.h3.dev) adapter. {@link attachWsRpcTransport}
210
+ * feeds them to the Node adapter; runtime-specific attachments (e.g. Bun's
211
+ * fetch-upgrade adapter) reuse the same hooks so every transport speaks the
212
+ * identical wire protocol — one birpc channel per peer, per-method
213
+ * `jsonSerializable` dispatch between strict JSON and structured-clone.
214
+ */
215
+ declare function createWsRpcPeerHooks<ClientFunctions extends object, ServerFunctions extends object>(rpcGroup: BirpcGroup<ClientFunctions, ServerFunctions, false>, options?: Pick<WsRpcTransportOptions, 'onConnected' | 'onDisconnected' | 'definitions' | 'serialize' | 'deserialize'>): Partial<Hooks>;
135
216
  /**
136
217
  * Attach a WebSocket transport to an existing RPC group, powered by
137
218
  * [crossws](https://crossws.h3.dev). Either attach to an existing HTTP(S)
@@ -144,4 +225,4 @@ declare function isAllowedOrigin(origin: string | undefined, allowedOrigins: rea
144
225
  */
145
226
  declare function attachWsRpcTransport<ClientFunctions extends object, ServerFunctions extends object>(rpcGroup: BirpcGroup<ClientFunctions, ServerFunctions, false>, options?: WsRpcTransportOptions): WsRpcTransport;
146
227
  //#endregion
147
- export { WsRpcTransportOptions as a, isAllowedOrigin as c, WsRpcTransport as i, isLoopbackHostname as l, DevframeNodeRpcSessionMeta as n, attachWsRpcTransport as o, WsOriginRegistry as r, createWsOriginRegistry as s, CreateWsOriginRegistryOptions as t };
228
+ export { attachWsRpcTransport as a, isAllowedOrigin as c, DevframeRpcConnection as d, DevframeRpcConnectionRequest as f, WsRpcTransportOptions as i, isLoopbackHostname as l, WsOriginRegistry as n, createWsOriginRegistry as o, DevframeRpcTransportKind as p, WsRpcTransport as r, createWsRpcPeerHooks as s, CreateWsOriginRegistryOptions as t, DevframeNodeRpcSessionMeta as u };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "devframe",
3
3
  "type": "module",
4
- "version": "0.8.2",
5
- "description": "Framework for building generic devframes",
4
+ "version": "0.9.0-beta.10",
5
+ "description": "Framework for building one portable devtool integration that runs in any viewer.",
6
6
  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
8
8
  "homepage": "https://github.com/devframes/devframe#readme",
@@ -22,23 +22,25 @@
22
22
  ".": "./dist/index.mjs",
23
23
  "./adapters/build": "./dist/adapters/build.mjs",
24
24
  "./adapters/cac": "./dist/adapters/cac.mjs",
25
- "./adapters/cli": "./dist/adapters/cli.mjs",
26
25
  "./adapters/dev": "./dist/adapters/dev.mjs",
27
26
  "./adapters/embedded": "./dist/adapters/embedded.mjs",
28
27
  "./adapters/mcp": "./dist/adapters/mcp.mjs",
29
28
  "./client": "./dist/client/index.mjs",
30
29
  "./constants": "./dist/constants.mjs",
31
- "./helpers/vite": "./dist/helpers/vite.mjs",
30
+ "./initiate": "./dist/adapters/initiate.mjs",
31
+ "./internal": "./dist/internal/index.mjs",
32
32
  "./node": "./dist/node/index.mjs",
33
33
  "./node/auth": "./dist/node/auth.mjs",
34
34
  "./node/hub-internals": "./dist/node/hub-internals.mjs",
35
35
  "./recipes/common-rpc-functions": "./dist/recipes/common-rpc-functions.mjs",
36
36
  "./recipes/interactive-auth": "./dist/recipes/interactive-auth.mjs",
37
- "./recipes/open-helpers": "./dist/recipes/open-helpers.mjs",
38
37
  "./rpc": "./dist/rpc/index.mjs",
39
38
  "./rpc/client": "./dist/rpc/client.mjs",
40
39
  "./rpc/dump": "./dist/rpc/dump.mjs",
41
40
  "./rpc/server": "./dist/rpc/server.mjs",
41
+ "./rpc/transports/sse-client": "./dist/rpc/transports/sse-client.mjs",
42
+ "./rpc/transports/sse-server": "./dist/rpc/transports/sse-server.mjs",
43
+ "./rpc/transports/ws-bun": "./dist/rpc/transports/ws-bun.mjs",
42
44
  "./rpc/transports/ws-client": "./dist/rpc/transports/ws-client.mjs",
43
45
  "./rpc/transports/ws-server": "./dist/rpc/transports/ws-server.mjs",
44
46
  "./types": "./dist/types/index.mjs",
@@ -46,13 +48,14 @@
46
48
  "./utils/colors": "./dist/utils/colors.mjs",
47
49
  "./utils/crypto-token": "./dist/utils/crypto-token.mjs",
48
50
  "./utils/events": "./dist/utils/events.mjs",
51
+ "./utils/get-port": "./dist/utils/get-port.mjs",
49
52
  "./utils/hash": "./dist/utils/hash.mjs",
50
53
  "./utils/launch-editor": "./dist/utils/launch-editor.mjs",
51
54
  "./utils/nanoid": "./dist/utils/nanoid.mjs",
55
+ "./utils/nostics": "./dist/utils/nostics.mjs",
52
56
  "./utils/open": "./dist/utils/open.mjs",
53
- "./utils/promise": "./dist/utils/promise.mjs",
57
+ "./utils/remote-assets": "./dist/utils/remote-assets.mjs",
54
58
  "./utils/simple-schema": "./dist/utils/simple-schema.mjs",
55
- "./utils/scope": "./dist/utils/scope.mjs",
56
59
  "./utils/serve-static": "./dist/utils/serve-static.mjs",
57
60
  "./utils/shared-state": "./dist/utils/shared-state.mjs",
58
61
  "./utils/streaming-channel": "./dist/utils/streaming-channel.mjs",
@@ -87,9 +90,8 @@
87
90
  },
88
91
  "dependencies": {
89
92
  "@standard-schema/spec": "^1.1.0",
90
- "birpc": "^4.0.0",
93
+ "birpc": "^4.1.0",
91
94
  "crossws": "^0.4.10",
92
- "destr": "^2.0.5",
93
95
  "h3": "^2.0.1-rc.26",
94
96
  "mrmime": "^2.0.1",
95
97
  "nostics": "^1.2.0",
@@ -101,12 +103,11 @@
101
103
  "@modelcontextprotocol/server": "^2.0.0",
102
104
  "cac": "^7.0.0",
103
105
  "get-port-please": "^3.2.0",
104
- "immer": "^11.1.15",
106
+ "immer": "^11.1.16",
105
107
  "launch-editor": "^2.14.1",
106
108
  "mlly": "^1.8.2",
107
109
  "obug": "^2.1.4",
108
110
  "ohash": "^2.0.11",
109
- "open": "^11.0.0",
110
111
  "p-limit": "^7.3.1",
111
112
  "perfect-debounce": "^2.1.0",
112
113
  "structured-clone-es": "^2.0.1",
@@ -115,7 +116,7 @@
115
116
  "ua-parser-modern": "^0.1.1",
116
117
  "valibot": "^1.4.2",
117
118
  "whenexpr": "^0.1.2",
118
- "ws": "^8.21.2"
119
+ "ws": "^8.21.3"
119
120
  },
120
121
  "scripts": {
121
122
  "build": "tsdown",