statewire 0.15.0 → 0.17.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 +14 -6
- package/dist/client/connection.d.ts +5 -1
- package/dist/client/connection.d.ts.map +1 -1
- package/dist/client/connection.js +19 -4
- package/dist/client/connection.js.map +1 -1
- package/dist/client/core.d.ts.map +1 -1
- package/dist/client/core.js +2 -0
- package/dist/client/core.js.map +1 -1
- package/dist/client/session.d.ts +15 -4
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +233 -52
- package/dist/client/session.js.map +1 -1
- package/dist/client/sessionStore.d.ts +5 -1
- package/dist/client/sessionStore.d.ts.map +1 -1
- package/dist/client/sessionStore.js +3 -1
- package/dist/client/sessionStore.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.map +1 -1
- package/dist/host/http.d.ts.map +1 -1
- package/dist/host/http.js +27 -9
- package/dist/host/http.js.map +1 -1
- package/dist/host/socket.d.ts +10 -3
- package/dist/host/socket.d.ts.map +1 -1
- package/dist/host/socket.js +103 -39
- package/dist/host/socket.js.map +1 -1
- package/dist/host-internal.d.ts +9 -1
- package/dist/host-internal.d.ts.map +1 -1
- package/dist/host-internal.js +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -3
- package/dist/statewire.d.ts +33 -2
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js +34 -3
- package/dist/statewire.js.map +1 -1
- package/dist/transports/fixtures.js +1 -0
- package/dist/transports/fixtures.js.map +1 -1
- package/dist/transports/remote.d.ts.map +1 -1
- package/dist/transports/remote.js +15 -8
- package/dist/transports/remote.js.map +1 -1
- package/dist/transports/sse.d.ts +2 -2
- package/dist/transports/sse.d.ts.map +1 -1
- package/dist/transports/sse.js +11 -11
- package/dist/transports/sse.js.map +1 -1
- package/dist/transports/testing.d.ts +1 -1
- package/dist/transports/testing.d.ts.map +1 -1
- package/dist/transports/testing.js +14 -6
- package/dist/transports/testing.js.map +1 -1
- package/dist/transports/ws.d.ts +2 -2
- package/dist/transports/ws.d.ts.map +1 -1
- package/dist/transports/ws.js +5 -5
- package/dist/transports/ws.js.map +1 -1
- package/dist/types.d.ts +7 -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 +25 -2
- package/dist/wire/documents.d.ts.map +1 -1
- package/dist/wire/documents.js +27 -7
- package/dist/wire/documents.js.map +1 -1
- package/dist/wire/protocol.d.ts +55 -0
- package/dist/wire/protocol.d.ts.map +1 -0
- package/dist/wire/protocol.js +221 -0
- package/dist/wire/protocol.js.map +1 -0
- package/dist/wire/version.d.ts +1 -3
- package/dist/wire/version.d.ts.map +1 -1
- package/dist/wire/version.js +1 -3
- package/dist/wire/version.js.map +1 -1
- package/package.json +1 -1
- package/src/client/connection.ts +43 -13
- package/src/client/core.ts +6 -0
- package/src/client/session.ts +339 -42
- package/src/client/sessionStore.ts +8 -0
- package/src/host/host.ts +1 -0
- package/src/host/http.ts +36 -7
- package/src/host/socket.ts +141 -50
- package/src/host-internal.ts +12 -1
- package/src/index.ts +5 -2
- package/src/statewire.ts +109 -6
- package/src/transports/fixtures.ts +1 -1
- package/src/transports/remote.ts +16 -7
- package/src/transports/sse.ts +28 -18
- package/src/transports/testing.ts +12 -4
- package/src/transports/ws.ts +8 -8
- package/src/types.ts +11 -0
- package/src/util/randomId.ts +3 -3
- package/src/wire/documents.ts +126 -8
- package/src/wire/protocol.ts +327 -0
- package/src/wire/version.ts +0 -2
|
@@ -1 +1 @@
|
|
|
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","WSOptions","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","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 {\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.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 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.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 { 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.WSOptions,\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 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 !== 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
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { StatewireDocuments } from "./wire/documents.js";
|
|
2
|
+
import { StatewireProtocol } from "./wire/protocol.js";
|
|
1
3
|
import { StatewireClient } from "./statewire.js";
|
|
2
4
|
import { StatewirePath } from "./wire/ops.js";
|
|
3
5
|
//#region src/types.d.ts
|
|
@@ -9,6 +11,7 @@ declare namespace Statewire {
|
|
|
9
11
|
}; }[keyof TCommands];
|
|
10
12
|
type CommandsProxy<TCommands extends Commands> = { [K in keyof TCommands]: (...args: Parameters<TCommands[K]>) => Promise<Awaited<ReturnType<TCommands[K]>>>; };
|
|
11
13
|
type CommandUpdate<TCommands extends Commands = Commands> = { [K in keyof TCommands]: {
|
|
14
|
+
readonly protocol?: string;
|
|
12
15
|
method: K;
|
|
13
16
|
params: Parameters<TCommands[K]>;
|
|
14
17
|
seq: number;
|
|
@@ -43,6 +46,10 @@ declare namespace Statewire {
|
|
|
43
46
|
}
|
|
44
47
|
interface Statewire<TState = any, TCommands extends Statewire.Commands = Statewire.Commands> {
|
|
45
48
|
state: TState;
|
|
49
|
+
documents?: StatewireDocuments.Source;
|
|
50
|
+
selection?: StatewireProtocol.Selection | undefined;
|
|
51
|
+
invoke?: (protocol: string, method: string, params: readonly unknown[]) => Promise<unknown>;
|
|
52
|
+
updateContext?: (ops: readonly unknown[]) => void;
|
|
46
53
|
commands: Statewire.CommandsProxy<TCommands>;
|
|
47
54
|
connection?: StatewireClient.Connection;
|
|
48
55
|
source?: Statewire.StateSource;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;kBAKiB;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;aACD;IACT,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,YAAY,mBAAmB;EAC/B,YAAY,kBAAkB;EAC9B,UACE,kBACA,gBACA,+BACG;EACL,iBAAiB;EACjB,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","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":";
|
|
1
|
+
{"version":3,"file":"types.js","names":["StatewireDocuments","StatewireProtocol","StatewireClient","StatewirePath","Statewire","Commands","Record","args","Command","TCommands","method","K","params","Parameters","CommandsProxy","Promise","Awaited","ReturnType","CommandUpdate","protocol","seq","status","result","failure","SendFailure","StateSource","snapshot","takeChanged","setOnChange","listener","Op","op","path","value","Changes","isChangedAt","getChangedKeys","state","TState","documents","Source","selection","Selection","invoke","updateContext","ops","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 { StatewireDocuments } from \"./wire/documents\";\nimport type { StatewireProtocol } from \"./wire/protocol\";\nimport 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 readonly protocol?: string;\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 documents?: StatewireDocuments.Source;\n selection?: StatewireProtocol.Selection | undefined;\n invoke?: (\n protocol: string,\n method: string,\n params: readonly unknown[],\n ) => Promise<unknown>;\n updateContext?: (ops: readonly unknown[]) => void;\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":";AAiFA,MAAM6D,YAAYtC,YAAiD;CACjE,QAAQA,QAAQuC,MAAhB;EACE,KAAK,YACH,OAAO,8BAA8BvC,QAAQwC,OAAO,KAAKxC,QAAQwC,KAAI,KAAM,KAAKxC,QAAQyC,UAAU,KAAKzC,QAAQyC,YAAY;EAC7H,KAAK,WACH,OAAO,+BAA+BzC,QAAQ0C,MAAK;EACrD,KAAK,QACH,OAAO,4BAA4B1C,QAAQ0C,MAAK;EAClD,KAAK,UACH,OAAO1C,QAAQyC,UACX,8BAA8BzC,QAAQyC,YACtC;EACN,KAAK,WACH,OAAO;EACT,KAAK,WACH,OAAO,sCAAsCzC,QAAQ0C,MAAK;CAC9D;AACF;AAEA,IAAaC,qBAAb,cAAwCC,MAAM;CAC5C;CAEAC,YAAY7C,SAAsC;EAChD,MAAMsC,SAAStC,OAAO,CAAC;EACvB,KAAK8C,OAAO;EACZ,KAAK9C,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"}
|
package/dist/wire/documents.d.ts
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
import { StatewirePath } from "./ops.js";
|
|
2
2
|
//#region src/wire/documents.d.ts
|
|
3
3
|
declare namespace StatewireDocuments {
|
|
4
|
+
type CacheEntry = {
|
|
5
|
+
version: string;
|
|
6
|
+
value: unknown;
|
|
7
|
+
};
|
|
8
|
+
type Storage = {
|
|
9
|
+
namespace?: string;
|
|
10
|
+
load: (identity: readonly string[]) => CacheEntry | undefined;
|
|
11
|
+
save: (identity: readonly string[], entry: CacheEntry) => void;
|
|
12
|
+
subscribe?: (listener: () => void) => () => void;
|
|
13
|
+
};
|
|
14
|
+
type Source = {
|
|
15
|
+
get: (protocol: string, key?: readonly string[], prefix?: readonly string[]) => unknown;
|
|
16
|
+
list: () => ReadonlyMap<number, Document>;
|
|
17
|
+
subscribe: (listener: () => void) => () => void;
|
|
18
|
+
};
|
|
4
19
|
type Document = {
|
|
5
20
|
key: readonly string[] | null;
|
|
21
|
+
protocol?: string;
|
|
22
|
+
main?: boolean;
|
|
6
23
|
value: unknown;
|
|
7
24
|
};
|
|
8
25
|
type State = {
|
|
@@ -10,11 +27,17 @@ declare namespace StatewireDocuments {
|
|
|
10
27
|
documents: ReadonlyMap<number, Document>;
|
|
11
28
|
};
|
|
12
29
|
}
|
|
13
|
-
declare const applyDocumentOps: (previous: StatewireDocuments.State, ops: readonly unknown[]
|
|
30
|
+
declare const applyDocumentOps: (previous: StatewireDocuments.State, ops: readonly unknown[], protocols?: readonly {
|
|
31
|
+
name: string;
|
|
32
|
+
version: string;
|
|
33
|
+
}[], strict?: boolean) => {
|
|
14
34
|
mode: "mounted" | "keyless";
|
|
15
35
|
documents: Map<number, StatewireDocuments.Document>;
|
|
16
36
|
changedPaths: StatewirePath[];
|
|
17
37
|
};
|
|
38
|
+
declare const mainDocument: (state: StatewireDocuments.State, protocol: string) => StatewireDocuments.Document | undefined;
|
|
39
|
+
declare const resolveDocumentKey: (key: readonly string[], prefix?: readonly string[]) => readonly string[] | undefined;
|
|
40
|
+
declare const findDocument: (state: StatewireDocuments.State, protocol: string, key?: readonly string[], prefix?: readonly string[]) => StatewireDocuments.Document | undefined;
|
|
18
41
|
//#endregion
|
|
19
|
-
export { StatewireDocuments, applyDocumentOps };
|
|
42
|
+
export { StatewireDocuments, applyDocumentOps, findDocument, mainDocument, resolveDocumentKey };
|
|
20
43
|
//# sourceMappingURL=documents.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documents.d.ts","names":[],"sources":["../../src/wire/documents.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"documents.d.ts","names":[],"sources":["../../src/wire/documents.ts"],"mappings":";;kBAKiB;OACH;IAAe;IAAiB;;OAChC;IACV;IACA,OAAO,gCAAgC;IACvC,OAAO,6BAA6B,OAAO;IAC3C,aAAa;;OAEH;IACV,MACE,kBACA,yBACA;IAEF,YAAY,oBAAoB;IAChC,YAAY;;OAEF;IACV;IACA;IACA;IACA;;OAEU;IACV;IACA,WAAW,oBAAoB;;;cAQtB,mBACX,UAAU,mBAAmB,OAC7B,yBACA;EAAuB;EAAc;KACrC;;;;;cAqJW,eACX,OAAO,mBAAmB,OAC1B,qBAAgB,mBAAA;cAML,qBACX,wBACA;cAQW,eACX,OAAO,mBAAmB,OAC1B,kBACA,yBACA,+BAA0B,mBAAA"}
|
package/dist/wire/documents.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import { protocolToken } from "./protocol.js";
|
|
1
2
|
import { applyOps } from "./ops.js";
|
|
2
3
|
import { reviveValue } from "./meta.js";
|
|
3
4
|
//#region src/wire/documents.ts
|
|
4
5
|
const invalid = (message) => {
|
|
5
6
|
throw new Error(`statewire: protocol error: ${message}`);
|
|
6
7
|
};
|
|
7
|
-
const applyDocumentOps = (previous, ops) => {
|
|
8
|
-
let mode = previous.mode;
|
|
8
|
+
const applyDocumentOps = (previous, ops, protocols, strict = false) => {
|
|
9
|
+
let mode = previous.mode ?? (protocols !== void 0 && ops.length === 0 ? "mounted" : null);
|
|
9
10
|
const documents = new Map(previous.documents);
|
|
10
11
|
const changedPaths = [];
|
|
11
12
|
for (const raw of ops) {
|
|
@@ -20,10 +21,12 @@ const applyDocumentOps = (previous, ops) => {
|
|
|
20
21
|
"replace",
|
|
21
22
|
"remove"
|
|
22
23
|
].includes(op.op)) {
|
|
24
|
+
if (strict) invalid("unknown context operation");
|
|
23
25
|
const { doc: _doc, ...unknown } = op;
|
|
24
26
|
applyOps(void 0, [unknown]);
|
|
25
27
|
continue;
|
|
26
28
|
}
|
|
29
|
+
if (protocols !== void 0 && mode === null && op.op !== "mount") invalid("negotiated state must start with mounts");
|
|
27
30
|
if (mode === null) {
|
|
28
31
|
if (op.op === "mount") mode = "mounted";
|
|
29
32
|
else if (op.op === "replace" && doc === 0 && Array.isArray(op.path) && op.path.length === 0 && "value" in op) {
|
|
@@ -37,22 +40,32 @@ const applyDocumentOps = (previous, ops) => {
|
|
|
37
40
|
if (mode === "keyless" && (doc !== 0 || op.op === "mount" || op.op === "unmount")) invalid("keyless mode only supports mutations of document 0");
|
|
38
41
|
if (op.op === "mount") {
|
|
39
42
|
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
|
|
43
|
+
if (op.key !== void 0 && (!Array.isArray(op.key) || !op.key.every((key) => typeof key === "string")) || !("value" in op)) invalid("mount requires value and an optional key array");
|
|
44
|
+
if (op.protocol !== void 0 && (typeof op.protocol !== "string" || !protocolToken.test(op.protocol) || op.protocol === "*")) invalid("invalid mount protocol");
|
|
45
|
+
if (protocols !== void 0 && !protocols.some((p) => p.name === op.protocol)) invalid("mount protocol was not negotiated");
|
|
46
|
+
if (op.main !== void 0 && typeof op.main !== "boolean") invalid("main must be boolean");
|
|
47
|
+
if (op.main === true && [...documents.values()].some((d) => d.protocol === op.protocol && d.main)) invalid("protocol already has a main document");
|
|
48
|
+
if (op.key !== void 0 && [...documents.values()].some((d) => d.protocol === op.protocol && JSON.stringify(d.key) === JSON.stringify(op.key))) invalid("protocol document key is already mounted");
|
|
41
49
|
documents.set(doc, {
|
|
42
|
-
key: op.key,
|
|
43
|
-
|
|
50
|
+
key: op.key === void 0 ? null : op.key,
|
|
51
|
+
...op.protocol === void 0 ? {} : { protocol: op.protocol },
|
|
52
|
+
...op.main === void 0 ? {} : { main: op.main },
|
|
53
|
+
value: op.meta === void 0 ? op.value : reviveValue(op.value, op.meta, { rejectUnknown: strict })
|
|
44
54
|
});
|
|
45
55
|
if (doc === 0) changedPaths.push([]);
|
|
46
56
|
} else if (op.op === "unmount") {
|
|
57
|
+
const document = documents.get(doc);
|
|
58
|
+
if (protocols !== void 0 && document !== void 0 && !protocols.some((p) => p.name === document.protocol)) invalid("document protocol was not negotiated");
|
|
47
59
|
if (!documents.delete(doc)) invalid("document is not mounted");
|
|
48
60
|
if (doc === 0) changedPaths.push([]);
|
|
49
61
|
} else if (op.op === "add" || op.op === "replace" || op.op === "remove") {
|
|
50
62
|
const document = documents.get(doc);
|
|
63
|
+
if (protocols !== void 0 && document !== void 0 && !protocols.some((p) => p.name === document.protocol)) invalid("document protocol was not negotiated");
|
|
51
64
|
if (!document) invalid("document is not mounted");
|
|
52
65
|
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
66
|
if (op.op !== "remove" && !("value" in op)) invalid("operation requires a value");
|
|
54
67
|
const { doc: _doc, meta, ...mutation } = op;
|
|
55
|
-
if (meta !== void 0) mutation.value = reviveValue(mutation.value, meta);
|
|
68
|
+
if (meta !== void 0) mutation.value = reviveValue(mutation.value, meta, { rejectUnknown: strict });
|
|
56
69
|
const applied = applyOps(document.value, [mutation]);
|
|
57
70
|
documents.set(doc, {
|
|
58
71
|
...document,
|
|
@@ -68,7 +81,14 @@ const applyDocumentOps = (previous, ops) => {
|
|
|
68
81
|
changedPaths
|
|
69
82
|
};
|
|
70
83
|
};
|
|
84
|
+
const mainDocument = (state, protocol) => [...state.documents.values()].find((document) => document.protocol === protocol && document.main);
|
|
85
|
+
const resolveDocumentKey = (key, prefix) => key[0] === "." ? prefix === void 0 ? void 0 : [...prefix, ...key.slice(1)] : key;
|
|
86
|
+
const findDocument = (state, protocol, key, prefix) => {
|
|
87
|
+
if (key === void 0) return mainDocument(state, protocol);
|
|
88
|
+
const resolved = resolveDocumentKey(key, prefix);
|
|
89
|
+
return [...state.documents.values()].find((document) => document.protocol === protocol && document.key !== null && (JSON.stringify(document.key) === JSON.stringify(key) || resolved !== void 0 && JSON.stringify(resolveDocumentKey(document.key, prefix)) === JSON.stringify(resolved)));
|
|
90
|
+
};
|
|
71
91
|
//#endregion
|
|
72
|
-
export { applyDocumentOps };
|
|
92
|
+
export { applyDocumentOps, findDocument, mainDocument, resolveDocumentKey };
|
|
73
93
|
|
|
74
94
|
//# sourceMappingURL=documents.js.map
|
|
@@ -1 +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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"}
|
|
1
|
+
{"version":3,"file":"documents.js","names":["applyOps","reviveValue","protocolToken","StatewirePath","StatewireDocuments","CacheEntry","version","value","Storage","namespace","load","identity","save","entry","subscribe","listener","Source","get","protocol","key","prefix","list","ReadonlyMap","Document","main","State","mode","documents","invalid","message","Error","applyDocumentOps","previous","ops","protocols","name","strict","undefined","length","Map","changedPaths","raw","Array","isArray","op","Record","doc","Number","isSafeInteger","includes","_doc","unknown","path","set","has","every","test","some","p","values","d","JSON","stringify","meta","rejectUnknown","push","document","delete","part","mutation","applied","state","mainDocument","find","resolveDocumentKey","slice","findDocument","resolved"],"sources":["../../src/wire/documents.ts"],"sourcesContent":["import { applyOps } from \"./ops\";\nimport { reviveValue } from \"./meta\";\nimport { protocolToken } from \"./protocol\";\nimport type { StatewirePath } from \"./ops\";\n\nexport namespace StatewireDocuments {\n export type CacheEntry = { version: string; value: unknown };\n export type Storage = {\n namespace?: string;\n load: (identity: readonly string[]) => CacheEntry | undefined;\n save: (identity: readonly string[], entry: CacheEntry) => void;\n subscribe?: (listener: () => void) => () => void;\n };\n export type Source = {\n get: (\n protocol: string,\n key?: readonly string[],\n prefix?: readonly string[],\n ) => unknown;\n list: () => ReadonlyMap<number, Document>;\n subscribe: (listener: () => void) => () => void;\n };\n export type Document = {\n key: readonly string[] | null;\n protocol?: string;\n main?: boolean;\n value: unknown;\n };\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 protocols?: readonly { name: string; version: string }[],\n strict = false,\n) => {\n let mode =\n previous.mode ??\n (protocols !== undefined && ops.length === 0 ? \"mounted\" : null);\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 if (strict) invalid(\"unknown context operation\");\n const { doc: _doc, ...unknown } = op;\n applyOps(undefined, [unknown]);\n continue;\n }\n if (protocols !== undefined && mode === null && op.op !== \"mount\")\n invalid(\"negotiated state must start with mounts\");\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 (op.key !== undefined &&\n (!Array.isArray(op.key) ||\n !op.key.every((key) => typeof key === \"string\"))) ||\n !(\"value\" in op)\n ) {\n invalid(\"mount requires value and an optional key array\");\n }\n if (\n op.protocol !== undefined &&\n (typeof op.protocol !== \"string\" ||\n !protocolToken.test(op.protocol) ||\n op.protocol === \"*\")\n )\n invalid(\"invalid mount protocol\");\n if (\n protocols !== undefined &&\n !protocols.some((p) => p.name === op.protocol)\n )\n invalid(\"mount protocol was not negotiated\");\n if (op.main !== undefined && typeof op.main !== \"boolean\")\n invalid(\"main must be boolean\");\n if (\n op.main === true &&\n [...documents.values()].some(\n (d) => d.protocol === op.protocol && d.main,\n )\n )\n invalid(\"protocol already has a main document\");\n if (\n op.key !== undefined &&\n [...documents.values()].some(\n (d) =>\n d.protocol === op.protocol &&\n JSON.stringify(d.key) === JSON.stringify(op.key),\n )\n )\n invalid(\"protocol document key is already mounted\");\n documents.set(doc, {\n key: op.key === undefined ? null : (op.key as string[]),\n ...(op.protocol === undefined\n ? {}\n : { protocol: op.protocol as string }),\n ...(op.main === undefined ? {} : { main: op.main as boolean }),\n value:\n op.meta === undefined\n ? op.value\n : reviveValue(op.value, op.meta, { rejectUnknown: strict }),\n });\n if (doc === 0) changedPaths.push([]);\n } else if (op.op === \"unmount\") {\n const document = documents.get(doc);\n if (\n protocols !== undefined &&\n document !== undefined &&\n !protocols.some((p) => p.name === document.protocol)\n )\n invalid(\"document protocol was not negotiated\");\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 (\n protocols !== undefined &&\n document !== undefined &&\n !protocols.some((p) => p.name === document.protocol)\n )\n invalid(\"document protocol was not negotiated\");\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 rejectUnknown: strict,\n });\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\nexport const mainDocument = (\n state: StatewireDocuments.State,\n protocol: string,\n) =>\n [...state.documents.values()].find(\n (document) => document.protocol === protocol && document.main,\n );\n\nexport const resolveDocumentKey = (\n key: readonly string[],\n prefix?: readonly string[],\n) =>\n key[0] === \".\"\n ? prefix === undefined\n ? undefined\n : [...prefix, ...key.slice(1)]\n : key;\n\nexport const findDocument = (\n state: StatewireDocuments.State,\n protocol: string,\n key?: readonly string[],\n prefix?: readonly string[],\n) => {\n if (key === undefined) return mainDocument(state, protocol);\n const resolved = resolveDocumentKey(key, prefix);\n return [...state.documents.values()].find(\n (document) =>\n document.protocol === protocol &&\n document.key !== null &&\n (JSON.stringify(document.key) === JSON.stringify(key) ||\n (resolved !== undefined &&\n JSON.stringify(resolveDocumentKey(document.key, prefix)) ===\n JSON.stringify(resolved))),\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
//#region src/wire/protocol.d.ts
|
|
2
|
+
declare namespace StatewireProtocol {
|
|
3
|
+
type Range = {
|
|
4
|
+
version: string;
|
|
5
|
+
minVersion?: string;
|
|
6
|
+
};
|
|
7
|
+
type Offer = Range & {
|
|
8
|
+
name: string;
|
|
9
|
+
optional?: boolean;
|
|
10
|
+
};
|
|
11
|
+
type Selection = {
|
|
12
|
+
version: string;
|
|
13
|
+
protocols: {
|
|
14
|
+
name: string;
|
|
15
|
+
version: string;
|
|
16
|
+
}[];
|
|
17
|
+
};
|
|
18
|
+
type Options = {
|
|
19
|
+
wireVersion?: Range;
|
|
20
|
+
applicationProtocols?: readonly Offer[] | "*";
|
|
21
|
+
protocol?: string;
|
|
22
|
+
keyPrefixes?: Readonly<Record<string, readonly string[]>>;
|
|
23
|
+
};
|
|
24
|
+
type Definition = Range & {
|
|
25
|
+
name: string;
|
|
26
|
+
requires?: Readonly<Record<string, Range>>;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
declare const WIRE_VERSION = "2026-09-07";
|
|
30
|
+
declare const DEFAULT_PROTOCOL = "default";
|
|
31
|
+
declare const PROTOCOL_HEADER = "Statewire-Protocol";
|
|
32
|
+
declare const VERSION_HEADER = "Statewire-Version";
|
|
33
|
+
declare const protocolToken: RegExp;
|
|
34
|
+
declare const validateVersion: (value: unknown) => string;
|
|
35
|
+
declare const validateRange: (range: StatewireProtocol.Range) => {
|
|
36
|
+
version: string;
|
|
37
|
+
minVersion: string;
|
|
38
|
+
};
|
|
39
|
+
declare const inRange: (value: string, range: StatewireProtocol.Range) => boolean;
|
|
40
|
+
declare const protocolOffers: (options: StatewireProtocol.Options) => readonly StatewireProtocol.Offer[] | "*";
|
|
41
|
+
declare const negotiationHeaders: (options: StatewireProtocol.Options) => Record<string, string>;
|
|
42
|
+
declare const parseNegotiation: (headers: Headers) => Required<Pick<StatewireProtocol.Options, "wireVersion" | "applicationProtocols">>;
|
|
43
|
+
declare const validateSelection: (value: unknown, options: StatewireProtocol.Options) => StatewireProtocol.Selection;
|
|
44
|
+
declare const negotiateProtocols: (headers: Headers, wire: StatewireProtocol.Range, definitions: readonly StatewireProtocol.Definition[]) => StatewireProtocol.Selection | {
|
|
45
|
+
fin: {
|
|
46
|
+
reason: "protocol-error";
|
|
47
|
+
code: string;
|
|
48
|
+
status: number;
|
|
49
|
+
message: string;
|
|
50
|
+
payload?: unknown;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
//#endregion
|
|
54
|
+
export { DEFAULT_PROTOCOL, PROTOCOL_HEADER, StatewireProtocol, VERSION_HEADER, WIRE_VERSION, inRange, negotiateProtocols, negotiationHeaders, parseNegotiation, protocolOffers, protocolToken, validateRange, validateSelection, validateVersion };
|
|
55
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","names":[],"sources":["../../src/wire/protocol.ts"],"mappings":";kBAAiB;OACH;IAAU;IAAiB;;OAC3B,QAAQ;IAAU;IAAc;;OAChC;IACV;IACA;MAAa;MAAc;;;OAEjB;IACV,cAAc;IACd,gCAAgC;IAChC;IACA,cAAc,SAAS;;OAEb,aAAa;IACvB;IACA,WAAW,SAAS,eAAe;;;cAI1B;cACA;cACA;cACA;cACA,eAAa;cAMb,kBAAmB;cAanB,gBAAiB,OAAO,kBAAkB;;;;cAU1C,UAAW,eAAe,OAAO,kBAAkB;cAKnD,iBAAkB,SAAS,kBAAkB,qBAAO,kBAAA;cAKpD,qBACX,SAAS,kBAAkB,YAC1B;cA6DU,mBACX,SAAS,YACR,SACD,KAAK,kBAAkB;cA0DZ,oBACX,gBACA,SAAS,kBAAkB,YAC1B,kBAAkB;cAqCR,qBACX,SAAS,SACT,MAAM,kBAAkB,OACxB,sBAAsB,kBAAkB,iBAEtC,kBAAkB;EAEhB;IACE;IACA;IACA;IACA;IACA"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
//#region src/wire/protocol.ts
|
|
2
|
+
const WIRE_VERSION = "2026-09-07";
|
|
3
|
+
const DEFAULT_PROTOCOL = "default";
|
|
4
|
+
const PROTOCOL_HEADER = "Statewire-Protocol";
|
|
5
|
+
const VERSION_HEADER = "Statewire-Version";
|
|
6
|
+
const protocolToken = /^[A-Za-z*][A-Za-z0-9!#$%&'*+.^_`|~:/-]*$/;
|
|
7
|
+
const invalid = (message) => {
|
|
8
|
+
throw new Error(`statewire: protocol error: ${message}`);
|
|
9
|
+
};
|
|
10
|
+
const validateVersion = (value) => {
|
|
11
|
+
if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return invalid("version must be a YYYY-MM-DD date");
|
|
12
|
+
const parsed = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
|
|
13
|
+
if (!Number.isFinite(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== value || value.startsWith("0000")) return invalid("invalid version date");
|
|
14
|
+
return value;
|
|
15
|
+
};
|
|
16
|
+
const validateRange = (range) => {
|
|
17
|
+
const version = validateVersion(range.version);
|
|
18
|
+
const minVersion = range.minVersion === void 0 ? version : validateVersion(range.minVersion);
|
|
19
|
+
if (minVersion > version) invalid("min-version exceeds version");
|
|
20
|
+
return {
|
|
21
|
+
version,
|
|
22
|
+
minVersion
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
const inRange = (value, range) => {
|
|
26
|
+
const checked = validateRange(range);
|
|
27
|
+
return value >= checked.minVersion && value <= checked.version;
|
|
28
|
+
};
|
|
29
|
+
const protocolOffers = (options) => options.applicationProtocols ?? [{
|
|
30
|
+
name: options.protocol ?? "default",
|
|
31
|
+
version: "2026-09-07"
|
|
32
|
+
}];
|
|
33
|
+
const negotiationHeaders = (options) => {
|
|
34
|
+
const suffix = (range) => {
|
|
35
|
+
const { version, minVersion } = validateRange(range);
|
|
36
|
+
return `"${version}"${minVersion === version ? "" : `; min-version="${minVersion}"`}`;
|
|
37
|
+
};
|
|
38
|
+
const offers = protocolOffers(options);
|
|
39
|
+
const seen = /* @__PURE__ */ new Set();
|
|
40
|
+
if (offers !== "*" && !offers.length) invalid("protocol list must not be empty");
|
|
41
|
+
return {
|
|
42
|
+
[VERSION_HEADER]: suffix(options.wireVersion ?? { version: "2026-09-07" }),
|
|
43
|
+
[PROTOCOL_HEADER]: offers === "*" ? "*" : offers.map((offer) => {
|
|
44
|
+
if (!protocolToken.test(offer.name) || offer.name === "*" || seen.has(offer.name)) invalid("invalid or duplicate protocol name");
|
|
45
|
+
if (offer.optional !== void 0 && typeof offer.optional !== "boolean") invalid("optional must be boolean");
|
|
46
|
+
seen.add(offer.name);
|
|
47
|
+
return `${offer.name}; version=${suffix(offer)}${offer.optional ? "; optional" : ""}`;
|
|
48
|
+
}).join(", ")
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
const parseItem = (input) => {
|
|
52
|
+
const atom = String.raw`"(?:[\x20-\x21\x23-\x5b\x5d-\x7e]|\\["\\])*"|[A-Za-z*][A-Za-z0-9!#$%&'*+.^_\x60|~:/-]*|\?[01]`;
|
|
53
|
+
const text = input.trim();
|
|
54
|
+
const match = new RegExp(`^(?:${atom})`).exec(text);
|
|
55
|
+
if (!match) return invalid("invalid structured item");
|
|
56
|
+
const quoted = match[0].startsWith("\"");
|
|
57
|
+
const decode = (raw) => raw.slice(1, -1).replace(/\\(["\\])/g, "$1");
|
|
58
|
+
const value = quoted ? decode(match[0]) : match[0];
|
|
59
|
+
const params = /* @__PURE__ */ new Map();
|
|
60
|
+
let rest = text.slice(match[0].length);
|
|
61
|
+
while (rest) {
|
|
62
|
+
const parameter = new RegExp(`^; *([a-z*][a-z0-9_.*-]*)(?:=(${atom}))?`).exec(rest);
|
|
63
|
+
if (!parameter) return invalid("invalid structured parameters");
|
|
64
|
+
const key = parameter[1];
|
|
65
|
+
const raw = parameter[2];
|
|
66
|
+
if (params.has(key)) invalid("duplicate parameter");
|
|
67
|
+
if (raw === void 0 || raw === "?1") params.set(key, true);
|
|
68
|
+
else if (raw === "?0") params.set(key, false);
|
|
69
|
+
else if (raw.startsWith("\"")) params.set(key, decode(raw));
|
|
70
|
+
else invalid("parameter must be string or boolean");
|
|
71
|
+
rest = rest.slice(parameter[0].length);
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
value,
|
|
75
|
+
quoted,
|
|
76
|
+
params
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
const parseNegotiation = (headers) => {
|
|
80
|
+
const wire = headers.get(VERSION_HEADER);
|
|
81
|
+
const application = headers.get(PROTOCOL_HEADER);
|
|
82
|
+
if (wire === null || application === null) return invalid("negotiation fields are required");
|
|
83
|
+
const item = parseItem(wire);
|
|
84
|
+
if (!item.quoted || [...item.params.keys()].some((k) => k !== "min-version")) invalid("invalid Statewire-Version");
|
|
85
|
+
const minVersion = item.params.get("min-version");
|
|
86
|
+
if (minVersion !== void 0 && typeof minVersion !== "string") return invalid("min-version must be a string");
|
|
87
|
+
const wireVersion = validateRange({
|
|
88
|
+
version: item.value,
|
|
89
|
+
...minVersion === void 0 ? {} : { minVersion }
|
|
90
|
+
});
|
|
91
|
+
if (application.trim() === "*") return {
|
|
92
|
+
wireVersion,
|
|
93
|
+
applicationProtocols: "*"
|
|
94
|
+
};
|
|
95
|
+
const names = /* @__PURE__ */ new Set();
|
|
96
|
+
return {
|
|
97
|
+
wireVersion,
|
|
98
|
+
applicationProtocols: application.split(",").map((text) => {
|
|
99
|
+
const { value: name, quoted, params } = parseItem(text);
|
|
100
|
+
if (quoted || !protocolToken.test(name) || name === "*" || names.has(name)) invalid("invalid or duplicate protocol name");
|
|
101
|
+
names.add(name);
|
|
102
|
+
if ([...params.keys()].some((k) => ![
|
|
103
|
+
"version",
|
|
104
|
+
"min-version",
|
|
105
|
+
"optional"
|
|
106
|
+
].includes(k))) invalid("unsupported parameter");
|
|
107
|
+
const version = params.get("version");
|
|
108
|
+
const minVersion = params.get("min-version");
|
|
109
|
+
const optional = params.get("optional") ?? false;
|
|
110
|
+
if (typeof version !== "string" || minVersion !== void 0 && typeof minVersion !== "string" || typeof optional !== "boolean") return invalid("invalid protocol parameters");
|
|
111
|
+
return {
|
|
112
|
+
name,
|
|
113
|
+
...validateRange({
|
|
114
|
+
version,
|
|
115
|
+
...minVersion === void 0 ? {} : { minVersion }
|
|
116
|
+
}),
|
|
117
|
+
optional
|
|
118
|
+
};
|
|
119
|
+
})
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
const validateSelection = (value, options) => {
|
|
123
|
+
if (typeof value !== "object" || value === null) return invalid("missing negotiation selection");
|
|
124
|
+
const syn = value;
|
|
125
|
+
const version = validateVersion(syn.version);
|
|
126
|
+
if (!inRange(version, options.wireVersion ?? { version: "2026-09-07" })) invalid("selected wire version is outside offer");
|
|
127
|
+
if (!Array.isArray(syn.protocols)) return invalid("syn.protocols must be an array");
|
|
128
|
+
const offers = protocolOffers(options);
|
|
129
|
+
const seen = /* @__PURE__ */ new Set();
|
|
130
|
+
const protocols = syn.protocols.map((entry) => {
|
|
131
|
+
if (typeof entry !== "object" || entry === null) return invalid("invalid selected protocol");
|
|
132
|
+
const item = entry;
|
|
133
|
+
const name = item.name;
|
|
134
|
+
if (typeof name !== "string" || !protocolToken.test(name) || name === "*" || seen.has(name)) return invalid("invalid selected protocol name");
|
|
135
|
+
const version = validateVersion(item.version);
|
|
136
|
+
seen.add(name);
|
|
137
|
+
if (offers !== "*") {
|
|
138
|
+
const offer = offers.find((o) => o.name === name);
|
|
139
|
+
if (!offer || !inRange(version, offer)) invalid("selected protocol is outside offer");
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
name,
|
|
143
|
+
version
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
if (offers !== "*" && offers.some((o) => !o.optional && !seen.has(o.name))) invalid("required protocol missing from selection");
|
|
147
|
+
return {
|
|
148
|
+
version,
|
|
149
|
+
protocols
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
const negotiateProtocols = (headers, wire, definitions) => {
|
|
153
|
+
const supported = {
|
|
154
|
+
...validateRange(wire),
|
|
155
|
+
protocols: definitions.map((d) => ({
|
|
156
|
+
name: d.name,
|
|
157
|
+
...validateRange(d)
|
|
158
|
+
}))
|
|
159
|
+
};
|
|
160
|
+
const failure = (code, message) => ({ fin: {
|
|
161
|
+
reason: "protocol-error",
|
|
162
|
+
code,
|
|
163
|
+
message,
|
|
164
|
+
status: code === "invalid-negotiation" ? 400 : 406,
|
|
165
|
+
...code === "invalid-negotiation" ? {} : { payload: supported }
|
|
166
|
+
} });
|
|
167
|
+
let request;
|
|
168
|
+
try {
|
|
169
|
+
request = parseNegotiation(headers);
|
|
170
|
+
} catch (error) {
|
|
171
|
+
return failure("invalid-negotiation", error instanceof Error ? error.message : "invalid negotiation");
|
|
172
|
+
}
|
|
173
|
+
const version = [wire.version, request.wireVersion.version].sort()[0];
|
|
174
|
+
if (!inRange(version, wire) || !inRange(version, request.wireVersion)) return failure("unsupported-version", "Statewire versions do not overlap");
|
|
175
|
+
const offers = request.applicationProtocols === "*" ? definitions.map((d) => ({
|
|
176
|
+
name: d.name,
|
|
177
|
+
version: d.version,
|
|
178
|
+
optional: false
|
|
179
|
+
})) : request.applicationProtocols;
|
|
180
|
+
const registry = new Map(definitions.map((d) => [d.name, d]));
|
|
181
|
+
if (registry.size !== definitions.length) throw new Error("statewire: duplicate registered protocol");
|
|
182
|
+
const offered = new Map(offers.map((o) => [o.name, o]));
|
|
183
|
+
const solve = (names) => {
|
|
184
|
+
const active = new Set(names);
|
|
185
|
+
for (const name of active) {
|
|
186
|
+
const definition = registry.get(name);
|
|
187
|
+
if (!definition || !offered.has(name)) return void 0;
|
|
188
|
+
for (const dependency of Object.keys(definition.requires ?? {})) active.add(dependency);
|
|
189
|
+
}
|
|
190
|
+
const result = /* @__PURE__ */ new Map();
|
|
191
|
+
for (const name of active) {
|
|
192
|
+
const ranges = [
|
|
193
|
+
registry.get(name),
|
|
194
|
+
offered.get(name),
|
|
195
|
+
...[...active].flatMap((parent) => {
|
|
196
|
+
const dependency = registry.get(parent).requires?.[name];
|
|
197
|
+
return dependency ? [dependency] : [];
|
|
198
|
+
})
|
|
199
|
+
].map(validateRange);
|
|
200
|
+
const lower = ranges.map((r) => r.minVersion).sort().at(-1);
|
|
201
|
+
const upper = ranges.map((r) => r.version).sort()[0];
|
|
202
|
+
if (lower > upper) return void 0;
|
|
203
|
+
result.set(name, upper);
|
|
204
|
+
}
|
|
205
|
+
return result;
|
|
206
|
+
};
|
|
207
|
+
let selected = solve(new Set(offers.filter((o) => !o.optional).map((o) => o.name)));
|
|
208
|
+
if (!selected) return failure("unsupported-protocol", "required protocol combination is unsupported");
|
|
209
|
+
for (const offer of offers) if (offer.optional) selected = solve(/* @__PURE__ */ new Set([...selected.keys(), offer.name])) ?? selected;
|
|
210
|
+
return {
|
|
211
|
+
version,
|
|
212
|
+
protocols: offers.flatMap((o) => selected.has(o.name) ? [{
|
|
213
|
+
name: o.name,
|
|
214
|
+
version: selected.get(o.name)
|
|
215
|
+
}] : [])
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
//#endregion
|
|
219
|
+
export { DEFAULT_PROTOCOL, PROTOCOL_HEADER, VERSION_HEADER, WIRE_VERSION, inRange, negotiateProtocols, negotiationHeaders, parseNegotiation, protocolOffers, protocolToken, validateRange, validateSelection, validateVersion };
|
|
220
|
+
|
|
221
|
+
//# sourceMappingURL=protocol.js.map
|