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,2 +1,233 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "../../constants.mjs";
|
|
2
|
+
import { t as createRpcWireCodec } from "../../wire-codec-BHGt0sSs.mjs";
|
|
3
|
+
import { n as isLoopbackHostname, t as isAllowedOrigin } from "../../origin-DZit9g29.mjs";
|
|
4
|
+
import { t as createRpcSessionMeta } from "../../session-DPfj6zQG.mjs";
|
|
5
|
+
import { n as randomToken, r as timingSafeEqual } from "../../crypto-token-TVbGGFuj.mjs";
|
|
6
|
+
import { createServer } from "node:http";
|
|
7
|
+
import { createServer as createServer$1 } from "node:https";
|
|
8
|
+
import crossws from "crossws/adapters/node";
|
|
9
|
+
//#region src/rpc/transports/ws-server.ts
|
|
10
|
+
/**
|
|
11
|
+
* Create a live, token-protected origin allowlist for external browser
|
|
12
|
+
* viewers. Pass it to {@link WsRpcTransportOptions.allowedOrigins}, then use
|
|
13
|
+
* `registerFromUrl()` in the connection metadata handler to authorize a
|
|
14
|
+
* viewer without sharing a mutable array or disabling DNS-rebinding protection.
|
|
15
|
+
*/
|
|
16
|
+
function createWsOriginRegistry(options = {}) {
|
|
17
|
+
const token = randomToken();
|
|
18
|
+
const origins = new Set(options.allowedOrigins ?? []);
|
|
19
|
+
function normalizeOrigin(origin) {
|
|
20
|
+
if (!origin) return;
|
|
21
|
+
try {
|
|
22
|
+
const url = new URL(origin);
|
|
23
|
+
const normalized = url.origin === "null" ? `${url.protocol}//${url.host}` : url.origin;
|
|
24
|
+
return origin === normalized ? normalized : void 0;
|
|
25
|
+
} catch {}
|
|
26
|
+
}
|
|
27
|
+
function registerOrigin(origin, candidateToken) {
|
|
28
|
+
const normalized = normalizeOrigin(origin);
|
|
29
|
+
if (!normalized || !candidateToken || !timingSafeEqual(token, candidateToken)) return false;
|
|
30
|
+
if (options.validateOrigin && !options.validateOrigin(normalized)) return false;
|
|
31
|
+
origins.add(normalized);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
token,
|
|
36
|
+
registerFromUrl(url) {
|
|
37
|
+
let parsed;
|
|
38
|
+
try {
|
|
39
|
+
parsed = new URL(url, "http://localhost");
|
|
40
|
+
} catch {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const origin = parsed.searchParams.get("devframe_viewer_origin") ?? void 0;
|
|
44
|
+
return registerOrigin(origin, parsed.searchParams.get("devframe_viewer_origin_token") ?? void 0) ? origin : void 0;
|
|
45
|
+
},
|
|
46
|
+
isAllowed(origin) {
|
|
47
|
+
return isAllowedOrigin(origin, [...origins]);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const EMPTY_DEFS = /* @__PURE__ */ new Map();
|
|
52
|
+
function NOOP() {}
|
|
53
|
+
function listen(server, port, host) {
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
const onError = (error) => reject(error);
|
|
56
|
+
server.once("error", onError);
|
|
57
|
+
try {
|
|
58
|
+
server.listen(port, host, () => {
|
|
59
|
+
server.off("error", onError);
|
|
60
|
+
resolve();
|
|
61
|
+
});
|
|
62
|
+
} catch (error) {
|
|
63
|
+
server.off("error", onError);
|
|
64
|
+
reject(error);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** Compare two URL paths ignoring a trailing slash. */
|
|
69
|
+
function pathMatches(a, b) {
|
|
70
|
+
const strip = (p) => p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
|
|
71
|
+
return strip(a) === strip(b);
|
|
72
|
+
}
|
|
73
|
+
function isWsOriginRegistry(value) {
|
|
74
|
+
return !!value && !Array.isArray(value);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Build the `upgrade` listener that hands a request to the crossws adapter,
|
|
78
|
+
* optionally filtered to a single `path`. Non-matching requests are left
|
|
79
|
+
* untouched so other upgrade listeners (e.g. a Vite dev server's HMR socket)
|
|
80
|
+
* can claim them, unless `destroyUnmatched` is set.
|
|
81
|
+
*/
|
|
82
|
+
function createUpgradeListener(ws, path, destroyUnmatched, allowedOrigins) {
|
|
83
|
+
return (req, socket, head) => {
|
|
84
|
+
socket.on("error", () => {});
|
|
85
|
+
if (path) {
|
|
86
|
+
let pathname = req.url ?? "/";
|
|
87
|
+
try {
|
|
88
|
+
pathname = new URL(req.url ?? "/", "http://localhost").pathname;
|
|
89
|
+
} catch {}
|
|
90
|
+
if (!pathMatches(pathname, path)) {
|
|
91
|
+
if (destroyUnmatched) {
|
|
92
|
+
socket.write("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
|
|
93
|
+
socket.destroy();
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const originAllowed = isWsOriginRegistry(allowedOrigins) ? allowedOrigins.isAllowed(req.headers.origin) : isAllowedOrigin(req.headers.origin, allowedOrigins || []);
|
|
99
|
+
if (allowedOrigins !== false && !originAllowed) {
|
|
100
|
+
socket.write("HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n");
|
|
101
|
+
socket.destroy();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
ws.handleUpgrade(req, socket, head);
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The per-peer lifecycle hooks driving a devframe RPC WebSocket, shaped for
|
|
109
|
+
* any [crossws](https://crossws.h3.dev) adapter. {@link attachWsRpcTransport}
|
|
110
|
+
* feeds them to the Node adapter; runtime-specific attachments (e.g. Bun's
|
|
111
|
+
* fetch-upgrade adapter) reuse the same hooks so every transport speaks the
|
|
112
|
+
* identical wire protocol: one birpc channel per peer, per-method
|
|
113
|
+
* `jsonSerializable` dispatch between strict JSON and structured-clone.
|
|
114
|
+
*/
|
|
115
|
+
function createWsRpcPeerHooks(rpcGroup, options = {}) {
|
|
116
|
+
const { onConnected = NOOP, onDisconnected = NOOP, definitions = EMPTY_DEFS, serialize: serializeOverride, deserialize: deserializeOverride } = options;
|
|
117
|
+
const states = /* @__PURE__ */ new WeakMap();
|
|
118
|
+
return {
|
|
119
|
+
open: (peer) => {
|
|
120
|
+
const meta = createRpcSessionMeta();
|
|
121
|
+
meta.peer = peer;
|
|
122
|
+
const connection = {
|
|
123
|
+
id: meta.id,
|
|
124
|
+
transport: "websocket",
|
|
125
|
+
request: peer.request,
|
|
126
|
+
send: (data) => peer.send(data),
|
|
127
|
+
close: (code, reason) => peer.close(code, reason),
|
|
128
|
+
peer
|
|
129
|
+
};
|
|
130
|
+
const codec = createRpcWireCodec(definitions);
|
|
131
|
+
const state = {
|
|
132
|
+
meta,
|
|
133
|
+
connection
|
|
134
|
+
};
|
|
135
|
+
const channel = {
|
|
136
|
+
post: (data) => {
|
|
137
|
+
peer.send(data);
|
|
138
|
+
},
|
|
139
|
+
on: (fn) => {
|
|
140
|
+
state.onMessage = fn;
|
|
141
|
+
},
|
|
142
|
+
serialize: serializeOverride ?? codec.serialize,
|
|
143
|
+
deserialize: deserializeOverride ?? codec.deserialize,
|
|
144
|
+
meta
|
|
145
|
+
};
|
|
146
|
+
state.channel = channel;
|
|
147
|
+
states.set(peer, state);
|
|
148
|
+
rpcGroup.updateChannels((channels) => {
|
|
149
|
+
channels.push(channel);
|
|
150
|
+
});
|
|
151
|
+
onConnected(connection, meta);
|
|
152
|
+
},
|
|
153
|
+
message: (peer, message) => {
|
|
154
|
+
states.get(peer)?.onMessage?.(message.text());
|
|
155
|
+
},
|
|
156
|
+
close: (peer) => {
|
|
157
|
+
const state = states.get(peer);
|
|
158
|
+
if (!state) return;
|
|
159
|
+
states.delete(peer);
|
|
160
|
+
rpcGroup.updateChannels((channels) => {
|
|
161
|
+
const index = state.channel ? channels.indexOf(state.channel) : -1;
|
|
162
|
+
if (index >= 0) channels.splice(index, 1);
|
|
163
|
+
});
|
|
164
|
+
onDisconnected(state.connection, state.meta);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Attach a WebSocket transport to an existing RPC group, powered by
|
|
170
|
+
* [crossws](https://crossws.h3.dev). Either attach to an existing HTTP(S)
|
|
171
|
+
* `server` (sharing its port, optionally scoped to a `path`), or let this
|
|
172
|
+
* helper create a standalone server from `port` / `host` / `https`.
|
|
173
|
+
*
|
|
174
|
+
* Returns the crossws node adapter, standalone-server readiness/address
|
|
175
|
+
* accessors, `detach` (remove the upgrade listener from a shared `server`),
|
|
176
|
+
* and `close` (full deterministic teardown).
|
|
177
|
+
*/
|
|
178
|
+
function attachWsRpcTransport(rpcGroup, options = {}) {
|
|
179
|
+
const { server, port, host = "localhost", path, destroyUnmatched = false, unbound, https, allowedOrigins } = options;
|
|
180
|
+
const ws = crossws({ hooks: createWsRpcPeerHooks(rpcGroup, options) });
|
|
181
|
+
const sharedUpgradeListener = createUpgradeListener(ws, path, destroyUnmatched, allowedOrigins);
|
|
182
|
+
const ownedUpgradeListener = createUpgradeListener(ws, path, true, allowedOrigins);
|
|
183
|
+
/** Bind a server's `upgrade` events, tracked so `close()` detaches them. */
|
|
184
|
+
const attachments = /* @__PURE__ */ new Set();
|
|
185
|
+
function attachTo(target, listener) {
|
|
186
|
+
target.on("upgrade", listener);
|
|
187
|
+
const detachOne = () => {
|
|
188
|
+
target.off("upgrade", listener);
|
|
189
|
+
attachments.delete(detachOne);
|
|
190
|
+
};
|
|
191
|
+
attachments.add(detachOne);
|
|
192
|
+
return detachOne;
|
|
193
|
+
}
|
|
194
|
+
let ready = Promise.resolve();
|
|
195
|
+
let ownedServer;
|
|
196
|
+
if (unbound) {} else if (server) attachTo(server, sharedUpgradeListener);
|
|
197
|
+
else if (https) {
|
|
198
|
+
ownedServer = createServer$1(https);
|
|
199
|
+
attachTo(ownedServer, ownedUpgradeListener);
|
|
200
|
+
ready = listen(ownedServer, port ?? 0, host);
|
|
201
|
+
} else {
|
|
202
|
+
ownedServer = createServer((_req, res) => {
|
|
203
|
+
res.writeHead(426, { "content-type": "text/plain" });
|
|
204
|
+
res.end("Upgrade Required");
|
|
205
|
+
});
|
|
206
|
+
attachTo(ownedServer, ownedUpgradeListener);
|
|
207
|
+
ready = listen(ownedServer, port ?? 0, host);
|
|
208
|
+
}
|
|
209
|
+
const activeServer = server ?? ownedServer;
|
|
210
|
+
function detachAll() {
|
|
211
|
+
for (const detachOne of [...attachments]) detachOne();
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
ws,
|
|
215
|
+
ready,
|
|
216
|
+
address: () => activeServer?.address() ?? null,
|
|
217
|
+
handleUpgrade: sharedUpgradeListener,
|
|
218
|
+
attach: (target) => attachTo(target, sharedUpgradeListener),
|
|
219
|
+
detach: detachAll,
|
|
220
|
+
async close() {
|
|
221
|
+
detachAll();
|
|
222
|
+
ws.closeAll(void 0, void 0, true);
|
|
223
|
+
if (ownedServer) {
|
|
224
|
+
const srv = ownedServer;
|
|
225
|
+
await ready.catch(() => {});
|
|
226
|
+
if (!srv.listening) return;
|
|
227
|
+
await new Promise((r) => srv.close(() => r()));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
//#endregion
|
|
2
233
|
export { attachWsRpcTransport, createWsOriginRegistry, createWsRpcPeerHooks, isAllowedOrigin, isLoopbackHostname };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { n as __exportAll } from "./rolldown-runtime-B4iAMlE-.mjs";
|
|
2
2
|
import { createRpcServer } from "./rpc/server.mjs";
|
|
3
|
-
import { t as diagnostics } from "./diagnostics-
|
|
3
|
+
import { t as diagnostics } from "./diagnostics-DZA6KQ_Z.mjs";
|
|
4
4
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
5
|
//#region src/node/rpc-core.ts
|
|
6
6
|
var rpc_core_exports = /* @__PURE__ */ __exportAll({ createContextRpcServer: () => createContextRpcServer });
|
|
7
7
|
/**
|
|
8
8
|
* Bind a devframe context's registered RPC functions to a birpc group,
|
|
9
|
-
* transport-agnostically
|
|
9
|
+
* transport-agnostically: the shared core under the instance shell's own
|
|
10
10
|
* HTTP+WS binding (Node http + WS) and the Bun fetch-upgrade tier of
|
|
11
11
|
* `createHandler`.
|
|
12
12
|
*
|
|
@@ -26,8 +26,20 @@ function createContextRpcServer(options) {
|
|
|
26
26
|
for (const fn of authHandler.rpcFunctions) if (!rpcHost.definitions.has(fn.name)) rpcHost.register(fn);
|
|
27
27
|
}
|
|
28
28
|
const rpcGroup = createRpcServer(rpcHost.functions, { rpcOptions: {
|
|
29
|
+
/**
|
|
30
|
+
* Forwarded as-is so a host with its own structured diagnostics
|
|
31
|
+
* keeps seeing RPC failures.
|
|
32
|
+
*/
|
|
29
33
|
onFunctionError: options.rpcOptions?.onFunctionError,
|
|
30
34
|
onGeneralError: options.rpcOptions?.onGeneralError,
|
|
35
|
+
/**
|
|
36
|
+
* Wrap each RPC handler in an AsyncLocalStorage context so
|
|
37
|
+
* `ctx.rpc.getCurrentRpcSession()` works inside handlers (used
|
|
38
|
+
* by streaming subscribe/unsubscribe/cancel and shared-state
|
|
39
|
+
* sync), and (when an `authorize` gate is configured) reject
|
|
40
|
+
* the call before it ever reaches the handler. Mirrors
|
|
41
|
+
* `packages/core/src/node/ws.ts`'s resolver.
|
|
42
|
+
*/
|
|
31
43
|
resolver(name, fn) {
|
|
32
44
|
const rpc = this;
|
|
33
45
|
if (!fn) return void 0;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import "./index-
|
|
1
|
+
import { c as DevframeNodeRpcSessionMeta, l as DevframeRpcConnection } from "./ws-server-D2nv68Hg.mjs";
|
|
2
|
+
import { Tt as DevframeAuthHandler, o as DevframeNodeContext, s as DevframeNodeRpcSession, vt as DevframeRpcClientFunctions, yt as DevframeRpcServerFunctions } from "./context-CHW4BZOj.mjs";
|
|
3
|
+
import "./index-CNC390b_.mjs";
|
|
4
4
|
import { BirpcGroup, EventOptions } from "birpc";
|
|
5
5
|
//#region src/node/rpc-core.d.ts
|
|
6
6
|
interface CreateContextRpcServerOptions {
|
|
@@ -33,7 +33,7 @@ interface ContextRpcServer {
|
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
35
|
* Bind a devframe context's registered RPC functions to a birpc group,
|
|
36
|
-
* transport-agnostically
|
|
36
|
+
* transport-agnostically: the shared core under the instance shell's own
|
|
37
37
|
* HTTP+WS binding (Node http + WS) and the Bun fetch-upgrade tier of
|
|
38
38
|
* `createHandler`.
|
|
39
39
|
*
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { i as defineDiagnostics } from "./nostics-D0PvLZsn.mjs";
|
|
2
|
+
//#region src/rpc/diagnostics.ts
|
|
3
|
+
const diagnostics = defineDiagnostics({
|
|
4
|
+
docsBase: "https://devfra.me/errors",
|
|
5
|
+
codes: {
|
|
6
|
+
DF0019: {
|
|
7
|
+
why: (p) => `RPC function "${p.name}" has \`agent\` set but \`jsonSerializable\` is not \`true\`; MCP requires JSON-serializable data.`,
|
|
8
|
+
fix: "Set `jsonSerializable: true` if the payload is JSON-safe, or remove `agent` to keep it RPC-only."
|
|
9
|
+
},
|
|
10
|
+
DF0020: {
|
|
11
|
+
why: (p) => `RPC function "${p.name}" declares \`jsonSerializable: true\` but the value at "${p.path}" is a ${p.type}.`,
|
|
12
|
+
fix: "Either drop `jsonSerializable: true` (falls back to structured-clone) or change the value to a JSON-safe shape."
|
|
13
|
+
},
|
|
14
|
+
DF0021: {
|
|
15
|
+
why: (p) => `RPC function "${p.name}" is already registered`,
|
|
16
|
+
fix: "Use the `force` parameter to overwrite an existing registration."
|
|
17
|
+
},
|
|
18
|
+
DF0022: { why: (p) => `RPC function "${p.name}" is not registered. Use register() to add new functions.` },
|
|
19
|
+
DF0023: { why: (p) => `RPC function "${p.name}" is not registered` },
|
|
20
|
+
DF0024: { why: (p) => `Either handler or setup function must be provided for RPC function "${p.name}"` },
|
|
21
|
+
DF0025: { why: (p) => `Function "${p.name}" not found in dump store` },
|
|
22
|
+
DF0026: { why: (p) => `No dump match for "${p.name}" with args: ${p.args}` },
|
|
23
|
+
DF0027: { why: (p) => `Function "${p.name}" with type "${p.type}" cannot have dump configuration. Only "static" and "query" types support dumps.` },
|
|
24
|
+
DF0028: {
|
|
25
|
+
why: (p) => `Function "${p.name}" with type "${p.type}" cannot use \`snapshot: true\`. Only "query" functions support this sugar; "static" functions have equivalent default behavior already.`,
|
|
26
|
+
fix: "Remove `snapshot: true`, or change the function type to `query`."
|
|
27
|
+
},
|
|
28
|
+
DF0043: {
|
|
29
|
+
why: (p) => `RPC function "${p.name}" received an invalid argument at position ${p.index}: ${p.issues}`,
|
|
30
|
+
fix: "Pass a value that satisfies the `args` schema declared for this function."
|
|
31
|
+
},
|
|
32
|
+
DF0044: {
|
|
33
|
+
why: (p) => `RPC function "${p.name}" returned a value that failed its \`returns\` schema: ${p.issues}`,
|
|
34
|
+
fix: "Make the handler return a value that satisfies the `returns` schema, or relax the schema."
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/rpc/serialization.ts
|
|
40
|
+
/**
|
|
41
|
+
* Wire format used by the live RPC transports (WebSocket and SSE).
|
|
42
|
+
*
|
|
43
|
+
* - **JSON (default, unprefixed):** payload is plain JSON text. Used when
|
|
44
|
+
* the dispatched method is declared `jsonSerializable: true`. Encoded
|
|
45
|
+
* via {@link strictJsonStringify} (rejects non-JSON values), decoded
|
|
46
|
+
* via `JSON.parse`.
|
|
47
|
+
* - **Structured-clone (`s:` prefix):** payload is `s:` followed by
|
|
48
|
+
* `structured-clone-es` text. Used when the method is declared
|
|
49
|
+
* `jsonSerializable: false` (or omitted, the default). Round-trips
|
|
50
|
+
* `Map`, `Set`, `Date`, `BigInt`, cycles, and class instances.
|
|
51
|
+
*
|
|
52
|
+
* birpc envelopes always start with `{`, so a leading byte that is not
|
|
53
|
+
* `s` is unambiguously JSON. Each direction independently chooses its
|
|
54
|
+
* encoding from local definitions; request and response are not
|
|
55
|
+
* coupled by a mirror rule.
|
|
56
|
+
*/
|
|
57
|
+
const STRUCTURED_CLONE_PREFIX = "s:";
|
|
58
|
+
/**
|
|
59
|
+
* `JSON.stringify` with a single-pass strict replacer.
|
|
60
|
+
*
|
|
61
|
+
* Throws `DF0020` synchronously when the value contains a type JSON
|
|
62
|
+
* cannot round-trip losslessly: `Map`, `Set`, `Date`, `BigInt`, class
|
|
63
|
+
* instances, or `undefined` inside an array (silently becomes `null`).
|
|
64
|
+
*
|
|
65
|
+
* Native pass-throughs (no extra work needed):
|
|
66
|
+
* - circular references: `JSON.stringify` raises `TypeError`.
|
|
67
|
+
* - `BigInt` at top level: caught here for a friendlier error path.
|
|
68
|
+
*
|
|
69
|
+
* Lenient cases (allowed without throwing):
|
|
70
|
+
* - `undefined` as an object property: legitimate optional field;
|
|
71
|
+
* JSON.stringify just omits it.
|
|
72
|
+
* - `undefined` at the root: legitimate "action returned nothing".
|
|
73
|
+
* - `Symbol` / `Function` values: semantically "drop me" in JSON.
|
|
74
|
+
*
|
|
75
|
+
* `fnName` is used only for the diagnostic message; pass the RPC
|
|
76
|
+
* function name when calling from a wire serializer / dump writer so
|
|
77
|
+
* the error points at the offending function.
|
|
78
|
+
*/
|
|
79
|
+
function strictJsonStringify(value, fnName = "") {
|
|
80
|
+
return JSON.stringify(value, function strictReplacer(key, val) {
|
|
81
|
+
const holder = this;
|
|
82
|
+
const original = holder != null ? holder[key] : val;
|
|
83
|
+
if (original === void 0) {
|
|
84
|
+
if (Array.isArray(holder)) throw nonJsonAt(fnName, "undefined", holder, key);
|
|
85
|
+
return val;
|
|
86
|
+
}
|
|
87
|
+
if (original !== null) assertJsonSafe(original, holder, key, fnName);
|
|
88
|
+
return val;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/** Throw `DF0020` when `original` is a type JSON can't round-trip losslessly. */
|
|
92
|
+
function assertJsonSafe(original, holder, key, fnName) {
|
|
93
|
+
if (typeof original === "bigint") throw nonJsonAt(fnName, "BigInt", holder, key);
|
|
94
|
+
if (typeof original !== "object") return;
|
|
95
|
+
if (original instanceof Map) throw nonJsonAt(fnName, "Map", holder, key);
|
|
96
|
+
if (original instanceof Set) throw nonJsonAt(fnName, "Set", holder, key);
|
|
97
|
+
if (original instanceof Date) throw nonJsonAt(fnName, "Date", holder, key);
|
|
98
|
+
if (Array.isArray(original)) return;
|
|
99
|
+
const proto = Object.getPrototypeOf(original);
|
|
100
|
+
if (proto !== null && proto !== Object.prototype) throw nonJsonAt(fnName, original.constructor?.name ?? "class instance", holder, key);
|
|
101
|
+
}
|
|
102
|
+
function nonJsonAt(fnName, type, parent, key) {
|
|
103
|
+
const path = formatPath(parent, key);
|
|
104
|
+
return diagnostics.DF0020({
|
|
105
|
+
name: fnName || "<anonymous>",
|
|
106
|
+
type,
|
|
107
|
+
path
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function formatPath(parent, key) {
|
|
111
|
+
if (Array.isArray(parent)) return `[${key}]`;
|
|
112
|
+
if (key === "") return "<root>";
|
|
113
|
+
return key;
|
|
114
|
+
}
|
|
115
|
+
//#endregion
|
|
116
|
+
export { strictJsonStringify as n, diagnostics as r, STRUCTURED_CLONE_PREFIX as t };
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { isAbsolute, join } from "pathe";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
//#region src/node/services-install.ts
|
|
5
|
+
/**
|
|
6
|
+
* Turn a `resolveFrom` value (a file path, a file URL like `import.meta.url`,
|
|
7
|
+
* or a directory) into something `createRequire` accepts; a directory gets a
|
|
8
|
+
* synthetic filename appended so resolution starts inside it.
|
|
9
|
+
*/
|
|
10
|
+
function toRequireBase(resolveFrom) {
|
|
11
|
+
if (resolveFrom.startsWith("file://")) return resolveFrom;
|
|
12
|
+
if ((resolveFrom.split(/[/\\]/).pop() ?? "").includes(".")) return resolveFrom;
|
|
13
|
+
return join(resolveFrom, "_devframe_resolve.js");
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Normalize an `install()` `resolveFrom` into a resolution base. Paths and
|
|
17
|
+
* file URLs pass through (the common case: the declaring plugin's
|
|
18
|
+
* `importMetaUrl`, so a service it declares resolves against the plugin's own
|
|
19
|
+
* dependencies); a bare npm package name resolves to that package's location
|
|
20
|
+
* from `cwd`. An unresolvable package name reads as no base (the caller's
|
|
21
|
+
* workspace fallbacks apply).
|
|
22
|
+
*/
|
|
23
|
+
function expandResolveFrom(resolveFrom, cwd) {
|
|
24
|
+
if (resolveFrom.startsWith("file://") || resolveFrom.startsWith(".") || isAbsolute(resolveFrom)) return resolveFrom;
|
|
25
|
+
const require = createRequire(join(cwd, "_devframe_resolve.js"));
|
|
26
|
+
try {
|
|
27
|
+
return require.resolve(`${resolveFrom}/package.json`);
|
|
28
|
+
} catch {}
|
|
29
|
+
try {
|
|
30
|
+
return require.resolve(resolveFrom);
|
|
31
|
+
} catch {}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Import a service package's module, trying each `resolveFrom` candidate in
|
|
35
|
+
* order (so a plugin-declared service resolves against the plugin's own
|
|
36
|
+
* dependency tree first, then the workspace fallback). Throws the last
|
|
37
|
+
* resolution error when no candidate succeeds.
|
|
38
|
+
*/
|
|
39
|
+
async function importServicePackage(pkg, resolveFroms) {
|
|
40
|
+
const candidates = [...new Set(resolveFroms.filter((x) => typeof x === "string" && x.length > 0))];
|
|
41
|
+
let lastError = /* @__PURE__ */ new Error(`no resolution base available for "${pkg}"`);
|
|
42
|
+
for (const from of candidates) {
|
|
43
|
+
let resolved;
|
|
44
|
+
try {
|
|
45
|
+
resolved = createRequire(toRequireBase(from)).resolve(pkg);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
lastError = error;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
return await import(
|
|
51
|
+
/* webpackIgnore: true */
|
|
52
|
+
/* @vite-ignore */
|
|
53
|
+
/* turbopackIgnore: true */
|
|
54
|
+
pathToFileURL(resolved).href
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
throw lastError;
|
|
58
|
+
}
|
|
59
|
+
function parseVersion(input) {
|
|
60
|
+
const [core, ...prerelease] = input.trim().replace(/^v/, "").split("-");
|
|
61
|
+
if (!core) return void 0;
|
|
62
|
+
const parts = core.split(".").map((part) => Number.parseInt(part, 10));
|
|
63
|
+
if (parts.length === 0 || parts.some((part) => Number.isNaN(part) || part < 0)) return void 0;
|
|
64
|
+
while (parts.length < 3) parts.push(0);
|
|
65
|
+
return {
|
|
66
|
+
parts,
|
|
67
|
+
...prerelease.length ? { prerelease: prerelease.join("-") } : {}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function compareVersions(a, b) {
|
|
71
|
+
for (let i = 0; i < 3; i++) {
|
|
72
|
+
const diff = (a.parts[i] ?? 0) - (b.parts[i] ?? 0);
|
|
73
|
+
if (diff !== 0) return diff;
|
|
74
|
+
}
|
|
75
|
+
if (a.prerelease && !b.prerelease) return -1;
|
|
76
|
+
if (!a.prerelease && b.prerelease) return 1;
|
|
77
|
+
if (a.prerelease && b.prerelease) return a.prerelease < b.prerelease ? -1 : a.prerelease > b.prerelease ? 1 : 0;
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
/** Whether `version` and `base` share their first `lockUpTo + 1` parts. */
|
|
81
|
+
function partsMatchUpTo(version, base, lockUpTo) {
|
|
82
|
+
for (let i = 0; i <= lockUpTo; i++) if ((version.parts[i] ?? 0) !== (base.parts[i] ?? 0)) return false;
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
function satisfiesCaret(version, base) {
|
|
86
|
+
if (compareVersions(version, base) < 0) return false;
|
|
87
|
+
const fixedIndex = base.parts.findIndex((part) => part !== 0);
|
|
88
|
+
return partsMatchUpTo(version, base, fixedIndex === -1 ? base.parts.length - 1 : fixedIndex);
|
|
89
|
+
}
|
|
90
|
+
function satisfiesTilde(version, base, segments) {
|
|
91
|
+
if (compareVersions(version, base) < 0) return false;
|
|
92
|
+
return partsMatchUpTo(version, base, segments.length >= 2 ? 1 : 0);
|
|
93
|
+
}
|
|
94
|
+
function satisfiesExactOrPrefix(version, base, segments) {
|
|
95
|
+
for (let i = 0; i < Math.max(segments.length, 3); i++) if (i < segments.length && (version.parts[i] ?? 0) !== (base.parts[i] ?? 0)) return false;
|
|
96
|
+
return segments.length >= 3 ? compareVersions(version, base) === 0 : true;
|
|
97
|
+
}
|
|
98
|
+
function satisfiesComparator(version, comparator) {
|
|
99
|
+
const raw = comparator.trim();
|
|
100
|
+
if (!raw || raw === "*" || raw === "x") return true;
|
|
101
|
+
const operatorMatch = raw.match(/^([\^~]|>=|<=|[><=])?(.+)$/);
|
|
102
|
+
if (!operatorMatch) return false;
|
|
103
|
+
const operator = operatorMatch[1];
|
|
104
|
+
const rest = operatorMatch[2].trim();
|
|
105
|
+
const segments = rest.replace(/\.[x*]/gi, "").split(".").filter(Boolean);
|
|
106
|
+
const base = parseVersion(rest.replace(/[x*]/gi, "0"));
|
|
107
|
+
if (!base) return false;
|
|
108
|
+
switch (operator) {
|
|
109
|
+
case ">": return compareVersions(version, base) > 0;
|
|
110
|
+
case ">=": return compareVersions(version, base) >= 0;
|
|
111
|
+
case "<": return compareVersions(version, base) < 0;
|
|
112
|
+
case "<=": return compareVersions(version, base) <= 0;
|
|
113
|
+
case "^": return satisfiesCaret(version, base);
|
|
114
|
+
case "~": return satisfiesTilde(version, base, segments);
|
|
115
|
+
default: return satisfiesExactOrPrefix(version, base, segments);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Pragmatic semver range check for service version declarations; supports
|
|
120
|
+
* the common forms (`1.2.3`, `^1.2.3`, `~1.2`, `>=1 <3`, `1.x`, `*`, and
|
|
121
|
+
* `||`-joined alternatives) without pulling in a semver dependency. An
|
|
122
|
+
* unparseable version or range reads as **not satisfied**.
|
|
123
|
+
*/
|
|
124
|
+
function satisfiesVersionRange(version, range) {
|
|
125
|
+
const parsed = parseVersion(version);
|
|
126
|
+
if (!parsed) return false;
|
|
127
|
+
const alternatives = range.split("||").map((alt) => alt.trim()).filter(Boolean);
|
|
128
|
+
if (alternatives.length === 0) return true;
|
|
129
|
+
return alternatives.some((alternative) => alternative.split(/\s+/).every((comparator) => satisfiesComparator(parsed, comparator)));
|
|
130
|
+
}
|
|
131
|
+
function isPlainObject(value) {
|
|
132
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
133
|
+
}
|
|
134
|
+
/** Deep-merge two values with the service option-set rules (see below). */
|
|
135
|
+
function deepMergeTwo(a, b) {
|
|
136
|
+
if (Array.isArray(a) && Array.isArray(b)) return [.../* @__PURE__ */ new Set([...a, ...b])];
|
|
137
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
138
|
+
const out = { ...a };
|
|
139
|
+
for (const key of Object.keys(b)) out[key] = key in a ? deepMergeTwo(a[key], b[key]) : b[key];
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
return b;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Default option-set merge when a service declares no `mergeOptions`:
|
|
146
|
+
* deep-merge in declaration order: objects recurse, arrays union-dedupe,
|
|
147
|
+
* scalars take the later value. Covers the built-in services (`roots` /
|
|
148
|
+
* `langs` union, `themes` per-key last-wins) without a custom hook.
|
|
149
|
+
*/
|
|
150
|
+
function deepMergeOptionSets(sets) {
|
|
151
|
+
return sets.reduce((merged, set) => deepMergeTwo(merged, set));
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
export { satisfiesVersionRange as i, expandResolveFrom as n, importServicePackage as r, deepMergeOptionSets as t };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/rpc/transports/session.ts
|
|
2
|
+
let sessionId = 0;
|
|
3
|
+
/**
|
|
4
|
+
* Mint the per-connection session meta every transport binding shares:
|
|
5
|
+
* one id space across transports, so session bookkeeping (streaming
|
|
6
|
+
* subscriptions, shared-state sync, auth trust) never collides between a
|
|
7
|
+
* WS peer and an SSE session on the same server.
|
|
8
|
+
*/
|
|
9
|
+
function createRpcSessionMeta() {
|
|
10
|
+
return {
|
|
11
|
+
id: sessionId++,
|
|
12
|
+
subscribedStates: /* @__PURE__ */ new Set()
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { createRpcSessionMeta as t };
|
|
@@ -931,8 +931,8 @@ function createSharedState(options) {
|
|
|
931
931
|
if (syncIds.has(syncId)) return;
|
|
932
932
|
rememberSyncId(syncIds, syncId);
|
|
933
933
|
if (enablePatches$1) {
|
|
934
|
-
const [
|
|
935
|
-
state =
|
|
934
|
+
const [nextState, patches] = produceWithPatches(state, fn);
|
|
935
|
+
state = nextState;
|
|
936
936
|
events.emit("updated", state, patches, syncId);
|
|
937
937
|
} else {
|
|
938
938
|
state = produce(state, fn);
|
|
@@ -37,7 +37,7 @@ function number() {
|
|
|
37
37
|
function boolean() {
|
|
38
38
|
return make("boolean", (v) => typeof v === "boolean" ? ok(v) : fail("Expected a boolean"));
|
|
39
39
|
}
|
|
40
|
-
/** `undefined
|
|
40
|
+
/** `undefined`, mirroring valibot's `void`. */
|
|
41
41
|
function voidType() {
|
|
42
42
|
return make("void", (v) => v === void 0 ? ok(void 0) : fail("Expected undefined"));
|
|
43
43
|
}
|
|
@@ -130,7 +130,7 @@ function describe(schema, description) {
|
|
|
130
130
|
};
|
|
131
131
|
}
|
|
132
132
|
/**
|
|
133
|
-
* Grouped access to every builder
|
|
133
|
+
* Grouped access to every builder: `s.string()`, `s.object({ ... })`,
|
|
134
134
|
* `s.void()`, etc. Handy for a valibot-like `import { s } from
|
|
135
135
|
* 'devframe/utils/simple-schema'` call site.
|
|
136
136
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { v as RpcFunctionDefinitionAny } from "./types-
|
|
1
|
+
import { v as RpcFunctionDefinitionAny } from "./types-Dq4i156u.mjs";
|
|
2
2
|
import { ChannelOptions } from "birpc";
|
|
3
3
|
//#region src/rpc/transports/sse-client.d.ts
|
|
4
4
|
interface SseRpcChannelOptions {
|
|
@@ -10,7 +10,7 @@ interface SseRpcChannelOptions {
|
|
|
10
10
|
authToken?: string;
|
|
11
11
|
/**
|
|
12
12
|
* RPC function definitions (or just the `jsonSerializable` flag per
|
|
13
|
-
* method) used to dispatch the per-call wire serializer
|
|
13
|
+
* method) used to dispatch the per-call wire serializer, the same contract
|
|
14
14
|
* as the WS channel.
|
|
15
15
|
*/
|
|
16
16
|
definitions?: ReadonlyMap<string, Pick<RpcFunctionDefinitionAny, 'jsonSerializable'>>;
|
|
@@ -19,7 +19,7 @@ interface SseRpcChannelOptions {
|
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
21
|
* Build a birpc `ChannelOptions` object backed by an SSE stream (server →
|
|
22
|
-
* client) and HTTP `POST` (client → server)
|
|
22
|
+
* client) and HTTP `POST` (client → server): the WebSocket-free counterpart
|
|
23
23
|
* to `createWsRpcChannel`, wire-compatible with `attachSseRpcTransport`.
|
|
24
24
|
*
|
|
25
25
|
* The stream is consumed via `fetch` streaming (not `EventSource`), so it
|
|
@@ -27,7 +27,7 @@ interface SseRpcChannelOptions {
|
|
|
27
27
|
* (`event: session`) carries the session id; every `POST` echoes it in the
|
|
28
28
|
* `x-birpc-session` header. A response to a client-initiated request comes
|
|
29
29
|
* back in the `POST`'s own body and is re-injected into the channel; a
|
|
30
|
-
* dropped stream terminates the channel
|
|
30
|
+
* dropped stream terminates the channel; there is no reconnect, matching
|
|
31
31
|
* the WS channel's closed-is-done semantics.
|
|
32
32
|
*/
|
|
33
33
|
declare function createSseRpcChannel(options: SseRpcChannelOptions): ChannelOptions & {
|