statewire 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +14 -6
  2. package/dist/client/connection.d.ts +5 -1
  3. package/dist/client/connection.d.ts.map +1 -1
  4. package/dist/client/connection.js +19 -4
  5. package/dist/client/connection.js.map +1 -1
  6. package/dist/client/core.d.ts.map +1 -1
  7. package/dist/client/core.js +2 -0
  8. package/dist/client/core.js.map +1 -1
  9. package/dist/client/session.d.ts +15 -4
  10. package/dist/client/session.d.ts.map +1 -1
  11. package/dist/client/session.js +233 -52
  12. package/dist/client/session.js.map +1 -1
  13. package/dist/client/sessionStore.d.ts +5 -1
  14. package/dist/client/sessionStore.d.ts.map +1 -1
  15. package/dist/client/sessionStore.js +3 -1
  16. package/dist/client/sessionStore.js.map +1 -1
  17. package/dist/host/host.d.ts +1 -0
  18. package/dist/host/host.d.ts.map +1 -1
  19. package/dist/host/host.js.map +1 -1
  20. package/dist/host/http.d.ts.map +1 -1
  21. package/dist/host/http.js +27 -9
  22. package/dist/host/http.js.map +1 -1
  23. package/dist/host/socket.d.ts +10 -3
  24. package/dist/host/socket.d.ts.map +1 -1
  25. package/dist/host/socket.js +103 -39
  26. package/dist/host/socket.js.map +1 -1
  27. package/dist/host-internal.d.ts +9 -1
  28. package/dist/host-internal.d.ts.map +1 -1
  29. package/dist/host-internal.js +1 -1
  30. package/dist/index.d.ts +5 -3
  31. package/dist/index.js +3 -3
  32. package/dist/statewire.d.ts +33 -2
  33. package/dist/statewire.d.ts.map +1 -1
  34. package/dist/statewire.js +34 -3
  35. package/dist/statewire.js.map +1 -1
  36. package/dist/transports/fixtures.js +1 -0
  37. package/dist/transports/fixtures.js.map +1 -1
  38. package/dist/transports/remote.d.ts.map +1 -1
  39. package/dist/transports/remote.js +15 -8
  40. package/dist/transports/remote.js.map +1 -1
  41. package/dist/transports/sse.d.ts +2 -2
  42. package/dist/transports/sse.d.ts.map +1 -1
  43. package/dist/transports/sse.js +11 -11
  44. package/dist/transports/sse.js.map +1 -1
  45. package/dist/transports/testing.d.ts +1 -1
  46. package/dist/transports/testing.d.ts.map +1 -1
  47. package/dist/transports/testing.js +14 -6
  48. package/dist/transports/testing.js.map +1 -1
  49. package/dist/transports/ws.d.ts +2 -2
  50. package/dist/transports/ws.d.ts.map +1 -1
  51. package/dist/transports/ws.js +5 -5
  52. package/dist/transports/ws.js.map +1 -1
  53. package/dist/types.d.ts +7 -0
  54. package/dist/types.d.ts.map +1 -1
  55. package/dist/types.js.map +1 -1
  56. package/dist/util/randomId.js +1 -1
  57. package/dist/util/randomId.js.map +1 -1
  58. package/dist/wire/documents.d.ts +25 -2
  59. package/dist/wire/documents.d.ts.map +1 -1
  60. package/dist/wire/documents.js +27 -7
  61. package/dist/wire/documents.js.map +1 -1
  62. package/dist/wire/protocol.d.ts +55 -0
  63. package/dist/wire/protocol.d.ts.map +1 -0
  64. package/dist/wire/protocol.js +221 -0
  65. package/dist/wire/protocol.js.map +1 -0
  66. package/dist/wire/version.d.ts +1 -3
  67. package/dist/wire/version.d.ts.map +1 -1
  68. package/dist/wire/version.js +1 -3
  69. package/dist/wire/version.js.map +1 -1
  70. package/package.json +1 -1
  71. package/src/client/connection.ts +43 -13
  72. package/src/client/core.ts +6 -0
  73. package/src/client/session.ts +339 -42
  74. package/src/client/sessionStore.ts +8 -0
  75. package/src/host/host.ts +1 -0
  76. package/src/host/http.ts +36 -7
  77. package/src/host/socket.ts +141 -50
  78. package/src/host-internal.ts +12 -1
  79. package/src/index.ts +5 -2
  80. package/src/statewire.ts +109 -6
  81. package/src/transports/fixtures.ts +1 -1
  82. package/src/transports/remote.ts +16 -7
  83. package/src/transports/sse.ts +28 -18
  84. package/src/transports/testing.ts +12 -4
  85. package/src/transports/ws.ts +8 -8
  86. package/src/types.ts +11 -0
  87. package/src/util/randomId.ts +3 -3
  88. package/src/wire/documents.ts +126 -8
  89. package/src/wire/protocol.ts +327 -0
  90. package/src/wire/version.ts +0 -2
@@ -0,0 +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,7 +1,5 @@
1
1
  //#region src/wire/version.d.ts
2
- declare const PROTOCOL_VERSION = "1";
3
- declare const VERSION_HEADER = "Statewire-Version";
4
2
  declare const WS_SUBPROTOCOL = "statewire.v1";
5
3
  //#endregion
6
- export { PROTOCOL_VERSION, VERSION_HEADER, WS_SUBPROTOCOL };
4
+ export { WS_SUBPROTOCOL };
7
5
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","names":[],"sources":["../../src/wire/version.ts"],"mappings":";cAAa;cACA;cACA"}
1
+ {"version":3,"file":"version.d.ts","names":[],"sources":["../../src/wire/version.ts"],"mappings":";cAAa"}
@@ -1,8 +1,6 @@
1
1
  //#region src/wire/version.ts
2
- const PROTOCOL_VERSION = "1";
3
- const VERSION_HEADER = "Statewire-Version";
4
2
  const WS_SUBPROTOCOL = "statewire.v1";
5
3
  //#endregion
6
- export { PROTOCOL_VERSION, VERSION_HEADER, WS_SUBPROTOCOL };
4
+ export { WS_SUBPROTOCOL };
7
5
 
8
6
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","names":["PROTOCOL_VERSION","VERSION_HEADER","WS_SUBPROTOCOL"],"sources":["../../src/wire/version.ts"],"sourcesContent":["export const PROTOCOL_VERSION = \"1\";\nexport const VERSION_HEADER = \"Statewire-Version\";\nexport const WS_SUBPROTOCOL = \"statewire.v1\";\n"],"mappings":";AAAA,MAAaA,mBAAmB;AAChC,MAAaC,iBAAiB;AAC9B,MAAaC,iBAAiB"}
1
+ {"version":3,"file":"version.js","names":["WS_SUBPROTOCOL"],"sources":["../../src/wire/version.ts"],"sourcesContent":["export const WS_SUBPROTOCOL = \"statewire.v1\";\n"],"mappings":";AAAA,MAAaA,iBAAiB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "statewire",
3
- "version": "0.15.0",
3
+ "version": "0.17.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": {
@@ -1,3 +1,9 @@
1
+ import {
2
+ negotiationHeaders,
3
+ validateSelection,
4
+ parseNegotiation,
5
+ } from "../wire/protocol";
6
+ import type { StatewireProtocol } from "../wire/protocol";
1
7
  import { useEffect, useRef, useState } from "react";
2
8
  import { flushTapSync } from "@assistant-ui/tap";
3
9
  import { reviveOps, reviveValue } from "../wire/meta";
@@ -39,6 +45,8 @@ export type StatewireCmd = {
39
45
  };
40
46
 
41
47
  export type StatewireSyn = {
48
+ version?: string;
49
+ protocols?: StatewireProtocol.Selection["protocols"];
42
50
  seq?: number;
43
51
  ctxSeq?: number;
44
52
  record?: "ephemeral";
@@ -95,6 +103,7 @@ const openGate = (g: Gate) => {
95
103
  export type AttemptOutcome = "stop" | "reattach" | "ended" | "idle";
96
104
 
97
105
  export type Conn = {
106
+ negotiation?: StatewireProtocol.Options;
98
107
  connection: StatewireClient.Connection;
99
108
  parked: boolean;
100
109
  lease: string | null;
@@ -283,7 +292,6 @@ const cmdsOf = (env: StatewireEnvelope): StatewireCmd[] => {
283
292
  const snapshotOf = (env: StatewireEnvelope): unknown[] => {
284
293
  const ops = env.ops;
285
294
  if (ops === undefined) return protocolError("hello missing snapshot");
286
- if (ops.length === 0) return protocolError("hello missing snapshot");
287
295
  return reviveOps(ops);
288
296
  };
289
297
 
@@ -298,6 +306,7 @@ const helloSynOf = (env: StatewireEnvelope): StatewireSyn => {
298
306
 
299
307
  const handleHello = (c: Conn, env: StatewireEnvelope) => {
300
308
  const syn = helloSynOf(env);
309
+ if (c.negotiation !== undefined) validateSelection(syn, c.negotiation);
301
310
  const ops = snapshotOf(env);
302
311
  c.ctx.onHello({
303
312
  ops,
@@ -323,7 +332,12 @@ const warnUnknownSynFact = () => {
323
332
  const deliverSyn = (c: Conn, value: unknown) => {
324
333
  if (!isObject(value)) return protocolError("syn must be an object");
325
334
  validateSynScalars(value);
326
- if (value["record"] !== undefined || value["idle"] !== undefined) {
335
+ if (
336
+ value["version"] !== undefined ||
337
+ value["protocols"] !== undefined ||
338
+ value["record"] !== undefined ||
339
+ value["idle"] !== undefined
340
+ ) {
327
341
  protocolError("invalid mid-stream syn");
328
342
  }
329
343
  if (
@@ -578,6 +592,12 @@ export const attachFrame = async (
578
592
  headers: StatewireClient.HeadersOption | undefined,
579
593
  ) => {
580
594
  const resolved = await resolveHeaders(headers, c.authStale);
595
+ for (const [name, value] of Object.entries(
596
+ negotiationHeaders(c.negotiation ?? {}),
597
+ )) {
598
+ if (!resolved.has(name)) resolved.set(name, value);
599
+ }
600
+ c.negotiation = parseNegotiation(resolved);
581
601
  c.authStale = false;
582
602
  const lanes = flushTapSync(() => c.ctx.takeAttachLanes());
583
603
  return {
@@ -589,15 +609,17 @@ export const attachFrame = async (
589
609
  };
590
610
  };
591
611
 
592
- export const useLink = (props: {
593
- url: string;
594
- isNew?: boolean | undefined;
595
- onChange?: ((connection: StatewireClient.Connection) => void) | undefined;
596
- onError?: ((error: StatewireClient.ConnectionError) => void) | undefined;
597
- ctx: StatewireRemoteProps;
598
- attemptOnce: (c: Conn, abort: AbortController) => Promise<AttemptOutcome>;
599
- send: (c: Conn, frame: StatewireFrame) => Promise<StatewireSubmitResult>;
600
- }): RemoteLink => {
612
+ export const useLink = (
613
+ props: StatewireProtocol.Options & {
614
+ url: string;
615
+ isNew?: boolean | undefined;
616
+ onChange?: ((connection: StatewireClient.Connection) => void) | undefined;
617
+ onError?: ((error: StatewireClient.ConnectionError) => void) | undefined;
618
+ ctx: StatewireRemoteProps;
619
+ attemptOnce: (c: Conn, abort: AbortController) => Promise<AttemptOutcome>;
620
+ send: (c: Conn, frame: StatewireFrame) => Promise<StatewireSubmitResult>;
621
+ },
622
+ ): RemoteLink => {
601
623
  const latest = useRef(props);
602
624
  latest.current = props;
603
625
  const [, setConnection] = useState<StatewireClient.Connection | null>(null);
@@ -622,7 +644,10 @@ export const useLink = (props: {
622
644
  },
623
645
  onError: (error) => latest.current.onError?.(error),
624
646
  onChange: (connection) => latest.current.onChange?.(connection),
625
- attemptOnce: (abort) => latest.current.attemptOnce(conn, abort),
647
+ attemptOnce: (abort) => {
648
+ conn.negotiation = latest.current;
649
+ return latest.current.attemptOnce(conn, abort);
650
+ },
626
651
  notify: setConnection,
627
652
  reconnect: () => reconnectConn(conn),
628
653
  connection: {
@@ -647,6 +672,11 @@ export const useLink = (props: {
647
672
  }),
648
673
  );
649
674
 
675
+ const negotiation = JSON.stringify([
676
+ props.protocol,
677
+ props.wireVersion,
678
+ props.applicationProtocols,
679
+ ]);
650
680
  useEffect(() => {
651
681
  c.lease = null;
652
682
  c.attempt = 0;
@@ -671,7 +701,7 @@ export const useLink = (props: {
671
701
  if (c.lifetime === lifetime) c.lifetime = null;
672
702
  openGate(c.gate);
673
703
  };
674
- }, [c, props.url]);
704
+ }, [c, props.url, negotiation]);
675
705
 
676
706
  return link;
677
707
  };
@@ -119,6 +119,11 @@ const recompute = (c: Core) => {
119
119
  for (const overlay of c.overlays) {
120
120
  if (overlay.dropped) continue;
121
121
  const { inv } = overlay;
122
+ if (
123
+ inv.protocol !== undefined &&
124
+ inv.protocol !== (c.getOptions().protocol ?? "default")
125
+ )
126
+ continue;
122
127
  const ctx: StatewireClient.OptimisticContext = {
123
128
  ts: inv.ts,
124
129
  seq: inv.seq,
@@ -235,6 +240,7 @@ const note = (
235
240
  ) => {
236
241
  if (!c.getOptions().onCommandChange) return;
237
242
  c.updates.push({
243
+ ...(inv.protocol !== undefined && { protocol: inv.protocol }),
238
244
  method: inv.method,
239
245
  params: inv.params as never[],
240
246
  seq: inv.seq,