devframe 0.8.2 → 0.9.0-beta.2

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 (103) hide show
  1. package/dist/_shared-CttmCMBi.mjs +70 -0
  2. package/dist/_shared-DmpWW-HH.d.mts +43 -0
  3. package/dist/adapters/build.d.mts +1 -1
  4. package/dist/adapters/build.mjs +6 -5
  5. package/dist/adapters/cac.d.mts +1 -1
  6. package/dist/adapters/cac.mjs +157 -1
  7. package/dist/adapters/dev.d.mts +20 -42
  8. package/dist/adapters/dev.mjs +2 -1
  9. package/dist/adapters/embedded.d.mts +1 -1
  10. package/dist/adapters/initiate.d.mts +223 -0
  11. package/dist/adapters/initiate.mjs +2 -0
  12. package/dist/adapters/mcp.d.mts +26 -2
  13. package/dist/adapters/mcp.mjs +2 -2
  14. package/dist/cli/main.mjs +2 -2
  15. package/dist/client/index.d.mts +70 -6
  16. package/dist/client/index.mjs +743 -408
  17. package/dist/constants.d.mts +19 -6
  18. package/dist/constants.mjs +19 -6
  19. package/dist/{context-CBg8iRbQ.mjs → context-C33rR7n0.mjs} +1 -1
  20. package/dist/{context-DoSozfvx.d.mts → context-CSCwoABx.d.mts} +1 -1
  21. package/dist/{host-h3-D2hYZvDK.mjs → context-Du11TLcO.mjs} +5 -255
  22. package/dist/dev-BEveeA5H.mjs +233 -0
  23. package/dist/{devframe-mQ4qZAuo.d.mts → devframe-Bdd9YXTy.d.mts} +88 -25
  24. package/dist/{diagnostics-D7wM2cwM.mjs → diagnostics-DLYH5oD3.mjs} +16 -0
  25. package/dist/dist-D8jAMwxU.mjs +227 -0
  26. package/dist/helpers/vite.d.mts +43 -39
  27. package/dist/helpers/vite.mjs +26 -38
  28. package/dist/host-agent-CpbAoqBx.mjs +230 -0
  29. package/dist/host-h3-CzCZwtuV.mjs +26 -0
  30. package/dist/{fetch-BKaD0Vb3.mjs → http-DLr_cJZ7.mjs} +42 -4
  31. package/dist/{index-b64Uuhy2.d.mts → index-CZRd2K1C.d.mts} +2 -2
  32. package/dist/{index-Dbw1p5ch.d.mts → index-DSCq4M4q.d.mts} +2 -31
  33. package/dist/index.d.mts +9 -4
  34. package/dist/index.mjs +8 -1
  35. package/dist/{instance-registry-BnnA2Eoh.mjs → instance-registry-BQqGoDO9.mjs} +9 -2
  36. package/dist/instance-shell-DBTXTSjN.d.mts +264 -0
  37. package/dist/instance-shell-KRdee1QF.mjs +485 -0
  38. package/dist/internal/index.d.mts +150 -0
  39. package/dist/internal/index.mjs +7 -0
  40. package/dist/node/auth.d.mts +2 -2
  41. package/dist/node/hub-internals.d.mts +2 -14
  42. package/dist/node/hub-internals.mjs +2 -2
  43. package/dist/node/index.d.mts +3 -267
  44. package/dist/node/index.mjs +3 -5
  45. package/dist/recipes/common-rpc-functions.d.mts +3 -3
  46. package/dist/recipes/interactive-auth.d.mts +3 -3
  47. package/dist/recipes/interactive-auth.mjs +6 -6
  48. package/dist/rolldown-runtime-JspESFgx.mjs +35 -0
  49. package/dist/rpc/dump.d.mts +77 -1
  50. package/dist/rpc/dump.mjs +1 -1
  51. package/dist/rpc/index.d.mts +2 -2
  52. package/dist/rpc/index.mjs +3 -17
  53. package/dist/rpc/transports/sse-client.d.mts +2 -0
  54. package/dist/rpc/transports/sse-client.mjs +142 -0
  55. package/dist/rpc/transports/sse-server.d.mts +61 -0
  56. package/dist/rpc/transports/sse-server.mjs +209 -0
  57. package/dist/rpc/transports/ws-bun.d.mts +35 -0
  58. package/dist/rpc/transports/ws-bun.mjs +31 -0
  59. package/dist/rpc/transports/ws-client.d.mts +1 -1
  60. package/dist/rpc/transports/ws-client.mjs +10 -18
  61. package/dist/rpc/transports/ws-server.d.mts +2 -2
  62. package/dist/rpc/transports/ws-server.mjs +2 -235
  63. package/dist/rpc-core-CGctXToO.d.mts +48 -0
  64. package/dist/rpc-core-CqcJkAu9.mjs +81 -0
  65. package/dist/{serialization-C8Mnw9hK.mjs → serialization-BHY8M5NQ.mjs} +1 -1
  66. package/dist/{shared-state-6kHolYZy.mjs → shared-state-fRRbRUtD.mjs} +1 -1
  67. package/dist/sse-client-BsIK3hMD.d.mts +37 -0
  68. package/dist/{dump-CgZShDRB.mjs → static-CvjkO8QE.mjs} +2 -265
  69. package/dist/{storage-CTNVbuFB.mjs → storage-BcmVEJRc.mjs} +2 -2
  70. package/dist/types/index.d.mts +3 -3
  71. package/dist/types/index.mjs +1 -6
  72. package/dist/utils/events.d.mts +1 -1
  73. package/dist/utils/launch-editor.mjs +1 -25
  74. package/dist/utils/open.mjs +1 -1
  75. package/dist/utils/serve-static.mjs +1 -1
  76. package/dist/utils/shared-state.d.mts +1 -1
  77. package/dist/utils/shared-state.mjs +1 -1
  78. package/dist/utils/simple-schema.d.mts +101 -1
  79. package/dist/utils/streaming-channel.d.mts +1 -1
  80. package/dist/validation-BiuDXTKW.mjs +266 -0
  81. package/dist/wire-codec-9VhIKnyp.mjs +58 -0
  82. package/dist/{ws-client-B6Tnr0ic.d.mts → ws-client-CdNuATYa.d.mts} +6 -1
  83. package/dist/ws-server-Bk1hOG3X.mjs +267 -0
  84. package/dist/{ws-server-uKQcClcJ.d.mts → ws-server-Ces08fjP.d.mts} +89 -8
  85. package/package.json +10 -9
  86. package/skills/devframe/SKILL.md +84 -85
  87. package/dist/_shared-bWRzeSa0.mjs +0 -19
  88. package/dist/adapters/cli.d.mts +0 -11
  89. package/dist/adapters/cli.mjs +0 -6
  90. package/dist/cac-D4_9ok0z.mjs +0 -157
  91. package/dist/dev-Bc49u3qg.mjs +0 -439
  92. package/dist/http-C4T68YV6.mjs +0 -38
  93. package/dist/index-vAX6bYuc.d.mts +0 -78
  94. package/dist/recipes/open-helpers.d.mts +0 -6
  95. package/dist/recipes/open-helpers.mjs +0 -6
  96. package/dist/server-CD8XmBXl.mjs +0 -167
  97. package/dist/server-UHWae43Q.d.mts +0 -149
  98. package/dist/simple-schema-BDzLeJDk.d.mts +0 -102
  99. package/dist/utils/promise.d.mts +0 -8
  100. package/dist/utils/promise.mjs +0 -15
  101. package/dist/utils/scope.d.mts +0 -11
  102. package/dist/utils/scope.mjs +0 -15
  103. package/dist/{open-Deb5xmIT.mjs → open-Dz4V4irZ.mjs} +1 -1
@@ -0,0 +1,142 @@
1
+ import { DEVFRAME_AUTH_TOKEN_QUERY_PARAM, DEVFRAME_SSE_SESSION_HEADER } from "../../constants.mjs";
2
+ import { t as createRpcWireCodec } from "../../wire-codec-9VhIKnyp.mjs";
3
+ //#region src/rpc/transports/sse-client.ts
4
+ function NOOP() {}
5
+ /**
6
+ * Build a birpc `ChannelOptions` object backed by an SSE stream (server →
7
+ * client) and HTTP `POST` (client → server) — the WebSocket-free counterpart
8
+ * to `createWsRpcChannel`, wire-compatible with `attachSseRpcTransport`.
9
+ *
10
+ * The stream is consumed via `fetch` streaming (not `EventSource`), so it
11
+ * works in browsers and server runtimes alike. The server's first frame
12
+ * (`event: session`) carries the session id; every `POST` echoes it in the
13
+ * `x-birpc-session` header. A response to a client-initiated request comes
14
+ * back in the `POST`'s own body and is re-injected into the channel; a
15
+ * dropped stream terminates the channel — there is no reconnect, matching
16
+ * the WS channel's closed-is-done semantics.
17
+ */
18
+ function createSseRpcChannel(options) {
19
+ const { onConnected = NOOP, onError = NOOP, onDisconnected = NOOP, definitions, fetch: fetchImpl = globalThis.fetch.bind(globalThis) } = options;
20
+ let url = options.url;
21
+ if (options.authToken) url = `${url}${url.includes("?") ? "&" : "?"}${DEVFRAME_AUTH_TOKEN_QUERY_PARAM}=${encodeURIComponent(options.authToken)}`;
22
+ const codec = createRpcWireCodec(definitions);
23
+ const abort = new AbortController();
24
+ let closed = false;
25
+ let onMessage;
26
+ let activeReader;
27
+ let resolveSession;
28
+ let rejectSession;
29
+ const sessionReady = new Promise((resolve, reject) => {
30
+ resolveSession = resolve;
31
+ rejectSession = reject;
32
+ });
33
+ sessionReady.catch(() => {});
34
+ function fail(error) {
35
+ if (closed) return;
36
+ closed = true;
37
+ rejectSession(error);
38
+ onError(error);
39
+ onDisconnected();
40
+ }
41
+ function end() {
42
+ if (closed) return;
43
+ closed = true;
44
+ rejectSession(/* @__PURE__ */ new Error("Devframe SSE stream closed"));
45
+ onDisconnected();
46
+ }
47
+ function dispatch(event, data) {
48
+ if (event === "session") {
49
+ resolveSession(data);
50
+ onConnected();
51
+ return;
52
+ }
53
+ onMessage?.(data);
54
+ }
55
+ /** Parse the SSE byte stream frame by frame and dispatch each event. */
56
+ async function consume(stream) {
57
+ const reader = stream.getReader();
58
+ activeReader = reader;
59
+ const decoder = new TextDecoder();
60
+ let buffer = "";
61
+ for (;;) {
62
+ const { done, value } = await reader.read();
63
+ if (done) break;
64
+ buffer += decoder.decode(value, { stream: true });
65
+ for (;;) {
66
+ const boundary = buffer.search(/\n\n|\r\n\r\n/);
67
+ if (boundary < 0) break;
68
+ const frame = buffer.slice(0, boundary);
69
+ buffer = buffer.slice(boundary + (buffer[boundary] === "\r" ? 4 : 2));
70
+ let event = "message";
71
+ const data = [];
72
+ for (const rawLine of frame.split(/\r?\n/)) {
73
+ if (rawLine.startsWith(":")) continue;
74
+ if (rawLine.startsWith("event:")) event = rawLine.slice(6).trimStart();
75
+ else if (rawLine.startsWith("data:")) data.push(rawLine.slice(5).replace(/^ /, ""));
76
+ }
77
+ if (data.length > 0) dispatch(event, data.join("\n"));
78
+ }
79
+ }
80
+ end();
81
+ }
82
+ (async () => {
83
+ try {
84
+ const response = await fetchImpl(url, {
85
+ headers: { accept: "text/event-stream" },
86
+ signal: abort.signal
87
+ });
88
+ if (!response.ok || !response.body) throw new Error(`Devframe SSE stream request failed: ${response.status}`);
89
+ await consume(response.body);
90
+ } catch (error) {
91
+ if (abort.signal.aborted) {
92
+ end();
93
+ return;
94
+ }
95
+ fail(error instanceof Error ? error : new Error(String(error)));
96
+ }
97
+ })();
98
+ return {
99
+ close: () => {
100
+ closed = true;
101
+ abort.abort();
102
+ activeReader?.cancel().catch(() => {});
103
+ },
104
+ on: (handler) => {
105
+ onMessage = handler;
106
+ },
107
+ post: async (data) => {
108
+ let sessionId;
109
+ try {
110
+ sessionId = await sessionReady;
111
+ } catch {
112
+ return;
113
+ }
114
+ if (closed) {
115
+ onError(/* @__PURE__ */ new Error("Devframe SSE channel is closed; message dropped"));
116
+ return;
117
+ }
118
+ try {
119
+ const response = await fetchImpl(url, {
120
+ method: "POST",
121
+ headers: {
122
+ "content-type": "text/plain; charset=utf-8",
123
+ [DEVFRAME_SSE_SESSION_HEADER]: sessionId
124
+ },
125
+ body: data
126
+ });
127
+ if (response.status === 200) {
128
+ const body = await response.text();
129
+ if (body) onMessage?.(body);
130
+ return;
131
+ }
132
+ if (!response.ok) throw new Error(`Devframe SSE POST failed: ${response.status}`);
133
+ } catch (error) {
134
+ onError(error instanceof Error ? error : new Error(String(error)));
135
+ }
136
+ },
137
+ serialize: codec.serialize,
138
+ deserialize: codec.deserialize
139
+ };
140
+ }
141
+ //#endregion
142
+ export { createSseRpcChannel };
@@ -0,0 +1,61 @@
1
+ import { v as RpcFunctionDefinitionAny } from "../../types-CnJSgRVa.mjs";
2
+ import { d as DevframeRpcConnection, n as WsOriginRegistry, u as DevframeNodeRpcSessionMeta } from "../../ws-server-Ces08fjP.mjs";
3
+ import { BirpcGroup } from "birpc";
4
+ //#region src/rpc/transports/sse-server.d.ts
5
+ interface SseRpcTransportOptions {
6
+ /**
7
+ * Same contract as `WsRpcTransportOptions.allowedOrigins`: extra origins
8
+ * to accept beyond the loopback default, a live origin registry, or
9
+ * `false` to disable the gate. Applied to the stream `GET`, the RPC
10
+ * `POST`, and the CORS preflight alike.
11
+ */
12
+ allowedOrigins?: readonly string[] | WsOriginRegistry | false;
13
+ /**
14
+ * RPC function definitions, used by the per-call wire serializer to
15
+ * dispatch between strict-JSON and structured-clone encoding based on
16
+ * each function's `jsonSerializable` flag. When omitted, all messages
17
+ * fall back to structured-clone — same contract as the WS transport.
18
+ */
19
+ definitions?: ReadonlyMap<string, Pick<RpcFunctionDefinitionAny, 'jsonSerializable'>>;
20
+ onConnected?: (connection: DevframeRpcConnection, meta: DevframeNodeRpcSessionMeta) => void;
21
+ onDisconnected?: (connection: DevframeRpcConnection, meta: DevframeNodeRpcSessionMeta) => void;
22
+ /**
23
+ * Milliseconds between keep-alive comment frames (`: ping`) written to
24
+ * every open stream, so intermediaries (reverse proxies, tunnels) don't
25
+ * drop an idle connection. `0` disables. Default: `30_000`.
26
+ */
27
+ keepAliveInterval?: number;
28
+ /** Mint a session id. Default: `crypto.randomUUID`. */
29
+ generateSessionId?: () => string;
30
+ }
31
+ interface SseRpcTransport {
32
+ /**
33
+ * Web-standard handler for the SSE route: `GET` opens the event stream,
34
+ * `POST` carries RPC frames (session id in the `x-birpc-session` header),
35
+ * `OPTIONS` answers the CORS preflight for registered cross-origin
36
+ * viewers. Mount it on the route the connection meta advertises.
37
+ */
38
+ handler: (request: Request) => Promise<Response> | Response;
39
+ /** Number of currently connected SSE sessions. */
40
+ sessionCount: () => number;
41
+ /** Terminate every open stream and detach their birpc channels. */
42
+ close: () => void;
43
+ }
44
+ /**
45
+ * Attach an SSE + HTTP POST transport to an existing RPC group — the
46
+ * WebSocket-free counterpart to `attachWsRpcTransport`, for hosts and
47
+ * proxies where the upgrade isn't available. Speaks the same birpc wire
48
+ * protocol (one channel per session, per-method `jsonSerializable`
49
+ * dispatch) over a half-duplex pair, mirroring birpc's own SSE helpers:
50
+ *
51
+ * - the server mints a session id as the stream's first frame
52
+ * (`event: session`);
53
+ * - every client `POST` echoes it in the `x-birpc-session` header;
54
+ * - a client-initiated request's response comes back in that `POST`'s
55
+ * own body (parked until the handler answers);
56
+ * - server-initiated calls/events stream down as `event: message`
57
+ * frames, and the client's replies up via `POST` get a bare `202`.
58
+ */
59
+ declare function attachSseRpcTransport<ClientFunctions extends object, ServerFunctions extends object>(rpcGroup: BirpcGroup<ClientFunctions, ServerFunctions, false>, options?: SseRpcTransportOptions): SseRpcTransport;
60
+ //#endregion
61
+ export { SseRpcTransport, SseRpcTransportOptions, attachSseRpcTransport };
@@ -0,0 +1,209 @@
1
+ import { DEVFRAME_SSE_SESSION_HEADER } from "../../constants.mjs";
2
+ import { n as peekRpcWireFrame, t as createRpcWireCodec } from "../../wire-codec-9VhIKnyp.mjs";
3
+ import { i as isAllowedOrigin, o as createRpcSessionMeta } from "../../ws-server-Bk1hOG3X.mjs";
4
+ //#region src/rpc/transports/sse-server.ts
5
+ const SSE_STREAM_HEADERS = {
6
+ "content-type": "text/event-stream",
7
+ "cache-control": "no-cache, no-transform",
8
+ "x-accel-buffering": "no"
9
+ };
10
+ function NOOP() {}
11
+ /**
12
+ * Attach an SSE + HTTP POST transport to an existing RPC group — the
13
+ * WebSocket-free counterpart to `attachWsRpcTransport`, for hosts and
14
+ * proxies where the upgrade isn't available. Speaks the same birpc wire
15
+ * protocol (one channel per session, per-method `jsonSerializable`
16
+ * dispatch) over a half-duplex pair, mirroring birpc's own SSE helpers:
17
+ *
18
+ * - the server mints a session id as the stream's first frame
19
+ * (`event: session`);
20
+ * - every client `POST` echoes it in the `x-birpc-session` header;
21
+ * - a client-initiated request's response comes back in that `POST`'s
22
+ * own body (parked until the handler answers);
23
+ * - server-initiated calls/events stream down as `event: message`
24
+ * frames, and the client's replies up via `POST` get a bare `202`.
25
+ */
26
+ function attachSseRpcTransport(rpcGroup, options = {}) {
27
+ const { allowedOrigins, definitions, onConnected = NOOP, onDisconnected = NOOP, keepAliveInterval = 3e4, generateSessionId = () => crypto.randomUUID() } = options;
28
+ const sessions = /* @__PURE__ */ new Map();
29
+ const encoder = new TextEncoder();
30
+ function originAllowed(request) {
31
+ if (allowedOrigins === false) return true;
32
+ const origin = request.headers.get("origin") ?? void 0;
33
+ return allowedOrigins && !Array.isArray(allowedOrigins) ? allowedOrigins.isAllowed(origin) : isAllowedOrigin(origin, allowedOrigins ?? []);
34
+ }
35
+ /**
36
+ * CORS headers for a browser on another (allowed) origin — the side-car /
37
+ * registered-viewer case. Same-origin requests carry no `Origin` (or their
38
+ * own), where these headers are inert.
39
+ */
40
+ function corsHeaders(request) {
41
+ const origin = request.headers.get("origin");
42
+ if (!origin) return {};
43
+ return {
44
+ "access-control-allow-origin": origin,
45
+ "vary": "origin"
46
+ };
47
+ }
48
+ function openStream(request) {
49
+ const meta = createRpcSessionMeta();
50
+ const id = generateSessionId();
51
+ const codec = createRpcWireCodec(definitions);
52
+ let controller;
53
+ let keepAlive;
54
+ let closeSession = () => {};
55
+ function write(text) {
56
+ try {
57
+ controller?.enqueue(encoder.encode(text));
58
+ } catch {
59
+ closeSession();
60
+ }
61
+ }
62
+ function writeEvent(event, data) {
63
+ write(`event: ${event}\n${data.split("\n").map((line) => `data: ${line}`).join("\n")}\n\n`);
64
+ }
65
+ const session = {
66
+ id,
67
+ meta,
68
+ connection: void 0,
69
+ channel: void 0,
70
+ parked: /* @__PURE__ */ new Map(),
71
+ closed: false,
72
+ close: () => {
73
+ if (session.closed) return;
74
+ session.closed = true;
75
+ if (keepAlive) clearInterval(keepAlive);
76
+ sessions.delete(id);
77
+ for (const resolve of session.parked.values()) resolve(null);
78
+ session.parked.clear();
79
+ try {
80
+ controller?.close();
81
+ } catch {}
82
+ rpcGroup.updateChannels((channels) => {
83
+ const index = channels.indexOf(session.channel);
84
+ if (index >= 0) channels.splice(index, 1);
85
+ });
86
+ onDisconnected(session.connection, meta);
87
+ }
88
+ };
89
+ closeSession = session.close;
90
+ const connection = {
91
+ id: meta.id,
92
+ transport: "sse",
93
+ request,
94
+ send: (data) => writeEvent("message", data),
95
+ close: () => session.close()
96
+ };
97
+ session.connection = connection;
98
+ const channel = {
99
+ post: (data) => {
100
+ const { t, i } = peekRpcWireFrame(data);
101
+ if (t === "s" && i) {
102
+ const resolve = session.parked.get(i);
103
+ if (resolve) {
104
+ session.parked.delete(i);
105
+ resolve(data);
106
+ return;
107
+ }
108
+ }
109
+ writeEvent("message", data);
110
+ },
111
+ on: (fn) => {
112
+ session.onMessage = fn;
113
+ },
114
+ serialize: codec.serialize,
115
+ deserialize: codec.deserialize,
116
+ meta
117
+ };
118
+ session.channel = channel;
119
+ const body = new ReadableStream({
120
+ start(c) {
121
+ controller = c;
122
+ sessions.set(id, session);
123
+ rpcGroup.updateChannels((channels) => {
124
+ channels.push(channel);
125
+ });
126
+ write(`event: session\ndata: ${id}\n\n`);
127
+ if (keepAliveInterval > 0) {
128
+ keepAlive = setInterval(write, keepAliveInterval, ": ping\n\n");
129
+ keepAlive.unref?.();
130
+ }
131
+ onConnected(connection, meta);
132
+ },
133
+ cancel() {
134
+ session.close();
135
+ }
136
+ });
137
+ return new Response(body, {
138
+ status: 200,
139
+ headers: {
140
+ ...SSE_STREAM_HEADERS,
141
+ ...corsHeaders(request)
142
+ }
143
+ });
144
+ }
145
+ async function handlePost(request) {
146
+ const id = request.headers.get(DEVFRAME_SSE_SESSION_HEADER);
147
+ const session = id ? sessions.get(id) : void 0;
148
+ if (!session || session.closed) return new Response("Unknown SSE session", {
149
+ status: 400,
150
+ headers: corsHeaders(request)
151
+ });
152
+ const frame = await request.text();
153
+ const { t, i } = peekRpcWireFrame(frame);
154
+ if (t === "q" && i) {
155
+ const parked = new Promise((resolve) => {
156
+ session.parked.set(i, resolve);
157
+ });
158
+ session.onMessage?.(frame);
159
+ const responseBody = await parked;
160
+ if (responseBody == null) return new Response("SSE session closed before the response settled", {
161
+ status: 409,
162
+ headers: corsHeaders(request)
163
+ });
164
+ return new Response(responseBody, {
165
+ status: 200,
166
+ headers: {
167
+ "content-type": "text/plain; charset=utf-8",
168
+ ...corsHeaders(request)
169
+ }
170
+ });
171
+ }
172
+ session.onMessage?.(frame);
173
+ return new Response(null, {
174
+ status: 202,
175
+ headers: corsHeaders(request)
176
+ });
177
+ }
178
+ function handlePreflight(request) {
179
+ return new Response(null, {
180
+ status: 204,
181
+ headers: {
182
+ "access-control-allow-methods": "GET, POST, OPTIONS",
183
+ "access-control-allow-headers": `content-type, ${DEVFRAME_SSE_SESSION_HEADER}`,
184
+ "access-control-max-age": "86400",
185
+ ...corsHeaders(request)
186
+ }
187
+ });
188
+ }
189
+ return {
190
+ handler: (request) => {
191
+ if (!originAllowed(request)) return new Response("Forbidden", { status: 403 });
192
+ switch (request.method) {
193
+ case "GET": return openStream(request);
194
+ case "POST": return handlePost(request);
195
+ case "OPTIONS": return handlePreflight(request);
196
+ default: return new Response("Method Not Allowed", {
197
+ status: 405,
198
+ headers: { allow: "GET, POST, OPTIONS" }
199
+ });
200
+ }
201
+ },
202
+ sessionCount: () => sessions.size,
203
+ close: () => {
204
+ for (const session of [...sessions.values()]) session.close();
205
+ }
206
+ };
207
+ }
208
+ //#endregion
209
+ export { attachSseRpcTransport };
@@ -0,0 +1,35 @@
1
+ import { n as WsOriginRegistry } from "../../ws-server-Ces08fjP.mjs";
2
+ import { t as ContextRpcServer } from "../../rpc-core-CGctXToO.mjs";
3
+ //#region src/rpc/transports/ws-bun.d.ts
4
+ interface AttachBunWsTransportOptions {
5
+ /** Same contract as `WsRpcTransportOptions.allowedOrigins`. */
6
+ allowedOrigins?: readonly string[] | WsOriginRegistry | false;
7
+ }
8
+ /**
9
+ * Structural view of the Bun `Bun.serve({ websocket })` handler object the
10
+ * crossws Bun adapter produces — typed loosely so devframe carries no
11
+ * dependency on Bun's own types.
12
+ */
13
+ interface BunWsTierWebSocket {
14
+ open?: (ws: unknown) => unknown;
15
+ message: (ws: unknown, message: unknown) => unknown;
16
+ close?: (ws: unknown, code?: number, reason?: string) => unknown;
17
+ drain?: (ws: unknown) => unknown;
18
+ }
19
+ interface BunWsTier {
20
+ /** Complete a WS upgrade request — `Bun.serve`'s server as 2nd argument. */
21
+ handleUpgrade: (request: Request, server: unknown) => Promise<Response | undefined>;
22
+ /** The handlers to spread into `Bun.serve({ websocket })`. */
23
+ websocket: BunWsTierWebSocket;
24
+ close: () => Promise<void>;
25
+ }
26
+ /**
27
+ * The Bun fetch-upgrade WebSocket tier for `initDevframe` / `initHub` — the
28
+ * same RPC peer wiring as `attachWsRpcTransport`, driven by crossws's Bun
29
+ * adapter so upgrades complete through `handler(request, server)` on the
30
+ * app's own origin, with no side-car server. Load it dynamically so the Bun
31
+ * adapter never enters a Node-only bundle path.
32
+ */
33
+ declare function attachBunWsTransport(core: ContextRpcServer, options?: AttachBunWsTransportOptions): Promise<BunWsTier>;
34
+ //#endregion
35
+ export { AttachBunWsTransportOptions, BunWsTier, BunWsTierWebSocket, attachBunWsTransport };
@@ -0,0 +1,31 @@
1
+ import { i as isAllowedOrigin, r as createWsRpcPeerHooks } from "../../ws-server-Bk1hOG3X.mjs";
2
+ //#region src/rpc/transports/ws-bun.ts
3
+ /**
4
+ * The Bun fetch-upgrade WebSocket tier for `initDevframe` / `initHub` — the
5
+ * same RPC peer wiring as `attachWsRpcTransport`, driven by crossws's Bun
6
+ * adapter so upgrades complete through `handler(request, server)` on the
7
+ * app's own origin, with no side-car server. Load it dynamically so the Bun
8
+ * adapter never enters a Node-only bundle path.
9
+ */
10
+ async function attachBunWsTransport(core, options = {}) {
11
+ const { default: bunAdapter } = await import("crossws/adapters/bun");
12
+ const { allowedOrigins } = options;
13
+ const ws = bunAdapter({ hooks: {
14
+ ...createWsRpcPeerHooks(core.rpcGroup, {
15
+ onConnected: core.onConnected,
16
+ onDisconnected: core.onDisconnected
17
+ }),
18
+ upgrade(request) {
19
+ const origin = request.headers.get("origin") ?? void 0;
20
+ const allowed = allowedOrigins && !Array.isArray(allowedOrigins) ? allowedOrigins.isAllowed(origin) : isAllowedOrigin(origin, allowedOrigins || []);
21
+ if (allowedOrigins !== false && !allowed) return new Response("Forbidden", { status: 403 });
22
+ }
23
+ } });
24
+ return {
25
+ handleUpgrade: (request, server) => ws.handleUpgrade(request, server),
26
+ websocket: ws.websocket,
27
+ close: () => ws.close()
28
+ };
29
+ }
30
+ //#endregion
31
+ export { attachBunWsTransport };
@@ -1,2 +1,2 @@
1
- import { n as createWsRpcChannel, t as WsRpcChannelOptions } from "../../ws-client-B6Tnr0ic.mjs";
1
+ import { n as createWsRpcChannel, t as WsRpcChannelOptions } from "../../ws-client-CdNuATYa.mjs";
2
2
  export { WsRpcChannelOptions, createWsRpcChannel };
@@ -1,12 +1,14 @@
1
1
  import { DEVFRAME_AUTH_TOKEN_QUERY_PARAM } from "../../constants.mjs";
2
- import { n as strictJsonStringify } from "../../serialization-C8Mnw9hK.mjs";
3
- import { n as structuredCloneStringify, t as structuredCloneParse } from "../../structured-clone-CbAV5rFI.mjs";
2
+ import { t as createRpcWireCodec } from "../../wire-codec-9VhIKnyp.mjs";
4
3
  //#region src/rpc/transports/ws-client.ts
5
4
  function NOOP() {}
6
5
  const EMPTY_DEFS = /* @__PURE__ */ new Map();
7
6
  /**
8
7
  * Build a birpc `ChannelOptions` object backed by a browser `WebSocket`.
9
8
  * Pass the result straight to `createRpcClient`'s `channel` option.
9
+ *
10
+ * Also returns `close()`, closing the underlying socket — mirroring the server transport's
11
+ * existing `WsRpcTransport.close()`. `birpc`'s own `ChannelOptions` has no teardown of its own.
10
12
  */
11
13
  function createWsRpcChannel(options) {
12
14
  let url = options.url;
@@ -23,8 +25,11 @@ function createWsRpcChannel(options) {
23
25
  ws.addEventListener("close", (e) => {
24
26
  onDisconnected(e);
25
27
  });
26
- const pendingRequestMethods = /* @__PURE__ */ new Map();
28
+ const codec = createRpcWireCodec(definitions);
27
29
  return {
30
+ close: () => {
31
+ ws.close();
32
+ },
28
33
  on: (handler) => {
29
34
  ws.addEventListener("message", (e) => {
30
35
  handler(e.data);
@@ -51,21 +56,8 @@ function createWsRpcChannel(options) {
51
56
  }
52
57
  onError(/* @__PURE__ */ new Error("Devframe WebSocket is not open; message dropped"));
53
58
  },
54
- serialize: (msg) => {
55
- let method;
56
- if (msg.t === "q") method = msg.m;
57
- else {
58
- method = pendingRequestMethods.get(msg.i);
59
- pendingRequestMethods.delete(msg.i);
60
- }
61
- if (!(msg.t === "s" && "e" in msg) && !!method && definitions.get(method)?.jsonSerializable === true) return strictJsonStringify(msg, method ?? "");
62
- return `s:${structuredCloneStringify(msg)}`;
63
- },
64
- deserialize: (raw) => {
65
- const msg = raw.startsWith("s:") ? structuredCloneParse(raw.slice(2)) : JSON.parse(raw);
66
- if (msg.t === "q" && msg.i && msg.m) pendingRequestMethods.set(msg.i, msg.m);
67
- return msg;
68
- }
59
+ serialize: codec.serialize,
60
+ deserialize: codec.deserialize
69
61
  };
70
62
  }
71
63
  //#endregion
@@ -1,2 +1,2 @@
1
- import { a as WsRpcTransportOptions, c as isAllowedOrigin, i as WsRpcTransport, l as isLoopbackHostname, n as DevframeNodeRpcSessionMeta, o as attachWsRpcTransport, r as WsOriginRegistry, s as createWsOriginRegistry, t as CreateWsOriginRegistryOptions } from "../../ws-server-uKQcClcJ.mjs";
2
- export { CreateWsOriginRegistryOptions, DevframeNodeRpcSessionMeta, WsOriginRegistry, WsRpcTransport, WsRpcTransportOptions, attachWsRpcTransport, createWsOriginRegistry, isAllowedOrigin, isLoopbackHostname };
1
+ import { a as attachWsRpcTransport, c as isAllowedOrigin, d as DevframeRpcConnection, f as DevframeRpcConnectionRequest, i as WsRpcTransportOptions, l as isLoopbackHostname, n as WsOriginRegistry, o as createWsOriginRegistry, p as DevframeRpcTransportKind, r as WsRpcTransport, s as createWsRpcPeerHooks, t as CreateWsOriginRegistryOptions, u as DevframeNodeRpcSessionMeta } from "../../ws-server-Ces08fjP.mjs";
2
+ export { CreateWsOriginRegistryOptions, type DevframeNodeRpcSessionMeta, type DevframeRpcConnection, type DevframeRpcConnectionRequest, type DevframeRpcTransportKind, WsOriginRegistry, WsRpcTransport, WsRpcTransportOptions, attachWsRpcTransport, createWsOriginRegistry, createWsRpcPeerHooks, isAllowedOrigin, isLoopbackHostname };