statewire 0.14.0 → 0.16.0
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/README.md +29 -80
- package/dist/client/connection.d.ts +19 -6
- package/dist/client/connection.d.ts.map +1 -1
- package/dist/client/connection.js +79 -40
- package/dist/client/connection.js.map +1 -1
- package/dist/client/core.d.ts +1 -0
- package/dist/client/core.d.ts.map +1 -1
- package/dist/client/core.js +5 -0
- package/dist/client/core.js.map +1 -1
- package/dist/client/session.d.ts +2 -0
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +98 -19
- package/dist/client/session.js.map +1 -1
- package/dist/host/adapter.d.ts +1 -1
- package/dist/host/adapter.d.ts.map +1 -1
- package/dist/host/adapter.js +7 -1
- package/dist/host/adapter.js.map +1 -1
- package/dist/host/host.d.ts +1 -0
- package/dist/host/host.d.ts.map +1 -1
- package/dist/host/host.js +45 -5
- package/dist/host/host.js.map +1 -1
- package/dist/host/http.d.ts +1 -1
- package/dist/host/http.d.ts.map +1 -1
- package/dist/host/http.js +75 -47
- package/dist/host/http.js.map +1 -1
- package/dist/host/socket.d.ts +23 -7
- package/dist/host/socket.d.ts.map +1 -1
- package/dist/host/socket.js +275 -74
- package/dist/host/socket.js.map +1 -1
- package/dist/host/work.d.ts +7 -0
- package/dist/host/work.d.ts.map +1 -0
- package/dist/host/work.js +53 -0
- package/dist/host/work.js.map +1 -0
- package/dist/host-internal.d.ts +35 -5
- package/dist/host-internal.d.ts.map +1 -1
- package/dist/host-internal.js +2 -1
- package/dist/host.d.ts +16 -3
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +2 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/statewire.d.ts +13 -4
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js.map +1 -1
- package/dist/transports/sse.d.ts +2 -5
- package/dist/transports/sse.d.ts.map +1 -1
- package/dist/transports/sse.js +50 -59
- package/dist/transports/sse.js.map +1 -1
- package/dist/transports/testing.js +4 -4
- package/dist/transports/testing.js.map +1 -1
- package/dist/transports/ws.d.ts +3 -3
- package/dist/transports/ws.d.ts.map +1 -1
- package/dist/transports/ws.js +57 -44
- package/dist/transports/ws.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/util/randomId.js +1 -1
- package/dist/util/randomId.js.map +1 -1
- package/dist/wire/documents.d.ts +20 -0
- package/dist/wire/documents.d.ts.map +1 -0
- package/dist/wire/documents.js +74 -0
- package/dist/wire/documents.js.map +1 -0
- package/dist/wire/limits.d.ts +10 -0
- package/dist/wire/limits.d.ts.map +1 -0
- package/dist/wire/limits.js +11 -0
- package/dist/wire/limits.js.map +1 -0
- package/dist/wire/meta.d.ts.map +1 -1
- package/dist/wire/meta.js +1 -1
- package/dist/wire/meta.js.map +1 -1
- package/dist/wire/ops.d.ts.map +1 -1
- package/dist/wire/ops.js +1 -2
- package/dist/wire/ops.js.map +1 -1
- package/dist/wire/version.d.ts +7 -0
- package/dist/wire/version.d.ts.map +1 -0
- package/dist/wire/version.js +8 -0
- package/dist/wire/version.js.map +1 -0
- package/package.json +1 -1
- package/src/client/connection.ts +159 -52
- package/src/client/core.ts +6 -0
- package/src/client/session.ts +105 -32
- package/src/host/adapter.ts +10 -2
- package/src/host/host.ts +54 -4
- package/src/host/http.ts +132 -66
- package/src/host/socket.ts +410 -81
- package/src/host/work.ts +50 -0
- package/src/host-internal.ts +32 -4
- package/src/host.ts +18 -2
- package/src/index.ts +2 -2
- package/src/statewire.ts +18 -4
- package/src/transports/sse.ts +70 -78
- package/src/transports/testing.ts +4 -4
- package/src/transports/ws.ts +36 -55
- package/src/types.ts +1 -0
- package/src/util/randomId.ts +3 -3
- package/src/wire/documents.ts +109 -0
- package/src/wire/limits.ts +6 -0
- package/src/wire/meta.ts +6 -1
- package/src/wire/ops.ts +1 -2
- package/src/wire/version.ts +3 -0
package/dist/transports/ws.js
CHANGED
|
@@ -1,27 +1,24 @@
|
|
|
1
|
+
import { WS_SUBPROTOCOL } from "../wire/version.js";
|
|
1
2
|
import { CLIENT_QUERY_PARAM } from "../host/adapter.js";
|
|
2
3
|
import { timing } from "../client/timing.js";
|
|
3
|
-
import { handleEnvelope,
|
|
4
|
+
import { attachFrame, handleEnvelope, unsupportedVersion, useLink } from "../client/connection.js";
|
|
4
5
|
import { useStatewireRemote } from "./remote.js";
|
|
5
6
|
import { useState } from "@assistant-ui/tap/react-shim";
|
|
6
7
|
import { flushTapSync, resource } from "@assistant-ui/tap";
|
|
8
|
+
import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
|
|
7
9
|
//#region src/transports/ws.ts
|
|
8
10
|
const OPEN = 1;
|
|
9
|
-
const WS_SUBPROTOCOL = "statewire.v1";
|
|
10
11
|
const socketUrl = async (options, clientId) => {
|
|
11
12
|
const { url, query } = options;
|
|
12
13
|
const params = new URLSearchParams(typeof query === "function" ? await query() : query);
|
|
13
14
|
params.set(CLIENT_QUERY_PARAM, clientId);
|
|
14
|
-
|
|
15
|
+
const resolved = new URL(url, globalThis.location?.href);
|
|
16
|
+
resolved.protocol = resolved.protocol.replace(/^http/, "ws");
|
|
17
|
+
resolved.pathname = `${resolved.pathname.replace(/\/$/, "")}/ws`;
|
|
18
|
+
resolved.search = params.toString();
|
|
19
|
+
return resolved.href;
|
|
15
20
|
};
|
|
16
|
-
const
|
|
17
|
-
const { headers } = options;
|
|
18
|
-
const resolved = new Headers(typeof headers === "function" ? await headers({ staleAuth: stale }) : headers);
|
|
19
|
-
return JSON.stringify({
|
|
20
|
-
headers: Object.fromEntries(resolved),
|
|
21
|
-
...lanes ?? {}
|
|
22
|
-
});
|
|
23
|
-
};
|
|
24
|
-
const runSocket = (c, w, ws, frame, abort) => new Promise((resolve, reject) => {
|
|
21
|
+
const runSocket = (c, ws, frame, abort) => new Promise((resolve, reject) => {
|
|
25
22
|
let timer;
|
|
26
23
|
let timedOut = false;
|
|
27
24
|
let outcome = null;
|
|
@@ -36,7 +33,15 @@ const runSocket = (c, w, ws, frame, abort) => new Promise((resolve, reject) => {
|
|
|
36
33
|
arm();
|
|
37
34
|
abort.signal.addEventListener("abort", () => ws.close());
|
|
38
35
|
ws.onerror = () => {};
|
|
39
|
-
ws.onopen = () =>
|
|
36
|
+
ws.onopen = () => {
|
|
37
|
+
const negotiated = ws.protocol.trim();
|
|
38
|
+
if (negotiated !== "" && negotiated !== "statewire.v1") {
|
|
39
|
+
outcome = unsupportedVersion(c, negotiated);
|
|
40
|
+
ws.close();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
ws.send(frame);
|
|
44
|
+
};
|
|
40
45
|
const stale = () => Boolean(outcome) || closed || abort.signal.aborted;
|
|
41
46
|
const fail = (error) => {
|
|
42
47
|
clearTimeout(timer);
|
|
@@ -50,14 +55,13 @@ const runSocket = (c, w, ws, frame, abort) => new Promise((resolve, reject) => {
|
|
|
50
55
|
try {
|
|
51
56
|
env = JSON.parse(String(event.data));
|
|
52
57
|
} catch {
|
|
53
|
-
fail(/* @__PURE__ */ new
|
|
58
|
+
fail(/* @__PURE__ */ new SyntaxError("invalid JSON packet"));
|
|
54
59
|
return;
|
|
55
60
|
}
|
|
56
61
|
if (typeof env === "object" && env !== null && Object.keys(env).length === 0) return;
|
|
57
62
|
try {
|
|
58
63
|
const done = flushTapSync(() => handleEnvelope(c, env));
|
|
59
64
|
if (done) {
|
|
60
|
-
if (env.fin?.reason === "unauthorized") w.authStale = true;
|
|
61
65
|
outcome = done;
|
|
62
66
|
ws.close();
|
|
63
67
|
}
|
|
@@ -76,17 +80,14 @@ const attemptSocket = async (c, w, options, abort) => {
|
|
|
76
80
|
let ws = null;
|
|
77
81
|
try {
|
|
78
82
|
const url = await socketUrl(options, c.ctx.clientId);
|
|
79
|
-
const
|
|
80
|
-
w.authStale = false;
|
|
81
|
-
const lanes = flushTapSync(() => c.ctx.takeAttachLanes());
|
|
82
|
-
const frame = await firstFrame(options, stale, lanes);
|
|
83
|
+
const { body } = await attachFrame(c, options.headers);
|
|
83
84
|
abort.signal.throwIfAborted();
|
|
84
85
|
const { protocols, webSocket } = options;
|
|
85
86
|
const WS = webSocket ?? globalThis.WebSocket;
|
|
86
87
|
if (!WS) throw new Error("no WebSocket implementation");
|
|
87
88
|
ws = new WS(url, [WS_SUBPROTOCOL, ...protocols === void 0 ? [] : typeof protocols === "string" ? [protocols] : protocols]);
|
|
88
89
|
w.socket = ws;
|
|
89
|
-
return await runSocket(c,
|
|
90
|
+
return await runSocket(c, ws, body, abort);
|
|
90
91
|
} finally {
|
|
91
92
|
if (w.socket === ws) w.socket = null;
|
|
92
93
|
}
|
|
@@ -102,31 +103,43 @@ const sendFrame = (w, frame) => {
|
|
|
102
103
|
return Promise.resolve({ type: "ok" });
|
|
103
104
|
};
|
|
104
105
|
const useWsLink = (options, ctx) => {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
106
|
+
const $ = c(10);
|
|
107
|
+
const [w] = useState(_temp);
|
|
108
|
+
let t0;
|
|
109
|
+
if ($[0] !== options || $[1] !== w) {
|
|
110
|
+
t0 = (c, abort) => attemptSocket(c, w, options, abort);
|
|
111
|
+
$[0] = options;
|
|
112
|
+
$[1] = w;
|
|
113
|
+
$[2] = t0;
|
|
114
|
+
} else t0 = $[2];
|
|
115
|
+
let t1;
|
|
116
|
+
if ($[3] !== w) {
|
|
117
|
+
t1 = (_, frame) => sendFrame(w, frame);
|
|
118
|
+
$[3] = w;
|
|
119
|
+
$[4] = t1;
|
|
120
|
+
} else t1 = $[4];
|
|
121
|
+
let t2;
|
|
122
|
+
if ($[5] !== ctx || $[6] !== options || $[7] !== t0 || $[8] !== t1) {
|
|
123
|
+
t2 = {
|
|
124
|
+
...options,
|
|
125
|
+
ctx,
|
|
126
|
+
attemptOnce: t0,
|
|
127
|
+
send: t1
|
|
128
|
+
};
|
|
129
|
+
$[5] = ctx;
|
|
130
|
+
$[6] = options;
|
|
131
|
+
$[7] = t0;
|
|
132
|
+
$[8] = t1;
|
|
133
|
+
$[9] = t2;
|
|
134
|
+
} else t2 = $[9];
|
|
135
|
+
return useLink(t2);
|
|
126
136
|
};
|
|
127
|
-
const
|
|
128
|
-
const
|
|
137
|
+
const useStatewireWebsocket = (options) => useStatewireRemote(options, useWsLink);
|
|
138
|
+
const StatewireWebsocket = resource(useStatewireWebsocket);
|
|
139
|
+
function _temp() {
|
|
140
|
+
return { socket: null };
|
|
141
|
+
}
|
|
129
142
|
//#endregion
|
|
130
|
-
export {
|
|
143
|
+
export { StatewireWebsocket, WS_SUBPROTOCOL };
|
|
131
144
|
|
|
132
145
|
//# sourceMappingURL=ws.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws.js","names":["useState","flushTapSync","resource","ResourceElement","timing","handleEnvelope","useStatewireConn","AttemptOutcome","Conn","StatewireEnvelope","Statewire","StatewireClient","useStatewireRemote","CLIENT_QUERY_PARAM","RemoteLink","StatewireFrame","StatewireSubmitResult","StatewireRemoteProps","WsChannel","socket","WebSocket","authStale","OPEN","WS_SUBPROTOCOL","socketUrl","options","WSOptions","clientId","url","query","params","URLSearchParams","set","base","replace","toString","firstFrame","stale","lanes","Promise","headers","resolved","Headers","staleAuth","JSON","stringify","Object","fromEntries","runSocket","c","w","ws","frame","abort","AbortController","resolve","reject","timer","ReturnType","setTimeout","timedOut","outcome","closed","arm","clearTimeout","close","heartbeatTimeoutMs","signal","addEventListener","onerror","onopen","send","Boolean","aborted","fail","error","Error","onmessage","event","env","parse","String","data","keys","length","done","fin","reason","onclose","attemptSocket","ctx","takeAttachLanes","throwIfAborted","protocols","webSocket","WS","globalThis","extra","undefined","sendFrame","readyState","type","useWsLink","isNew","onChange","attemptOnce","link","connection","parked","useStatewireWS","StatewireWS","Commands","TState","TCommands"],"sources":["../../src/transports/ws.ts"],"sourcesContent":["import { useState } from \"react\";\nimport { flushTapSync, resource } from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { timing } from \"../client/timing\";\nimport { handleEnvelope, useStatewireConn } from \"../client/connection\";\nimport type {\n AttemptOutcome,\n Conn,\n StatewireEnvelope,\n} from \"../client/connection\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireClient } from \"../statewire\";\nimport { useStatewireRemote } from \"./remote\";\nimport { CLIENT_QUERY_PARAM } from \"../host/adapter\";\nimport type {\n RemoteLink,\n StatewireFrame,\n StatewireSubmitResult,\n StatewireRemoteProps,\n} from \"./sse\";\n\ntype WsChannel = {\n socket: WebSocket | null;\n authStale: boolean;\n};\n\nconst OPEN = 1;\n\nexport const WS_SUBPROTOCOL = \"statewire.v1\";\n\nconst socketUrl = async (\n options: StatewireClient.WSOptions,\n clientId: string,\n) => {\n const { url, query } = options;\n const params = new URLSearchParams(\n typeof query === \"function\" ? await query() : query,\n );\n params.set(CLIENT_QUERY_PARAM, clientId);\n const base = url.replace(/\\/$/, \"\").replace(/^http/, \"ws\");\n return `${base}/ws?${params.toString()}`;\n};\n\nconst firstFrame = async (\n options: StatewireClient.WSOptions,\n stale: boolean,\n lanes: StatewireFrame | null,\n): Promise<string> => {\n const { headers } = options;\n const resolved = new Headers(\n typeof headers === \"function\"\n ? await headers({ staleAuth: stale })\n : headers,\n );\n return JSON.stringify({\n headers: Object.fromEntries(resolved),\n ...(lanes ?? {}),\n });\n};\n\nconst runSocket = (\n c: Conn,\n w: WsChannel,\n ws: WebSocket,\n frame: string,\n abort: AbortController,\n): Promise<AttemptOutcome> =>\n new Promise((resolve, reject) => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n let timedOut = false;\n let outcome: AttemptOutcome | null = null;\n let closed = false;\n const arm = () => {\n clearTimeout(timer);\n timer = setTimeout(() => {\n timedOut = true;\n ws.close();\n }, timing.heartbeatTimeoutMs);\n };\n arm();\n abort.signal.addEventListener(\"abort\", () => ws.close());\n ws.onerror = () => {};\n ws.onopen = () => ws.send(frame);\n const stale = () => Boolean(outcome) || closed || abort.signal.aborted;\n const fail = (error: unknown) => {\n clearTimeout(timer);\n reject(error instanceof Error ? error : new Error(\"protocol\"));\n ws.close();\n };\n ws.onmessage = (event) => {\n if (stale()) return;\n arm();\n let env: StatewireEnvelope;\n try {\n env = JSON.parse(String(event.data)) as StatewireEnvelope;\n } catch {\n fail(new Error(\"protocol\"));\n return;\n }\n // {} heartbeats live below the packet layer: invisible to handlers.\n if (\n typeof env === \"object\" &&\n env !== null &&\n Object.keys(env).length === 0\n ) {\n return;\n }\n try {\n const done = flushTapSync(() => handleEnvelope(c, env));\n if (done) {\n if (env.fin?.reason === \"unauthorized\") w.authStale = true;\n outcome = done;\n ws.close();\n }\n } catch (error) {\n fail(error);\n }\n };\n ws.onclose = () => {\n closed = true;\n clearTimeout(timer);\n if (outcome) resolve(outcome);\n else reject(new Error(timedOut ? \"timeout\" : \"network\"));\n };\n });\n\nconst attemptSocket = async (\n c: Conn,\n w: WsChannel,\n options: StatewireClient.WSOptions,\n abort: AbortController,\n): Promise<AttemptOutcome> => {\n let ws: WebSocket | null = null;\n try {\n const url = await socketUrl(options, c.ctx.clientId);\n const stale = w.authStale;\n w.authStale = false;\n const lanes = flushTapSync(() => c.ctx.takeAttachLanes());\n const frame = await firstFrame(options, stale, lanes);\n abort.signal.throwIfAborted();\n const { protocols, webSocket } = options;\n const WS = webSocket ?? globalThis.WebSocket;\n if (!WS) throw new Error(\"no WebSocket implementation\");\n const extra =\n protocols === undefined\n ? []\n : typeof protocols === \"string\"\n ? [protocols]\n : protocols;\n ws = new WS(url, [WS_SUBPROTOCOL, ...extra]);\n w.socket = ws;\n return await runSocket(c, w, ws, frame, abort);\n } finally {\n if (w.socket === ws) w.socket = null;\n }\n};\n\nconst sendFrame = (\n w: WsChannel,\n frame: StatewireFrame,\n): Promise<StatewireSubmitResult> => {\n const ws = w.socket;\n if (!ws || ws.readyState !== OPEN) {\n return Promise.reject(new Error(\"network\"));\n }\n try {\n ws.send(JSON.stringify(frame));\n } catch {\n return Promise.reject(new Error(\"network\"));\n }\n return Promise.resolve({ type: \"ok\" });\n};\n\nconst useWsLink = (\n options: StatewireClient.WSOptions,\n ctx: StatewireRemoteProps,\n): RemoteLink => {\n const [w] = useState((): WsChannel => ({ socket: null, authStale: false }));\n const c = useStatewireConn({\n url: options.url,\n isNew: options.isNew,\n onChange: options.onChange,\n ctx,\n attemptOnce: (c, abort) => attemptSocket(c, w, options, abort),\n });\n const [link] = useState(\n (): RemoteLink => ({\n get connection() {\n return c.connection;\n },\n get parked() {\n return c.parked;\n },\n send: (frame) => sendFrame(w, frame),\n }),\n );\n return link;\n};\n\nconst useStatewireWS = (\n options: StatewireClient.WSOptions,\n): Statewire<any, any> => useStatewireRemote(options, useWsLink);\n\nexport const StatewireWS = resource(useStatewireWS) as <\n TState = any,\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: StatewireClient.WSOptions,\n) => ResourceElement<Statewire<TState | undefined, TCommands>>;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ws.js","names":["useState","flushTapSync","resource","ResourceElement","timing","attachFrame","handleEnvelope","unsupportedVersion","useLink","AttemptOutcome","Conn","StatewireEnvelope","Statewire","StatewireClient","useStatewireRemote","CLIENT_QUERY_PARAM","WS_SUBPROTOCOL","RemoteLink","StatewireFrame","StatewireSubmitResult","StatewireRemoteProps","WsChannel","socket","WebSocket","OPEN","socketUrl","options","WebsocketOptions","clientId","url","query","params","URLSearchParams","set","resolved","URL","globalThis","location","href","protocol","replace","pathname","search","toString","runSocket","c","ws","frame","abort","AbortController","Promise","resolve","reject","timer","ReturnType","setTimeout","timedOut","outcome","closed","arm","clearTimeout","close","heartbeatTimeoutMs","signal","addEventListener","onerror","onopen","negotiated","trim","send","stale","Boolean","aborted","fail","error","Error","onmessage","event","env","JSON","parse","String","data","SyntaxError","Object","keys","length","done","onclose","attemptSocket","w","ctx","body","headers","throwIfAborted","protocols","webSocket","WS","extra","undefined","sendFrame","readyState","stringify","type","useWsLink","$","_c","_temp","t0","t1","_","t2","attemptOnce","useStatewireWebsocket","StatewireWebsocket","Commands","TState","TCommands"],"sources":["../../src/transports/ws.ts"],"sourcesContent":["import { useState } from \"react\";\nimport { flushTapSync, resource } from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { timing } from \"../client/timing\";\nimport {\n attachFrame,\n handleEnvelope,\n unsupportedVersion,\n useLink,\n} from \"../client/connection\";\nimport type {\n AttemptOutcome,\n Conn,\n StatewireEnvelope,\n} from \"../client/connection\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireClient } from \"../statewire\";\nimport { useStatewireRemote } from \"./remote\";\nimport { CLIENT_QUERY_PARAM } from \"../host/adapter\";\nimport { WS_SUBPROTOCOL } from \"../wire/version\";\nimport type {\n RemoteLink,\n StatewireFrame,\n StatewireSubmitResult,\n StatewireRemoteProps,\n} from \"./sse\";\n\ntype WsChannel = {\n socket: WebSocket | null;\n};\n\nconst OPEN = 1;\n\nexport { WS_SUBPROTOCOL };\n\nconst socketUrl = async (\n options: StatewireClient.WebsocketOptions,\n clientId: string,\n) => {\n const { url, query } = options;\n const params = new URLSearchParams(\n typeof query === \"function\" ? await query() : query,\n );\n params.set(CLIENT_QUERY_PARAM, clientId);\n const resolved = new URL(url, globalThis.location?.href);\n resolved.protocol = resolved.protocol.replace(/^http/, \"ws\");\n resolved.pathname = `${resolved.pathname.replace(/\\/$/, \"\")}/ws`;\n resolved.search = params.toString();\n return resolved.href;\n};\n\nconst runSocket = (\n c: Conn,\n ws: WebSocket,\n frame: string,\n abort: AbortController,\n): Promise<AttemptOutcome> =>\n new Promise((resolve, reject) => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n let timedOut = false;\n let outcome: AttemptOutcome | null = null;\n let closed = false;\n const arm = () => {\n clearTimeout(timer);\n timer = setTimeout(() => {\n timedOut = true;\n ws.close();\n }, timing.heartbeatTimeoutMs);\n };\n arm();\n abort.signal.addEventListener(\"abort\", () => ws.close());\n ws.onerror = () => {};\n ws.onopen = () => {\n const negotiated = ws.protocol.trim();\n if (negotiated !== \"\" && negotiated !== WS_SUBPROTOCOL) {\n outcome = unsupportedVersion(c, negotiated);\n ws.close();\n return;\n }\n ws.send(frame);\n };\n const stale = () => Boolean(outcome) || closed || abort.signal.aborted;\n const fail = (error: unknown) => {\n clearTimeout(timer);\n reject(error instanceof Error ? error : new Error(\"protocol\"));\n ws.close();\n };\n ws.onmessage = (event) => {\n if (stale()) return;\n arm();\n let env: StatewireEnvelope;\n try {\n env = JSON.parse(String(event.data)) as StatewireEnvelope;\n } catch {\n fail(new SyntaxError(\"invalid JSON packet\"));\n return;\n }\n // {} heartbeats live below the packet layer: invisible to handlers.\n if (\n typeof env === \"object\" &&\n env !== null &&\n Object.keys(env).length === 0\n ) {\n return;\n }\n try {\n const done = flushTapSync(() => handleEnvelope(c, env));\n if (done) {\n outcome = done;\n ws.close();\n }\n } catch (error) {\n fail(error);\n }\n };\n ws.onclose = () => {\n closed = true;\n clearTimeout(timer);\n if (outcome) resolve(outcome);\n else reject(new Error(timedOut ? \"timeout\" : \"network\"));\n };\n });\n\nconst attemptSocket = async (\n c: Conn,\n w: WsChannel,\n options: StatewireClient.WebsocketOptions,\n abort: AbortController,\n): Promise<AttemptOutcome> => {\n let ws: WebSocket | null = null;\n try {\n const url = await socketUrl(options, c.ctx.clientId);\n const { body } = await attachFrame(c, options.headers);\n abort.signal.throwIfAborted();\n const { protocols, webSocket } = options;\n const WS = webSocket ?? globalThis.WebSocket;\n if (!WS) throw new Error(\"no WebSocket implementation\");\n const extra =\n protocols === undefined\n ? []\n : typeof protocols === \"string\"\n ? [protocols]\n : protocols;\n ws = new WS(url, [WS_SUBPROTOCOL, ...extra]);\n w.socket = ws;\n return await runSocket(c, ws, body, abort);\n } finally {\n if (w.socket === ws) w.socket = null;\n }\n};\n\nconst sendFrame = (\n w: WsChannel,\n frame: StatewireFrame,\n): Promise<StatewireSubmitResult> => {\n const ws = w.socket;\n if (!ws || ws.readyState !== OPEN) {\n return Promise.reject(new Error(\"network\"));\n }\n try {\n ws.send(JSON.stringify(frame));\n } catch {\n return Promise.reject(new Error(\"network\"));\n }\n return Promise.resolve({ type: \"ok\" });\n};\n\nconst useWsLink = (\n options: StatewireClient.WebsocketOptions,\n ctx: StatewireRemoteProps,\n): RemoteLink => {\n const [w] = useState((): WsChannel => ({ socket: null }));\n return useLink({\n ...options,\n ctx,\n attemptOnce: (c, abort) => attemptSocket(c, w, options, abort),\n send: (_, frame) => sendFrame(w, frame),\n });\n};\n\nconst useStatewireWebsocket = (\n options: StatewireClient.WebsocketOptions,\n): Statewire<any, any> => useStatewireRemote(options, useWsLink);\n\nexport const StatewireWebsocket = resource(useStatewireWebsocket) as <\n TState = any,\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: StatewireClient.WebsocketOptions,\n) => ResourceElement<Statewire<TState | undefined, TCommands>>;\n"],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -50,6 +50,9 @@ interface Statewire<TState = any, TCommands extends Statewire.Commands = Statewi
|
|
|
50
50
|
durableSeq?: number;
|
|
51
51
|
/** Negation of the server's broadcast idle level; absent means always busy. */
|
|
52
52
|
isBusy?: boolean;
|
|
53
|
+
work?: {
|
|
54
|
+
readonly isIdle: boolean;
|
|
55
|
+
};
|
|
53
56
|
/** Replace this client's ambient context on the server; it rides the ctx lane (no verdict). */
|
|
54
57
|
setContext?: (value: Record<string, unknown>) => void;
|
|
55
58
|
/** The retained failed-send lane; present on transports with a durable session. */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;kBAGiB;OACH,WAAW,mBAAmB;OAE9B,QAAQ,kBAAkB,WAAW,eAC9C,WAAW;IAAc,QAAQ;IAAG,QAAQ,WAAW,UAAU;aAC5D;OAEI,cAAc,kBAAkB,eACzC,WAAW,gBACP,MAAM,WAAW,UAAU,QAC3B,QAAQ,QAAQ,WAAW,UAAU;OAGhC,cAAc,kBAAkB,WAAW,eACpD,WAAW;IACV,QAAQ;IACR,QAAQ,WAAW,UAAU;IAC7B;IACA;IACA;IACA,UAAU,gBAAgB;aAEtB;;OAGI;IACV;;IAEA,mBAAmB;;IAEnB,cAAc;;OAGJ;IACN;IAAe,MAAM;IAAe;;IACpC;IAAW,MAAM;IAAe;;IAChC;IAAc,MAAM;;OAEd;IACV,YAAY;IACZ,eAAe;;;UAIF,UACf,cACA,kBAAkB,UAAU,WAAW,UAAU;EAEjD,OAAO;EACP,UAAU,UAAU,cAAc;EAClC,aAAa,gBAAgB;EAC7B,SAAS,UAAU;;EAEnB;;EAEA
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;kBAGiB;OACH,WAAW,mBAAmB;OAE9B,QAAQ,kBAAkB,WAAW,eAC9C,WAAW;IAAc,QAAQ;IAAG,QAAQ,WAAW,UAAU;aAC5D;OAEI,cAAc,kBAAkB,eACzC,WAAW,gBACP,MAAM,WAAW,UAAU,QAC3B,QAAQ,QAAQ,WAAW,UAAU;OAGhC,cAAc,kBAAkB,WAAW,eACpD,WAAW;IACV,QAAQ;IACR,QAAQ,WAAW,UAAU;IAC7B;IACA;IACA;IACA,UAAU,gBAAgB;aAEtB;;OAGI;IACV;;IAEA,mBAAmB;;IAEnB,cAAc;;OAGJ;IACN;IAAe,MAAM;IAAe;;IACpC;IAAW,MAAM;IAAe;;IAChC;IAAc,MAAM;;OAEd;IACV,YAAY;IACZ,eAAe;;;UAIF,UACf,cACA,kBAAkB,UAAU,WAAW,UAAU;EAEjD,OAAO;EACP,UAAU,UAAU,cAAc;EAClC,aAAa,gBAAgB;EAC7B,SAAS,UAAU;;EAEnB;;EAEA;EACA;aAAkB;;;EAElB,cAAc,OAAO;;EAErB;IACE,qBAAqB,gBAAgB;IACrC,QAAQ,OAAO,gBAAgB,kBAAkB;IACjD,UAAU,OAAO,gBAAgB;;;cAuBxB,2BAA2B;WAC7B,SAAS,gBAAgB;EAEtB,YAAA,SAAS,gBAAgB"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":["StatewireClient","StatewirePath","Statewire","Commands","Record","args","Command","TCommands","method","K","params","Parameters","CommandsProxy","Promise","Awaited","ReturnType","CommandUpdate","seq","status","result","failure","SendFailure","StateSource","snapshot","takeChanged","setOnChange","listener","Op","op","path","value","Changes","isChangedAt","getChangedKeys","state","TState","commands","connection","Connection","source","durableSeq","isBusy","setContext","failures","list","FailedCommand","retry","entry","discard","describe","fate","code","message","cause","StatewireSendError","Error","constructor","name"],"sources":["../src/types.ts"],"sourcesContent":["import type { StatewireClient } from \"./statewire\";\nimport type { StatewirePath } from \"./wire/ops\";\n\nexport namespace Statewire {\n export type Commands = Record<string, (...args: never[]) => unknown>;\n\n export type Command<TCommands extends Commands = Commands> = {\n [K in keyof TCommands]: { method: K; params: Parameters<TCommands[K]> };\n }[keyof TCommands];\n\n export type CommandsProxy<TCommands extends Commands> = {\n [K in keyof TCommands]: (\n ...args: Parameters<TCommands[K]>\n ) => Promise<Awaited<ReturnType<TCommands[K]>>>;\n };\n\n export type CommandUpdate<TCommands extends Commands = Commands> = {\n [K in keyof TCommands]: {\n method: K;\n params: Parameters<TCommands[K]>;\n seq: number;\n status: \"queued\" | \"posted\" | \"applied\" | \"settled\";\n result?: unknown;\n failure?: StatewireClient.SendFailure;\n };\n }[keyof TCommands];\n\n /** Push-based canonical state feed a statewire hands to the client core. */\n export type StateSource = {\n snapshot: () => unknown;\n /** Drain the paths changed since the last take (`[]` entry = whole state). */\n takeChanged: () => StatewirePath[];\n /** Synchronous, per change; the consumer owns coalescing. */\n setOnChange: (listener: (() => void) | null) => void;\n };\n\n export type Op =\n | { op: \"replace\"; path: StatewirePath; value: unknown }\n | { op: \"add\"; path: StatewirePath; value: unknown }\n | { op: \"remove\"; path: StatewirePath };\n\n export type Changes = {\n isChangedAt(path: readonly (string | number)[]): boolean;\n getChangedKeys(path: readonly (string | number)[]): string[];\n };\n}\n\nexport interface Statewire<\n TState = any,\n TCommands extends Statewire.Commands = Statewire.Commands,\n> {\n state: TState;\n commands: Statewire.CommandsProxy<TCommands>;\n connection?: StatewireClient.Connection;\n source?: Statewire.StateSource;\n /** Highest command seq the server's dur flag states durably admitted — never lost, never run again — and released from the resend queue. */\n durableSeq?: number;\n /** Negation of the server's broadcast idle level; absent means always busy. */\n isBusy?: boolean;\n /** Replace this client's ambient context on the server; it rides the ctx lane (no verdict). */\n setContext?: (value: Record<string, unknown>) => void;\n /** The retained failed-send lane; present on transports with a durable session. */\n failures?: {\n list: () => readonly StatewireClient.FailedCommand[];\n retry: (entry: StatewireClient.FailedCommand) => Promise<unknown>;\n discard: (entry: StatewireClient.FailedCommand) => void;\n };\n}\n\nconst describe = (failure: StatewireClient.SendFailure): string => {\n switch (failure.fate) {\n case \"rejected\":\n return `statewire: command rejected${failure.code ? ` (${failure.code})` : \"\"}${failure.message ? `: ${failure.message}` : \"\"}`;\n case \"aborted\":\n return `statewire: command aborted (${failure.cause})`;\n case \"lost\":\n return `statewire: command lost (${failure.cause})`;\n case \"failed\":\n return failure.message\n ? `statewire: command failed: ${failure.message}`\n : \"statewire: command failed\";\n case \"crashed\":\n return \"statewire: command crashed\";\n case \"expired\":\n return `statewire: command result expired (${failure.cause})`;\n }\n};\n\nexport class StatewireSendError extends Error {\n readonly failure: StatewireClient.SendFailure;\n\n constructor(failure: StatewireClient.SendFailure) {\n super(describe(failure));\n this.name = \"StatewireSendError\";\n this.failure = failure;\n }\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"file":"types.js","names":["StatewireClient","StatewirePath","Statewire","Commands","Record","args","Command","TCommands","method","K","params","Parameters","CommandsProxy","Promise","Awaited","ReturnType","CommandUpdate","seq","status","result","failure","SendFailure","StateSource","snapshot","takeChanged","setOnChange","listener","Op","op","path","value","Changes","isChangedAt","getChangedKeys","state","TState","commands","connection","Connection","source","durableSeq","isBusy","work","isIdle","setContext","failures","list","FailedCommand","retry","entry","discard","describe","fate","code","message","cause","StatewireSendError","Error","constructor","name"],"sources":["../src/types.ts"],"sourcesContent":["import type { StatewireClient } from \"./statewire\";\nimport type { StatewirePath } from \"./wire/ops\";\n\nexport namespace Statewire {\n export type Commands = Record<string, (...args: never[]) => unknown>;\n\n export type Command<TCommands extends Commands = Commands> = {\n [K in keyof TCommands]: { method: K; params: Parameters<TCommands[K]> };\n }[keyof TCommands];\n\n export type CommandsProxy<TCommands extends Commands> = {\n [K in keyof TCommands]: (\n ...args: Parameters<TCommands[K]>\n ) => Promise<Awaited<ReturnType<TCommands[K]>>>;\n };\n\n export type CommandUpdate<TCommands extends Commands = Commands> = {\n [K in keyof TCommands]: {\n method: K;\n params: Parameters<TCommands[K]>;\n seq: number;\n status: \"queued\" | \"posted\" | \"applied\" | \"settled\";\n result?: unknown;\n failure?: StatewireClient.SendFailure;\n };\n }[keyof TCommands];\n\n /** Push-based canonical state feed a statewire hands to the client core. */\n export type StateSource = {\n snapshot: () => unknown;\n /** Drain the paths changed since the last take (`[]` entry = whole state). */\n takeChanged: () => StatewirePath[];\n /** Synchronous, per change; the consumer owns coalescing. */\n setOnChange: (listener: (() => void) | null) => void;\n };\n\n export type Op =\n | { op: \"replace\"; path: StatewirePath; value: unknown }\n | { op: \"add\"; path: StatewirePath; value: unknown }\n | { op: \"remove\"; path: StatewirePath };\n\n export type Changes = {\n isChangedAt(path: readonly (string | number)[]): boolean;\n getChangedKeys(path: readonly (string | number)[]): string[];\n };\n}\n\nexport interface Statewire<\n TState = any,\n TCommands extends Statewire.Commands = Statewire.Commands,\n> {\n state: TState;\n commands: Statewire.CommandsProxy<TCommands>;\n connection?: StatewireClient.Connection;\n source?: Statewire.StateSource;\n /** Highest command seq the server's dur flag states durably admitted — never lost, never run again — and released from the resend queue. */\n durableSeq?: number;\n /** Negation of the server's broadcast idle level; absent means always busy. */\n isBusy?: boolean;\n work?: { readonly isIdle: boolean };\n /** Replace this client's ambient context on the server; it rides the ctx lane (no verdict). */\n setContext?: (value: Record<string, unknown>) => void;\n /** The retained failed-send lane; present on transports with a durable session. */\n failures?: {\n list: () => readonly StatewireClient.FailedCommand[];\n retry: (entry: StatewireClient.FailedCommand) => Promise<unknown>;\n discard: (entry: StatewireClient.FailedCommand) => void;\n };\n}\n\nconst describe = (failure: StatewireClient.SendFailure): string => {\n switch (failure.fate) {\n case \"rejected\":\n return `statewire: command rejected${failure.code ? ` (${failure.code})` : \"\"}${failure.message ? `: ${failure.message}` : \"\"}`;\n case \"aborted\":\n return `statewire: command aborted (${failure.cause})`;\n case \"lost\":\n return `statewire: command lost (${failure.cause})`;\n case \"failed\":\n return failure.message\n ? `statewire: command failed: ${failure.message}`\n : \"statewire: command failed\";\n case \"crashed\":\n return \"statewire: command crashed\";\n case \"expired\":\n return `statewire: command result expired (${failure.cause})`;\n }\n};\n\nexport class StatewireSendError extends Error {\n readonly failure: StatewireClient.SendFailure;\n\n constructor(failure: StatewireClient.SendFailure) {\n super(describe(failure));\n this.name = \"StatewireSendError\";\n this.failure = failure;\n }\n}\n"],"mappings":";AAsEA,MAAMmD,YAAY/B,YAAiD;CACjE,QAAQA,QAAQgC,MAAhB;EACE,KAAK,YACH,OAAO,8BAA8BhC,QAAQiC,OAAO,KAAKjC,QAAQiC,KAAI,KAAM,KAAKjC,QAAQkC,UAAU,KAAKlC,QAAQkC,YAAY;EAC7H,KAAK,WACH,OAAO,+BAA+BlC,QAAQmC,MAAK;EACrD,KAAK,QACH,OAAO,4BAA4BnC,QAAQmC,MAAK;EAClD,KAAK,UACH,OAAOnC,QAAQkC,UACX,8BAA8BlC,QAAQkC,YACtC;EACN,KAAK,WACH,OAAO;EACT,KAAK,WACH,OAAO,sCAAsClC,QAAQmC,MAAK;CAC9D;AACF;AAEA,IAAaC,qBAAb,cAAwCC,MAAM;CAC5C;CAEAC,YAAYtC,SAAsC;EAChD,MAAM+B,SAAS/B,OAAO,CAAC;EACvB,KAAKuC,OAAO;EACZ,KAAKvC,UAAUA;CACjB;AACF"}
|
package/dist/util/randomId.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/util/randomId.ts
|
|
2
|
-
const randomId = () =>
|
|
2
|
+
const randomId = () => Array.from(crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16)), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
3
3
|
//#endregion
|
|
4
4
|
export { randomId };
|
|
5
5
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"randomId.js","names":["randomId","
|
|
1
|
+
{"version":3,"file":"randomId.js","names":["randomId","Array","from","crypto","getRandomValues","Uint8Array","byte","toString","padStart","join"],"sources":["../../src/util/randomId.ts"],"sourcesContent":["export const randomId = (): string =>\n Array.from(crypto.getRandomValues(new Uint8Array(16)), (byte) =>\n byte.toString(16).padStart(2, \"0\"),\n ).join(\"\");\n"],"mappings":";AAAA,MAAaA,iBACXC,MAAMC,KAAKC,OAAOC,gCAAgB,IAAIC,WAAW,EAAE,CAAC,IAAIC,SACtDA,KAAKC,SAAS,EAAE,CAAC,CAACC,SAAS,GAAG,GAAG,CACnC,CAAC,CAACC,KAAK,EAAE"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { StatewirePath } from "./ops.js";
|
|
2
|
+
//#region src/wire/documents.d.ts
|
|
3
|
+
declare namespace StatewireDocuments {
|
|
4
|
+
type Document = {
|
|
5
|
+
key: readonly string[] | null;
|
|
6
|
+
value: unknown;
|
|
7
|
+
};
|
|
8
|
+
type State = {
|
|
9
|
+
mode: "mounted" | "keyless" | null;
|
|
10
|
+
documents: ReadonlyMap<number, Document>;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
declare const applyDocumentOps: (previous: StatewireDocuments.State, ops: readonly unknown[]) => {
|
|
14
|
+
mode: "mounted" | "keyless";
|
|
15
|
+
documents: Map<number, StatewireDocuments.Document>;
|
|
16
|
+
changedPaths: StatewirePath[];
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
export { StatewireDocuments, applyDocumentOps };
|
|
20
|
+
//# sourceMappingURL=documents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"documents.d.ts","names":[],"sources":["../../src/wire/documents.ts"],"mappings":";;kBAIiB;OACH;IAAa;IAA+B;;OAC5C;IACV;IACA,WAAW,oBAAoB;;;cAQtB,mBACX,UAAU,mBAAmB,OAC7B"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { applyOps } from "./ops.js";
|
|
2
|
+
import { reviveValue } from "./meta.js";
|
|
3
|
+
//#region src/wire/documents.ts
|
|
4
|
+
const invalid = (message) => {
|
|
5
|
+
throw new Error(`statewire: protocol error: ${message}`);
|
|
6
|
+
};
|
|
7
|
+
const applyDocumentOps = (previous, ops) => {
|
|
8
|
+
let mode = previous.mode;
|
|
9
|
+
const documents = new Map(previous.documents);
|
|
10
|
+
const changedPaths = [];
|
|
11
|
+
for (const raw of ops) {
|
|
12
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) invalid("operation must be an object");
|
|
13
|
+
const op = raw;
|
|
14
|
+
const doc = op.doc === void 0 ? 0 : op.doc;
|
|
15
|
+
if (typeof doc !== "number" || !Number.isSafeInteger(doc) || doc < 0) invalid("doc must be a nonnegative safe integer");
|
|
16
|
+
if (![
|
|
17
|
+
"mount",
|
|
18
|
+
"unmount",
|
|
19
|
+
"add",
|
|
20
|
+
"replace",
|
|
21
|
+
"remove"
|
|
22
|
+
].includes(op.op)) {
|
|
23
|
+
const { doc: _doc, ...unknown } = op;
|
|
24
|
+
applyOps(void 0, [unknown]);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (mode === null) {
|
|
28
|
+
if (op.op === "mount") mode = "mounted";
|
|
29
|
+
else if (op.op === "replace" && doc === 0 && Array.isArray(op.path) && op.path.length === 0 && "value" in op) {
|
|
30
|
+
mode = "keyless";
|
|
31
|
+
documents.set(0, {
|
|
32
|
+
key: null,
|
|
33
|
+
value: void 0
|
|
34
|
+
});
|
|
35
|
+
} else invalid("first operation must mount a document or replace document 0 at the root");
|
|
36
|
+
}
|
|
37
|
+
if (mode === "keyless" && (doc !== 0 || op.op === "mount" || op.op === "unmount")) invalid("keyless mode only supports mutations of document 0");
|
|
38
|
+
if (op.op === "mount") {
|
|
39
|
+
if (documents.has(doc)) invalid("document is already mounted");
|
|
40
|
+
if (!Array.isArray(op.key) || !op.key.every((key) => typeof key === "string") || !("value" in op)) invalid("mount requires a key array and value");
|
|
41
|
+
documents.set(doc, {
|
|
42
|
+
key: op.key,
|
|
43
|
+
value: op.meta === void 0 ? op.value : reviveValue(op.value, op.meta)
|
|
44
|
+
});
|
|
45
|
+
if (doc === 0) changedPaths.push([]);
|
|
46
|
+
} else if (op.op === "unmount") {
|
|
47
|
+
if (!documents.delete(doc)) invalid("document is not mounted");
|
|
48
|
+
if (doc === 0) changedPaths.push([]);
|
|
49
|
+
} else if (op.op === "add" || op.op === "replace" || op.op === "remove") {
|
|
50
|
+
const document = documents.get(doc);
|
|
51
|
+
if (!document) invalid("document is not mounted");
|
|
52
|
+
if (!Array.isArray(op.path) || !op.path.every((part) => typeof part === "string" || typeof part === "number" && Number.isSafeInteger(part) && part >= 0)) invalid("operation requires a valid path");
|
|
53
|
+
if (op.op !== "remove" && !("value" in op)) invalid("operation requires a value");
|
|
54
|
+
const { doc: _doc, meta, ...mutation } = op;
|
|
55
|
+
if (meta !== void 0) mutation.value = reviveValue(mutation.value, meta);
|
|
56
|
+
const applied = applyOps(document.value, [mutation]);
|
|
57
|
+
documents.set(doc, {
|
|
58
|
+
...document,
|
|
59
|
+
value: applied.state
|
|
60
|
+
});
|
|
61
|
+
if (doc === 0) changedPaths.push(...applied.changedPaths);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (mode === null) invalid("hello missing snapshot");
|
|
65
|
+
return {
|
|
66
|
+
mode,
|
|
67
|
+
documents,
|
|
68
|
+
changedPaths
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
//#endregion
|
|
72
|
+
export { applyDocumentOps };
|
|
73
|
+
|
|
74
|
+
//# sourceMappingURL=documents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"documents.js","names":["applyOps","reviveValue","StatewirePath","StatewireDocuments","Document","key","value","State","mode","documents","ReadonlyMap","invalid","message","Error","applyDocumentOps","previous","ops","Map","changedPaths","raw","Array","isArray","op","Record","doc","undefined","Number","isSafeInteger","includes","_doc","unknown","path","length","set","has","every","meta","push","delete","document","get","part","mutation","applied","state"],"sources":["../../src/wire/documents.ts"],"sourcesContent":["import { applyOps } from \"./ops\";\nimport { reviveValue } from \"./meta\";\nimport type { StatewirePath } from \"./ops\";\n\nexport namespace StatewireDocuments {\n export type Document = { key: readonly string[] | null; value: unknown };\n export type State = {\n mode: \"mounted\" | \"keyless\" | null;\n documents: ReadonlyMap<number, Document>;\n };\n}\n\nconst invalid: (message: string) => never = (message) => {\n throw new Error(`statewire: protocol error: ${message}`);\n};\n\nexport const applyDocumentOps = (\n previous: StatewireDocuments.State,\n ops: readonly unknown[],\n) => {\n let mode = previous.mode;\n const documents = new Map(previous.documents);\n const changedPaths: StatewirePath[] = [];\n for (const raw of ops) {\n if (typeof raw !== \"object\" || raw === null || Array.isArray(raw)) {\n invalid(\"operation must be an object\");\n }\n const op = raw as Record<string, unknown>;\n const doc = op.doc === undefined ? 0 : op.doc;\n if (typeof doc !== \"number\" || !Number.isSafeInteger(doc) || doc < 0) {\n invalid(\"doc must be a nonnegative safe integer\");\n }\n if (\n ![\"mount\", \"unmount\", \"add\", \"replace\", \"remove\"].includes(\n op.op as string,\n )\n ) {\n const { doc: _doc, ...unknown } = op;\n applyOps(undefined, [unknown]);\n continue;\n }\n if (mode === null) {\n if (op.op === \"mount\") mode = \"mounted\";\n else if (\n op.op === \"replace\" &&\n doc === 0 &&\n Array.isArray(op.path) &&\n op.path.length === 0 &&\n \"value\" in op\n ) {\n mode = \"keyless\";\n documents.set(0, { key: null, value: undefined });\n } else\n invalid(\n \"first operation must mount a document or replace document 0 at the root\",\n );\n }\n if (\n mode === \"keyless\" &&\n (doc !== 0 || op.op === \"mount\" || op.op === \"unmount\")\n ) {\n invalid(\"keyless mode only supports mutations of document 0\");\n }\n if (op.op === \"mount\") {\n if (documents.has(doc)) invalid(\"document is already mounted\");\n if (\n !Array.isArray(op.key) ||\n !op.key.every((key) => typeof key === \"string\") ||\n !(\"value\" in op)\n ) {\n invalid(\"mount requires a key array and value\");\n }\n documents.set(doc, {\n key: op.key,\n value:\n op.meta === undefined ? op.value : reviveValue(op.value, op.meta),\n });\n if (doc === 0) changedPaths.push([]);\n } else if (op.op === \"unmount\") {\n if (!documents.delete(doc)) invalid(\"document is not mounted\");\n if (doc === 0) changedPaths.push([]);\n } else if (op.op === \"add\" || op.op === \"replace\" || op.op === \"remove\") {\n const document = documents.get(doc);\n if (!document) invalid(\"document is not mounted\");\n if (\n !Array.isArray(op.path) ||\n !op.path.every(\n (part) =>\n typeof part === \"string\" ||\n (typeof part === \"number\" &&\n Number.isSafeInteger(part) &&\n part >= 0),\n )\n ) {\n invalid(\"operation requires a valid path\");\n }\n if (op.op !== \"remove\" && !(\"value\" in op))\n invalid(\"operation requires a value\");\n const { doc: _doc, meta, ...mutation } = op;\n if (meta !== undefined)\n mutation.value = reviveValue(mutation.value, meta);\n const applied = applyOps(document.value, [mutation]);\n documents.set(doc, { ...document, value: applied.state });\n if (doc === 0) changedPaths.push(...applied.changedPaths);\n }\n }\n if (mode === null) invalid(\"hello missing snapshot\");\n return { mode, documents, changedPaths };\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/wire/limits.d.ts
|
|
2
|
+
declare const MAX_COMMANDS_PER_FRAME = 256;
|
|
3
|
+
declare const MAX_INFLIGHT_COMMANDS_PER_CLIENT = 256;
|
|
4
|
+
declare const MAX_FRAME_BODY_BYTES: number;
|
|
5
|
+
declare const MAX_CONTEXT_VALUE_BYTES: number;
|
|
6
|
+
declare const MAX_INFLIGHT_COMMANDS_PER_HOST = 1024;
|
|
7
|
+
declare const COMMAND_CAPACITY_RETRY_MS = 1000;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { COMMAND_CAPACITY_RETRY_MS, MAX_COMMANDS_PER_FRAME, MAX_CONTEXT_VALUE_BYTES, MAX_FRAME_BODY_BYTES, MAX_INFLIGHT_COMMANDS_PER_CLIENT, MAX_INFLIGHT_COMMANDS_PER_HOST };
|
|
10
|
+
//# sourceMappingURL=limits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limits.d.ts","names":[],"sources":["../../src/wire/limits.ts"],"mappings":";cAAa;cACA;cACA;cACA;cACA;cACA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/wire/limits.ts
|
|
2
|
+
const MAX_COMMANDS_PER_FRAME = 256;
|
|
3
|
+
const MAX_INFLIGHT_COMMANDS_PER_CLIENT = 256;
|
|
4
|
+
const MAX_FRAME_BODY_BYTES = 8388608;
|
|
5
|
+
const MAX_CONTEXT_VALUE_BYTES = 65536;
|
|
6
|
+
const MAX_INFLIGHT_COMMANDS_PER_HOST = 1024;
|
|
7
|
+
const COMMAND_CAPACITY_RETRY_MS = 1e3;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { COMMAND_CAPACITY_RETRY_MS, MAX_COMMANDS_PER_FRAME, MAX_CONTEXT_VALUE_BYTES, MAX_FRAME_BODY_BYTES, MAX_INFLIGHT_COMMANDS_PER_CLIENT, MAX_INFLIGHT_COMMANDS_PER_HOST };
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=limits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limits.js","names":["MAX_COMMANDS_PER_FRAME","MAX_INFLIGHT_COMMANDS_PER_CLIENT","MAX_FRAME_BODY_BYTES","MAX_CONTEXT_VALUE_BYTES","MAX_INFLIGHT_COMMANDS_PER_HOST","COMMAND_CAPACITY_RETRY_MS"],"sources":["../../src/wire/limits.ts"],"sourcesContent":["export const MAX_COMMANDS_PER_FRAME = 256;\nexport const MAX_INFLIGHT_COMMANDS_PER_CLIENT = 256;\nexport const MAX_FRAME_BODY_BYTES = 8 * 1024 * 1024;\nexport const MAX_CONTEXT_VALUE_BYTES = 64 * 1024;\nexport const MAX_INFLIGHT_COMMANDS_PER_HOST = 1024;\nexport const COMMAND_CAPACITY_RETRY_MS = 1_000;\n"],"mappings":";AAAA,MAAaA,yBAAyB;AACtC,MAAaC,mCAAmC;AAChD,MAAaC,uBAAuB;AACpC,MAAaC,0BAA0B;AACvC,MAAaC,iCAAiC;AAC9C,MAAaC,4BAA4B"}
|
package/dist/wire/meta.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meta.d.ts","names":[],"sources":["../../src/wire/meta.ts"],"mappings":";KAEY;EACV;EACA;;;;cAiBW,cACX,gBACA,iBACA;;;
|
|
1
|
+
{"version":3,"file":"meta.d.ts","names":[],"sources":["../../src/wire/meta.ts"],"mappings":";KAEY;EACV;EACA;;;;cAiBW,cACX,gBACA,iBACA;;;cAkCW,YAAa;cAOb,iBAAkB;EAAY;EAAmB;GAC5D,UAAU,MACT;;;cA+BU,eACX;EACG;EAAmB,OAAO;;;cAOlB,cACX;EACG;EAAgB,OAAO"}
|
package/dist/wire/meta.js
CHANGED
|
@@ -19,7 +19,7 @@ const reviveValue = (value, meta, { rejectUnknown = false } = {}) => {
|
|
|
19
19
|
if (walked === void 0) return fault(`meta path ${path.join(".")} does not resolve`);
|
|
20
20
|
if (typeof walked.leaf !== "string") return fault(`meta path ${path.join(".")} must name a string`);
|
|
21
21
|
const date = new Date(walked.leaf);
|
|
22
|
-
if (Number.isNaN(date.getTime())) return fault(`invalid date at meta path ${path.join(".")}`);
|
|
22
|
+
if (!/(?:T| )\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})$/i.test(walked.leaf) || Number.isNaN(date.getTime())) return fault(`invalid date at meta path ${path.join(".")}`);
|
|
23
23
|
if (walked.parent === void 0) return date;
|
|
24
24
|
walked.parent[walked.key] = date;
|
|
25
25
|
}
|
package/dist/wire/meta.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meta.js","names":["walkStatePath","StatewireMetaEntry","path","type","fault","message","Error","isPath","Array","isArray","every","seg","Number","isInteger","reviveValue","value","meta","rejectUnknown","entry","String","walked","undefined","join","leaf","date","Date","isNaN","getTime","parent","Record","key","reviveOps","ops","map","op","rest","reviveResponse","payload","response","T","walkDates","node","push","toISOString","item","i","Object","fromEntries","keys","encodeParams","params","encoded","param","length","encodeValue"],"sources":["../../src/wire/meta.ts"],"sourcesContent":["import { walkStatePath } from \"../util/statePath\";\n\nexport type StatewireMetaEntry = {\n path: (string | number)[];\n type: string;\n};\n\nconst fault = (message: string): never => {\n throw new Error(`statewire: protocol error: ${message}`);\n};\n\nconst isPath = (path: unknown): path is (string | number)[] =>\n Array.isArray(path) &&\n path.every(\n (seg) =>\n typeof seg === \"string\" ||\n (typeof seg === \"number\" && Number.isInteger(seg)),\n );\n\n/** Revive meta-typed locations inside a wire value (in place). Clients skip\n * unknown types; hosts pass `rejectUnknown` to fault on them instead. */\nexport const reviveValue = (\n value: unknown,\n meta: unknown,\n { rejectUnknown = false } = {},\n): unknown => {\n if (!Array.isArray(meta)) return fault(\"meta must be an array\");\n for (const entry of meta as { path?: unknown; type?: unknown }[]) {\n const path =\n typeof entry === \"object\" && entry !== null ? entry.path : null;\n if (!isPath(path)) return fault(\"meta entry must carry a path array\");\n if (entry.type !== \"date\") {\n if (rejectUnknown)\n return fault(`unknown meta type: ${String(entry.type)}`);\n continue;\n }\n const walked = walkStatePath(value, path, \"resolve\");\n if (walked === undefined) {\n return fault(`meta path ${path.join(\".\")} does not resolve`);\n }\n if (typeof walked.leaf !== \"string\") {\n return fault(`meta path ${path.join(\".\")} must name a string`);\n }\n const date = new Date(walked.leaf);\n if (Number.isNaN(date.getTime())) {\n return fault(`invalid date at meta path ${path.join(\".\")}`);\n }\n if (walked.parent === undefined) return date;\n (walked.parent as Record<string | number, unknown>)[walked.key] = date;\n }\n return value;\n};\n\nexport const reviveOps = (ops: unknown[]): unknown[] =>\n ops.map((op) => {\n if (typeof op !== \"object\" || op === null || !(\"meta\" in op)) return op;\n const { meta, ...rest } = op as { meta: unknown; value?: unknown };\n return { ...rest, value: reviveValue(rest.value, meta) };\n });\n\nexport const reviveResponse = <T extends { payload?: unknown; meta?: unknown }>(\n response: T,\n): T => {\n if (response.meta === undefined) return response;\n const { meta, ...rest } = response;\n return { ...rest, payload: reviveValue(rest.payload, meta) } as T;\n};\n\nconst walkDates = (\n node: unknown,\n path: (string | number)[],\n meta: StatewireMetaEntry[],\n): unknown => {\n if (node instanceof Date) {\n meta.push({ path, type: \"date\" });\n return node.toISOString();\n }\n if (Array.isArray(node)) {\n return node.map((item, i) => walkDates(item, [...path, i], meta));\n }\n if (typeof node === \"object\" && node !== null) {\n return Object.fromEntries(\n Object.keys(node).map((key) => [\n key,\n walkDates((node as Record<string, unknown>)[key], [...path, key], meta),\n ]),\n );\n }\n return node;\n};\n\n/** Encode Date instances inside command params as canonical ISO strings,\n * recording their paths (starting at the param index) in meta. */\nexport const encodeParams = (\n params: readonly unknown[],\n): { params: unknown[]; meta?: StatewireMetaEntry[] } => {\n const meta: StatewireMetaEntry[] = [];\n const encoded = params.map((param, i) => walkDates(param, [i], meta));\n return meta.length > 0 ? { params: encoded, meta } : { params: encoded };\n};\n\n/** Encode Date instances inside a wire value, recording their paths in meta. */\nexport const encodeValue = (\n value: unknown,\n): { value: unknown; meta?: StatewireMetaEntry[] } => {\n const meta: StatewireMetaEntry[] = [];\n const encoded = walkDates(value, [], meta);\n return meta.length > 0 ? { value: encoded, meta } : { value: encoded };\n};\n"],"mappings":";;AAOA,MAAMI,SAASC,YAA2B;CACxC,MAAM,IAAIC,MAAM,8BAA8BD,SAAS;AACzD;AAEA,MAAME,UAAUL,SACdM,MAAMC,QAAQP,IAAI,KAClBA,KAAKQ,OACFC,QACC,OAAOA,QAAQ,YACd,OAAOA,QAAQ,YAAYC,OAAOC,UAAUF,GAAG,CACpD;;;AAIF,MAAaG,eACXC,OACAC,MACA,EAAEC,gBAAgB,UAAU,CAAC,MACjB;CACZ,IAAI,CAACT,MAAMC,QAAQO,IAAI,GAAG,OAAOZ,MAAM,uBAAuB;CAC9D,KAAK,MAAMc,SAASF,MAA8C;EAChE,MAAMd,OACJ,OAAOgB,UAAU,YAAYA,UAAU,OAAOA,MAAMhB,OAAO;EAC7D,IAAI,CAACK,OAAOL,IAAI,GAAG,OAAOE,MAAM,oCAAoC;EACpE,IAAIc,MAAMf,SAAS,QAAQ;GACzB,IAAIc,eACF,OAAOb,MAAM,sBAAsBe,OAAOD,MAAMf,IAAI,GAAG;GACzD;EACF;EACA,MAAMiB,SAASpB,cAAce,OAAOb,MAAM,SAAS;EACnD,IAAIkB,WAAWC,KAAAA,GACb,OAAOjB,MAAM,aAAaF,KAAKoB,KAAK,GAAG,EAAC,kBAAmB;EAE7D,IAAI,OAAOF,OAAOG,SAAS,UACzB,OAAOnB,MAAM,aAAaF,KAAKoB,KAAK,GAAG,EAAC,oBAAqB;EAE/D,MAAME,OAAO,IAAIC,KAAKL,OAAOG,IAAI;EACjC,
|
|
1
|
+
{"version":3,"file":"meta.js","names":["walkStatePath","StatewireMetaEntry","path","type","fault","message","Error","isPath","Array","isArray","every","seg","Number","isInteger","reviveValue","value","meta","rejectUnknown","entry","String","walked","undefined","join","leaf","date","Date","test","isNaN","getTime","parent","Record","key","reviveOps","ops","map","op","rest","reviveResponse","payload","response","T","walkDates","node","push","toISOString","item","i","Object","fromEntries","keys","encodeParams","params","encoded","param","length","encodeValue"],"sources":["../../src/wire/meta.ts"],"sourcesContent":["import { walkStatePath } from \"../util/statePath\";\n\nexport type StatewireMetaEntry = {\n path: (string | number)[];\n type: string;\n};\n\nconst fault = (message: string): never => {\n throw new Error(`statewire: protocol error: ${message}`);\n};\n\nconst isPath = (path: unknown): path is (string | number)[] =>\n Array.isArray(path) &&\n path.every(\n (seg) =>\n typeof seg === \"string\" ||\n (typeof seg === \"number\" && Number.isInteger(seg)),\n );\n\n/** Revive meta-typed locations inside a wire value (in place). Clients skip\n * unknown types; hosts pass `rejectUnknown` to fault on them instead. */\nexport const reviveValue = (\n value: unknown,\n meta: unknown,\n { rejectUnknown = false } = {},\n): unknown => {\n if (!Array.isArray(meta)) return fault(\"meta must be an array\");\n for (const entry of meta as { path?: unknown; type?: unknown }[]) {\n const path =\n typeof entry === \"object\" && entry !== null ? entry.path : null;\n if (!isPath(path)) return fault(\"meta entry must carry a path array\");\n if (entry.type !== \"date\") {\n if (rejectUnknown)\n return fault(`unknown meta type: ${String(entry.type)}`);\n continue;\n }\n const walked = walkStatePath(value, path, \"resolve\");\n if (walked === undefined) {\n return fault(`meta path ${path.join(\".\")} does not resolve`);\n }\n if (typeof walked.leaf !== \"string\") {\n return fault(`meta path ${path.join(\".\")} must name a string`);\n }\n const date = new Date(walked.leaf);\n if (\n !/(?:T| )\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d+)?)?(?:Z|[+-]\\d{2}:?\\d{2})$/i.test(\n walked.leaf,\n ) ||\n Number.isNaN(date.getTime())\n ) {\n return fault(`invalid date at meta path ${path.join(\".\")}`);\n }\n if (walked.parent === undefined) return date;\n (walked.parent as Record<string | number, unknown>)[walked.key] = date;\n }\n return value;\n};\n\nexport const reviveOps = (ops: unknown[]): unknown[] =>\n ops.map((op) => {\n if (typeof op !== \"object\" || op === null || !(\"meta\" in op)) return op;\n const { meta, ...rest } = op as { meta: unknown; value?: unknown };\n return { ...rest, value: reviveValue(rest.value, meta) };\n });\n\nexport const reviveResponse = <T extends { payload?: unknown; meta?: unknown }>(\n response: T,\n): T => {\n if (response.meta === undefined) return response;\n const { meta, ...rest } = response;\n return { ...rest, payload: reviveValue(rest.payload, meta) } as T;\n};\n\nconst walkDates = (\n node: unknown,\n path: (string | number)[],\n meta: StatewireMetaEntry[],\n): unknown => {\n if (node instanceof Date) {\n meta.push({ path, type: \"date\" });\n return node.toISOString();\n }\n if (Array.isArray(node)) {\n return node.map((item, i) => walkDates(item, [...path, i], meta));\n }\n if (typeof node === \"object\" && node !== null) {\n return Object.fromEntries(\n Object.keys(node).map((key) => [\n key,\n walkDates((node as Record<string, unknown>)[key], [...path, key], meta),\n ]),\n );\n }\n return node;\n};\n\n/** Encode Date instances inside command params as canonical ISO strings,\n * recording their paths (starting at the param index) in meta. */\nexport const encodeParams = (\n params: readonly unknown[],\n): { params: unknown[]; meta?: StatewireMetaEntry[] } => {\n const meta: StatewireMetaEntry[] = [];\n const encoded = params.map((param, i) => walkDates(param, [i], meta));\n return meta.length > 0 ? { params: encoded, meta } : { params: encoded };\n};\n\n/** Encode Date instances inside a wire value, recording their paths in meta. */\nexport const encodeValue = (\n value: unknown,\n): { value: unknown; meta?: StatewireMetaEntry[] } => {\n const meta: StatewireMetaEntry[] = [];\n const encoded = walkDates(value, [], meta);\n return meta.length > 0 ? { value: encoded, meta } : { value: encoded };\n};\n"],"mappings":"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"}
|
package/dist/wire/ops.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ops.d.ts","names":[],"sources":["../../src/wire/ops.ts"],"mappings":";;KAIY;cAwDC,gBAAiB,gBAAgB;
|
|
1
|
+
{"version":3,"file":"ops.d.ts","names":[],"sources":["../../src/wire/ops.ts"],"mappings":";;KAIY;cAwDC,gBAAiB,gBAAgB;cAgIjC,UACX,eACA,IAAI,UAAU;EACX;EAAgB,cAAc;;cAUtB,WACX,gBACA;EAEA;EACA,cAAc"}
|
package/dist/wire/ops.js
CHANGED
|
@@ -92,8 +92,7 @@ const applyOpToDraft = (box, op) => {
|
|
|
92
92
|
return;
|
|
93
93
|
}
|
|
94
94
|
if (op.path.length === 0) {
|
|
95
|
-
|
|
96
|
-
box.state = sanitizeValue(op.value);
|
|
95
|
+
box.state = op.op === "remove" ? void 0 : sanitizeValue(op.value);
|
|
97
96
|
return;
|
|
98
97
|
}
|
|
99
98
|
const walked = walkStatePath(box.state, op.path, "parent");
|
package/dist/wire/ops.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ops.js","names":["recordMutations","isUnsafeKey","walkStatePath","Statewire","StatewirePath","warnStripped","key","parent","process","env","console","warn","undefined","unserializable","kind","path","Error","length","join","sanitizeAt","value","Date","getTime","Array","isArray","map","item","index","out","Record","Object","keys","sanitizeValue","invalid","op","Op","utf16Index","text","offset","remaining","codePointAt","countCodePoints","count","last","str","insertedText","current","Extract","memoized","next","slice","applyAdd","box","state","walked","Number","isInteger","target","leaf","splice","applyInList","list","applyInObject","obj","hasOwn","applyOpToDraft","isMountPlaneOp","doc","warnedOps","Set","warnUnknownOp","type","has","add","JSON","stringify","unboxPaths","paths","applyOp","root","changedPaths","recorded","draft","applyOps","ops","mutable","push"],"sources":["../../src/wire/ops.ts"],"sourcesContent":["import { recordMutations } from \"../util/recordMutations\";\nimport { isUnsafeKey, walkStatePath } from \"../util/statePath\";\nimport type { Statewire } from \"../types\";\n\nexport type StatewirePath = readonly (string | number)[];\n\nconst warnStripped = (key: string, parent: string | undefined) => {\n if (\n typeof process === \"undefined\" ||\n process.env[\"NODE_ENV\"] !== \"production\"\n ) {\n console.warn(\n `statewire: stripped unsafe key \"${key}\"` +\n (parent === undefined ? \"\" : ` under \"${parent}\"`),\n );\n }\n};\n\nconst unserializable = (\n kind: \"function\" | \"undefined\",\n path: readonly (string | number)[],\n): never => {\n throw new Error(\n `statewire: cannot serialize ${kind}` +\n (path.length === 0 ? \"\" : ` at ${path.join(\".\")}`),\n );\n};\n\nconst sanitizeAt = (\n value: unknown,\n parent: string | undefined,\n path: readonly (string | number)[],\n): unknown => {\n if (value instanceof Date) return new Date(value.getTime());\n if (Array.isArray(value)) {\n return value.map((item, index) => {\n if (item === undefined) unserializable(\"undefined\", [...path, index]);\n return sanitizeAt(item, undefined, [...path, index]);\n });\n }\n if (typeof value === \"object\" && value !== null) {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n if (isUnsafeKey(key, parent)) {\n warnStripped(key, parent);\n continue;\n }\n const item = (value as Record<string, unknown>)[key];\n if (item === undefined) continue;\n out[key] = sanitizeAt(item, key, [...path, key]);\n }\n return out;\n }\n if (typeof value === \"function\") unserializable(\"function\", path);\n return value;\n};\n\n// One clone pass: strips unsafe keys (with a warning); explicit-undefined\n// object entries are omitted (delete semantics), functions and undefined\n// list items throw.\nexport const sanitizeValue = (value: unknown, parent?: string): unknown =>\n sanitizeAt(value, parent, parent === undefined ? [] : [parent]);\n\nconst invalid = (op: Statewire.Op) =>\n new Error(`Statewire cannot apply ${op.op} at path: ${op.path.join(\".\")}`);\n\n// Text-insert offsets are Unicode code points on the wire, not UTF-16 units.\nconst utf16Index = (text: string, offset: number): number => {\n let index = 0;\n for (let remaining = offset; remaining > 0; remaining -= 1) {\n if (index >= text.length) return -1;\n index += text.codePointAt(index)! > 0xffff ? 2 : 1;\n }\n return index;\n};\n\nconst countCodePoints = (text: string): number => {\n let count = 0;\n for (let index = 0; index < text.length; index += 1) {\n if (text.codePointAt(index)! > 0xffff) index += 1;\n count += 1;\n }\n return count;\n};\n\nlet last: { str: string; count: number } | null = null;\n\nconst insertedText = (\n current: string,\n op: Extract<Statewire.Op, { op: \"add\" }>,\n offset: number,\n): string => {\n if (typeof op.value !== \"string\") throw invalid(op);\n const memoized =\n last !== null && last.str === current && last.count === offset;\n const index = memoized ? current.length : utf16Index(current, offset);\n if (index < 0) throw invalid(op);\n const next = current.slice(0, index) + op.value + current.slice(index);\n if (index === current.length) {\n last = { str: next, count: offset + countCodePoints(op.value) };\n }\n return next;\n};\n\nconst applyAdd = (\n box: { state: unknown },\n op: Extract<Statewire.Op, { op: \"add\" }>,\n) => {\n const offset = op.path[op.path.length - 1];\n const walked = walkStatePath(box.state, op.path.slice(0, -1), \"resolve\");\n if (\n walked === undefined ||\n typeof offset !== \"number\" ||\n !Number.isInteger(offset) ||\n offset < 0\n ) {\n throw invalid(op);\n }\n const target = walked.leaf;\n if (Array.isArray(target)) {\n if (offset > target.length) throw invalid(op);\n target.splice(offset, 0, sanitizeValue(op.value));\n } else if (typeof target === \"string\") {\n const next = insertedText(target, op, offset);\n if (op.path.length === 1) {\n box.state = next;\n } else {\n (walked.parent as Record<string | number, unknown>)[walked.key] = next;\n }\n } else {\n throw invalid(op);\n }\n};\n\nconst applyInList = (list: unknown[], index: number, op: Statewire.Op) => {\n if (index >= list.length) throw invalid(op);\n if (op.op === \"replace\") {\n list[index] = sanitizeValue(op.value);\n } else {\n list.splice(index, 1);\n }\n};\n\nconst applyInObject = (\n obj: Record<string, unknown>,\n key: string,\n op: Statewire.Op,\n) => {\n if (op.op === \"replace\") {\n obj[key] = sanitizeValue(op.value, key);\n } else {\n if (!Object.hasOwn(obj, key)) throw invalid(op);\n delete obj[key];\n }\n};\n\nconst applyOpToDraft = (box: { state: unknown }, op: Statewire.Op): void => {\n if (op.op === \"add\") {\n if (op.path.length === 0) throw invalid(op);\n applyAdd(box, op);\n return;\n }\n if (op.path.length === 0) {\n if (op.op !== \"replace\") throw invalid(op);\n box.state = sanitizeValue(op.value);\n return;\n }\n const walked = walkStatePath(box.state, op.path, \"parent\");\n if (walked === undefined) throw invalid(op);\n if (typeof walked.key === \"number\") {\n applyInList(walked.parent as unknown[], walked.key, op);\n } else {\n applyInObject(walked.parent as Record<string, unknown>, walked.key, op);\n }\n};\n\nconst isMountPlaneOp = (op: { op?: unknown; doc?: unknown }) =>\n \"doc\" in op || op.op === \"mount\" || op.op === \"unmount\";\n\nconst warnedOps = new Set<unknown>();\nconst warnUnknownOp = (type: unknown) => {\n if (warnedOps.has(type)) return;\n warnedOps.add(type);\n console.warn(`statewire: ignoring unknown op type ${JSON.stringify(type)}`);\n};\n\nconst unboxPaths = (paths: readonly StatewirePath[]): StatewirePath[] =>\n paths.map((path) => path.slice(1));\n\nexport const applyOp = (\n root: unknown,\n op: Statewire.Op,\n): { state: unknown; changedPaths: StatewirePath[] } => {\n const recorded = recordMutations({ state: root }, (draft) =>\n applyOpToDraft(draft, op),\n );\n return {\n state: recorded.state.state,\n changedPaths: unboxPaths(recorded.changedPaths),\n };\n};\n\nexport const applyOps = (\n state: unknown,\n ops: readonly unknown[],\n): {\n state: unknown;\n changedPaths: StatewirePath[];\n} => {\n const mutable: Statewire.Op[] = [];\n for (const op of ops as Statewire.Op[]) {\n if (isMountPlaneOp(op)) continue;\n if (op.op === \"replace\" || op.op === \"add\" || op.op === \"remove\") {\n mutable.push(op);\n } else {\n warnUnknownOp((op as { op: unknown }).op);\n }\n }\n const recorded = recordMutations({ state }, (draft) => {\n for (const op of mutable) applyOpToDraft(draft, op);\n });\n return {\n state: recorded.state.state,\n changedPaths: unboxPaths(recorded.changedPaths),\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ops.js","names":["recordMutations","isUnsafeKey","walkStatePath","Statewire","StatewirePath","warnStripped","key","parent","process","env","console","warn","undefined","unserializable","kind","path","Error","length","join","sanitizeAt","value","Date","getTime","Array","isArray","map","item","index","out","Record","Object","keys","sanitizeValue","invalid","op","Op","utf16Index","text","offset","remaining","codePointAt","countCodePoints","count","last","str","insertedText","current","Extract","memoized","next","slice","applyAdd","box","state","walked","Number","isInteger","target","leaf","splice","applyInList","list","applyInObject","obj","hasOwn","applyOpToDraft","isMountPlaneOp","doc","warnedOps","Set","warnUnknownOp","type","has","add","JSON","stringify","unboxPaths","paths","applyOp","root","changedPaths","recorded","draft","applyOps","ops","mutable","push"],"sources":["../../src/wire/ops.ts"],"sourcesContent":["import { recordMutations } from \"../util/recordMutations\";\nimport { isUnsafeKey, walkStatePath } from \"../util/statePath\";\nimport type { Statewire } from \"../types\";\n\nexport type StatewirePath = readonly (string | number)[];\n\nconst warnStripped = (key: string, parent: string | undefined) => {\n if (\n typeof process === \"undefined\" ||\n process.env[\"NODE_ENV\"] !== \"production\"\n ) {\n console.warn(\n `statewire: stripped unsafe key \"${key}\"` +\n (parent === undefined ? \"\" : ` under \"${parent}\"`),\n );\n }\n};\n\nconst unserializable = (\n kind: \"function\" | \"undefined\",\n path: readonly (string | number)[],\n): never => {\n throw new Error(\n `statewire: cannot serialize ${kind}` +\n (path.length === 0 ? \"\" : ` at ${path.join(\".\")}`),\n );\n};\n\nconst sanitizeAt = (\n value: unknown,\n parent: string | undefined,\n path: readonly (string | number)[],\n): unknown => {\n if (value instanceof Date) return new Date(value.getTime());\n if (Array.isArray(value)) {\n return value.map((item, index) => {\n if (item === undefined) unserializable(\"undefined\", [...path, index]);\n return sanitizeAt(item, undefined, [...path, index]);\n });\n }\n if (typeof value === \"object\" && value !== null) {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n if (isUnsafeKey(key, parent)) {\n warnStripped(key, parent);\n continue;\n }\n const item = (value as Record<string, unknown>)[key];\n if (item === undefined) continue;\n out[key] = sanitizeAt(item, key, [...path, key]);\n }\n return out;\n }\n if (typeof value === \"function\") unserializable(\"function\", path);\n return value;\n};\n\n// One clone pass: strips unsafe keys (with a warning); explicit-undefined\n// object entries are omitted (delete semantics), functions and undefined\n// list items throw.\nexport const sanitizeValue = (value: unknown, parent?: string): unknown =>\n sanitizeAt(value, parent, parent === undefined ? [] : [parent]);\n\nconst invalid = (op: Statewire.Op) =>\n new Error(`Statewire cannot apply ${op.op} at path: ${op.path.join(\".\")}`);\n\n// Text-insert offsets are Unicode code points on the wire, not UTF-16 units.\nconst utf16Index = (text: string, offset: number): number => {\n let index = 0;\n for (let remaining = offset; remaining > 0; remaining -= 1) {\n if (index >= text.length) return -1;\n index += text.codePointAt(index)! > 0xffff ? 2 : 1;\n }\n return index;\n};\n\nconst countCodePoints = (text: string): number => {\n let count = 0;\n for (let index = 0; index < text.length; index += 1) {\n if (text.codePointAt(index)! > 0xffff) index += 1;\n count += 1;\n }\n return count;\n};\n\nlet last: { str: string; count: number } | null = null;\n\nconst insertedText = (\n current: string,\n op: Extract<Statewire.Op, { op: \"add\" }>,\n offset: number,\n): string => {\n if (typeof op.value !== \"string\") throw invalid(op);\n const memoized =\n last !== null && last.str === current && last.count === offset;\n const index = memoized ? current.length : utf16Index(current, offset);\n if (index < 0) throw invalid(op);\n const next = current.slice(0, index) + op.value + current.slice(index);\n if (index === current.length) {\n last = { str: next, count: offset + countCodePoints(op.value) };\n }\n return next;\n};\n\nconst applyAdd = (\n box: { state: unknown },\n op: Extract<Statewire.Op, { op: \"add\" }>,\n) => {\n const offset = op.path[op.path.length - 1];\n const walked = walkStatePath(box.state, op.path.slice(0, -1), \"resolve\");\n if (\n walked === undefined ||\n typeof offset !== \"number\" ||\n !Number.isInteger(offset) ||\n offset < 0\n ) {\n throw invalid(op);\n }\n const target = walked.leaf;\n if (Array.isArray(target)) {\n if (offset > target.length) throw invalid(op);\n target.splice(offset, 0, sanitizeValue(op.value));\n } else if (typeof target === \"string\") {\n const next = insertedText(target, op, offset);\n if (op.path.length === 1) {\n box.state = next;\n } else {\n (walked.parent as Record<string | number, unknown>)[walked.key] = next;\n }\n } else {\n throw invalid(op);\n }\n};\n\nconst applyInList = (list: unknown[], index: number, op: Statewire.Op) => {\n if (index >= list.length) throw invalid(op);\n if (op.op === \"replace\") {\n list[index] = sanitizeValue(op.value);\n } else {\n list.splice(index, 1);\n }\n};\n\nconst applyInObject = (\n obj: Record<string, unknown>,\n key: string,\n op: Statewire.Op,\n) => {\n if (op.op === \"replace\") {\n obj[key] = sanitizeValue(op.value, key);\n } else {\n if (!Object.hasOwn(obj, key)) throw invalid(op);\n delete obj[key];\n }\n};\n\nconst applyOpToDraft = (box: { state: unknown }, op: Statewire.Op): void => {\n if (op.op === \"add\") {\n if (op.path.length === 0) throw invalid(op);\n applyAdd(box, op);\n return;\n }\n if (op.path.length === 0) {\n box.state = op.op === \"remove\" ? undefined : sanitizeValue(op.value);\n return;\n }\n const walked = walkStatePath(box.state, op.path, \"parent\");\n if (walked === undefined) throw invalid(op);\n if (typeof walked.key === \"number\") {\n applyInList(walked.parent as unknown[], walked.key, op);\n } else {\n applyInObject(walked.parent as Record<string, unknown>, walked.key, op);\n }\n};\n\nconst isMountPlaneOp = (op: { op?: unknown; doc?: unknown }) =>\n \"doc\" in op || op.op === \"mount\" || op.op === \"unmount\";\n\nconst warnedOps = new Set<unknown>();\nconst warnUnknownOp = (type: unknown) => {\n if (warnedOps.has(type)) return;\n warnedOps.add(type);\n console.warn(`statewire: ignoring unknown op type ${JSON.stringify(type)}`);\n};\n\nconst unboxPaths = (paths: readonly StatewirePath[]): StatewirePath[] =>\n paths.map((path) => path.slice(1));\n\nexport const applyOp = (\n root: unknown,\n op: Statewire.Op,\n): { state: unknown; changedPaths: StatewirePath[] } => {\n const recorded = recordMutations({ state: root }, (draft) =>\n applyOpToDraft(draft, op),\n );\n return {\n state: recorded.state.state,\n changedPaths: unboxPaths(recorded.changedPaths),\n };\n};\n\nexport const applyOps = (\n state: unknown,\n ops: readonly unknown[],\n): {\n state: unknown;\n changedPaths: StatewirePath[];\n} => {\n const mutable: Statewire.Op[] = [];\n for (const op of ops as Statewire.Op[]) {\n if (isMountPlaneOp(op)) continue;\n if (op.op === \"replace\" || op.op === \"add\" || op.op === \"remove\") {\n mutable.push(op);\n } else {\n warnUnknownOp((op as { op: unknown }).op);\n }\n }\n const recorded = recordMutations({ state }, (draft) => {\n for (const op of mutable) applyOpToDraft(draft, op);\n });\n return {\n state: recorded.state.state,\n changedPaths: unboxPaths(recorded.changedPaths),\n };\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/wire/version.d.ts
|
|
2
|
+
declare const PROTOCOL_VERSION = "1";
|
|
3
|
+
declare const VERSION_HEADER = "Statewire-Version";
|
|
4
|
+
declare const WS_SUBPROTOCOL = "statewire.v1";
|
|
5
|
+
//#endregion
|
|
6
|
+
export { PROTOCOL_VERSION, VERSION_HEADER, WS_SUBPROTOCOL };
|
|
7
|
+
//# sourceMappingURL=version.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","names":[],"sources":["../../src/wire/version.ts"],"mappings":";cAAa;cACA;cACA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/wire/version.ts
|
|
2
|
+
const PROTOCOL_VERSION = "1";
|
|
3
|
+
const VERSION_HEADER = "Statewire-Version";
|
|
4
|
+
const WS_SUBPROTOCOL = "statewire.v1";
|
|
5
|
+
//#endregion
|
|
6
|
+
export { PROTOCOL_VERSION, VERSION_HEADER, WS_SUBPROTOCOL };
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","names":["PROTOCOL_VERSION","VERSION_HEADER","WS_SUBPROTOCOL"],"sources":["../../src/wire/version.ts"],"sourcesContent":["export const PROTOCOL_VERSION = \"1\";\nexport const VERSION_HEADER = \"Statewire-Version\";\nexport const WS_SUBPROTOCOL = \"statewire.v1\";\n"],"mappings":";AAAA,MAAaA,mBAAmB;AAChC,MAAaC,iBAAiB;AAC9B,MAAaC,iBAAiB"}
|