statewire 0.18.1 → 0.19.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 (86) hide show
  1. package/dist/client/connection.d.ts +8 -5
  2. package/dist/client/connection.d.ts.map +1 -1
  3. package/dist/client/connection.js +24 -13
  4. package/dist/client/connection.js.map +1 -1
  5. package/dist/client/session.d.ts +13 -15
  6. package/dist/client/session.d.ts.map +1 -1
  7. package/dist/client/session.js +296 -401
  8. package/dist/client/session.js.map +1 -1
  9. package/dist/client/timing.d.ts +2 -1
  10. package/dist/client/timing.d.ts.map +1 -1
  11. package/dist/client/timing.js +2 -1
  12. package/dist/client/timing.js.map +1 -1
  13. package/dist/host/engine.d.ts +2 -0
  14. package/dist/host/engine.d.ts.map +1 -1
  15. package/dist/host/engine.js +2 -0
  16. package/dist/host/engine.js.map +1 -1
  17. package/dist/host/host.d.ts +3 -1
  18. package/dist/host/host.d.ts.map +1 -1
  19. package/dist/host/host.js +38 -7
  20. package/dist/host/host.js.map +1 -1
  21. package/dist/host/http.d.ts.map +1 -1
  22. package/dist/host/http.js +9 -19
  23. package/dist/host/http.js.map +1 -1
  24. package/dist/host/socket.d.ts +48 -25
  25. package/dist/host/socket.d.ts.map +1 -1
  26. package/dist/host/socket.js +399 -266
  27. package/dist/host/socket.js.map +1 -1
  28. package/dist/host/synced.d.ts +9 -0
  29. package/dist/host/synced.d.ts.map +1 -0
  30. package/dist/host/synced.js +45 -0
  31. package/dist/host/synced.js.map +1 -0
  32. package/dist/host-internal.d.ts +20 -12
  33. package/dist/host-internal.d.ts.map +1 -1
  34. package/dist/host.d.ts +33 -9
  35. package/dist/host.d.ts.map +1 -1
  36. package/dist/host.js +2 -1
  37. package/dist/index.d.ts +2 -2
  38. package/dist/index.js +2 -1
  39. package/dist/statewire.d.ts +8 -20
  40. package/dist/statewire.d.ts.map +1 -1
  41. package/dist/statewire.js +0 -14
  42. package/dist/statewire.js.map +1 -1
  43. package/dist/storage.d.ts +2 -5
  44. package/dist/storage.d.ts.map +1 -1
  45. package/dist/storage.js +54 -89
  46. package/dist/storage.js.map +1 -1
  47. package/dist/transports/remote.d.ts.map +1 -1
  48. package/dist/transports/remote.js +1 -6
  49. package/dist/transports/remote.js.map +1 -1
  50. package/dist/transports/sse.d.ts +2 -5
  51. package/dist/transports/sse.d.ts.map +1 -1
  52. package/dist/transports/sse.js.map +1 -1
  53. package/dist/transports/testing.d.ts +7 -5
  54. package/dist/transports/testing.d.ts.map +1 -1
  55. package/dist/transports/testing.js +19 -18
  56. package/dist/transports/testing.js.map +1 -1
  57. package/dist/types.d.ts +3 -5
  58. package/dist/types.d.ts.map +1 -1
  59. package/dist/types.js.map +1 -1
  60. package/dist/wire/limits.d.ts +2 -2
  61. package/dist/wire/limits.js +2 -2
  62. package/dist/wire/limits.js.map +1 -1
  63. package/dist/wire/protocol.d.ts +6 -1
  64. package/dist/wire/protocol.d.ts.map +1 -1
  65. package/dist/wire/protocol.js +33 -12
  66. package/dist/wire/protocol.js.map +1 -1
  67. package/package.json +1 -1
  68. package/src/client/connection.ts +46 -33
  69. package/src/client/session.ts +442 -517
  70. package/src/client/timing.ts +2 -0
  71. package/src/host/engine.ts +3 -1
  72. package/src/host/host.ts +50 -3
  73. package/src/host/http.ts +23 -31
  74. package/src/host/socket.ts +560 -339
  75. package/src/host/synced.ts +60 -0
  76. package/src/host-internal.ts +21 -7
  77. package/src/host.ts +40 -8
  78. package/src/index.ts +1 -0
  79. package/src/statewire.ts +8 -37
  80. package/src/storage.ts +90 -157
  81. package/src/transports/remote.ts +0 -7
  82. package/src/transports/sse.ts +8 -3
  83. package/src/transports/testing.ts +34 -18
  84. package/src/types.ts +3 -5
  85. package/src/wire/limits.ts +1 -1
  86. package/src/wire/protocol.ts +40 -11
@@ -2,10 +2,10 @@
2
2
  const MAX_COMMANDS_PER_FRAME = 256;
3
3
  const MAX_INFLIGHT_COMMANDS_PER_CLIENT = 256;
4
4
  const MAX_FRAME_BODY_BYTES = 8388608;
5
- const MAX_CONTEXT_VALUE_BYTES = 65536;
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
8
  //#endregion
9
- export { COMMAND_CAPACITY_RETRY_MS, MAX_COMMANDS_PER_FRAME, MAX_CONTEXT_VALUE_BYTES, MAX_FRAME_BODY_BYTES, MAX_INFLIGHT_COMMANDS_PER_CLIENT, MAX_INFLIGHT_COMMANDS_PER_HOST };
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
10
 
11
11
  //# 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_CONTEXT_VALUE_BYTES","MAX_INFLIGHT_COMMANDS_PER_HOST","COMMAND_CAPACITY_RETRY_MS"],"sources":["../../src/wire/limits.ts"],"sourcesContent":["export const MAX_COMMANDS_PER_FRAME = 256;\nexport const MAX_INFLIGHT_COMMANDS_PER_CLIENT = 256;\nexport const MAX_FRAME_BODY_BYTES = 8 * 1024 * 1024;\nexport const MAX_CONTEXT_VALUE_BYTES = 64 * 1024;\nexport const MAX_INFLIGHT_COMMANDS_PER_HOST = 1024;\nexport const COMMAND_CAPACITY_RETRY_MS = 1_000;\n"],"mappings":";AAAA,MAAaA,yBAAyB;AACtC,MAAaC,mCAAmC;AAChD,MAAaC,uBAAuB;AACpC,MAAaC,0BAA0B;AACvC,MAAaC,iCAAiC;AAC9C,MAAaC,4BAA4B"}
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"}
@@ -13,6 +13,7 @@ declare namespace StatewireProtocol {
13
13
  protocols: {
14
14
  name: string;
15
15
  version: string;
16
+ record?: "ephemeral";
16
17
  }[];
17
18
  };
18
19
  type Options = {
@@ -24,9 +25,13 @@ declare namespace StatewireProtocol {
24
25
  type Definition = Range & {
25
26
  name: string;
26
27
  requires?: Readonly<Record<string, Range>>;
28
+ subprotocols?: readonly {
29
+ name: string;
30
+ record?: "ephemeral";
31
+ }[];
27
32
  };
28
33
  }
29
- declare const WIRE_VERSION = "2026-09-07";
34
+ declare const WIRE_VERSION = "2026-09-13";
30
35
  declare const DEFAULT_PROTOCOL = "default";
31
36
  declare const PROTOCOL_HEADER = "Statewire-Protocol";
32
37
  declare const VERSION_HEADER = "Statewire-Version";
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.d.ts","names":[],"sources":["../../src/wire/protocol.ts"],"mappings":";kBAAiB;OACH;IAAU;IAAiB;;OAC3B,QAAQ;IAAU;IAAc;;OAChC;IACV;IACA;MAAa;MAAc;;;OAEjB;IACV,cAAc;IACd,gCAAgC;IAChC;IACA,cAAc,SAAS;;OAEb,aAAa;IACvB;IACA,WAAW,SAAS,eAAe;;;cAI1B;cACA;cACA;cACA;cACA,eAAa;cAMb,kBAAmB;cAanB,gBAAiB,OAAO,kBAAkB;;;;cAU1C,UAAW,eAAe,OAAO,kBAAkB;cAKnD,iBAAkB,SAAS,kBAAkB,qBAAO,kBAAA;cAKpD,qBACX,SAAS,kBAAkB,YAC1B;cA6DU,mBACX,SAAS,YACR,SACD,KAAK,kBAAkB;cA0DZ,oBACX,gBACA,SAAS,kBAAkB,YAC1B,kBAAkB;cAqCR,qBACX,SAAS,SACT,MAAM,kBAAkB,OACxB,sBAAsB,kBAAkB,iBAEtC,kBAAkB;EAEhB;IACE;IACA;IACA;IACA;IACA"}
1
+ {"version":3,"file":"protocol.d.ts","names":[],"sources":["../../src/wire/protocol.ts"],"mappings":";kBAAiB;OACH;IAAU;IAAiB;;OAC3B,QAAQ;IAAU;IAAc;;OAChC;IACV;IACA;MAAa;MAAc;MAAiB;;;OAElC;IACV,cAAc;IACd,gCAAgC;IAChC;IACA,cAAc,SAAS;;OAEb,aAAa;IACvB;IACA,WAAW,SAAS,eAAe;IACnC;MAA0B;MAAc;;;;cAI/B;cACA;cACA;cACA;cACA,eAAa;cAMb,kBAAmB;cAanB,gBAAiB,OAAO,kBAAkB;;;;cAU1C,UAAW,eAAe,OAAO,kBAAkB;cAKnD,iBAAkB,SAAS,kBAAkB,qBAAO,kBAAA;cAKpD,qBACX,SAAS,kBAAkB,YAC1B;cA6DU,mBACX,SAAS,YACR,SACD,KAAK,kBAAkB;cA0DZ,oBACX,gBACA,SAAS,kBAAkB,YAC1B,kBAAkB;cA6CR,qBACX,SAAS,SACT,MAAM,kBAAkB,OACxB,sBAAsB,kBAAkB,iBAEtC,kBAAkB;EAEhB;IACE;IACA;IACA;IACA;IACA"}
@@ -1,5 +1,5 @@
1
1
  //#region src/wire/protocol.ts
2
- const WIRE_VERSION = "2026-09-07";
2
+ const WIRE_VERSION = "2026-09-13";
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-07"
31
+ version: "2026-09-13"
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-07" }),
42
+ [VERSION_HEADER]: suffix(options.wireVersion ?? { version: "2026-09-13" }),
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,24 +123,30 @@ 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-07" })) invalid("selected wire version is outside offer");
126
+ if (!inRange(version, options.wireVersion ?? { version: "2026-09-13" })) 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
- const seen = /* @__PURE__ */ new Set();
129
+ const seen = /* @__PURE__ */ new Map();
130
130
  const protocols = syn.protocols.map((entry) => {
131
131
  if (typeof entry !== "object" || entry === null) return invalid("invalid selected protocol");
132
132
  const item = entry;
133
133
  const name = item.name;
134
134
  if (typeof name !== "string" || !protocolToken.test(name) || name === "*" || seen.has(name)) return invalid("invalid selected protocol name");
135
135
  const version = validateVersion(item.version);
136
- seen.add(name);
137
- if (offers !== "*") {
136
+ if (item.record !== void 0 && item.record !== "ephemeral") invalid("invalid selected protocol record");
137
+ const record = item.record;
138
+ const parent = name.slice(0, name.lastIndexOf("/"));
139
+ if (parent !== "" && seen.has(parent)) {
140
+ if (seen.get(parent) !== version) invalid("subprotocol version differs from its parent");
141
+ } else if (offers !== "*") {
138
142
  const offer = offers.find((o) => o.name === name);
139
143
  if (!offer || !inRange(version, offer)) invalid("selected protocol is outside offer");
140
144
  }
145
+ seen.set(name, version);
141
146
  return {
142
147
  name,
143
- version
148
+ version,
149
+ ...record !== void 0 && { record }
144
150
  };
145
151
  });
146
152
  if (offers !== "*" && offers.some((o) => !o.optional && !seen.has(o.name))) invalid("required protocol missing from selection");
@@ -179,6 +185,13 @@ const negotiateProtocols = (headers, wire, definitions) => {
179
185
  })) : request.applicationProtocols;
180
186
  const registry = new Map(definitions.map((d) => [d.name, d]));
181
187
  if (registry.size !== definitions.length) throw new Error("statewire: duplicate registered protocol");
188
+ for (const definition of definitions) {
189
+ const children = /* @__PURE__ */ new Set();
190
+ for (const child of definition.subprotocols ?? []) {
191
+ if (!protocolToken.test(child.name) || child.name.includes("/") || children.has(child.name) || child.record !== void 0 && child.record !== "ephemeral") throw new Error("statewire: invalid subprotocol registration");
192
+ children.add(child.name);
193
+ }
194
+ }
182
195
  const offered = new Map(offers.map((o) => [o.name, o]));
183
196
  const solve = (names) => {
184
197
  const active = new Set(names);
@@ -209,10 +222,18 @@ const negotiateProtocols = (headers, wire, definitions) => {
209
222
  for (const offer of offers) if (offer.optional) selected = solve(/* @__PURE__ */ new Set([...selected.keys(), offer.name])) ?? selected;
210
223
  return {
211
224
  version,
212
- protocols: offers.flatMap((o) => selected.has(o.name) ? [{
213
- name: o.name,
214
- version: selected.get(o.name)
215
- }] : [])
225
+ protocols: offers.flatMap((o) => {
226
+ const version = selected.get(o.name);
227
+ if (version === void 0) return [];
228
+ return [{
229
+ name: o.name,
230
+ version
231
+ }, ...(registry.get(o.name).subprotocols ?? []).map((child) => ({
232
+ name: `${o.name}/${child.name}`,
233
+ version,
234
+ ...child.record !== void 0 && { record: child.record }
235
+ }))];
236
+ })
216
237
  };
217
238
  };
218
239
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.js","names":["StatewireProtocol","Range","version","minVersion","Offer","name","optional","Selection","protocols","Options","wireVersion","applicationProtocols","protocol","keyPrefixes","Readonly","Record","Definition","requires","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","find","o","negotiateProtocols","definitions","fin","reason","code","status","payload","supported","d","failure","const","request","ReturnType","error","sort","registry","size","offered","solve","active","definition","dependency","Object","result","ranges","flatMap","parent","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 }[];\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 };\n}\n\nexport const WIRE_VERSION = \"2026-09-07\";\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 Set<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 seen.add(name);\n 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 return { name, version };\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 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 selected!.has(o.name)\n ? [{ name: o.name, version: selected!.get(o.name)! }]\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-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":";AAoBA,MAAaoB,eAAe;AAC5B,MAAaC,mBAAmB;AAChC,MAAaC,kBAAkB;AAC/B,MAAaC,iBAAiB;AAC9B,MAAaC,gBAAgB;AAE7B,MAAMC,WAAWC,YAA2B;CAC1C,MAAM,IAAIC,MAAM,8BAA8BD,SAAS;AACzD;AAEA,MAAaE,mBAAmBC,UAA2B;CACzD,IAAI,OAAOA,UAAU,YAAY,CAAC,sBAAsBC,KAAKD,KAAK,GAChE,OAAOJ,QAAQ,mCAAmC;CACpD,MAAMM,yBAAS,IAAIC,KAAK,GAAGH,MAAK,eAAgB;CAChD,IACE,CAACI,OAAOC,SAASH,OAAOI,QAAQ,CAAC,KACjCJ,OAAOK,YAAY,CAAC,CAACC,MAAM,GAAG,EAAE,MAAMR,SACtCA,MAAMS,WAAW,MAAM,GAEvB,OAAOb,QAAQ,sBAAsB;CACvC,OAAOI;AACT;AAEA,MAAaU,iBAAiBC,UAAmC;CAC/D,MAAMtC,UAAU0B,gBAAgBY,MAAMtC,OAAO;CAC7C,MAAMC,aACJqC,MAAMrC,eAAesC,KAAAA,IACjBvC,UACA0B,gBAAgBY,MAAMrC,UAAU;CACtC,IAAIA,aAAaD,SAASuB,QAAQ,6BAA6B;CAC/D,OAAO;EAAEvB;EAASC;CAAW;AAC/B;AAEA,MAAauC,WAAWb,OAAeW,UAAmC;CACxE,MAAMG,UAAUJ,cAAcC,KAAK;CACnC,OAAOX,SAASc,QAAQxC,cAAc0B,SAASc,QAAQzC;AACzD;AAEA,MAAa0C,kBAAkBC,YAC7BA,QAAQjC,wBAAwB,CAC9B;CAAEP,MAAMwC,QAAQhC,YAAAA;CAA8BX,SAAAA;AAAsB,CAAC;AAGzE,MAAa4C,sBACXD,YAC2B;CAC3B,MAAME,UAAUP,UAAmC;EACjD,MAAM,EAAEtC,SAASC,eAAeoC,cAAcC,KAAK;EACnD,OAAO,IAAItC,QAAO,GAAIC,eAAeD,UAAU,KAAK,kBAAkBC,WAAU;CAClF;CACA,MAAM6C,SAASJ,eAAeC,OAAO;CACrC,MAAMI,uBAAO,IAAIC,IAAY;CAC7B,IAAIF,WAAW,OAAO,CAACA,OAAOG,QAC5B1B,QAAQ,iCAAiC;CAC3C,OAAO;GACJF,iBAAiBwB,OAAOF,QAAQlC,eAAe,EAAET,SAAAA,aAAsB,CAAC;GACxEoB,kBACC0B,WAAW,MACP,MACAA,OACGI,KAAKC,UAAU;GACd,IACE,CAAC7B,cAAcM,KAAKuB,MAAMhD,IAAI,KAC9BgD,MAAMhD,SAAS,OACf4C,KAAKK,IAAID,MAAMhD,IAAI,GAEnBoB,QAAQ,oCAAoC;GAC9C,IACE4B,MAAM/C,aAAamC,KAAAA,KACnB,OAAOY,MAAM/C,aAAa,WAE1BmB,QAAQ,0BAA0B;GACpCwB,KAAKM,IAAIF,MAAMhD,IAAI;GACnB,OAAO,GAAGgD,MAAMhD,KAAI,YAAa0C,OAAOM,KAAK,IAAIA,MAAM/C,WAAW,eAAe;EACnF,CAAC,CAAC,CACDkD,KAAK,IAAI;CACpB;AACF;AAEA,MAAMC,aAAaC,UAAkB;CACnC,MAAMC,OAAOC,OAAOC,GAAG;CACvB,MAAMC,OAAOJ,MAAMK,KAAK;CACxB,MAAMC,QAAQ,IAAIC,OAAO,OAAON,KAAI,EAAG,CAAC,CAACO,KAAKJ,IAAI;CAClD,IAAI,CAACE,OAAO,OAAOvC,QAAQ,yBAAyB;CACpD,MAAM0C,SAASH,MAAM,EAAE,CAAC1B,WAAW,IAAG;CACtC,MAAM8B,UAAUP,QAAgBA,IAAIxB,MAAM,GAAG,EAAE,CAAC,CAACgC,QAAQ,cAAc,IAAI;CAC3E,MAAMxC,QAAQsC,SAASC,OAAOJ,MAAM,EAAE,IAAIA,MAAM;CAChD,MAAMM,yBAAS,IAAIC,IAA8B;CACjD,IAAIC,OAAOV,KAAKzB,MAAM2B,MAAM,EAAE,CAACb,MAAM;CACrC,OAAOqB,MAAM;EACX,MAAMC,YAAY,IAAIR,OACpB,iCAAiCN,KAAI,IACvC,CAAC,CAACO,KAAKM,IAAI;EACX,IAAI,CAACC,WAAW,OAAOhD,QAAQ,+BAA+B;EAC9D,MAAMiD,MAAMD,UAAU;EACtB,MAAMZ,MAAMY,UAAU;EACtB,IAAIH,OAAOhB,IAAIoB,GAAG,GAAGjD,QAAQ,qBAAqB;EAClD,IAAIoC,QAAQpB,KAAAA,KAAaoB,QAAQ,MAAMS,OAAOK,IAAID,KAAK,IAAI;OACtD,IAAIb,QAAQ,MAAMS,OAAOK,IAAID,KAAK,KAAK;OACvC,IAAIb,IAAIvB,WAAW,IAAG,GAAGgC,OAAOK,IAAID,KAAKN,OAAOP,GAAG,CAAC;OACpDpC,QAAQ,qCAAqC;EAClD+C,OAAOA,KAAKnC,MAAMoC,UAAU,EAAE,CAACtB,MAAM;CACvC;CACA,OAAO;EAAEtB;EAAOsC;EAAQG;CAAO;AACjC;AAEA,MAAaM,oBACXC,YAGG;CACH,MAAMI,OAAOJ,QAAQK,IAAI3D,cAAc;CACvC,MAAM4D,cAAcN,QAAQK,IAAI5D,eAAe;CAC/C,IAAI2D,SAAS,QAAQE,gBAAgB,MACnC,OAAO1D,QAAQ,iCAAiC;CAClD,MAAM2D,OAAO3B,UAAUwB,IAAI;CAC3B,IAAI,CAACG,KAAKjB,UAAU,CAAC,GAAGiB,KAAKd,OAAOe,KAAK,CAAC,CAAC,CAACC,MAAMC,MAAMA,MAAM,aAAa,GACzE9D,QAAQ,2BAA2B;CACrC,MAAMtB,aAAaiF,KAAKd,OAAOY,IAAI,aAAa;CAChD,IAAI/E,eAAesC,KAAAA,KAAa,OAAOtC,eAAe,UACpD,OAAOsB,QAAQ,8BAA8B;CAC/C,MAAMd,cAAc4B,cAAc;EAChCrC,SAASkF,KAAKvD;EACd,GAAI1B,eAAesC,KAAAA,IAAY,CAAC,IAAI,EAAEtC,WAAW;CACnD,CAAC;CACD,IAAIgF,YAAYpB,KAAK,MAAM,KACzB,OAAO;EAAEpD;EAAaC,sBAAsB;CAAI;CAClD,MAAM4E,wBAAQ,IAAItC,IAAY;CAqC9B,OAAO;EAAEvC;EAAaC,sBApCOuE,YAC1BM,MAAM,GAAG,CAAC,CACVrC,KAAKU,SAAkC;GACtC,MAAM,EAAEjC,OAAOxB,MAAM8D,QAAQG,WAAWb,UAAUK,IAAI;GACtD,IACEK,UACA,CAAC3C,cAAcM,KAAKzB,IAAI,KACxBA,SAAS,OACTmF,MAAMlC,IAAIjD,IAAI,GAEdoB,QAAQ,oCAAoC;GAC9C+D,MAAMjC,IAAIlD,IAAI;GACd,IACE,CAAC,GAAGiE,OAAOe,KAAK,CAAC,CAAC,CAACC,MAChBC,MAAM,CAAC;IAAC;IAAW;IAAe;GAAU,CAAC,CAACG,SAASH,CAAC,CAC3D,GAEA9D,QAAQ,uBAAuB;GACjC,MAAMvB,UAAUoE,OAAOY,IAAI,SAAS;GACpC,MAAM/E,aAAamE,OAAOY,IAAI,aAAa;GAC3C,MAAM5E,WAAWgE,OAAOY,IAAI,UAAU,KAAK;GAC3C,IACE,OAAOhF,YAAY,YAClBC,eAAesC,KAAAA,KAAa,OAAOtC,eAAe,YACnD,OAAOG,aAAa,WAEpB,OAAOmB,QAAQ,6BAA6B;GAC9C,OAAO;IACLpB;IACA,GAAGkC,cAAc;KACfrC;KACA,GAAIC,eAAesC,KAAAA,IAAY,CAAC,IAAI,EAAEtC,WAAW;IACnD,CAAC;IACDG;GACF;EACF,CACoBM;CAAqB;AAC7C;AAEA,MAAa+E,qBACX9D,OACAgB,YACgC;CAChC,IAAI,OAAOhB,UAAU,YAAYA,UAAU,MACzC,OAAOJ,QAAQ,+BAA+B;CAChD,MAAMmE,MAAM/D;CACZ,MAAM3B,UAAU0B,gBAAgBgE,IAAI1F,OAAO;CAC3C,IAAI,CAACwC,QAAQxC,SAAS2C,QAAQlC,eAAe,EAAET,SAAAA,aAAsB,CAAC,GACpEuB,QAAQ,wCAAwC;CAClD,IAAI,CAACoE,MAAMC,QAAQF,IAAIpF,SAAS,GAC9B,OAAOiB,QAAQ,gCAAgC;CACjD,MAAMuB,SAASJ,eAAeC,OAAO;CACrC,MAAMI,uBAAO,IAAIsB,IAAoB;CACrC,MAAM/D,YAAYoF,IAAIpF,UAAU4C,KAAK2C,UAAmB;EACtD,IAAI,OAAOA,UAAU,YAAYA,UAAU,MACzC,OAAOtE,QAAQ,2BAA2B;EAC5C,MAAM2D,OAAOW;EACb,MAAM1F,OAAO+E,KAAK/E;EAClB,IACE,OAAOA,SAAS,YAChB,CAACmB,cAAcM,KAAKzB,IAAI,KACxBA,SAAS,OACT4C,KAAKK,IAAIjD,IAAI,GAEb,OAAOoB,QAAQ,gCAAgC;EACjD,MAAMvB,UAAU0B,gBAAgBwD,KAAKlF,OAAO;EAC5C,IAAIkF,KAAK3E,WAAWgC,KAAAA,KAAa2C,KAAK3E,WAAW,aAC/CgB,QAAQ,kCAAkC;EAC5C,MAAMhB,SAAS2E,KAAK3E;EACpB,MAAMuF,SAAS3F,KAAKgC,MAAM,GAAGhC,KAAK4F,YAAY,GAAG,CAAC;EAElD,IADcD,WAAW,MAAM/C,KAAKK,IAAI0C,MAAM,GAExC/C;OAAAA,KAAKiC,IAAIc,MAAM,MAAM9F,SACvBuB,QAAQ,6CAA6C;EAAA,OAClD,IAAIuB,WAAW,KAAK;GACzB,MAAMK,QAAQL,OAAOmD,MAAMC,MAAMA,EAAE/F,SAASA,IAAI;GAChD,IAAI,CAACgD,SAAS,CAACX,QAAQxC,SAASmD,KAAK,GACnC5B,QAAQ,oCAAoC;EAChD;EACAwB,KAAK0B,IAAItE,MAAMH,OAAO;EACtB,OAAO;GAAEG;GAAMH;GAAS,GAAIO,WAAWgC,KAAAA,KAAa,EAAEhC,OAAO;EAAG;CAClE,CAAC;CACD,IAAIuC,WAAW,OAAOA,OAAOsC,MAAMc,MAAM,CAACA,EAAE9F,YAAY,CAAC2C,KAAKK,IAAI8C,EAAE/F,IAAI,CAAC,GACvEoB,QAAQ,0CAA0C;CACpD,OAAO;EAAEvB;EAASM;CAAU;AAC9B;AAEA,MAAa6F,sBACXxB,SACAI,MACAqB,gBAWO;CACP,MAAMM,YAAY;EAChB,GAAGrE,cAAc0C,IAAI;EACrBzE,WAAW8F,YAAYlD,KAAKyD,OAAO;GAAExG,MAAMwG,EAAExG;GAAM,GAAGkC,cAAcsE,CAAC;EAAE,EAAE;CAC3E;CACA,MAAMC,WAAWL,MAAc/E,aAAqB,EAClD6E,KAAK;EACHC,QAAQ;EACRC;EACA/E;EACAgF,QAAQD,SAAS,wBAAwB,MAAM;EAC/C,GAAIA,SAAS,wBAAwB,CAAC,IAAI,EAAEE,SAASC,UAAU;CACjE,EACF;CACA,IAAII;CACJ,IAAI;EACFA,UAAUpC,iBAAiBC,OAAO;CACpC,SAASqC,OAAO;EACd,OAAOJ,QACL,uBACAI,iBAAiBvF,QAAQuF,MAAMxF,UAAU,qBAC3C;CACF;CACA,MAAMxB,UAAU,CAAC+E,KAAK/E,SAAS8G,QAAQrG,YAAYT,OAAO,CAAC,CAACiH,KAAK,CAAC,CAAC;CACnE,IAAI,CAACzE,QAAQxC,SAAS+E,IAAI,KAAK,CAACvC,QAAQxC,SAAS8G,QAAQrG,WAAW,GAClE,OAAOmG,QAAQ,uBAAuB,mCAAmC;CAC3E,MAAM9D,SACJgE,QAAQpG,yBAAyB,MAC7B0F,YAAYlD,KAAKyD,OAAO;EACtBxG,MAAMwG,EAAExG;EACRH,SAAS2G,EAAE3G;EACXI,UAAU;CACZ,EAAE,IACF0G,QAAQpG;CACd,MAAMwG,WAAW,IAAI7C,IAAI+B,YAAYlD,KAAKyD,MAAM,CAACA,EAAExG,MAAMwG,CAAC,CAAC,CAAC;CAC5D,IAAIO,SAASC,SAASf,YAAYnD,QAChC,MAAM,IAAIxB,MAAM,0CAA0C;CAC5D,KAAK,MAAM2F,cAAchB,aAAa;EACpC,MAAMiB,2BAAW,IAAIrE,IAAY;EACjC,KAAK,MAAMgD,SAASoB,WAAWnG,gBAAgB,CAAA,GAAI;GACjD,IACE,CAACK,cAAcM,KAAKoE,MAAM7F,IAAI,KAC9B6F,MAAM7F,KAAKqF,SAAS,GAAG,KACvB6B,SAASjE,IAAI4C,MAAM7F,IAAI,KACtB6F,MAAMzF,WAAWgC,KAAAA,KAAayD,MAAMzF,WAAW,aAEhD,MAAM,IAAIkB,MAAM,6CAA6C;GAC/D4F,SAAShE,IAAI2C,MAAM7F,IAAI;EACzB;CACF;CACA,MAAMmH,UAAU,IAAIjD,IAAIvB,OAAOI,KAAKgD,MAAM,CAACA,EAAE/F,MAAM+F,CAAC,CAAC,CAAC;CACtD,MAAMqB,SAASjC,UAAuB;EACpC,MAAMkC,SAAS,IAAIxE,IAAIsC,KAAK;EAC5B,KAAK,MAAMnF,QAAQqH,QAAQ;GACzB,MAAMJ,aAAaF,SAASlC,IAAI7E,IAAI;GACpC,IAAI,CAACiH,cAAc,CAACE,QAAQlE,IAAIjD,IAAI,GAAG,OAAOoC,KAAAA;GAC9C,KAAK,MAAMkF,cAAcC,OAAOvC,KAAKiC,WAAWpG,YAAY,CAAC,CAAC,GAC5DwG,OAAOnE,IAAIoE,UAAU;EACzB;EACA,MAAME,yBAAS,IAAItD,IAAoB;EACvC,KAAK,MAAMlE,QAAQqH,QAAQ;GACzB,MAAMI,SAAS;IACbV,SAASlC,IAAI7E,IAAI;IACjBmH,QAAQtC,IAAI7E,IAAI;IAChB,GAAG,CAAC,GAAGqH,MAAM,CAAC,CAACK,SAAS/B,WAAW;KACjC,MAAM2B,aAAaP,SAASlC,IAAIc,MAAM,CAAC,CAAE9E,WAAWb;KACpD,OAAOsH,aAAa,CAACA,UAAU,IAAI,CAAA;IACrC,CAAC;GAAC,CACH,CAACvE,IAAIb,aAAa;GACnB,MAAMyF,QAAQF,OACX1E,KAAK6E,MAAMA,EAAE9H,UAAU,CAAC,CACxBgH,KAAK,CAAC,CACNe,GAAG,EAAE;GACR,MAAMC,QAAQL,OAAO1E,KAAK6E,MAAMA,EAAE/H,OAAO,CAAC,CAACiH,KAAK,CAAC,CAAC;GAClD,IAAIa,QAAQG,OAAO,OAAO1F,KAAAA;GAC1BoF,OAAOlD,IAAItE,MAAM8H,KAAK;EACxB;EACA,OAAON;CACT;CACA,IAAIO,WAAWX,MACb,IAAIvE,IAAIF,OAAOqF,QAAQjC,MAAM,CAACA,EAAE9F,QAAQ,CAAC,CAAC8C,KAAKgD,MAAMA,EAAE/F,IAAI,CAAC,CAC9D;CACA,IAAI,CAAC+H,UACH,OAAOtB,QACL,wBACA,8CACF;CACF,KAAK,MAAMzD,SAASL,QAClB,IAAIK,MAAM/C,UACR8H,WAAWX,sBAAM,IAAIvE,IAAI,CAAC,GAAGkF,SAAS/C,KAAK,GAAGhC,MAAMhD,IAAI,CAAC,CAAC,KAAK+H;CAEnE,OAAO;EACLlI;EACAM,WAAWwC,OAAO+E,SAAS3B,MAAM;GAC/B,MAAMlG,UAAUkI,SAAUlD,IAAIkB,EAAE/F,IAAI;GACpC,IAAIH,YAAYuC,KAAAA,GAAW,OAAO,CAAA;GAClC,OAAO,CACL;IAAEpC,MAAM+F,EAAE/F;IAAMH;GAAQ,GACxB,IAAIkH,SAASlC,IAAIkB,EAAE/F,IAAI,CAAC,CAAEc,gBAAgB,CAAA,EAAA,CAAIiC,KAAK8C,WAAW;IAC5D7F,MAAM,GAAG+F,EAAE/F,KAAI,GAAI6F,MAAM7F;IACzBH;IACA,GAAIgG,MAAMzF,WAAWgC,KAAAA,KAAa,EAAEhC,QAAQyF,MAAMzF,OAAO;GAC3D,EAAE,CAAC;EAEP,CAAC;CACH;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "statewire",
3
- "version": "0.18.1",
3
+ "version": "0.19.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": {
@@ -7,7 +7,7 @@ import type { StatewireProtocol } from "../wire/protocol";
7
7
  import { useEffect, useRef, useState } from "react";
8
8
  import { flushTapSync } from "@assistant-ui/tap";
9
9
  import { reviveOps, reviveValue } from "../wire/meta";
10
- import { backoff, timing } from "./timing";
10
+ import { backoff, retryHint, timing } from "./timing";
11
11
  import { isStorageError } from "./storageError";
12
12
  import type {
13
13
  ConnectionState,
@@ -35,6 +35,7 @@ export const TERMINAL = [
35
35
  ] as const;
36
36
 
37
37
  export type StatewireCmd = {
38
+ protocol: string;
38
39
  seq: number;
39
40
  type?: "applied" | (typeof TERMINAL)[number];
40
41
  code?: string;
@@ -47,10 +48,12 @@ export type StatewireCmd = {
47
48
 
48
49
  export type StatewireSyn = {
49
50
  version?: string;
50
- protocols?: StatewireProtocol.Selection["protocols"];
51
- seq?: number;
52
- ctxSeq?: number;
53
- record?: "ephemeral";
51
+ protocols?: {
52
+ name: string;
53
+ version?: string;
54
+ record?: "ephemeral";
55
+ seq?: number;
56
+ }[];
54
57
  id?: string;
55
58
  idle?: boolean;
56
59
  retry?: number;
@@ -197,13 +200,33 @@ const validateOptionalString = (value: unknown, field: string): void => {
197
200
  }
198
201
  };
199
202
 
200
- const validateSynScalars = (syn: Record<string, unknown>) => {
201
- if (syn["seq"] !== undefined && !isNonnegativeSafeInteger(syn["seq"])) {
202
- protocolError("syn.seq must be a nonnegative safe integer");
203
- }
204
- if (syn["ctxSeq"] !== undefined && !isNonnegativeSafeInteger(syn["ctxSeq"])) {
205
- protocolError("syn.ctxSeq must be a nonnegative safe integer");
203
+ const validateSynProtocols = (value: unknown, hello: boolean): void => {
204
+ if (value === undefined) return;
205
+ if (!Array.isArray(value))
206
+ return protocolError("syn.protocols must be an array");
207
+ for (const entry of value) {
208
+ if (!isObject(entry))
209
+ return protocolError("syn.protocols entry must be an object");
210
+ if (typeof entry["name"] !== "string")
211
+ protocolError("syn.protocols[].name must be a string");
212
+ if (
213
+ !hello &&
214
+ (entry["version"] !== undefined || entry["record"] !== undefined)
215
+ )
216
+ protocolError("invalid mid-stream syn");
217
+ if (
218
+ hello
219
+ ? !isNonnegativeSafeInteger(entry["seq"])
220
+ : entry["seq"] !== undefined && !isNonnegativeSafeInteger(entry["seq"])
221
+ )
222
+ protocolError("syn.protocols[].seq must be a nonnegative safe integer");
223
+ if (hello && entry["record"] === "ephemeral" && entry["seq"] !== 0)
224
+ protocolError("ephemeral lane hello seq must be 0");
206
225
  }
226
+ };
227
+
228
+ const validateSynScalars = (syn: Record<string, unknown>, hello: boolean) => {
229
+ validateSynProtocols(syn["protocols"], hello);
207
230
  if (syn["retry"] !== undefined && !isNonnegativeSafeInteger(syn["retry"])) {
208
231
  protocolError("syn.retry must be a nonnegative safe integer");
209
232
  }
@@ -211,24 +234,19 @@ const validateSynScalars = (syn: Record<string, unknown>) => {
211
234
  if (syn["idle"] !== undefined && typeof syn["idle"] !== "boolean") {
212
235
  protocolError("syn.idle must be a boolean");
213
236
  }
214
- validateOptionalString(syn["record"], "syn.record");
215
237
  };
216
238
 
217
239
  export const validateRefusalSyn = (value: unknown): StatewireSyn => {
218
240
  if (!isObject(value)) return protocolError("syn must be an object");
219
- validateSynScalars(value);
241
+ validateSynScalars(value, false);
220
242
  if (
221
243
  value["id"] !== undefined ||
222
244
  value["idle"] !== undefined ||
223
- value["record"] !== undefined
245
+ value["version"] !== undefined
224
246
  ) {
225
247
  protocolError("invalid refusal syn");
226
248
  }
227
- if (
228
- value["seq"] === undefined &&
229
- value["ctxSeq"] === undefined &&
230
- value["retry"] === undefined
231
- ) {
249
+ if (value["protocols"] === undefined && value["retry"] === undefined) {
232
250
  protocolError("refusal syn has no watermarks or retry");
233
251
  }
234
252
  return value as StatewireSyn;
@@ -236,6 +254,9 @@ export const validateRefusalSyn = (value: unknown): StatewireSyn => {
236
254
 
237
255
  const validateCmd = (value: unknown): value is StatewireCmd => {
238
256
  if (!isObject(value)) return protocolError("cmd entry must be an object");
257
+ if (typeof value["protocol"] !== "string") {
258
+ protocolError("cmd.protocol must be a string");
259
+ }
239
260
  if (!isPositiveSafeInteger(value["seq"])) {
240
261
  protocolError("cmd.seq must be a positive safe integer");
241
262
  }
@@ -313,7 +334,7 @@ const snapshotOf = (env: StatewireEnvelope): unknown[] => {
313
334
 
314
335
  const helloSynOf = (env: StatewireEnvelope): StatewireSyn => {
315
336
  if (!isObject(env.syn)) return protocolError("expected hello");
316
- validateSynScalars(env.syn);
337
+ validateSynScalars(env.syn, true);
317
338
  if (env.syn["retry"] !== undefined) {
318
339
  protocolError("hello syn cannot carry retry");
319
340
  }
@@ -328,6 +349,7 @@ const handleHello = (c: Conn, env: StatewireEnvelope) => {
328
349
  ops,
329
350
  syn,
330
351
  });
352
+ if (env.cmd !== undefined) c.ctx.onCmds(env.cmd);
331
353
  c.ctx.onIdle(syn.idle ?? false);
332
354
  c.awaitingHello = false;
333
355
  c.error = undefined;
@@ -347,20 +369,11 @@ const warnUnknownSynFact = () => {
347
369
 
348
370
  const deliverSyn = (c: Conn, value: unknown) => {
349
371
  if (!isObject(value)) return protocolError("syn must be an object");
350
- validateSynScalars(value);
351
- if (
352
- value["version"] !== undefined ||
353
- value["protocols"] !== undefined ||
354
- value["record"] !== undefined ||
355
- value["idle"] !== undefined
356
- ) {
372
+ validateSynScalars(value, false);
373
+ if (value["version"] !== undefined || value["idle"] !== undefined) {
357
374
  protocolError("invalid mid-stream syn");
358
375
  }
359
- if (
360
- value["seq"] !== undefined ||
361
- value["ctxSeq"] !== undefined ||
362
- value["retry"] !== undefined
363
- ) {
376
+ if (value["protocols"] !== undefined || value["retry"] !== undefined) {
364
377
  c.ctx.onRefusal(validateRefusalSyn(value));
365
378
  return;
366
379
  }
@@ -394,7 +407,7 @@ export const settleFin = (c: Conn, fin: StatewireHost.Fin): AttemptOutcome => {
394
407
  ? fin
395
408
  : { ...fin, payload: reviveValue(fin.payload, fin.meta) };
396
409
  releaseHeld(c);
397
- c.retryAt = Date.now() + (finish.retry ?? 0);
410
+ c.retryAt = Date.now() + retryHint(finish.retry ?? 0);
398
411
  if (finish.reason === "idle") {
399
412
  c.ctx.onIdle(true);
400
413
  c.attempt = 0;