statewire 0.16.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 (83) hide show
  1. package/README.md +12 -4
  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 +232 -50
  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 +3 -1
  31. package/dist/statewire.d.ts +33 -2
  32. package/dist/statewire.d.ts.map +1 -1
  33. package/dist/statewire.js +34 -3
  34. package/dist/statewire.js.map +1 -1
  35. package/dist/transports/fixtures.js +1 -0
  36. package/dist/transports/fixtures.js.map +1 -1
  37. package/dist/transports/remote.d.ts.map +1 -1
  38. package/dist/transports/remote.js +15 -8
  39. package/dist/transports/remote.js.map +1 -1
  40. package/dist/transports/sse.d.ts.map +1 -1
  41. package/dist/transports/sse.js +8 -8
  42. package/dist/transports/sse.js.map +1 -1
  43. package/dist/transports/testing.d.ts +1 -1
  44. package/dist/transports/testing.d.ts.map +1 -1
  45. package/dist/transports/testing.js +12 -4
  46. package/dist/transports/testing.js.map +1 -1
  47. package/dist/transports/ws.js +2 -2
  48. package/dist/transports/ws.js.map +1 -1
  49. package/dist/types.d.ts +7 -0
  50. package/dist/types.d.ts.map +1 -1
  51. package/dist/types.js.map +1 -1
  52. package/dist/wire/documents.d.ts +25 -2
  53. package/dist/wire/documents.d.ts.map +1 -1
  54. package/dist/wire/documents.js +27 -7
  55. package/dist/wire/documents.js.map +1 -1
  56. package/dist/wire/protocol.d.ts +55 -0
  57. package/dist/wire/protocol.d.ts.map +1 -0
  58. package/dist/wire/protocol.js +221 -0
  59. package/dist/wire/protocol.js.map +1 -0
  60. package/dist/wire/version.d.ts +1 -3
  61. package/dist/wire/version.d.ts.map +1 -1
  62. package/dist/wire/version.js +1 -3
  63. package/dist/wire/version.js.map +1 -1
  64. package/package.json +1 -1
  65. package/src/client/connection.ts +43 -13
  66. package/src/client/core.ts +6 -0
  67. package/src/client/session.ts +338 -39
  68. package/src/client/sessionStore.ts +8 -0
  69. package/src/host/host.ts +1 -0
  70. package/src/host/http.ts +36 -7
  71. package/src/host/socket.ts +141 -50
  72. package/src/host-internal.ts +12 -1
  73. package/src/index.ts +3 -0
  74. package/src/statewire.ts +109 -6
  75. package/src/transports/fixtures.ts +1 -1
  76. package/src/transports/remote.ts +16 -7
  77. package/src/transports/sse.ts +18 -8
  78. package/src/transports/testing.ts +10 -2
  79. package/src/transports/ws.ts +1 -1
  80. package/src/types.ts +11 -0
  81. package/src/wire/documents.ts +126 -8
  82. package/src/wire/protocol.ts +327 -0
  83. package/src/wire/version.ts +0 -2
@@ -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,