statewire 0.19.3 → 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.
Files changed (73) hide show
  1. package/README.md +15 -1
  2. package/dist/client/connection.d.ts.map +1 -1
  3. package/dist/client/connection.js +5 -4
  4. package/dist/client/connection.js.map +1 -1
  5. package/dist/client/session.d.ts +8 -1
  6. package/dist/client/session.d.ts.map +1 -1
  7. package/dist/client/session.js +52 -12
  8. package/dist/client/session.js.map +1 -1
  9. package/dist/client/sessionStore.d.ts +2 -0
  10. package/dist/client/sessionStore.d.ts.map +1 -1
  11. package/dist/client/sessionStore.js.map +1 -1
  12. package/dist/client/timing.d.ts +2 -0
  13. package/dist/client/timing.d.ts.map +1 -1
  14. package/dist/client/timing.js +5 -4
  15. package/dist/client/timing.js.map +1 -1
  16. package/dist/host/host.d.ts +2 -1
  17. package/dist/host/host.d.ts.map +1 -1
  18. package/dist/host/host.js +6 -1
  19. package/dist/host/host.js.map +1 -1
  20. package/dist/host/http.js +1 -1
  21. package/dist/host/socket.d.ts +6 -1
  22. package/dist/host/socket.d.ts.map +1 -1
  23. package/dist/host/socket.js +79 -9
  24. package/dist/host/socket.js.map +1 -1
  25. package/dist/host-internal.d.ts +1 -1
  26. package/dist/host.d.ts +6 -0
  27. package/dist/host.d.ts.map +1 -1
  28. package/dist/index.d.ts +1 -1
  29. package/dist/statewire.d.ts +3 -0
  30. package/dist/statewire.d.ts.map +1 -1
  31. package/dist/statewire.js +22 -5
  32. package/dist/statewire.js.map +1 -1
  33. package/dist/transports/remote.d.ts.map +1 -1
  34. package/dist/transports/remote.js +3 -2
  35. package/dist/transports/remote.js.map +1 -1
  36. package/dist/transports/sse.d.ts +4 -0
  37. package/dist/transports/sse.d.ts.map +1 -1
  38. package/dist/transports/sse.js +5 -0
  39. package/dist/transports/sse.js.map +1 -1
  40. package/dist/transports/testing.d.ts +2 -0
  41. package/dist/transports/testing.d.ts.map +1 -1
  42. package/dist/transports/testing.js +11 -4
  43. package/dist/transports/testing.js.map +1 -1
  44. package/dist/types.d.ts +4 -0
  45. package/dist/types.d.ts.map +1 -1
  46. package/dist/types.js.map +1 -1
  47. package/dist/wire/context.d.ts +14 -0
  48. package/dist/wire/context.d.ts.map +1 -0
  49. package/dist/wire/context.js +31 -0
  50. package/dist/wire/context.js.map +1 -0
  51. package/dist/wire/limits.d.ts +2 -1
  52. package/dist/wire/limits.d.ts.map +1 -1
  53. package/dist/wire/limits.js +2 -1
  54. package/dist/wire/limits.js.map +1 -1
  55. package/dist/wire/protocol.d.ts +1 -1
  56. package/dist/wire/protocol.js +4 -4
  57. package/dist/wire/protocol.js.map +1 -1
  58. package/package.json +1 -1
  59. package/src/client/connection.ts +6 -3
  60. package/src/client/session.ts +74 -4
  61. package/src/client/sessionStore.ts +5 -1
  62. package/src/client/timing.ts +12 -7
  63. package/src/host/host.ts +9 -0
  64. package/src/host/socket.ts +121 -7
  65. package/src/host.ts +5 -0
  66. package/src/statewire.ts +29 -3
  67. package/src/transports/remote.ts +3 -0
  68. package/src/transports/sse.ts +7 -0
  69. package/src/transports/testing.ts +14 -3
  70. package/src/types.ts +12 -0
  71. package/src/wire/context.ts +40 -0
  72. package/src/wire/limits.ts +2 -0
  73. 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"}
@@ -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"}
@@ -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
@@ -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"}
@@ -31,7 +31,7 @@ declare namespace StatewireProtocol {
31
31
  }[];
32
32
  };
33
33
  }
34
- declare const WIRE_VERSION = "2026-09-13";
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";
@@ -1,5 +1,5 @@
1
1
  //#region src/wire/protocol.ts
2
- const WIRE_VERSION = "2026-09-13";
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-13"
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-13" }),
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-13" })) invalid("selected wire version is outside offer");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "statewire",
3
- "version": "0.19.3",
3
+ "version": "0.20.0",
4
4
  "description": "State over the wire: snapshot-first delta streaming and command submission for any state and any command",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -490,12 +490,14 @@ export const handleEnvelope = (
490
490
  return null;
491
491
  };
492
492
 
493
+ // A dial that no wait preceded is part of the current connect: only a
494
+ // scheduled delay surfaces `retrying`, and the dial after it keeps that status.
493
495
  const dialing = (c: Conn) =>
494
496
  setStatus(
495
497
  c,
496
- c.attempt === 0
497
- ? { status: "connecting", degraded: false }
498
- : { status: "retrying", attempt: c.attempt, degraded: true },
498
+ c.connection.status === "retrying"
499
+ ? { status: "retrying", attempt: c.attempt, degraded: true }
500
+ : { status: "connecting", degraded: false },
499
501
  );
500
502
 
501
503
  const scheduleRetry = (
@@ -513,6 +515,7 @@ const scheduleRetry = (
513
515
  immediate ? 0 : backoff(c.attempt),
514
516
  c.retryAt - Date.now(),
515
517
  );
518
+ if (delay === 0) return delay;
516
519
  setStatus(c, {
517
520
  status: "retrying",
518
521
  degraded: true,
@@ -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
- if (entries.length === 0) return null;
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
- cmd: entries.map((p) => {
921
- const { produced: _produced, ...command } = p.command;
922
- return { ...command, seq: p.seq };
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<{ isNew?: boolean | undefined }>({});
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
@@ -1,6 +1,8 @@
1
1
  export const timing = {
2
- backoffBaseMs: 250,
2
+ backoffFirstMs: 0,
3
+ backoffBaseMs: 1_000,
3
4
  backoffMaxMs: 30_000,
5
+ backoffJitter: 0.5,
4
6
  backoffResetMs: 30_000,
5
7
  heartbeatTimeoutMs: 30_000,
6
8
  pingIntervalMs: 10_000,
@@ -9,11 +11,14 @@ export const timing = {
9
11
 
10
12
  export const retryHint = (ms: number) => Math.min(ms, timing.backoffMaxMs);
11
13
 
14
+ // Jitter only stretches the floor, so attempt n never lands before floor(n).
12
15
  export const backoff = (attempt: number) => {
13
- const limit = Math.min(
14
- timing.backoffBaseMs * 2 ** (attempt - 1),
15
- timing.backoffMaxMs,
16
- );
17
- const floor = Math.min(timing.backoffBaseMs, limit);
18
- return Math.round(floor + (limit - floor) * timing.random());
16
+ const floor =
17
+ attempt <= 1
18
+ ? timing.backoffFirstMs
19
+ : Math.min(
20
+ timing.backoffBaseMs * 2 ** (attempt - 2),
21
+ timing.backoffMaxMs,
22
+ );
23
+ return Math.round(floor * (1 + timing.backoffJitter * timing.random()));
19
24
  };
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;