statewire 0.0.0-0 → 0.0.2

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 (154) hide show
  1. package/README.md +84 -1
  2. package/dist/client/changes.d.ts +7 -0
  3. package/dist/client/changes.d.ts.map +1 -0
  4. package/dist/client/changes.js +25 -0
  5. package/dist/client/changes.js.map +1 -0
  6. package/dist/client/connection.d.ts +63 -0
  7. package/dist/client/connection.d.ts.map +1 -0
  8. package/dist/client/connection.js +211 -0
  9. package/dist/client/connection.js.map +1 -0
  10. package/dist/client/core.d.ts +14 -0
  11. package/dist/client/core.d.ts.map +1 -0
  12. package/dist/client/core.js +272 -0
  13. package/dist/client/core.js.map +1 -0
  14. package/dist/client/session.d.ts +71 -0
  15. package/dist/client/session.d.ts.map +1 -0
  16. package/dist/client/session.js +529 -0
  17. package/dist/client/session.js.map +1 -0
  18. package/dist/client/sessionStore.d.ts +16 -0
  19. package/dist/client/sessionStore.d.ts.map +1 -0
  20. package/dist/client/sessionStore.js +14 -0
  21. package/dist/client/sessionStore.js.map +1 -0
  22. package/dist/client/timing.d.ts +11 -0
  23. package/dist/client/timing.d.ts.map +1 -0
  24. package/dist/client/timing.js +15 -0
  25. package/dist/client/timing.js.map +1 -0
  26. package/dist/host/adapter.d.ts +8 -0
  27. package/dist/host/adapter.d.ts.map +1 -0
  28. package/dist/host/adapter.js +23 -0
  29. package/dist/host/adapter.js.map +1 -0
  30. package/dist/host/draft.d.ts +28 -0
  31. package/dist/host/draft.d.ts.map +1 -0
  32. package/dist/host/draft.js +211 -0
  33. package/dist/host/draft.js.map +1 -0
  34. package/dist/host/engine.d.ts +14 -0
  35. package/dist/host/engine.d.ts.map +1 -0
  36. package/dist/host/engine.js +17 -0
  37. package/dist/host/engine.js.map +1 -0
  38. package/dist/host/host.d.ts +62 -0
  39. package/dist/host/host.d.ts.map +1 -0
  40. package/dist/host/host.js +331 -0
  41. package/dist/host/host.js.map +1 -0
  42. package/dist/host/socket.d.ts +14 -0
  43. package/dist/host/socket.d.ts.map +1 -0
  44. package/dist/host/socket.js +459 -0
  45. package/dist/host/socket.js.map +1 -0
  46. package/dist/host-internal.d.ts +27 -0
  47. package/dist/host-internal.d.ts.map +1 -0
  48. package/dist/host-internal.js +3 -0
  49. package/dist/host.d.ts +44 -0
  50. package/dist/host.d.ts.map +1 -0
  51. package/dist/host.js +4 -0
  52. package/dist/index.d.ts +5 -0
  53. package/dist/index.js +5 -0
  54. package/dist/statewire.d.ts +104 -0
  55. package/dist/statewire.d.ts.map +1 -0
  56. package/dist/statewire.js +53 -0
  57. package/dist/statewire.js.map +1 -0
  58. package/dist/transports/fake-sse.d.ts +12 -0
  59. package/dist/transports/fake-sse.d.ts.map +1 -0
  60. package/dist/transports/fake-sse.js +39 -0
  61. package/dist/transports/fake-sse.js.map +1 -0
  62. package/dist/transports/fixtures.d.ts +33 -0
  63. package/dist/transports/fixtures.d.ts.map +1 -0
  64. package/dist/transports/fixtures.js +50 -0
  65. package/dist/transports/fixtures.js.map +1 -0
  66. package/dist/transports/remote.d.ts +12 -0
  67. package/dist/transports/remote.d.ts.map +1 -0
  68. package/dist/transports/remote.js +21 -0
  69. package/dist/transports/remote.js.map +1 -0
  70. package/dist/transports/sse-parser.d.ts +5 -0
  71. package/dist/transports/sse-parser.d.ts.map +1 -0
  72. package/dist/transports/sse-parser.js +26 -0
  73. package/dist/transports/sse-parser.js.map +1 -0
  74. package/dist/transports/sse.d.ts +64 -0
  75. package/dist/transports/sse.d.ts.map +1 -0
  76. package/dist/transports/sse.js +137 -0
  77. package/dist/transports/sse.js.map +1 -0
  78. package/dist/transports/testing.d.ts +32 -0
  79. package/dist/transports/testing.d.ts.map +1 -0
  80. package/dist/transports/testing.js +94 -0
  81. package/dist/transports/testing.js.map +1 -0
  82. package/dist/transports/ws.d.ts +9 -0
  83. package/dist/transports/ws.d.ts.map +1 -0
  84. package/dist/transports/ws.js +169 -0
  85. package/dist/transports/ws.js.map +1 -0
  86. package/dist/types.d.ts +60 -0
  87. package/dist/types.d.ts.map +1 -0
  88. package/dist/types.js +24 -0
  89. package/dist/types.js.map +1 -0
  90. package/dist/util/draftTraps.d.ts +6 -0
  91. package/dist/util/draftTraps.d.ts.map +1 -0
  92. package/dist/util/draftTraps.js +25 -0
  93. package/dist/util/draftTraps.js.map +1 -0
  94. package/dist/util/macrotask.d.ts +5 -0
  95. package/dist/util/macrotask.d.ts.map +1 -0
  96. package/dist/util/macrotask.js +25 -0
  97. package/dist/util/macrotask.js.map +1 -0
  98. package/dist/util/methodProxy.d.ts +5 -0
  99. package/dist/util/methodProxy.d.ts.map +1 -0
  100. package/dist/util/methodProxy.js +17 -0
  101. package/dist/util/methodProxy.js.map +1 -0
  102. package/dist/util/randomId.d.ts +5 -0
  103. package/dist/util/randomId.d.ts.map +1 -0
  104. package/dist/util/randomId.js +6 -0
  105. package/dist/util/randomId.js.map +1 -0
  106. package/dist/util/recordMutations.d.ts +10 -0
  107. package/dist/util/recordMutations.d.ts.map +1 -0
  108. package/dist/util/recordMutations.js +109 -0
  109. package/dist/util/recordMutations.js.map +1 -0
  110. package/dist/util/statePath.d.ts +14 -0
  111. package/dist/util/statePath.d.ts.map +1 -0
  112. package/dist/util/statePath.js +31 -0
  113. package/dist/util/statePath.js.map +1 -0
  114. package/dist/wire/meta.d.ts +27 -0
  115. package/dist/wire/meta.d.ts.map +1 -0
  116. package/dist/wire/meta.js +75 -0
  117. package/dist/wire/meta.js.map +1 -0
  118. package/dist/wire/ops.d.ts +19 -0
  119. package/dist/wire/ops.d.ts.map +1 -0
  120. package/dist/wire/ops.js +112 -0
  121. package/dist/wire/ops.js.map +1 -0
  122. package/package.json +52 -5
  123. package/src/client/changes.ts +38 -0
  124. package/src/client/connection.ts +301 -0
  125. package/src/client/core.ts +312 -0
  126. package/src/client/session.ts +696 -0
  127. package/src/client/sessionStore.ts +23 -0
  128. package/src/client/timing.ts +14 -0
  129. package/src/host/adapter.ts +33 -0
  130. package/src/host/draft.ts +323 -0
  131. package/src/host/engine.ts +13 -0
  132. package/src/host/host.ts +482 -0
  133. package/src/host/socket.ts +542 -0
  134. package/src/host-internal.ts +28 -0
  135. package/src/host.ts +61 -0
  136. package/src/index.ts +5 -0
  137. package/src/statewire.ts +200 -0
  138. package/src/transports/fake-sse.ts +51 -0
  139. package/src/transports/fixtures.ts +60 -0
  140. package/src/transports/remote.ts +30 -0
  141. package/src/transports/sse-parser.ts +24 -0
  142. package/src/transports/sse.ts +267 -0
  143. package/src/transports/testing.ts +123 -0
  144. package/src/transports/ws.ts +256 -0
  145. package/src/types.ts +88 -0
  146. package/src/util/draftTraps.ts +32 -0
  147. package/src/util/macrotask.ts +25 -0
  148. package/src/util/methodProxy.ts +16 -0
  149. package/src/util/randomId.ts +4 -0
  150. package/src/util/recordMutations.ts +0 -0
  151. package/src/util/statePath.ts +61 -0
  152. package/src/wire/meta.ts +101 -0
  153. package/src/wire/ops.ts +202 -0
  154. package/index.js +0 -1
@@ -0,0 +1,104 @@
1
+ import { ConnectionState } from "./transports/sse.js";
2
+ import { StatewireSessionStoreOptions } from "./client/sessionStore.js";
3
+ import { Statewire } from "./types.js";
4
+ import { ResourceElement } from "@assistant-ui/tap";
5
+ //#region src/statewire.d.ts
6
+ declare const useStatewire: <TState, TCommands extends Statewire.Commands = Statewire.Commands, TEvent = any>(options: StatewireClient.Options<TState, TCommands, TEvent>) => StatewireClient.Helpers<TState, TCommands>;
7
+ declare const StatewireResource: <TState, TCommands extends Statewire.Commands = Statewire.Commands, TEvent = any>(options: StatewireClient.Options<TState, TCommands, TEvent>) => ResourceElement<StatewireClient.Helpers<TState, TCommands>>;
8
+ declare class StatewireClient<TState, TCommands extends Statewire.Commands = Statewire.Commands, TEvent = any> {
9
+ private readonly root;
10
+ private disposed;
11
+ constructor(options: StatewireClient.Options<TState, TCommands, TEvent>);
12
+ get state(): TState;
13
+ get connection(): StatewireClient.Connection;
14
+ get commands(): Statewire.CommandsProxy<TCommands>;
15
+ subscribe(listener: () => void): () => void;
16
+ dispose(): void;
17
+ }
18
+ declare namespace StatewireClient {
19
+ type Options<TState, TCommands extends Statewire.Commands = Statewire.Commands, TEvent = any> = {
20
+ transport: ResourceElement<Statewire<TState, TCommands, TEvent>>;
21
+ optimistic?: Optimistic<NonNullable<TState>, TCommands>;
22
+ onEvent?: (event: TEvent, ctx: {
23
+ state: NonNullable<TState>;
24
+ }) => void;
25
+ onStateChange?: (state: NonNullable<TState>, ctx: {
26
+ changes: Statewire.Changes;
27
+ }) => void;
28
+ onCommandChange?: (update: Statewire.CommandUpdate<TCommands>, ctx: {
29
+ state: NonNullable<TState>;
30
+ }) => void;
31
+ };
32
+ type Helpers<TState, TCommands extends Statewire.Commands = Statewire.Commands> = {
33
+ /** The statewire's state type — remote statewires include `undefined` until the first attach snapshot applies. */
34
+ state: TState;
35
+ /** Identity-stable across renders; reads the current canonical state without subscribing the caller. */
36
+ getState: () => TState;
37
+ connection: Connection;
38
+ commands: Statewire.CommandsProxy<TCommands>;
39
+ };
40
+ type Connection = ConnectionState & {
41
+ reconnect: () => void;
42
+ };
43
+ type ConnectionStatus = Connection["status"];
44
+ /**
45
+ * Mutate the draft in place; the client records the next state plus the exact
46
+ * changed paths. Re-run against the new base on every rebase.
47
+ */
48
+ type Optimistic<TState, TCommands extends Statewire.Commands = Statewire.Commands> = (draft: TState, command: Statewire.Command<TCommands>, ctx: OptimisticContext) => void;
49
+ type OptimisticContext = {
50
+ ts: number;
51
+ seq: number;
52
+ clientId?: string;
53
+ };
54
+ /**
55
+ * fate: "aborted" = never sent, "rejected" = the handler refused,
56
+ * "unknown-command" = no such command, "invalid-params" = the args did not
57
+ * match the command's schema, "lost" = sent but never covered by an ack,
58
+ * "crashed" = acked but the processing died, "result-unavailable" = acked
59
+ * but the result is gone.
60
+ * cause is which channel settled it: "server" = the server's answer;
61
+ * the rest are channel deaths the client settled locally.
62
+ */
63
+ type SendFailure = {
64
+ fate: "rejected";
65
+ cause: "server";
66
+ message: string;
67
+ payload?: unknown;
68
+ } | {
69
+ fate: "unknown-command";
70
+ cause: "server";
71
+ } | {
72
+ fate: "invalid-params";
73
+ cause: "server";
74
+ message: string;
75
+ } | {
76
+ fate: "crashed";
77
+ cause: "server";
78
+ } | {
79
+ fate: "result-unavailable";
80
+ cause: "server" | "unmount" | "stream-gone" | "session-lost" | "superseded";
81
+ } | {
82
+ fate: "aborted";
83
+ cause: "cascade" | "unmount" | "stream-gone" | "session-lost" | "superseded";
84
+ } | {
85
+ fate: "lost";
86
+ cause: "unmount" | "stream-gone" | "session-lost" | "superseded";
87
+ };
88
+ type SSEOptions = StatewireSessionStoreOptions & {
89
+ url: string;
90
+ headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
91
+ fetch?: typeof globalThis.fetch;
92
+ onChange?: (connection: Connection) => void;
93
+ };
94
+ type WSOptions = StatewireSessionStoreOptions & {
95
+ url: string;
96
+ protocols?: string | string[];
97
+ query?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
98
+ webSocket?: new (url: string, protocols?: string | string[]) => WebSocket;
99
+ onChange?: (connection: Connection) => void;
100
+ };
101
+ }
102
+ //#endregion
103
+ export { StatewireClient, StatewireResource, useStatewire };
104
+ //# sourceMappingURL=statewire.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statewire.d.ts","names":[],"sources":["../src/statewire.ts"],"mappings":";;;;;cAaa,eACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD,cAEA,SAAS,gBAAgB,QAAQ,QAAQ,WAAW,YACnD,gBAAgB,QAAQ,QAAQ;cA6BtB,oBACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD,cAEA,SAAS,gBAAgB,QAAQ,QAAQ,WAAW,YACnD,gBAAgB,gBAAgB,QAAQ,QAAQ;cAGtC,gBACX,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD;mBAEiB;UACT;EAEI,YAAA,SAAS,gBAAgB,QAAQ,QAAQ,WAAW;MAK5D,SAAS;MAIT,cAAc,gBAAgB;MAI9B,YAAY,UAAU,cAAc;EAIxC,UAAU;EAIV;;kBAOe;OACH,QACV,QACA,kBAAkB,UAAU,WAAW,UAAU,UACjD;IAEA,WAAW,gBAAgB,UAAU,QAAQ,WAAW;IACxD,aAAa,WAAW,YAAY,SAAS;IAC7C,WAAW,OAAO,QAAQ;MAAO,OAAO,YAAY;;IACpD,iBACE,OAAO,YAAY,SACnB;MAAO,SAAS,UAAU;;IAE5B,mBACE,QAAQ,UAAU,cAAc,YAChC;MAAO,OAAO,YAAY;;;OAIlB,QACV,QACA,kBAAkB,UAAU,WAAW,UAAU;;IAGjD,OAAO;;IAEP,gBAAgB;IAChB,YAAY;IACZ,UAAU,UAAU,cAAc;;OAGxB,aAAa;IAAoB;;OACjC,mBAAmB;;;;;OAMnB,WACV,QACA,kBAAkB,UAAU,WAAW,UAAU,aAEjD,OAAO,QACP,SAAS,UAAU,QAAQ,YAC3B,KAAK;OAGK;IACV;IACA;IACA;;;;;;;;;;;OAYU;IACN;IAAkB;IAAiB;IAAiB;;IACpD;IAAyB;;IACzB;IAAwB;IAAiB;;IACzC;IAAiB;;IAEjB;IACA;;IAQA;IACA;;IAQA;IACA;;OAGM,aAAa;IACvB;IACA,UAAU,qBAAqB,cAAc,QAAQ;IACrD,eAAe,WAAW;IAC1B,YAAY,YAAY;;OAGd,YAAY;IACtB;IACA;IACA,QACI,gCACO,yBAAyB,QAAQ;IAC5C,iBAAiB,aAAa,kCAAkC;IAChE,YAAY,YAAY"}
@@ -0,0 +1,53 @@
1
+ import { LIVE_CONNECTION, useStatewireCore } from "./client/core.js";
2
+ import { useMemo, useRef, useState, useSyncExternalStore } from "@assistant-ui/tap/react-shim";
3
+ import { createTapRoot, flushTapSync, resource, useResource } from "@assistant-ui/tap";
4
+ //#region src/statewire.ts
5
+ const useStatewire = (options) => {
6
+ const latest = useRef(options);
7
+ latest.current = options;
8
+ const value = useResource(options.transport);
9
+ const core = useStatewireCore(() => latest.current, value);
10
+ const [subscribe] = useState(() => (listener) => core.subscribe(() => flushTapSync(listener)));
11
+ const state = useSyncExternalStore(subscribe, core.getState, core.getState);
12
+ const connection = value.connection ?? LIVE_CONNECTION;
13
+ return useMemo(() => ({
14
+ state,
15
+ getState: core.getState,
16
+ connection,
17
+ commands: core.commands
18
+ }), [
19
+ state,
20
+ connection,
21
+ core
22
+ ]);
23
+ };
24
+ const StatewireResource = (options) => resource(useStatewire)(options);
25
+ var StatewireClient = class {
26
+ root;
27
+ disposed = false;
28
+ constructor(options) {
29
+ const ClientRoot = () => useStatewire(options);
30
+ this.root = createTapRoot(ClientRoot);
31
+ }
32
+ get state() {
33
+ return this.root.getValue().getState();
34
+ }
35
+ get connection() {
36
+ return this.root.getValue().connection;
37
+ }
38
+ get commands() {
39
+ return this.root.getValue().commands;
40
+ }
41
+ subscribe(listener) {
42
+ return this.root.subscribe(listener);
43
+ }
44
+ dispose() {
45
+ if (this.disposed) return;
46
+ this.disposed = true;
47
+ this.root.unmount();
48
+ }
49
+ };
50
+ //#endregion
51
+ export { StatewireClient, StatewireResource, useStatewire };
52
+
53
+ //# sourceMappingURL=statewire.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"statewire.js","names":["useMemo","useRef","useState","useSyncExternalStore","createTapRoot","flushTapSync","resource","useResource","ResourceElement","LIVE_CONNECTION","useStatewireCore","Statewire","ConnectionState","StatewireSessionStoreOptions","useStatewire","Commands","options","StatewireClient","Options","TState","TCommands","TEvent","Helpers","latest","current","value","transport","core","subscribe","listener","state","getState","connection","commands","CommandsProxy","StatewireResource","root","disposed","constructor","ClientRoot","getValue","Connection","dispose","unmount","optimistic","Optimistic","NonNullable","onEvent","event","ctx","onStateChange","changes","Changes","onCommandChange","update","CommandUpdate","reconnect","ConnectionStatus","draft","command","Command","OptimisticContext","ts","seq","clientId","SendFailure","fate","cause","message","payload","SSEOptions","url","headers","HeadersInit","Promise","fetch","globalThis","onChange","WSOptions","protocols","query","Record","webSocket","WebSocket"],"sources":["../src/statewire.ts"],"sourcesContent":["import { useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport {\n createTapRoot,\n flushTapSync,\n resource,\n useResource,\n} from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { LIVE_CONNECTION, useStatewireCore } from \"./client/core\";\nimport type { Statewire } from \"./types\";\nimport type { ConnectionState } from \"./transports/sse\";\nimport type { StatewireSessionStoreOptions } from \"./client/sessionStore\";\n\nexport const useStatewire = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n>(\n options: StatewireClient.Options<TState, TCommands, TEvent>,\n): StatewireClient.Helpers<TState, TCommands> => {\n const latest = useRef(options);\n latest.current = options;\n\n const value = useResource(options.transport) as Statewire<unknown>;\n const core = useStatewireCore(() => latest.current, value);\n const [subscribe] = useState(\n () => (listener: () => void) =>\n core.subscribe(() => flushTapSync(listener)),\n );\n\n const state = useSyncExternalStore(\n subscribe,\n core.getState,\n core.getState,\n ) as TState;\n const connection = value.connection ?? LIVE_CONNECTION;\n\n return useMemo(\n () => ({\n state,\n getState: core.getState as () => TState,\n connection,\n commands: core.commands as Statewire.CommandsProxy<TCommands>,\n }),\n [state, connection, core],\n );\n};\n\nexport const StatewireResource = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n>(\n options: StatewireClient.Options<TState, TCommands, TEvent>,\n): ResourceElement<StatewireClient.Helpers<TState, TCommands>> =>\n resource(useStatewire<TState, TCommands, TEvent>)(options);\n\nexport class StatewireClient<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n> {\n private readonly root;\n private disposed = false;\n\n constructor(options: StatewireClient.Options<TState, TCommands, TEvent>) {\n const ClientRoot = () => useStatewire<TState, TCommands, TEvent>(options);\n this.root = createTapRoot(ClientRoot);\n }\n\n get state(): TState {\n return this.root.getValue().getState();\n }\n\n get connection(): StatewireClient.Connection {\n return this.root.getValue().connection;\n }\n\n get commands(): Statewire.CommandsProxy<TCommands> {\n return this.root.getValue().commands;\n }\n\n subscribe(listener: () => void): () => void {\n return this.root.subscribe(listener);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.root.unmount();\n }\n}\n\nexport namespace StatewireClient {\n export type Options<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n TEvent = any,\n > = {\n transport: ResourceElement<Statewire<TState, TCommands, TEvent>>;\n optimistic?: Optimistic<NonNullable<TState>, TCommands>;\n onEvent?: (event: TEvent, ctx: { state: NonNullable<TState> }) => void;\n onStateChange?: (\n state: NonNullable<TState>,\n ctx: { changes: Statewire.Changes },\n ) => void;\n onCommandChange?: (\n update: Statewire.CommandUpdate<TCommands>,\n ctx: { state: NonNullable<TState> },\n ) => void;\n };\n\n export type Helpers<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = {\n /** The statewire's state type — remote statewires include `undefined` until the first attach snapshot applies. */\n state: TState;\n /** Identity-stable across renders; reads the current canonical state without subscribing the caller. */\n getState: () => TState;\n connection: Connection;\n commands: Statewire.CommandsProxy<TCommands>;\n };\n\n export type Connection = ConnectionState & { reconnect: () => void };\n export type ConnectionStatus = Connection[\"status\"];\n\n /**\n * Mutate the draft in place; the client records the next state plus the exact\n * changed paths. Re-run against the new base on every rebase.\n */\n export type Optimistic<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = (\n draft: TState,\n command: Statewire.Command<TCommands>,\n ctx: OptimisticContext,\n ) => void;\n\n export type OptimisticContext = {\n ts: number;\n seq: number;\n clientId?: string;\n };\n\n /**\n * fate: \"aborted\" = never sent, \"rejected\" = the handler refused,\n * \"unknown-command\" = no such command, \"invalid-params\" = the args did not\n * match the command's schema, \"lost\" = sent but never covered by an ack,\n * \"crashed\" = acked but the processing died, \"result-unavailable\" = acked\n * but the result is gone.\n * cause is which channel settled it: \"server\" = the server's answer;\n * the rest are channel deaths the client settled locally.\n */\n export type SendFailure =\n | { fate: \"rejected\"; cause: \"server\"; message: string; payload?: unknown }\n | { fate: \"unknown-command\"; cause: \"server\" }\n | { fate: \"invalid-params\"; cause: \"server\"; message: string }\n | { fate: \"crashed\"; cause: \"server\" }\n | {\n fate: \"result-unavailable\";\n cause:\n | \"server\"\n | \"unmount\"\n | \"stream-gone\"\n | \"session-lost\"\n | \"superseded\";\n }\n | {\n fate: \"aborted\";\n cause:\n | \"cascade\"\n | \"unmount\"\n | \"stream-gone\"\n | \"session-lost\"\n | \"superseded\";\n }\n | {\n fate: \"lost\";\n cause: \"unmount\" | \"stream-gone\" | \"session-lost\" | \"superseded\";\n };\n\n export type SSEOptions = StatewireSessionStoreOptions & {\n url: string;\n headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);\n fetch?: typeof globalThis.fetch;\n onChange?: (connection: Connection) => void;\n };\n\n export type WSOptions = StatewireSessionStoreOptions & {\n url: string;\n protocols?: string | string[];\n query?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n webSocket?: new (url: string, protocols?: string | string[]) => WebSocket;\n onChange?: (connection: Connection) => void;\n };\n}\n"],"mappings":";;;;AAaA,MAAac,gBAKXE,YAC+C;CAC/C,MAAMO,SAAStB,OAAOe,OAAO;CAC7BO,OAAOC,UAAUR;CAEjB,MAAMS,QAAQlB,YAAYS,QAAQU,SAAS;CAC3C,MAAMC,OAAOjB,uBAAuBa,OAAOC,SAASC,KAAK;CACzD,MAAM,CAACG,aAAa1B,gBACX2B,aACLF,KAAKC,gBAAgBvB,aAAawB,QAAQ,CAAC,CAC/C;CAEA,MAAMC,QAAQ3B,qBACZyB,WACAD,KAAKI,UACLJ,KAAKI,QACP;CACA,MAAMC,aAAaP,MAAMO,cAAcvB;CAEvC,OAAOT,eACE;EACL8B;EACAC,UAAUJ,KAAKI;EACfC;EACAC,UAAUN,KAAKM;CACjB,IACA;EAACH;EAAOE;EAAYL;CAAI,CAC1B;AACF;AAEA,MAAaQ,qBAKXnB,YAEAV,SAASQ,YAAuC,CAAC,CAACE,OAAO;AAE3D,IAAaC,kBAAb,MAIE;CACA;CACA,WAAmB;CAEnBqB,YAAYtB,SAA6D;EACvE,MAAMuB,mBAAmBzB,aAAwCE,OAAO;EACxE,KAAKoB,OAAOhC,cAAcmC,UAAU;CACtC;CAEA,IAAIT,QAAgB;EAClB,OAAO,KAAKM,KAAKI,SAAS,CAAC,CAACT,SAAS;CACvC;CAEA,IAAIC,aAAyC;EAC3C,OAAO,KAAKI,KAAKI,SAAS,CAAC,CAACR;CAC9B;CAEA,IAAIC,WAA+C;EACjD,OAAO,KAAKG,KAAKI,SAAS,CAAC,CAACP;CAC9B;CAEAL,UAAUC,UAAkC;EAC1C,OAAO,KAAKO,KAAKR,UAAUC,QAAQ;CACrC;CAEAa,UAAgB;EACd,IAAI,KAAKL,UAAU;EACnB,KAAKA,WAAW;EAChB,KAAKD,KAAKO,QAAQ;CACpB;AACF"}
@@ -0,0 +1,12 @@
1
+ import { StatewireEnvelope } from "../client/connection.js";
2
+ //#region src/transports/fake-sse.d.ts
3
+ type FakeStreamHandle = {
4
+ clientId: string;
5
+ send: (text: string) => void;
6
+ close: () => void;
7
+ };
8
+ declare const sseFrame: (envelope: StatewireEnvelope) => string;
9
+ declare const openSSEStream: (streams: Set<FakeStreamHandle>, clientId: string, onOpen: (handle: FakeStreamHandle) => void, signal?: AbortSignal) => Response;
10
+ //#endregion
11
+ export { FakeStreamHandle, openSSEStream, sseFrame };
12
+ //# sourceMappingURL=fake-sse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-sse.d.ts","names":[],"sources":["../../src/transports/fake-sse.ts"],"mappings":";;KAEY;EACV;EACA,OAAO;EACP;;cAGW,WAAY,UAAU;cAGtB,gBACX,SAAS,IAAI,mBACb,kBACA,SAAS,QAAQ,2BACjB,SAAS,gBACR"}
@@ -0,0 +1,39 @@
1
+ //#region src/transports/fake-sse.ts
2
+ const sseFrame = (envelope) => `data: ${JSON.stringify(envelope)}\n\n`;
3
+ const openSSEStream = (streams, clientId, onOpen, signal) => {
4
+ const encoder = new TextEncoder();
5
+ let handle;
6
+ const body = new ReadableStream({
7
+ start: (controller) => {
8
+ let open = true;
9
+ handle = {
10
+ clientId,
11
+ send: (text) => {
12
+ if (open) controller.enqueue(encoder.encode(text));
13
+ },
14
+ close: () => {
15
+ if (!open) return;
16
+ open = false;
17
+ streams.delete(handle);
18
+ controller.close();
19
+ }
20
+ };
21
+ streams.add(handle);
22
+ onOpen(handle);
23
+ },
24
+ cancel: () => {
25
+ streams.delete(handle);
26
+ }
27
+ });
28
+ signal?.addEventListener("abort", () => {
29
+ for (const stream of [...streams]) if (stream.clientId === clientId) stream.close();
30
+ });
31
+ return new Response(body, {
32
+ status: 200,
33
+ headers: { "content-type": "text/event-stream" }
34
+ });
35
+ };
36
+ //#endregion
37
+ export { openSSEStream, sseFrame };
38
+
39
+ //# sourceMappingURL=fake-sse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-sse.js","names":["StatewireEnvelope","FakeStreamHandle","clientId","send","text","close","sseFrame","envelope","JSON","stringify","openSSEStream","streams","Set","onOpen","handle","signal","AbortSignal","Response","encoder","TextEncoder","body","ReadableStream","Uint8Array","start","controller","open","enqueue","encode","delete","add","cancel","addEventListener","stream","status","headers"],"sources":["../../src/transports/fake-sse.ts"],"sourcesContent":["import type { StatewireEnvelope } from \"../client/connection\";\n\nexport type FakeStreamHandle = {\n clientId: string;\n send: (text: string) => void;\n close: () => void;\n};\n\nexport const sseFrame = (envelope: StatewireEnvelope) =>\n `data: ${JSON.stringify(envelope)}\\n\\n`;\n\nexport const openSSEStream = (\n streams: Set<FakeStreamHandle>,\n clientId: string,\n onOpen: (handle: FakeStreamHandle) => void,\n signal?: AbortSignal,\n): Response => {\n const encoder = new TextEncoder();\n let handle: FakeStreamHandle;\n const body = new ReadableStream<Uint8Array>({\n start: (controller) => {\n let open = true;\n handle = {\n clientId,\n send: (text) => {\n if (open) controller.enqueue(encoder.encode(text));\n },\n close: () => {\n if (!open) return;\n open = false;\n streams.delete(handle);\n controller.close();\n },\n };\n streams.add(handle);\n onOpen(handle);\n },\n cancel: () => {\n streams.delete(handle);\n },\n });\n signal?.addEventListener(\"abort\", () => {\n for (const stream of [...streams]) {\n if (stream.clientId === clientId) stream.close();\n }\n });\n return new Response(body, {\n status: 200,\n headers: { \"content-type\": \"text/event-stream\" },\n });\n};\n"],"mappings":";AAQA,MAAaM,YAAYC,aACvB,SAASC,KAAKC,UAAUF,QAAQ,EAAC;AAEnC,MAAaG,iBACXC,SACAT,UACAW,QACAE,WACa;CACb,MAAMG,UAAU,IAAIC,YAAY;CAChC,IAAIL;CACJ,MAAMM,OAAO,IAAIC,eAA2B;EAC1CE,QAAQC,eAAe;GACrB,IAAIC,OAAO;GACXX,SAAS;IACPZ;IACAC,OAAOC,SAAS;KACd,IAAIqB,MAAMD,WAAWE,QAAQR,QAAQS,OAAOvB,IAAI,CAAC;IACnD;IACAC,aAAa;KACX,IAAI,CAACoB,MAAM;KACXA,OAAO;KACPd,QAAQiB,OAAOd,MAAM;KACrBU,WAAWnB,MAAM;IACnB;GACF;GACAM,QAAQkB,IAAIf,MAAM;GAClBD,OAAOC,MAAM;EACf;EACAgB,cAAc;GACZnB,QAAQiB,OAAOd,MAAM;EACvB;CACF,CAAC;CACDC,QAAQgB,iBAAiB,eAAe;EACtC,KAAK,MAAMC,UAAU,CAAC,GAAGrB,OAAO,GAC9B,IAAIqB,OAAO9B,aAAaA,UAAU8B,OAAO3B,MAAM;CAEnD,CAAC;CACD,OAAO,IAAIY,SAASG,MAAM;EACxBa,QAAQ;EACRC,SAAS,EAAE,gBAAgB,oBAAoB;CACjD,CAAC;AACH"}
@@ -0,0 +1,33 @@
1
+ import { StatewireHost } from "../host.js";
2
+ import { Statewire } from "../types.js";
3
+ import "../index.js";
4
+ //#region src/transports/fixtures.d.ts
5
+ /** Let several rounds of queued microtasks and op batches drain. */
6
+ declare const settle: () => Promise<void>;
7
+ /** A minimal statewire host element: `{ count }` state with an `increment` command. */
8
+ declare const createCounterElement: () => import("@assistant-ui/tap").ResourceElement<{
9
+ state: {
10
+ count: number;
11
+ };
12
+ commands: Statewire.CommandsProxy<StatewireHost.HandledCommands<{
13
+ increment: () => void;
14
+ }>>;
15
+ emit: (event: unknown) => void;
16
+ }>;
17
+ /** Serialize a command frame as a client would send it over a socket. */
18
+ declare const commandFrame: (method: string, seq: number, params?: unknown[]) => string;
19
+ type SocketRecorder = {
20
+ sent: string[];
21
+ closes: {
22
+ code?: number;
23
+ reason?: string;
24
+ }[];
25
+ last: () => any;
26
+ send: (data: string) => void;
27
+ close: (code?: number, reason?: string) => void;
28
+ };
29
+ /** Record sent frames and close calls; `last()` parses the latest frame. */
30
+ declare const createSocketRecorder: () => SocketRecorder;
31
+ //#endregion
32
+ export { SocketRecorder, commandFrame, createCounterElement, createSocketRecorder, settle };
33
+ //# sourceMappingURL=fixtures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.d.ts","names":[],"sources":["../../src/transports/fixtures.ts"],"mappings":";;;;;cASa,cAAM;;cAKN,wDAAoB;;;;YAApB,UAAA,cAAA,cAAA;;;;;;cAaA,eACX,gBACA,aACA;KAGU;EACV;EACA;IAAU;IAAe;;EACzB;EACA,OAAO;EACP,QAAQ,eAAe;;;cAIZ,4BAA2B"}
@@ -0,0 +1,50 @@
1
+ import { useStatewireCommands, useStatewireEmit, useStatewireState } from "../host/host.js";
2
+ import "../host.js";
3
+ import { tick } from "./testing.js";
4
+ import { resource } from "@assistant-ui/tap";
5
+ //#region src/transports/fixtures.ts
6
+ /** Let several rounds of queued microtasks and op batches drain. */
7
+ const settle = async () => {
8
+ for (let i = 0; i < 3; i++) await tick();
9
+ };
10
+ /** A minimal statewire host element: `{ count }` state with an `increment` command. */
11
+ const createCounterElement = () => resource(() => {
12
+ const [state] = useStatewireState(() => ({ count: 0 }));
13
+ const emit = useStatewireEmit();
14
+ return {
15
+ state,
16
+ commands: useStatewireCommands({ increment: () => {
17
+ state.count += 1;
18
+ } }),
19
+ emit
20
+ };
21
+ })();
22
+ /** Serialize a command frame as a client would send it over a socket. */
23
+ const commandFrame = (method, seq, params = []) => JSON.stringify({
24
+ method,
25
+ params,
26
+ seq
27
+ });
28
+ /** Record sent frames and close calls; `last()` parses the latest frame. */
29
+ const createSocketRecorder = () => {
30
+ const sent = [];
31
+ const closes = [];
32
+ return {
33
+ sent,
34
+ closes,
35
+ last: () => JSON.parse(sent.at(-1)),
36
+ send: (data) => {
37
+ sent.push(data);
38
+ },
39
+ close: (code, reason) => {
40
+ closes.push({
41
+ ...code !== void 0 && { code },
42
+ ...reason !== void 0 && { reason }
43
+ });
44
+ }
45
+ };
46
+ };
47
+ //#endregion
48
+ export { commandFrame, createCounterElement, createSocketRecorder, settle };
49
+
50
+ //# sourceMappingURL=fixtures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.js","names":["resource","useStatewireCommands","useStatewireEmit","useStatewireState","tick","settle","i","createCounterElement","state","count","emit","commands","increment","commandFrame","method","seq","params","JSON","stringify","SocketRecorder","sent","closes","code","reason","last","send","data","close","createSocketRecorder","parse","at","push","undefined"],"sources":["../../src/transports/fixtures.ts"],"sourcesContent":["import { resource } from \"@assistant-ui/tap\";\nimport {\n useStatewireCommands,\n useStatewireEmit,\n useStatewireState,\n} from \"../host\";\nimport { tick } from \"./testing\";\n\n/** Let several rounds of queued microtasks and op batches drain. */\nexport const settle = async () => {\n for (let i = 0; i < 3; i++) await tick();\n};\n\n/** A minimal statewire host element: `{ count }` state with an `increment` command. */\nexport const createCounterElement = () =>\n resource(() => {\n const [state] = useStatewireState(() => ({ count: 0 }));\n const emit = useStatewireEmit();\n const commands = useStatewireCommands({\n increment: () => {\n state.count += 1;\n },\n });\n return { state, commands, emit };\n })();\n\n/** Serialize a command frame as a client would send it over a socket. */\nexport const commandFrame = (\n method: string,\n seq: number,\n params: unknown[] = [],\n) => JSON.stringify({ method, params, seq });\n\nexport type SocketRecorder = {\n sent: string[];\n closes: { code?: number; reason?: string }[];\n last: () => any;\n send: (data: string) => void;\n close: (code?: number, reason?: string) => void;\n};\n\n/** Record sent frames and close calls; `last()` parses the latest frame. */\nexport const createSocketRecorder = (): SocketRecorder => {\n const sent: string[] = [];\n const closes: { code?: number; reason?: string }[] = [];\n return {\n sent,\n closes,\n last: () => JSON.parse(sent.at(-1)!),\n send: (data) => {\n sent.push(data);\n },\n close: (code, reason) => {\n closes.push({\n ...(code !== undefined && { code }),\n ...(reason !== undefined && { reason }),\n });\n },\n };\n};\n"],"mappings":";;;;;;AASA,MAAaK,SAAS,YAAY;CAChC,KAAK,IAAIC,IAAI,GAAGA,IAAI,GAAGA,KAAK,MAAMF,KAAK;AACzC;;AAGA,MAAaG,6BACXP,eAAe;CACb,MAAM,CAACQ,SAASL,yBAAyB,EAAEM,OAAO,EAAE,EAAE;CACtD,MAAMC,OAAOR,iBAAiB;CAM9B,OAAO;EAAEM;EAAOG,UALCV,qBAAqB,EACpCW,iBAAiB;GACfJ,MAAMC,SAAS;EACjB,EACF,CACgBE;EAAUD;CAAK;AACjC,CAAC,CAAC,CAAC;;AAGL,MAAaG,gBACXC,QACAC,KACAC,SAAoB,CAAA,MACjBC,KAAKC,UAAU;CAAEJ;CAAQE;CAAQD;AAAI,CAAC;;AAW3C,MAAaa,6BAA6C;CACxD,MAAMR,OAAiB,CAAA;CACvB,MAAMC,SAA+C,CAAA;CACrD,OAAO;EACLD;EACAC;EACAG,YAAYP,KAAKY,MAAMT,KAAKU,GAAG,EAAE,CAAE;EACnCL,OAAOC,SAAS;GACdN,KAAKW,KAAKL,IAAI;EAChB;EACAC,QAAQL,MAAMC,WAAW;GACvBF,OAAOU,KAAK;IACV,GAAIT,SAASU,KAAAA,KAAa,EAAEV,KAAK;IACjC,GAAIC,WAAWS,KAAAA,KAAa,EAAET,OAAO;GACvC,CAAC;EACH;CACF;AACF"}
@@ -0,0 +1,12 @@
1
+ import { RemoteLink, StatewireRemoteProps } from "./sse.js";
2
+ import { StatewireSessionStoreOptions } from "../client/sessionStore.js";
3
+ import { StatewireClient } from "../statewire.js";
4
+ import { Statewire } from "../types.js";
5
+ //#region src/transports/remote.d.ts
6
+ declare const useStatewireRemote: <TOptions extends {
7
+ url: string;
8
+ onChange?: (connection: StatewireClient.Connection) => void;
9
+ } & StatewireSessionStoreOptions>(options: TOptions, useLink: (options: TOptions, ctx: StatewireRemoteProps) => RemoteLink) => Statewire<any, any, any>;
10
+ //#endregion
11
+ export { useStatewireRemote };
12
+ //# sourceMappingURL=remote.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote.d.ts","names":[],"sources":["../../src/transports/remote.ts"],"mappings":";;;;;cAQa,qBACX;EACE;EACA,YAAY,YAAY,gBAAgB;IACtC,8BAEJ,SAAS,UACT,UAAU,SAAS,UAAU,KAAK,yBAAyB,eAC1D"}
@@ -0,0 +1,21 @@
1
+ import { useStatewireEmit } from "../host/host.js";
2
+ import { setTransport, useStatewireSession } from "../client/session.js";
3
+ import { useEffect } from "@assistant-ui/tap/react-shim";
4
+ //#region src/transports/remote.ts
5
+ const useStatewireRemote = (options, useLink) => {
6
+ const emit = useStatewireEmit();
7
+ const s = useStatewireSession({ sessionStore: options.sessionStore }, emit);
8
+ const conn = useLink(options, s.ctx);
9
+ useEffect(() => setTransport(s, conn), [s, conn]);
10
+ return {
11
+ state: s.canonical,
12
+ commands: s.commands,
13
+ emit,
14
+ connection: conn.connection,
15
+ source: s.source
16
+ };
17
+ };
18
+ //#endregion
19
+ export { useStatewireRemote };
20
+
21
+ //# sourceMappingURL=remote.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote.js","names":["useEffect","useStatewireEmit","Statewire","setTransport","useStatewireSession","StatewireSessionStoreOptions","StatewireClient","RemoteLink","StatewireRemoteProps","useStatewireRemote","url","onChange","connection","Connection","options","TOptions","useLink","ctx","emit","s","sessionStore","conn","state","canonical","commands","source"],"sources":["../../src/transports/remote.ts"],"sourcesContent":["import { useEffect } from \"react\";\nimport { useStatewireEmit } from \"../host/host\";\nimport type { Statewire } from \"../types\";\nimport { setTransport, useStatewireSession } from \"../client/session\";\nimport type { StatewireSessionStoreOptions } from \"../client/sessionStore\";\nimport type { StatewireClient } from \"../statewire\";\nimport type { RemoteLink, StatewireRemoteProps } from \"./sse\";\n\nexport const useStatewireRemote = <\n TOptions extends {\n url: string;\n onChange?: (connection: StatewireClient.Connection) => void;\n } & StatewireSessionStoreOptions,\n>(\n options: TOptions,\n useLink: (options: TOptions, ctx: StatewireRemoteProps) => RemoteLink,\n): Statewire<any, any, any> => {\n const emit = useStatewireEmit<any>();\n const s = useStatewireSession({ sessionStore: options.sessionStore }, emit);\n const conn = useLink(options, s.ctx);\n useEffect(() => setTransport(s, conn), [s, conn]);\n\n return {\n state: s.canonical,\n commands: s.commands,\n emit,\n connection: conn.connection,\n source: s.source,\n };\n};\n"],"mappings":";;;;AAQA,MAAaS,sBAMXK,SACAE,YAC6B;CAC7B,MAAME,OAAOjB,iBAAsB;CACnC,MAAMkB,IAAIf,oBAAoB,EAAEgB,cAAcN,QAAQM,aAAa,GAAGF,IAAI;CAC1E,MAAMG,OAAOL,QAAQF,SAASK,EAAEF,GAAG;CACnCjB,gBAAgBG,aAAagB,GAAGE,IAAI,GAAG,CAACF,GAAGE,IAAI,CAAC;CAEhD,OAAO;EACLC,OAAOH,EAAEI;EACTC,UAAUL,EAAEK;EACZN;EACAN,YAAYS,KAAKT;EACjBa,QAAQN,EAAEM;CACZ;AACF"}
@@ -0,0 +1,5 @@
1
+ //#region src/transports/sse-parser.d.ts
2
+ declare const createSseParser: () => ((chunk: string) => string[]);
3
+ //#endregion
4
+ export { createSseParser };
5
+ //# sourceMappingURL=sse-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-parser.d.ts","names":[],"sources":["../../src/transports/sse-parser.ts"],"mappings":";cAAa,yBAAwB"}
@@ -0,0 +1,26 @@
1
+ //#region src/transports/sse-parser.ts
2
+ const createSseParser = () => {
3
+ let buffer = "";
4
+ let dataLines = [];
5
+ return (chunk) => {
6
+ const events = [];
7
+ buffer += chunk;
8
+ const held = buffer.endsWith("\r") ? "\r" : "";
9
+ const lines = buffer.slice(0, buffer.length - held.length).split(/\r\n|\r|\n/);
10
+ buffer = lines.pop() + held;
11
+ for (const line of lines) {
12
+ if (line === "") {
13
+ if (dataLines.length > 0) events.push(dataLines.join("\n"));
14
+ dataLines = [];
15
+ continue;
16
+ }
17
+ const match = /^data(?:: ?(.*))?$/.exec(line);
18
+ if (match) dataLines.push(match[1] ?? "");
19
+ }
20
+ return events;
21
+ };
22
+ };
23
+ //#endregion
24
+ export { createSseParser };
25
+
26
+ //# sourceMappingURL=sse-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-parser.js","names":["createSseParser","chunk","buffer","dataLines","events","held","endsWith","lines","slice","length","split","pop","line","push","join","match","exec"],"sources":["../../src/transports/sse-parser.ts"],"sourcesContent":["export const createSseParser = (): ((chunk: string) => string[]) => {\n let buffer = \"\";\n let dataLines: string[] = [];\n\n return (chunk: string) => {\n const events: string[] = [];\n buffer += chunk;\n const held = buffer.endsWith(\"\\r\") ? \"\\r\" : \"\";\n const lines = buffer\n .slice(0, buffer.length - held.length)\n .split(/\\r\\n|\\r|\\n/);\n buffer = lines.pop()! + held;\n for (const line of lines) {\n if (line === \"\") {\n if (dataLines.length > 0) events.push(dataLines.join(\"\\n\"));\n dataLines = [];\n continue;\n }\n const match = /^data(?:: ?(.*))?$/.exec(line);\n if (match) dataLines.push(match[1] ?? \"\");\n }\n return events;\n };\n};\n"],"mappings":";AAAA,MAAaA,wBAAuD;CAClE,IAAIE,SAAS;CACb,IAAIC,YAAsB,CAAA;CAE1B,QAAQF,UAAkB;EACxB,MAAMG,SAAmB,CAAA;EACzBF,UAAUD;EACV,MAAMI,OAAOH,OAAOI,SAAS,IAAI,IAAI,OAAO;EAC5C,MAAMC,QAAQL,OACXM,MAAM,GAAGN,OAAOO,SAASJ,KAAKI,MAAM,CAAC,CACrCC,MAAM,YAAY;EACrBR,SAASK,MAAMI,IAAI,IAAKN;EACxB,KAAK,MAAMO,QAAQL,OAAO;GACxB,IAAIK,SAAS,IAAI;IACf,IAAIT,UAAUM,SAAS,GAAGL,OAAOS,KAAKV,UAAUW,KAAK,IAAI,CAAC;IAC1DX,YAAY,CAAA;IACZ;GACF;GACA,MAAMY,QAAQ,qBAAqBC,KAAKJ,IAAI;GAC5C,IAAIG,OAAOZ,UAAUU,KAAKE,MAAM,MAAM,EAAE;EAC1C;EACA,OAAOX;CACT;AACF"}
@@ -0,0 +1,64 @@
1
+ import { StatewireHost } from "../host.js";
2
+ import { StatewireResponse } from "../client/connection.js";
3
+ import { StatewireClient } from "../statewire.js";
4
+ import { Statewire } from "../types.js";
5
+ import { ResourceElement } from "@assistant-ui/tap";
6
+ //#region src/transports/sse.d.ts
7
+ type StatewireRemoteProps = {
8
+ clientId: string;
9
+ onSnapshot: (args: {
10
+ ops: unknown[];
11
+ lastSeq: number;
12
+ }) => void;
13
+ onOps: (args: {
14
+ ops: unknown[];
15
+ res?: StatewireResponse[];
16
+ ack?: number;
17
+ }) => void;
18
+ onFinish: (args: StatewireHost.Fin) => void;
19
+ };
20
+ type StatewireWireCommand = {
21
+ method: string;
22
+ params: readonly unknown[];
23
+ };
24
+ type StatewireSubmitResult = {
25
+ type: "receipt";
26
+ response?: StatewireResponse;
27
+ } | {
28
+ type: "rejected";
29
+ message: string;
30
+ } | {
31
+ type: "seq-gap";
32
+ } | {
33
+ type: "unknown-client";
34
+ } | {
35
+ type: "stale-lease";
36
+ };
37
+ type ConnectionState = {
38
+ status: "connecting";
39
+ } | {
40
+ status: "live";
41
+ } | {
42
+ status: "reconnecting";
43
+ cause: "dropped" | "evicted" | "error";
44
+ attempt: number;
45
+ nextRetryAt: number;
46
+ lastError?: string;
47
+ message?: string;
48
+ } | {
49
+ status: "gone";
50
+ message?: string;
51
+ payload?: unknown;
52
+ };
53
+ /** The live wire a remote session drives: connection status plus command POST. */
54
+ type RemoteLink = {
55
+ connection: StatewireClient.Connection;
56
+ send: (args: {
57
+ seq: number;
58
+ command: StatewireWireCommand;
59
+ }) => Promise<StatewireSubmitResult>;
60
+ };
61
+ declare const StatewireSSE: <TState = any, TCommands extends Statewire.Commands = Statewire.Commands, TEvent = any>(options: StatewireClient.SSEOptions) => ResourceElement<Statewire<TState | undefined, TCommands, TEvent>>;
62
+ //#endregion
63
+ export { ConnectionState, RemoteLink, StatewireRemoteProps, StatewireSSE, StatewireSubmitResult, StatewireWireCommand };
64
+ //# sourceMappingURL=sse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse.d.ts","names":[],"sources":["../../src/transports/sse.ts"],"mappings":";;;;;;KAuBY;EACV;EACA,aAAa;IAAQ;IAAgB;;EACrC,QAAQ;IACN;IACA,MAAM;IACN;;EAEF,WAAW,MAAM,cAAc;;KAGrB;EACV;EACA;;KAGU;EACN;EAAiB,WAAW;;EAC5B;EAAkB;;EAClB;;EACA;;EACA;;KAEM;EACN;;EACA;;EAEA;EACA;EACA;EACA;EACA;EACA;;EAEA;EAAgB;EAAkB;;;KAG5B;EACV,YAAY,gBAAgB;EAC5B,OAAO;IACL;IACA,SAAS;QACL,QAAQ;;cAmMH,eACX,cACA,kBAAkB,UAAU,WAAW,UAAU,UACjD,cAEA,SAAS,gBAAgB,eACtB,gBAAgB,UAAU,oBAAoB,WAAW"}
@@ -0,0 +1,137 @@
1
+ import { encodeParams, reviveResponse } from "../wire/meta.js";
2
+ import { createSseParser } from "./sse-parser.js";
3
+ import { timing } from "../client/timing.js";
4
+ import { classify, handleEnvelope, overloadedError, parseRetryAfter, useStatewireConn } from "../client/connection.js";
5
+ import { useStatewireRemote } from "./remote.js";
6
+ import { useRef, useState } from "@assistant-ui/tap/react-shim";
7
+ import { flushTapSync, resource } from "@assistant-ui/tap";
8
+ //#region src/transports/sse.ts
9
+ const baseUrl = (options) => options.url.replace(/\/$/, "");
10
+ const requestHeaders = async (options, extra) => {
11
+ const { headers } = options;
12
+ const merged = new Headers(typeof headers === "function" ? await headers() : headers);
13
+ for (const [name, value] of Object.entries(extra)) merged.set(name, value);
14
+ return merged;
15
+ };
16
+ const timedFetch = async (options, input, init, timeout) => {
17
+ try {
18
+ return await (options.fetch ?? globalThis.fetch)(input, init);
19
+ } catch {
20
+ throw classify(timeout);
21
+ }
22
+ };
23
+ const consume = async (c, reader, heartbeat, dead) => {
24
+ const decoder = new TextDecoder();
25
+ const parse = createSseParser();
26
+ for (;;) {
27
+ const timer = setTimeout(() => heartbeat.abort(), timing.heartbeatTimeoutMs);
28
+ let result;
29
+ try {
30
+ result = await reader.read();
31
+ } catch {
32
+ throw classify(heartbeat.signal);
33
+ } finally {
34
+ clearTimeout(timer);
35
+ }
36
+ if (result.done) return "ended";
37
+ for (const data of parse(decoder.decode(result.value, { stream: true }))) {
38
+ if (dead()) return "ended";
39
+ const env = JSON.parse(data);
40
+ const done = flushTapSync(() => handleEnvelope(c, env));
41
+ if (done) return done;
42
+ }
43
+ }
44
+ };
45
+ const attemptStream = async (c, options, abort) => {
46
+ const headers = await requestHeaders(options, {
47
+ "Statewire-Client-Id": c.ctx.clientId,
48
+ accept: "text/event-stream"
49
+ });
50
+ const heartbeat = new AbortController();
51
+ const headerTimer = setTimeout(() => heartbeat.abort(), timing.heartbeatTimeoutMs);
52
+ let res;
53
+ try {
54
+ res = await timedFetch(options, `${baseUrl(options)}/stream`, {
55
+ headers,
56
+ cache: "no-store",
57
+ signal: AbortSignal.any([abort.signal, heartbeat.signal])
58
+ }, heartbeat.signal);
59
+ } finally {
60
+ clearTimeout(headerTimer);
61
+ }
62
+ if (res.status === 429 || res.status === 503) throw overloaded(res);
63
+ if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);
64
+ const reader = res.body.getReader();
65
+ try {
66
+ return await consume(c, reader, heartbeat, () => abort.signal.aborted);
67
+ } finally {
68
+ reader.cancel().catch(() => {});
69
+ }
70
+ };
71
+ const overloaded = (res) => overloadedError(parseRetryAfter(res.headers.get("Retry-After")));
72
+ const sendCommand = async (c, options, { seq, command }) => {
73
+ const timeout = AbortSignal.timeout(timing.heartbeatTimeoutMs);
74
+ const res = await timedFetch(options, `${baseUrl(options)}/commands`, {
75
+ method: "POST",
76
+ headers: await requestHeaders(options, {
77
+ "content-type": "application/json",
78
+ "Statewire-Client-Id": c.ctx.clientId,
79
+ "Statewire-Command-Seq": String(seq),
80
+ ...c.lease !== null && { "Statewire-Lease": c.lease }
81
+ }),
82
+ body: JSON.stringify({
83
+ method: command.method,
84
+ ...encodeParams(command.params)
85
+ }),
86
+ signal: AbortSignal.any([c.lifetime?.signal ?? AbortSignal.abort(), timeout])
87
+ }, timeout);
88
+ if (res.ok) {
89
+ let body;
90
+ try {
91
+ body = await res.json();
92
+ } catch {
93
+ return {
94
+ type: "rejected",
95
+ message: "statewire: protocol error: malformed /commands receipt body"
96
+ };
97
+ }
98
+ return body?.res !== void 0 ? {
99
+ type: "receipt",
100
+ response: reviveResponse(body.res)
101
+ } : { type: "receipt" };
102
+ }
103
+ if (res.status === 409 || res.status === 412 || res.status === 423) {
104
+ const verdict = res.status === 409 ? "seq-gap" : res.status === 412 ? "unknown-client" : "stale-lease";
105
+ if ((await res.json().catch(() => null))?.error === verdict) return { type: verdict };
106
+ throw new Error(`HTTP ${res.status}`);
107
+ }
108
+ if (res.status === 429 || res.status === 503) throw overloaded(res);
109
+ if (res.status >= 400 && res.status < 500) return {
110
+ type: "rejected",
111
+ message: `HTTP ${res.status}`
112
+ };
113
+ throw new Error(`HTTP ${res.status}`);
114
+ };
115
+ const useSseLink = (options, ctx) => {
116
+ const latest = useRef(options);
117
+ latest.current = options;
118
+ const c_0 = useStatewireConn({
119
+ url: options.url,
120
+ onChange: options.onChange,
121
+ ctx,
122
+ attemptOnce: (c, abort) => attemptStream(c, options, abort)
123
+ });
124
+ const [link] = useState(() => ({
125
+ get connection() {
126
+ return c_0.connection;
127
+ },
128
+ send: (args) => sendCommand(c_0, latest.current, args)
129
+ }));
130
+ return link;
131
+ };
132
+ const useStatewireSSE = (options) => useStatewireRemote(options, useSseLink);
133
+ const StatewireSSE = resource(useStatewireSSE);
134
+ //#endregion
135
+ export { StatewireSSE };
136
+
137
+ //# sourceMappingURL=sse.js.map