statewire 0.19.4 → 0.20.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 +15 -1
- package/dist/client/session.d.ts +8 -1
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +52 -12
- package/dist/client/session.js.map +1 -1
- package/dist/client/sessionStore.d.ts +2 -0
- package/dist/client/sessionStore.d.ts.map +1 -1
- package/dist/client/sessionStore.js.map +1 -1
- package/dist/host/host.d.ts +2 -1
- package/dist/host/host.d.ts.map +1 -1
- package/dist/host/host.js +6 -1
- package/dist/host/host.js.map +1 -1
- package/dist/host/http.js +1 -1
- package/dist/host/socket.d.ts +6 -1
- package/dist/host/socket.d.ts.map +1 -1
- package/dist/host/socket.js +79 -9
- package/dist/host/socket.js.map +1 -1
- package/dist/host-internal.d.ts +1 -1
- package/dist/host.d.ts +6 -0
- package/dist/host.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/statewire.d.ts +3 -0
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js +22 -5
- package/dist/statewire.js.map +1 -1
- package/dist/transports/remote.d.ts.map +1 -1
- package/dist/transports/remote.js +3 -2
- package/dist/transports/remote.js.map +1 -1
- package/dist/transports/sse.d.ts +4 -0
- package/dist/transports/sse.d.ts.map +1 -1
- package/dist/transports/sse.js +5 -0
- package/dist/transports/sse.js.map +1 -1
- package/dist/transports/testing.d.ts +2 -0
- package/dist/transports/testing.d.ts.map +1 -1
- package/dist/transports/testing.js +11 -4
- package/dist/transports/testing.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/wire/context.d.ts +14 -0
- package/dist/wire/context.d.ts.map +1 -0
- package/dist/wire/context.js +31 -0
- package/dist/wire/context.js.map +1 -0
- package/dist/wire/limits.d.ts +2 -1
- package/dist/wire/limits.d.ts.map +1 -1
- package/dist/wire/limits.js +2 -1
- package/dist/wire/limits.js.map +1 -1
- package/dist/wire/protocol.d.ts +1 -1
- package/dist/wire/protocol.js +4 -4
- package/dist/wire/protocol.js.map +1 -1
- package/package.json +1 -1
- package/src/client/session.ts +74 -4
- package/src/client/sessionStore.ts +5 -1
- package/src/host/host.ts +9 -0
- package/src/host/socket.ts +121 -7
- package/src/host.ts +5 -0
- package/src/statewire.ts +29 -3
- package/src/transports/remote.ts +3 -0
- package/src/transports/sse.ts +7 -0
- package/src/transports/testing.ts +14 -3
- package/src/types.ts +12 -0
- package/src/wire/context.ts +40 -0
- package/src/wire/limits.ts +2 -0
- package/src/wire/protocol.ts +1 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { decodeDocument, encodeDocument } from "../document.js";
|
|
2
|
+
import "./limits.js";
|
|
3
|
+
import { protocolToken } from "./protocol.js";
|
|
4
|
+
//#region src/wire/context.ts
|
|
5
|
+
const freeze = (value) => {
|
|
6
|
+
if (typeof value !== "object" || value === null) return;
|
|
7
|
+
for (const child of Object.values(value)) freeze(child);
|
|
8
|
+
Object.freeze(value);
|
|
9
|
+
};
|
|
10
|
+
const contextValue = (encoded) => {
|
|
11
|
+
const value = decodeDocument(encoded);
|
|
12
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date) throw new TypeError("statewire: context root must be an object");
|
|
13
|
+
freeze(value);
|
|
14
|
+
return value;
|
|
15
|
+
};
|
|
16
|
+
const encodeContext = (protocol, value) => {
|
|
17
|
+
if (!protocolToken.test(protocol)) throw new TypeError("statewire: invalid context protocol");
|
|
18
|
+
const encoded = encodeDocument(value);
|
|
19
|
+
contextValue(encoded);
|
|
20
|
+
const entry = {
|
|
21
|
+
protocol,
|
|
22
|
+
...encoded
|
|
23
|
+
};
|
|
24
|
+
if (new TextEncoder().encode(JSON.stringify(entry)).byteLength > 65536) throw new TypeError("statewire: context entry too large");
|
|
25
|
+
return entry;
|
|
26
|
+
};
|
|
27
|
+
const captureContext = (value) => contextValue(encodeDocument(value));
|
|
28
|
+
//#endregion
|
|
29
|
+
export { captureContext, contextValue, encodeContext };
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","names":["decodeDocument","encodeDocument","Statewire","MAX_CONTEXT_BYTES","protocolToken","freeze","value","child","Object","values","contextValue","encoded","Context","Array","isArray","Date","TypeError","encodeContext","protocol","test","entry","TextEncoder","encode","JSON","stringify","byteLength","captureContext"],"sources":["../../src/wire/context.ts"],"sourcesContent":["import { decodeDocument, encodeDocument } from \"../document\";\nimport type { Statewire } from \"../types\";\nimport { MAX_CONTEXT_BYTES } from \"./limits\";\nimport { protocolToken } from \"./protocol\";\n\nconst freeze = (value: unknown): void => {\n if (typeof value !== \"object\" || value === null) return;\n for (const child of Object.values(value)) freeze(child);\n Object.freeze(value);\n};\n\nexport const contextValue = (encoded: unknown): Statewire.Context => {\n const value = decodeDocument(encoded);\n if (\n typeof value !== \"object\" ||\n value === null ||\n Array.isArray(value) ||\n value instanceof Date\n )\n throw new TypeError(\"statewire: context root must be an object\");\n freeze(value);\n return value as Statewire.Context;\n};\n\nexport const encodeContext = (protocol: string, value: Statewire.Context) => {\n if (!protocolToken.test(protocol))\n throw new TypeError(\"statewire: invalid context protocol\");\n const encoded = encodeDocument(value);\n contextValue(encoded);\n const entry = { protocol, ...encoded };\n if (\n new TextEncoder().encode(JSON.stringify(entry)).byteLength >\n MAX_CONTEXT_BYTES\n )\n throw new TypeError(\"statewire: context entry too large\");\n return entry;\n};\n\nexport const captureContext = (value: Statewire.Context): Statewire.Context =>\n contextValue(encodeDocument(value));\n"],"mappings":";;;;AAKA,MAAMK,UAAUC,UAAyB;CACvC,IAAI,OAAOA,UAAU,YAAYA,UAAU,MAAM;CACjD,KAAK,MAAMC,SAASC,OAAOC,OAAOH,KAAK,GAAGD,OAAOE,KAAK;CACtDC,OAAOH,OAAOC,KAAK;AACrB;AAEA,MAAaI,gBAAgBC,YAAwC;CACnE,MAAML,QAAQN,eAAeW,OAAO;CACpC,IACE,OAAOL,UAAU,YACjBA,UAAU,QACVO,MAAMC,QAAQR,KAAK,KACnBA,iBAAiBS,MAEjB,MAAM,IAAIC,UAAU,2CAA2C;CACjEX,OAAOC,KAAK;CACZ,OAAOA;AACT;AAEA,MAAaW,iBAAiBC,UAAkBZ,UAA6B;CAC3E,IAAI,CAACF,cAAce,KAAKD,QAAQ,GAC9B,MAAM,IAAIF,UAAU,qCAAqC;CAC3D,MAAML,UAAUV,eAAeK,KAAK;CACpCI,aAAaC,OAAO;CACpB,MAAMS,QAAQ;EAAEF;EAAU,GAAGP;CAAQ;CACrC,IACE,IAAIU,YAAY,CAAC,CAACC,OAAOC,KAAKC,UAAUJ,KAAK,CAAC,CAAC,CAACK,aAAAA,OAGhD,MAAM,IAAIT,UAAU,oCAAoC;CAC1D,OAAOI;AACT;AAEA,MAAaM,kBAAkBpB,UAC7BI,aAAaT,eAAeK,KAAK,CAAC"}
|
package/dist/wire/limits.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ declare const MAX_FRAME_BODY_BYTES: number;
|
|
|
5
5
|
declare const MAX_EPHEMERAL_DOCUMENT_BYTES: number;
|
|
6
6
|
declare const MAX_INFLIGHT_COMMANDS_PER_HOST = 1024;
|
|
7
7
|
declare const COMMAND_CAPACITY_RETRY_MS = 1000;
|
|
8
|
+
declare const MAX_CONTEXT_BYTES: number;
|
|
8
9
|
//#endregion
|
|
9
|
-
export { COMMAND_CAPACITY_RETRY_MS, MAX_COMMANDS_PER_FRAME, MAX_EPHEMERAL_DOCUMENT_BYTES, MAX_FRAME_BODY_BYTES, MAX_INFLIGHT_COMMANDS_PER_CLIENT, MAX_INFLIGHT_COMMANDS_PER_HOST };
|
|
10
|
+
export { COMMAND_CAPACITY_RETRY_MS, MAX_COMMANDS_PER_FRAME, MAX_CONTEXT_BYTES, MAX_EPHEMERAL_DOCUMENT_BYTES, MAX_FRAME_BODY_BYTES, MAX_INFLIGHT_COMMANDS_PER_CLIENT, MAX_INFLIGHT_COMMANDS_PER_HOST };
|
|
10
11
|
//# sourceMappingURL=limits.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"limits.d.ts","names":[],"sources":["../../src/wire/limits.ts"],"mappings":";cAAa;cACA;cACA;cACA;cACA;cACA"}
|
|
1
|
+
{"version":3,"file":"limits.d.ts","names":[],"sources":["../../src/wire/limits.ts"],"mappings":";cAAa;cACA;cACA;cACA;cACA;cACA;cAEA"}
|
package/dist/wire/limits.js
CHANGED
|
@@ -5,7 +5,8 @@ const MAX_FRAME_BODY_BYTES = 8388608;
|
|
|
5
5
|
const MAX_EPHEMERAL_DOCUMENT_BYTES = 65536;
|
|
6
6
|
const MAX_INFLIGHT_COMMANDS_PER_HOST = 1024;
|
|
7
7
|
const COMMAND_CAPACITY_RETRY_MS = 1e3;
|
|
8
|
+
const MAX_CONTEXT_BYTES = 65536;
|
|
8
9
|
//#endregion
|
|
9
|
-
export { COMMAND_CAPACITY_RETRY_MS, MAX_COMMANDS_PER_FRAME, MAX_EPHEMERAL_DOCUMENT_BYTES, MAX_FRAME_BODY_BYTES, MAX_INFLIGHT_COMMANDS_PER_CLIENT, MAX_INFLIGHT_COMMANDS_PER_HOST };
|
|
10
|
+
export { COMMAND_CAPACITY_RETRY_MS, MAX_COMMANDS_PER_FRAME, MAX_CONTEXT_BYTES, MAX_EPHEMERAL_DOCUMENT_BYTES, MAX_FRAME_BODY_BYTES, MAX_INFLIGHT_COMMANDS_PER_CLIENT, MAX_INFLIGHT_COMMANDS_PER_HOST };
|
|
10
11
|
|
|
11
12
|
//# sourceMappingURL=limits.js.map
|
package/dist/wire/limits.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"limits.js","names":["MAX_COMMANDS_PER_FRAME","MAX_INFLIGHT_COMMANDS_PER_CLIENT","MAX_FRAME_BODY_BYTES","MAX_EPHEMERAL_DOCUMENT_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_EPHEMERAL_DOCUMENT_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,+BAA+B;AAC5C,MAAaC,iCAAiC;AAC9C,MAAaC,4BAA4B"}
|
|
1
|
+
{"version":3,"file":"limits.js","names":["MAX_COMMANDS_PER_FRAME","MAX_INFLIGHT_COMMANDS_PER_CLIENT","MAX_FRAME_BODY_BYTES","MAX_EPHEMERAL_DOCUMENT_BYTES","MAX_INFLIGHT_COMMANDS_PER_HOST","COMMAND_CAPACITY_RETRY_MS","MAX_CONTEXT_BYTES"],"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_EPHEMERAL_DOCUMENT_BYTES = 64 * 1024;\nexport const MAX_INFLIGHT_COMMANDS_PER_HOST = 1024;\nexport const COMMAND_CAPACITY_RETRY_MS = 1_000;\n\nexport const MAX_CONTEXT_BYTES = 64 * 1024;\n"],"mappings":";AAAA,MAAaA,yBAAyB;AACtC,MAAaC,mCAAmC;AAChD,MAAaC,uBAAuB;AACpC,MAAaC,+BAA+B;AAC5C,MAAaC,iCAAiC;AAC9C,MAAaC,4BAA4B;AAEzC,MAAaC,oBAAoB"}
|
package/dist/wire/protocol.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ declare namespace StatewireProtocol {
|
|
|
31
31
|
}[];
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
|
-
declare const WIRE_VERSION = "2026-09-
|
|
34
|
+
declare const WIRE_VERSION = "2026-09-30";
|
|
35
35
|
declare const DEFAULT_PROTOCOL = "default";
|
|
36
36
|
declare const PROTOCOL_HEADER = "Statewire-Protocol";
|
|
37
37
|
declare const VERSION_HEADER = "Statewire-Version";
|
package/dist/wire/protocol.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/wire/protocol.ts
|
|
2
|
-
const WIRE_VERSION = "2026-09-
|
|
2
|
+
const WIRE_VERSION = "2026-09-30";
|
|
3
3
|
const DEFAULT_PROTOCOL = "default";
|
|
4
4
|
const PROTOCOL_HEADER = "Statewire-Protocol";
|
|
5
5
|
const VERSION_HEADER = "Statewire-Version";
|
|
@@ -28,7 +28,7 @@ const inRange = (value, range) => {
|
|
|
28
28
|
};
|
|
29
29
|
const protocolOffers = (options) => options.applicationProtocols ?? [{
|
|
30
30
|
name: options.protocol ?? "default",
|
|
31
|
-
version: "2026-09-
|
|
31
|
+
version: "2026-09-30"
|
|
32
32
|
}];
|
|
33
33
|
const negotiationHeaders = (options) => {
|
|
34
34
|
const suffix = (range) => {
|
|
@@ -39,7 +39,7 @@ const negotiationHeaders = (options) => {
|
|
|
39
39
|
const seen = /* @__PURE__ */ new Set();
|
|
40
40
|
if (offers !== "*" && !offers.length) invalid("protocol list must not be empty");
|
|
41
41
|
return {
|
|
42
|
-
[VERSION_HEADER]: suffix(options.wireVersion ?? { version: "2026-09-
|
|
42
|
+
[VERSION_HEADER]: suffix(options.wireVersion ?? { version: "2026-09-30" }),
|
|
43
43
|
[PROTOCOL_HEADER]: offers === "*" ? "*" : offers.map((offer) => {
|
|
44
44
|
if (!protocolToken.test(offer.name) || offer.name === "*" || seen.has(offer.name)) invalid("invalid or duplicate protocol name");
|
|
45
45
|
if (offer.optional !== void 0 && typeof offer.optional !== "boolean") invalid("optional must be boolean");
|
|
@@ -123,7 +123,7 @@ const validateSelection = (value, options) => {
|
|
|
123
123
|
if (typeof value !== "object" || value === null) return invalid("missing negotiation selection");
|
|
124
124
|
const syn = value;
|
|
125
125
|
const version = validateVersion(syn.version);
|
|
126
|
-
if (!inRange(version, options.wireVersion ?? { version: "2026-09-
|
|
126
|
+
if (!inRange(version, options.wireVersion ?? { version: "2026-09-30" })) invalid("selected wire version is outside offer");
|
|
127
127
|
if (!Array.isArray(syn.protocols)) return invalid("syn.protocols must be an array");
|
|
128
128
|
const offers = protocolOffers(options);
|
|
129
129
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.js","names":["StatewireProtocol","Range","version","minVersion","Offer","name","optional","Selection","protocols","record","Options","wireVersion","applicationProtocols","protocol","keyPrefixes","Readonly","Record","Definition","requires","subprotocols","WIRE_VERSION","DEFAULT_PROTOCOL","PROTOCOL_HEADER","VERSION_HEADER","protocolToken","invalid","message","Error","validateVersion","value","test","parsed","Date","Number","isFinite","getTime","toISOString","slice","startsWith","validateRange","range","undefined","inRange","checked","protocolOffers","options","negotiationHeaders","suffix","offers","seen","Set","length","map","offer","has","add","join","parseItem","input","atom","String","raw","text","trim","match","RegExp","exec","quoted","decode","replace","params","Map","rest","parameter","key","set","parseNegotiation","headers","Headers","Required","Pick","wire","get","application","item","keys","some","k","names","split","includes","validateSelection","syn","Array","isArray","entry","parent","lastIndexOf","child","find","o","negotiateProtocols","definitions","fin","reason","code","status","payload","supported","d","failure","const","request","ReturnType","error","sort","registry","size","definition","children","offered","solve","active","dependency","Object","result","ranges","flatMap","lower","r","at","upper","selected","filter"],"sources":["../../src/wire/protocol.ts"],"sourcesContent":["export namespace StatewireProtocol {\n export type Range = { version: string; minVersion?: string };\n export type Offer = Range & { name: string; optional?: boolean };\n export type Selection = {\n version: string;\n protocols: { name: string; version: string; record?: \"ephemeral\" }[];\n };\n export type Options = {\n wireVersion?: Range;\n applicationProtocols?: readonly Offer[] | \"*\";\n protocol?: string;\n keyPrefixes?: Readonly<Record<string, readonly string[]>>;\n };\n export type Definition = Range & {\n name: string;\n requires?: Readonly<Record<string, Range>>;\n subprotocols?: readonly { name: string; record?: \"ephemeral\" }[];\n };\n}\n\nexport const WIRE_VERSION = \"2026-09-13\";\nexport const DEFAULT_PROTOCOL = \"default\";\nexport const PROTOCOL_HEADER = \"Statewire-Protocol\";\nexport const VERSION_HEADER = \"Statewire-Version\";\nexport const protocolToken = /^[A-Za-z*][A-Za-z0-9!#$%&'*+.^_`|~:/-]*$/;\n\nconst invalid = (message: string): never => {\n throw new Error(`statewire: protocol error: ${message}`);\n};\n\nexport const validateVersion = (value: unknown): string => {\n if (typeof value !== \"string\" || !/^\\d{4}-\\d{2}-\\d{2}$/.test(value))\n return invalid(\"version must be a YYYY-MM-DD date\");\n const parsed = new Date(`${value}T00:00:00.000Z`);\n if (\n !Number.isFinite(parsed.getTime()) ||\n parsed.toISOString().slice(0, 10) !== value ||\n value.startsWith(\"0000\")\n )\n return invalid(\"invalid version date\");\n return value;\n};\n\nexport const validateRange = (range: StatewireProtocol.Range) => {\n const version = validateVersion(range.version);\n const minVersion =\n range.minVersion === undefined\n ? version\n : validateVersion(range.minVersion);\n if (minVersion > version) invalid(\"min-version exceeds version\");\n return { version, minVersion };\n};\n\nexport const inRange = (value: string, range: StatewireProtocol.Range) => {\n const checked = validateRange(range);\n return value >= checked.minVersion && value <= checked.version;\n};\n\nexport const protocolOffers = (options: StatewireProtocol.Options) =>\n options.applicationProtocols ?? [\n { name: options.protocol ?? DEFAULT_PROTOCOL, version: WIRE_VERSION },\n ];\n\nexport const negotiationHeaders = (\n options: StatewireProtocol.Options,\n): Record<string, string> => {\n const suffix = (range: StatewireProtocol.Range) => {\n const { version, minVersion } = validateRange(range);\n return `\"${version}\"${minVersion === version ? \"\" : `; min-version=\"${minVersion}\"`}`;\n };\n const offers = protocolOffers(options);\n const seen = new Set<string>();\n if (offers !== \"*\" && !offers.length)\n invalid(\"protocol list must not be empty\");\n return {\n [VERSION_HEADER]: suffix(options.wireVersion ?? { version: WIRE_VERSION }),\n [PROTOCOL_HEADER]:\n offers === \"*\"\n ? \"*\"\n : offers\n .map((offer) => {\n if (\n !protocolToken.test(offer.name) ||\n offer.name === \"*\" ||\n seen.has(offer.name)\n )\n invalid(\"invalid or duplicate protocol name\");\n if (\n offer.optional !== undefined &&\n typeof offer.optional !== \"boolean\"\n )\n invalid(\"optional must be boolean\");\n seen.add(offer.name);\n return `${offer.name}; version=${suffix(offer)}${offer.optional ? \"; optional\" : \"\"}`;\n })\n .join(\", \"),\n };\n};\n\nconst parseItem = (input: string) => {\n const atom = String.raw`\"(?:[\\x20-\\x21\\x23-\\x5b\\x5d-\\x7e]|\\\\[\"\\\\])*\"|[A-Za-z*][A-Za-z0-9!#$%&'*+.^_\\x60|~:/-]*|\\?[01]`;\n const text = input.trim();\n const match = new RegExp(`^(?:${atom})`).exec(text);\n if (!match) return invalid(\"invalid structured item\");\n const quoted = match[0].startsWith('\"');\n const decode = (raw: string) => raw.slice(1, -1).replace(/\\\\([\"\\\\])/g, \"$1\");\n const value = quoted ? decode(match[0]) : match[0];\n const params = new Map<string, string | boolean>();\n let rest = text.slice(match[0].length);\n while (rest) {\n const parameter = new RegExp(\n `^; *([a-z*][a-z0-9_.*-]*)(?:=(${atom}))?`,\n ).exec(rest);\n if (!parameter) return invalid(\"invalid structured parameters\");\n const key = parameter[1]!;\n const raw = parameter[2];\n if (params.has(key)) invalid(\"duplicate parameter\");\n if (raw === undefined || raw === \"?1\") params.set(key, true);\n else if (raw === \"?0\") params.set(key, false);\n else if (raw.startsWith('\"')) params.set(key, decode(raw));\n else invalid(\"parameter must be string or boolean\");\n rest = rest.slice(parameter[0].length);\n }\n return { value, quoted, params };\n};\n\nexport const parseNegotiation = (\n headers: Headers,\n): Required<\n Pick<StatewireProtocol.Options, \"wireVersion\" | \"applicationProtocols\">\n> => {\n const wire = headers.get(VERSION_HEADER);\n const application = headers.get(PROTOCOL_HEADER);\n if (wire === null || application === null)\n return invalid(\"negotiation fields are required\");\n const item = parseItem(wire);\n if (!item.quoted || [...item.params.keys()].some((k) => k !== \"min-version\"))\n invalid(\"invalid Statewire-Version\");\n const minVersion = item.params.get(\"min-version\");\n if (minVersion !== undefined && typeof minVersion !== \"string\")\n return invalid(\"min-version must be a string\");\n const wireVersion = validateRange({\n version: item.value,\n ...(minVersion === undefined ? {} : { minVersion }),\n });\n if (application.trim() === \"*\")\n return { wireVersion, applicationProtocols: \"*\" };\n const names = new Set<string>();\n const applicationProtocols = application\n .split(\",\")\n .map((text): StatewireProtocol.Offer => {\n const { value: name, quoted, params } = parseItem(text);\n if (\n quoted ||\n !protocolToken.test(name) ||\n name === \"*\" ||\n names.has(name)\n )\n invalid(\"invalid or duplicate protocol name\");\n names.add(name);\n if (\n [...params.keys()].some(\n (k) => ![\"version\", \"min-version\", \"optional\"].includes(k),\n )\n )\n invalid(\"unsupported parameter\");\n const version = params.get(\"version\");\n const minVersion = params.get(\"min-version\");\n const optional = params.get(\"optional\") ?? false;\n if (\n typeof version !== \"string\" ||\n (minVersion !== undefined && typeof minVersion !== \"string\") ||\n typeof optional !== \"boolean\"\n )\n return invalid(\"invalid protocol parameters\");\n return {\n name,\n ...validateRange({\n version,\n ...(minVersion === undefined ? {} : { minVersion }),\n }),\n optional,\n };\n });\n return { wireVersion, applicationProtocols };\n};\n\nexport const validateSelection = (\n value: unknown,\n options: StatewireProtocol.Options,\n): StatewireProtocol.Selection => {\n if (typeof value !== \"object\" || value === null)\n return invalid(\"missing negotiation selection\");\n const syn = value as Record<string, unknown>;\n const version = validateVersion(syn.version);\n if (!inRange(version, options.wireVersion ?? { version: WIRE_VERSION }))\n invalid(\"selected wire version is outside offer\");\n if (!Array.isArray(syn.protocols))\n return invalid(\"syn.protocols must be an array\");\n const offers = protocolOffers(options);\n const seen = new Map<string, string>();\n const protocols = syn.protocols.map((entry: unknown) => {\n if (typeof entry !== \"object\" || entry === null)\n return invalid(\"invalid selected protocol\");\n const item = entry as Record<string, unknown>;\n const name = item.name;\n if (\n typeof name !== \"string\" ||\n !protocolToken.test(name) ||\n name === \"*\" ||\n seen.has(name)\n )\n return invalid(\"invalid selected protocol name\");\n const version = validateVersion(item.version);\n if (item.record !== undefined && item.record !== \"ephemeral\")\n invalid(\"invalid selected protocol record\");\n const record = item.record as \"ephemeral\" | undefined;\n const parent = name.slice(0, name.lastIndexOf(\"/\"));\n const child = parent !== \"\" && seen.has(parent);\n if (child) {\n if (seen.get(parent) !== version)\n invalid(\"subprotocol version differs from its parent\");\n } else if (offers !== \"*\") {\n const offer = offers.find((o) => o.name === name);\n if (!offer || !inRange(version, offer))\n invalid(\"selected protocol is outside offer\");\n }\n seen.set(name, version);\n return { name, version, ...(record !== undefined && { record }) };\n });\n if (offers !== \"*\" && offers.some((o) => !o.optional && !seen.has(o.name)))\n invalid(\"required protocol missing from selection\");\n return { version, protocols };\n};\n\nexport const negotiateProtocols = (\n headers: Headers,\n wire: StatewireProtocol.Range,\n definitions: readonly StatewireProtocol.Definition[],\n):\n | StatewireProtocol.Selection\n | {\n fin: {\n reason: \"protocol-error\";\n code: string;\n status: number;\n message: string;\n payload?: unknown;\n };\n } => {\n const supported = {\n ...validateRange(wire),\n protocols: definitions.map((d) => ({ name: d.name, ...validateRange(d) })),\n };\n const failure = (code: string, message: string) => ({\n fin: {\n reason: \"protocol-error\" as const,\n code,\n message,\n status: code === \"invalid-negotiation\" ? 400 : 406,\n ...(code === \"invalid-negotiation\" ? {} : { payload: supported }),\n },\n });\n let request: ReturnType<typeof parseNegotiation>;\n try {\n request = parseNegotiation(headers);\n } catch (error) {\n return failure(\n \"invalid-negotiation\",\n error instanceof Error ? error.message : \"invalid negotiation\",\n );\n }\n const version = [wire.version, request.wireVersion.version].sort()[0]!;\n if (!inRange(version, wire) || !inRange(version, request.wireVersion))\n return failure(\"unsupported-version\", \"Statewire versions do not overlap\");\n const offers =\n request.applicationProtocols === \"*\"\n ? definitions.map((d) => ({\n name: d.name,\n version: d.version,\n optional: false,\n }))\n : request.applicationProtocols;\n const registry = new Map(definitions.map((d) => [d.name, d]));\n if (registry.size !== definitions.length)\n throw new Error(\"statewire: duplicate registered protocol\");\n for (const definition of definitions) {\n const children = new Set<string>();\n for (const child of definition.subprotocols ?? []) {\n if (\n !protocolToken.test(child.name) ||\n child.name.includes(\"/\") ||\n children.has(child.name) ||\n (child.record !== undefined && child.record !== \"ephemeral\")\n )\n throw new Error(\"statewire: invalid subprotocol registration\");\n children.add(child.name);\n }\n }\n const offered = new Map(offers.map((o) => [o.name, o]));\n const solve = (names: Set<string>) => {\n const active = new Set(names);\n for (const name of active) {\n const definition = registry.get(name);\n if (!definition || !offered.has(name)) return undefined;\n for (const dependency of Object.keys(definition.requires ?? {}))\n active.add(dependency);\n }\n const result = new Map<string, string>();\n for (const name of active) {\n const ranges = [\n registry.get(name)!,\n offered.get(name)!,\n ...[...active].flatMap((parent) => {\n const dependency = registry.get(parent)!.requires?.[name];\n return dependency ? [dependency] : [];\n }),\n ].map(validateRange);\n const lower = ranges\n .map((r) => r.minVersion)\n .sort()\n .at(-1)!;\n const upper = ranges.map((r) => r.version).sort()[0]!;\n if (lower > upper) return undefined;\n result.set(name, upper);\n }\n return result;\n };\n let selected = solve(\n new Set(offers.filter((o) => !o.optional).map((o) => o.name)),\n );\n if (!selected)\n return failure(\n \"unsupported-protocol\",\n \"required protocol combination is unsupported\",\n );\n for (const offer of offers) {\n if (offer.optional)\n selected = solve(new Set([...selected.keys(), offer.name])) ?? selected;\n }\n return {\n version,\n protocols: offers.flatMap((o) => {\n const version = selected!.get(o.name);\n if (version === undefined) return [];\n return [\n { name: o.name, version },\n ...(registry.get(o.name)!.subprotocols ?? []).map((child) => ({\n name: `${o.name}/${child.name}`,\n version,\n ...(child.record !== undefined && { record: child.record }),\n })),\n ];\n }),\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"protocol.js","names":["StatewireProtocol","Range","version","minVersion","Offer","name","optional","Selection","protocols","record","Options","wireVersion","applicationProtocols","protocol","keyPrefixes","Readonly","Record","Definition","requires","subprotocols","WIRE_VERSION","DEFAULT_PROTOCOL","PROTOCOL_HEADER","VERSION_HEADER","protocolToken","invalid","message","Error","validateVersion","value","test","parsed","Date","Number","isFinite","getTime","toISOString","slice","startsWith","validateRange","range","undefined","inRange","checked","protocolOffers","options","negotiationHeaders","suffix","offers","seen","Set","length","map","offer","has","add","join","parseItem","input","atom","String","raw","text","trim","match","RegExp","exec","quoted","decode","replace","params","Map","rest","parameter","key","set","parseNegotiation","headers","Headers","Required","Pick","wire","get","application","item","keys","some","k","names","split","includes","validateSelection","syn","Array","isArray","entry","parent","lastIndexOf","child","find","o","negotiateProtocols","definitions","fin","reason","code","status","payload","supported","d","failure","const","request","ReturnType","error","sort","registry","size","definition","children","offered","solve","active","dependency","Object","result","ranges","flatMap","lower","r","at","upper","selected","filter"],"sources":["../../src/wire/protocol.ts"],"sourcesContent":["export namespace StatewireProtocol {\n export type Range = { version: string; minVersion?: string };\n export type Offer = Range & { name: string; optional?: boolean };\n export type Selection = {\n version: string;\n protocols: { name: string; version: string; record?: \"ephemeral\" }[];\n };\n export type Options = {\n wireVersion?: Range;\n applicationProtocols?: readonly Offer[] | \"*\";\n protocol?: string;\n keyPrefixes?: Readonly<Record<string, readonly string[]>>;\n };\n export type Definition = Range & {\n name: string;\n requires?: Readonly<Record<string, Range>>;\n subprotocols?: readonly { name: string; record?: \"ephemeral\" }[];\n };\n}\n\nexport const WIRE_VERSION = \"2026-09-30\";\nexport const DEFAULT_PROTOCOL = \"default\";\nexport const PROTOCOL_HEADER = \"Statewire-Protocol\";\nexport const VERSION_HEADER = \"Statewire-Version\";\nexport const protocolToken = /^[A-Za-z*][A-Za-z0-9!#$%&'*+.^_`|~:/-]*$/;\n\nconst invalid = (message: string): never => {\n throw new Error(`statewire: protocol error: ${message}`);\n};\n\nexport const validateVersion = (value: unknown): string => {\n if (typeof value !== \"string\" || !/^\\d{4}-\\d{2}-\\d{2}$/.test(value))\n return invalid(\"version must be a YYYY-MM-DD date\");\n const parsed = new Date(`${value}T00:00:00.000Z`);\n if (\n !Number.isFinite(parsed.getTime()) ||\n parsed.toISOString().slice(0, 10) !== value ||\n value.startsWith(\"0000\")\n )\n return invalid(\"invalid version date\");\n return value;\n};\n\nexport const validateRange = (range: StatewireProtocol.Range) => {\n const version = validateVersion(range.version);\n const minVersion =\n range.minVersion === undefined\n ? version\n : validateVersion(range.minVersion);\n if (minVersion > version) invalid(\"min-version exceeds version\");\n return { version, minVersion };\n};\n\nexport const inRange = (value: string, range: StatewireProtocol.Range) => {\n const checked = validateRange(range);\n return value >= checked.minVersion && value <= checked.version;\n};\n\nexport const protocolOffers = (options: StatewireProtocol.Options) =>\n options.applicationProtocols ?? [\n { name: options.protocol ?? DEFAULT_PROTOCOL, version: WIRE_VERSION },\n ];\n\nexport const negotiationHeaders = (\n options: StatewireProtocol.Options,\n): Record<string, string> => {\n const suffix = (range: StatewireProtocol.Range) => {\n const { version, minVersion } = validateRange(range);\n return `\"${version}\"${minVersion === version ? \"\" : `; min-version=\"${minVersion}\"`}`;\n };\n const offers = protocolOffers(options);\n const seen = new Set<string>();\n if (offers !== \"*\" && !offers.length)\n invalid(\"protocol list must not be empty\");\n return {\n [VERSION_HEADER]: suffix(options.wireVersion ?? { version: WIRE_VERSION }),\n [PROTOCOL_HEADER]:\n offers === \"*\"\n ? \"*\"\n : offers\n .map((offer) => {\n if (\n !protocolToken.test(offer.name) ||\n offer.name === \"*\" ||\n seen.has(offer.name)\n )\n invalid(\"invalid or duplicate protocol name\");\n if (\n offer.optional !== undefined &&\n typeof offer.optional !== \"boolean\"\n )\n invalid(\"optional must be boolean\");\n seen.add(offer.name);\n return `${offer.name}; version=${suffix(offer)}${offer.optional ? \"; optional\" : \"\"}`;\n })\n .join(\", \"),\n };\n};\n\nconst parseItem = (input: string) => {\n const atom = String.raw`\"(?:[\\x20-\\x21\\x23-\\x5b\\x5d-\\x7e]|\\\\[\"\\\\])*\"|[A-Za-z*][A-Za-z0-9!#$%&'*+.^_\\x60|~:/-]*|\\?[01]`;\n const text = input.trim();\n const match = new RegExp(`^(?:${atom})`).exec(text);\n if (!match) return invalid(\"invalid structured item\");\n const quoted = match[0].startsWith('\"');\n const decode = (raw: string) => raw.slice(1, -1).replace(/\\\\([\"\\\\])/g, \"$1\");\n const value = quoted ? decode(match[0]) : match[0];\n const params = new Map<string, string | boolean>();\n let rest = text.slice(match[0].length);\n while (rest) {\n const parameter = new RegExp(\n `^; *([a-z*][a-z0-9_.*-]*)(?:=(${atom}))?`,\n ).exec(rest);\n if (!parameter) return invalid(\"invalid structured parameters\");\n const key = parameter[1]!;\n const raw = parameter[2];\n if (params.has(key)) invalid(\"duplicate parameter\");\n if (raw === undefined || raw === \"?1\") params.set(key, true);\n else if (raw === \"?0\") params.set(key, false);\n else if (raw.startsWith('\"')) params.set(key, decode(raw));\n else invalid(\"parameter must be string or boolean\");\n rest = rest.slice(parameter[0].length);\n }\n return { value, quoted, params };\n};\n\nexport const parseNegotiation = (\n headers: Headers,\n): Required<\n Pick<StatewireProtocol.Options, \"wireVersion\" | \"applicationProtocols\">\n> => {\n const wire = headers.get(VERSION_HEADER);\n const application = headers.get(PROTOCOL_HEADER);\n if (wire === null || application === null)\n return invalid(\"negotiation fields are required\");\n const item = parseItem(wire);\n if (!item.quoted || [...item.params.keys()].some((k) => k !== \"min-version\"))\n invalid(\"invalid Statewire-Version\");\n const minVersion = item.params.get(\"min-version\");\n if (minVersion !== undefined && typeof minVersion !== \"string\")\n return invalid(\"min-version must be a string\");\n const wireVersion = validateRange({\n version: item.value,\n ...(minVersion === undefined ? {} : { minVersion }),\n });\n if (application.trim() === \"*\")\n return { wireVersion, applicationProtocols: \"*\" };\n const names = new Set<string>();\n const applicationProtocols = application\n .split(\",\")\n .map((text): StatewireProtocol.Offer => {\n const { value: name, quoted, params } = parseItem(text);\n if (\n quoted ||\n !protocolToken.test(name) ||\n name === \"*\" ||\n names.has(name)\n )\n invalid(\"invalid or duplicate protocol name\");\n names.add(name);\n if (\n [...params.keys()].some(\n (k) => ![\"version\", \"min-version\", \"optional\"].includes(k),\n )\n )\n invalid(\"unsupported parameter\");\n const version = params.get(\"version\");\n const minVersion = params.get(\"min-version\");\n const optional = params.get(\"optional\") ?? false;\n if (\n typeof version !== \"string\" ||\n (minVersion !== undefined && typeof minVersion !== \"string\") ||\n typeof optional !== \"boolean\"\n )\n return invalid(\"invalid protocol parameters\");\n return {\n name,\n ...validateRange({\n version,\n ...(minVersion === undefined ? {} : { minVersion }),\n }),\n optional,\n };\n });\n return { wireVersion, applicationProtocols };\n};\n\nexport const validateSelection = (\n value: unknown,\n options: StatewireProtocol.Options,\n): StatewireProtocol.Selection => {\n if (typeof value !== \"object\" || value === null)\n return invalid(\"missing negotiation selection\");\n const syn = value as Record<string, unknown>;\n const version = validateVersion(syn.version);\n if (!inRange(version, options.wireVersion ?? { version: WIRE_VERSION }))\n invalid(\"selected wire version is outside offer\");\n if (!Array.isArray(syn.protocols))\n return invalid(\"syn.protocols must be an array\");\n const offers = protocolOffers(options);\n const seen = new Map<string, string>();\n const protocols = syn.protocols.map((entry: unknown) => {\n if (typeof entry !== \"object\" || entry === null)\n return invalid(\"invalid selected protocol\");\n const item = entry as Record<string, unknown>;\n const name = item.name;\n if (\n typeof name !== \"string\" ||\n !protocolToken.test(name) ||\n name === \"*\" ||\n seen.has(name)\n )\n return invalid(\"invalid selected protocol name\");\n const version = validateVersion(item.version);\n if (item.record !== undefined && item.record !== \"ephemeral\")\n invalid(\"invalid selected protocol record\");\n const record = item.record as \"ephemeral\" | undefined;\n const parent = name.slice(0, name.lastIndexOf(\"/\"));\n const child = parent !== \"\" && seen.has(parent);\n if (child) {\n if (seen.get(parent) !== version)\n invalid(\"subprotocol version differs from its parent\");\n } else if (offers !== \"*\") {\n const offer = offers.find((o) => o.name === name);\n if (!offer || !inRange(version, offer))\n invalid(\"selected protocol is outside offer\");\n }\n seen.set(name, version);\n return { name, version, ...(record !== undefined && { record }) };\n });\n if (offers !== \"*\" && offers.some((o) => !o.optional && !seen.has(o.name)))\n invalid(\"required protocol missing from selection\");\n return { version, protocols };\n};\n\nexport const negotiateProtocols = (\n headers: Headers,\n wire: StatewireProtocol.Range,\n definitions: readonly StatewireProtocol.Definition[],\n):\n | StatewireProtocol.Selection\n | {\n fin: {\n reason: \"protocol-error\";\n code: string;\n status: number;\n message: string;\n payload?: unknown;\n };\n } => {\n const supported = {\n ...validateRange(wire),\n protocols: definitions.map((d) => ({ name: d.name, ...validateRange(d) })),\n };\n const failure = (code: string, message: string) => ({\n fin: {\n reason: \"protocol-error\" as const,\n code,\n message,\n status: code === \"invalid-negotiation\" ? 400 : 406,\n ...(code === \"invalid-negotiation\" ? {} : { payload: supported }),\n },\n });\n let request: ReturnType<typeof parseNegotiation>;\n try {\n request = parseNegotiation(headers);\n } catch (error) {\n return failure(\n \"invalid-negotiation\",\n error instanceof Error ? error.message : \"invalid negotiation\",\n );\n }\n const version = [wire.version, request.wireVersion.version].sort()[0]!;\n if (!inRange(version, wire) || !inRange(version, request.wireVersion))\n return failure(\"unsupported-version\", \"Statewire versions do not overlap\");\n const offers =\n request.applicationProtocols === \"*\"\n ? definitions.map((d) => ({\n name: d.name,\n version: d.version,\n optional: false,\n }))\n : request.applicationProtocols;\n const registry = new Map(definitions.map((d) => [d.name, d]));\n if (registry.size !== definitions.length)\n throw new Error(\"statewire: duplicate registered protocol\");\n for (const definition of definitions) {\n const children = new Set<string>();\n for (const child of definition.subprotocols ?? []) {\n if (\n !protocolToken.test(child.name) ||\n child.name.includes(\"/\") ||\n children.has(child.name) ||\n (child.record !== undefined && child.record !== \"ephemeral\")\n )\n throw new Error(\"statewire: invalid subprotocol registration\");\n children.add(child.name);\n }\n }\n const offered = new Map(offers.map((o) => [o.name, o]));\n const solve = (names: Set<string>) => {\n const active = new Set(names);\n for (const name of active) {\n const definition = registry.get(name);\n if (!definition || !offered.has(name)) return undefined;\n for (const dependency of Object.keys(definition.requires ?? {}))\n active.add(dependency);\n }\n const result = new Map<string, string>();\n for (const name of active) {\n const ranges = [\n registry.get(name)!,\n offered.get(name)!,\n ...[...active].flatMap((parent) => {\n const dependency = registry.get(parent)!.requires?.[name];\n return dependency ? [dependency] : [];\n }),\n ].map(validateRange);\n const lower = ranges\n .map((r) => r.minVersion)\n .sort()\n .at(-1)!;\n const upper = ranges.map((r) => r.version).sort()[0]!;\n if (lower > upper) return undefined;\n result.set(name, upper);\n }\n return result;\n };\n let selected = solve(\n new Set(offers.filter((o) => !o.optional).map((o) => o.name)),\n );\n if (!selected)\n return failure(\n \"unsupported-protocol\",\n \"required protocol combination is unsupported\",\n );\n for (const offer of offers) {\n if (offer.optional)\n selected = solve(new Set([...selected.keys(), offer.name])) ?? selected;\n }\n return {\n version,\n protocols: offers.flatMap((o) => {\n const version = selected!.get(o.name);\n if (version === undefined) return [];\n return [\n { name: o.name, version },\n ...(registry.get(o.name)!.subprotocols ?? []).map((child) => ({\n name: `${o.name}/${child.name}`,\n version,\n ...(child.record !== undefined && { record: child.record }),\n })),\n ];\n }),\n };\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
package/src/client/session.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { encodeContext } from "../wire/context";
|
|
1
2
|
import { wrapStorageError } from "./storageError";
|
|
2
3
|
import {
|
|
3
4
|
DEFAULT_PROTOCOL,
|
|
@@ -76,6 +77,7 @@ const rejectedSend = (
|
|
|
76
77
|
type AttachState = "detached" | "attached" | "finished";
|
|
77
78
|
|
|
78
79
|
export type StatewireSessionOptions = StatewireProtocol.Options & {
|
|
80
|
+
context?: Readonly<Record<string, Statewire.Context>>;
|
|
79
81
|
sessionStore?: StatewireClient.SessionStorage | undefined;
|
|
80
82
|
onSessionLost?: StatewireClient.OnSessionLost | undefined;
|
|
81
83
|
onDone?: (() => void) | undefined;
|
|
@@ -89,6 +91,10 @@ type Shared = {
|
|
|
89
91
|
|
|
90
92
|
type Session = {
|
|
91
93
|
options: StatewireSessionOptions;
|
|
94
|
+
configuredContext: StatewireSessionOptions["context"];
|
|
95
|
+
contexts: Map<string, ReturnType<typeof encodeContext>>;
|
|
96
|
+
dirtyContexts: Set<string>;
|
|
97
|
+
attachContexts: Map<string, ReturnType<typeof encodeContext>>;
|
|
92
98
|
selection: StatewireProtocol.Selection | undefined;
|
|
93
99
|
record: StatewireClient.SessionRecord;
|
|
94
100
|
ctx: StatewireRemoteProps;
|
|
@@ -760,6 +766,16 @@ const handleHello = (
|
|
|
760
766
|
s: Session,
|
|
761
767
|
{ ops, syn }: { ops: unknown[]; syn: StatewireSyn },
|
|
762
768
|
) => {
|
|
769
|
+
if (s.attach === "attached") {
|
|
770
|
+
s.ephemeralSeqs = {};
|
|
771
|
+
detachEphemeralLanes(s);
|
|
772
|
+
}
|
|
773
|
+
for (const [protocol, value] of s.contexts) {
|
|
774
|
+
if (s.attachContexts.get(protocol) === value)
|
|
775
|
+
s.dirtyContexts.delete(protocol);
|
|
776
|
+
else s.dirtyContexts.add(protocol);
|
|
777
|
+
}
|
|
778
|
+
s.attachContexts.clear();
|
|
763
779
|
s.selection = selectionOf(syn);
|
|
764
780
|
s.documents = { mode: null, documents: new Map() };
|
|
765
781
|
ingestOps(s, ops);
|
|
@@ -914,12 +930,16 @@ const buildLanes = (
|
|
|
914
930
|
last[protocol] = entry.seq;
|
|
915
931
|
entries.push(entry);
|
|
916
932
|
}
|
|
917
|
-
|
|
933
|
+
const ctx = [...s.dirtyContexts].map((protocol) => s.contexts.get(protocol)!);
|
|
934
|
+
if (entries.length === 0 && ctx.length === 0) return null;
|
|
918
935
|
return {
|
|
919
936
|
frame: {
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
937
|
+
...(ctx.length > 0 && { ctx }),
|
|
938
|
+
...(entries.length > 0 && {
|
|
939
|
+
cmd: entries.map((p) => {
|
|
940
|
+
const { produced: _produced, ...command } = p.command;
|
|
941
|
+
return { ...command, seq: p.seq };
|
|
942
|
+
}),
|
|
923
943
|
}),
|
|
924
944
|
},
|
|
925
945
|
entries,
|
|
@@ -930,6 +950,8 @@ const takeAttachLanes = (s: Session): StatewireFrame | null => {
|
|
|
930
950
|
s.attachEntries = new Set();
|
|
931
951
|
const wanted = s.lanesOnAttach;
|
|
932
952
|
s.lanesOnAttach = false;
|
|
953
|
+
for (const protocol of s.contexts.keys()) s.dirtyContexts.add(protocol);
|
|
954
|
+
s.attachContexts = new Map(s.contexts);
|
|
933
955
|
const lanes = buildLanes(s, wanted && s.sessionKnown ? "persisted" : "none");
|
|
934
956
|
if (!lanes) return null;
|
|
935
957
|
s.attachEntries = new Set(lanes.entries);
|
|
@@ -966,6 +988,7 @@ const pump = async (s: Session) => {
|
|
|
966
988
|
if (!s.hydrated) await s.ready;
|
|
967
989
|
const transport = s.transport;
|
|
968
990
|
if (buildLanes(s) === null) return;
|
|
991
|
+
if (transport?.parked && s.pending.length === 0) return;
|
|
969
992
|
if (s.attach !== "attached" || !transport) {
|
|
970
993
|
if (
|
|
971
994
|
(transport?.connection.status === "standby" &&
|
|
@@ -1000,12 +1023,27 @@ const pump = async (s: Session) => {
|
|
|
1000
1023
|
: send());
|
|
1001
1024
|
} catch {
|
|
1002
1025
|
if (!s.running || isFinished(s)) return;
|
|
1026
|
+
if (lanes.frame.ctx?.length) {
|
|
1027
|
+
s.lanesOnAttach = true;
|
|
1028
|
+
transport.connection.reconnect();
|
|
1029
|
+
await waitForWake(s);
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
1003
1032
|
s.frameFailures += 1;
|
|
1004
1033
|
await sleepUntilWake(s, backoff(s.frameFailures));
|
|
1005
1034
|
continue;
|
|
1006
1035
|
}
|
|
1007
1036
|
if (!s.running) return;
|
|
1008
1037
|
s.frameFailures = 0;
|
|
1038
|
+
if (
|
|
1039
|
+
epoch === s.attachEpoch &&
|
|
1040
|
+
(result.type === "ok" || result.type === "refusal")
|
|
1041
|
+
) {
|
|
1042
|
+
for (const entry of lanes.frame.ctx ?? []) {
|
|
1043
|
+
if (s.contexts.get(entry.protocol) === entry)
|
|
1044
|
+
s.dirtyContexts.delete(entry.protocol);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1009
1047
|
if (result.type === "ok") {
|
|
1010
1048
|
if (epoch === s.attachEpoch) {
|
|
1011
1049
|
for (const entry of lanes.entries) {
|
|
@@ -1477,6 +1515,23 @@ const loadSessionRecord = (
|
|
|
1477
1515
|
return record;
|
|
1478
1516
|
};
|
|
1479
1517
|
|
|
1518
|
+
export const setContext = (
|
|
1519
|
+
s: Session,
|
|
1520
|
+
protocol: string,
|
|
1521
|
+
value: Statewire.Context,
|
|
1522
|
+
): void => {
|
|
1523
|
+
if (
|
|
1524
|
+
s.selection
|
|
1525
|
+
? !s.selection.protocols.some((p) => p.name === protocol)
|
|
1526
|
+
: !offered(s, protocol)
|
|
1527
|
+
)
|
|
1528
|
+
throw new Error(`statewire: protocol "${protocol}" is not negotiated`);
|
|
1529
|
+
s.contexts.set(protocol, encodeContext(protocol, value));
|
|
1530
|
+
s.dirtyContexts.add(protocol);
|
|
1531
|
+
wake(s);
|
|
1532
|
+
void pump(s);
|
|
1533
|
+
};
|
|
1534
|
+
|
|
1480
1535
|
export const useStatewireSession = (
|
|
1481
1536
|
options: StatewireSessionOptions,
|
|
1482
1537
|
): Session => {
|
|
@@ -1501,6 +1556,15 @@ export const useStatewireSession = (
|
|
|
1501
1556
|
: { clientId: newClientId(), lanes: {} };
|
|
1502
1557
|
const session: Session = {
|
|
1503
1558
|
options,
|
|
1559
|
+
configuredContext: options.context,
|
|
1560
|
+
contexts: new Map(
|
|
1561
|
+
Object.entries(options.context ?? {}).map(([protocol, value]) => [
|
|
1562
|
+
protocol,
|
|
1563
|
+
encodeContext(protocol, value),
|
|
1564
|
+
]),
|
|
1565
|
+
),
|
|
1566
|
+
dirtyContexts: new Set(Object.keys(options.context ?? {})),
|
|
1567
|
+
attachContexts: new Map(),
|
|
1504
1568
|
selection: undefined,
|
|
1505
1569
|
record,
|
|
1506
1570
|
ctx: {
|
|
@@ -1598,6 +1662,12 @@ export const useStatewireSession = (
|
|
|
1598
1662
|
"statewire: session storage changed sharedness across renders — lock/subscribe must be present from the first render or never",
|
|
1599
1663
|
);
|
|
1600
1664
|
}
|
|
1665
|
+
useEffect(() => {
|
|
1666
|
+
if (s.configuredContext === options.context) return;
|
|
1667
|
+
s.configuredContext = options.context;
|
|
1668
|
+
for (const [protocol, value] of Object.entries(options.context ?? {}))
|
|
1669
|
+
setContext(s, protocol, value);
|
|
1670
|
+
}, [s, options.context]);
|
|
1601
1671
|
s.options = options;
|
|
1602
1672
|
s.store = store;
|
|
1603
1673
|
s.documentStore = documentStore;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { StatewireDocuments } from "../wire/documents";
|
|
2
2
|
import { createContext } from "react";
|
|
3
3
|
import type { StatewireProtocol } from "../wire/protocol";
|
|
4
|
+
import type { Statewire } from "../types";
|
|
4
5
|
import type { StatewireClient } from "../statewire";
|
|
5
6
|
|
|
6
7
|
/** The session storage `useStatewire` mounts, read by the transport's session. */
|
|
@@ -21,7 +22,10 @@ export const createMemorySessionStore = (): StatewireClient.SessionStorage => {
|
|
|
21
22
|
export const ProtocolContext = createContext<StatewireProtocol.Options>({});
|
|
22
23
|
|
|
23
24
|
/** Link options `useStatewire` hands down to whichever transport it mounts. */
|
|
24
|
-
export const LinkContext = createContext<{
|
|
25
|
+
export const LinkContext = createContext<{
|
|
26
|
+
isNew?: boolean | undefined;
|
|
27
|
+
context?: Readonly<Record<string, Statewire.Context>> | undefined;
|
|
28
|
+
}>({});
|
|
25
29
|
|
|
26
30
|
export const DocumentStorageContext = createContext<
|
|
27
31
|
StatewireDocuments.Storage | undefined
|
package/src/host/host.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { captureContext } from "../wire/context";
|
|
1
2
|
import { useEffect, useState } from "react";
|
|
2
3
|
import { applyOps, sanitizeValue } from "../wire/ops";
|
|
3
4
|
import type { StatewirePath } from "../wire/ops";
|
|
@@ -167,6 +168,7 @@ export type CommandsSubmission = {
|
|
|
167
168
|
params: readonly unknown[];
|
|
168
169
|
clientId?: string;
|
|
169
170
|
caller?: StatewireHost.Caller;
|
|
171
|
+
context?: Statewire.Context;
|
|
170
172
|
attach?: StatewireHost.Attach;
|
|
171
173
|
onApplied?: () => void;
|
|
172
174
|
onSettle?: (outcome: CommandsOutcome) => void;
|
|
@@ -190,6 +192,7 @@ export const bindCommandsHost = (commands: object, host: CommandsHost) => {
|
|
|
190
192
|
};
|
|
191
193
|
|
|
192
194
|
type QueuedCall = {
|
|
195
|
+
context?: Statewire.Context;
|
|
193
196
|
method: string;
|
|
194
197
|
params: readonly unknown[];
|
|
195
198
|
ts: number;
|
|
@@ -206,6 +209,7 @@ type ActiveInvocation = CommandsInvocation & {
|
|
|
206
209
|
};
|
|
207
210
|
|
|
208
211
|
type CommandsCell = {
|
|
212
|
+
context: (() => Statewire.Context) | undefined;
|
|
209
213
|
handlers: Record<string, (...args: never[]) => unknown> | null;
|
|
210
214
|
queue: QueuedCall[];
|
|
211
215
|
active: Map<ActiveInvocation, QueuedCall>;
|
|
@@ -343,6 +347,7 @@ const dispatch = (cell: CommandsCell, call: QueuedCall) => {
|
|
|
343
347
|
const attach = call.origin?.attach;
|
|
344
348
|
const ctx: StatewireHost.Ctx = {
|
|
345
349
|
caller: call.origin?.caller ?? LOCAL_CALLER,
|
|
350
|
+
context: call.context ?? call.origin?.context ?? Object.freeze({}),
|
|
346
351
|
get attach() {
|
|
347
352
|
if (attach === undefined)
|
|
348
353
|
throw new Error("statewire: in-process dispatch has no attach");
|
|
@@ -416,9 +421,11 @@ export const useStatewireCommands = <THandlers extends Statewire.Commands>(
|
|
|
416
421
|
| undefined
|
|
417
422
|
| null
|
|
418
423
|
| false,
|
|
424
|
+
options?: StatewireHost.CommandsOptions,
|
|
419
425
|
): Statewire.CommandsProxy<StatewireHost.HandledCommands<THandlers>> => {
|
|
420
426
|
const [built] = useState(() => {
|
|
421
427
|
const cell: CommandsCell = {
|
|
428
|
+
context: undefined,
|
|
422
429
|
handlers: null,
|
|
423
430
|
queue: [],
|
|
424
431
|
active: new Map(),
|
|
@@ -438,6 +445,7 @@ export const useStatewireCommands = <THandlers extends Statewire.Commands>(
|
|
|
438
445
|
return;
|
|
439
446
|
}
|
|
440
447
|
cell.queue.push({
|
|
448
|
+
context: captureContext(cell.context?.() ?? {}),
|
|
441
449
|
method,
|
|
442
450
|
params,
|
|
443
451
|
ts: Date.now(),
|
|
@@ -479,6 +487,7 @@ export const useStatewireCommands = <THandlers extends Statewire.Commands>(
|
|
|
479
487
|
return { cell, proxy };
|
|
480
488
|
});
|
|
481
489
|
|
|
490
|
+
built.cell.context = options?.context;
|
|
482
491
|
useEffect(() => {
|
|
483
492
|
const { cell } = built;
|
|
484
493
|
cell.unmounted = false;
|