@marimo-team/frontend 0.25.3-dev81 → 0.25.3-dev85

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 (101) hide show
  1. package/dist/assets/{CellStatus-DXroYLOP.js → CellStatus-DYRvLOiP.js} +1 -1
  2. package/dist/assets/{ErrorBoundary-DaQ9THH_.js → ErrorBoundary-BKQo68Df.js} +2 -2
  3. package/dist/assets/{JsonOutput-VxzHyItt.js → JsonOutput-3NheuQEt.js} +1 -1
  4. package/dist/assets/{MarimoErrorOutput-D-hLaDA-.js → MarimoErrorOutput-XrTcOYTI.js} +1 -1
  5. package/dist/assets/{RenderHTML-DjOEZL25.js → RenderHTML-DaVbCAsb.js} +1 -1
  6. package/dist/assets/{RunButton-CuhNTo5_.js → RunButton-D3ScLvwb.js} +1 -1
  7. package/dist/assets/{add-cell-with-ai-BbVntTh7.js → add-cell-with-ai-BKk0pvon.js} +1 -1
  8. package/dist/assets/{add-connection-dialog-Qf63aKV6.js → add-connection-dialog-CNIwGJVL.js} +1 -1
  9. package/dist/assets/{agent-panel-DMOfF3qF.js → agent-panel-BCCe7-0m.js} +1 -1
  10. package/dist/assets/{ai-model-dropdown-B15l0R62.js → ai-model-dropdown-prnCk5hq.js} +1 -1
  11. package/dist/assets/{app-config-button-BIMB8qjD.js → app-config-button-O_yI5nzB.js} +1 -1
  12. package/dist/assets/{blob-De9GR2s6.js → blob-FjxwDE1i.js} +1 -1
  13. package/dist/assets/{cell-editor-DFNLDjfX.js → cell-editor-B6Wvf232.js} +32 -11
  14. package/dist/assets/{cell-link-DR3W2HC7.js → cell-link-D8EbRFJJ.js} +1 -1
  15. package/dist/assets/{cells-styV7IX8.js → cells-DfF1DLV_.js} +1 -1
  16. package/dist/assets/{chat-display-BzdYdN39.js → chat-display-BtQI9LeB.js} +1 -1
  17. package/dist/assets/{chat-panel-De3YzkSF.js → chat-panel-tuUN0wet.js} +1 -1
  18. package/dist/assets/{chat-ui-NTNapdtP.js → chat-ui-Bb-HxAv4.js} +1 -1
  19. package/dist/assets/{code-visibility-csc3tgA0.js → code-visibility-ny5n3-GH.js} +1 -1
  20. package/dist/assets/{column-preview-DkH-oD8f.js → column-preview-BNPxoRnw.js} +1 -1
  21. package/dist/assets/{command-palette-OOQHBTnr.js → command-palette-CFLLUKbE.js} +1 -1
  22. package/dist/assets/{common-D7VAS6rd.js → common-D2oGA09G.js} +1 -1
  23. package/dist/assets/{components-CNmvC781.js → components-1zsrzJ_K.js} +1 -1
  24. package/dist/assets/config-BWKfkUWA.js +1 -0
  25. package/dist/assets/{config-BbRbT9Ww.js → config-CP8Y8_R0.js} +1 -1
  26. package/dist/assets/{datasource-C3yfeDa2.js → datasource-rV_-mqbc.js} +1 -1
  27. package/dist/assets/{dependency-graph-panel-CjoA6L0w.js → dependency-graph-panel-By-cv0Nw.js} +1 -1
  28. package/dist/assets/{documentation-panel-DHWFOiG1.js → documentation-panel-DHMjvOPt.js} +1 -1
  29. package/dist/assets/{download-BrCpRZKN.js → download-CMTzX3rG.js} +1 -1
  30. package/dist/assets/{edit-page-DafDcIAI.js → edit-page-Bh44QoBu.js} +4 -4
  31. package/dist/assets/{error-entries-BkEAmmnD.js → error-entries-BGCjRkVy.js} +1 -1
  32. package/dist/assets/{error-panel-BhTfPA_i.js → error-panel-BDTFHLEt.js} +1 -1
  33. package/dist/assets/{feedback-button-D9InK87G.js → feedback-button-C-ScLxY2.js} +1 -1
  34. package/dist/assets/feedback-button-CLRAP9DE.js +1 -0
  35. package/dist/assets/{file-explorer-panel-BSd_ZbUC.js → file-explorer-panel-CAq2LZFc.js} +1 -1
  36. package/dist/assets/{file-name-input-RHURglL_.js → file-name-input-DIvrcC82.js} +1 -1
  37. package/dist/assets/{floating-outline-Cm49rYYe.js → floating-outline-DJnZ45VA.js} +1 -1
  38. package/dist/assets/{gallery-page-DdMdOtdv.js → gallery-page-CHJD1mdU.js} +1 -1
  39. package/dist/assets/{glide-data-editor-eiSlEUyR.js → glide-data-editor-BsE9Em-a.js} +1 -1
  40. package/dist/assets/{home-page-DwWvBB0R.js → home-page-CCbdig5p.js} +1 -1
  41. package/dist/assets/{hooks-C4GjmtQ-.js → hooks-BPw6SVXo.js} +1 -1
  42. package/dist/assets/{index-jR_KRDMC.js → index-D2O1B6ZY.js} +7 -7
  43. package/dist/assets/{layout-Bq7wRzdR.js → layout-NkCTvSyj.js} +3 -3
  44. package/dist/assets/{logs-panel-CAAk72Gb.js → logs-panel-B34vxRV7.js} +1 -1
  45. package/dist/assets/{markdown-renderer-CAyCgogC.js → markdown-renderer-CkN_xAjW.js} +1 -1
  46. package/dist/assets/{name-cell-input-DA8hU9XT.js → name-cell-input-eyYneuGK.js} +1 -1
  47. package/dist/assets/{outline-panel-Cc5oQ4LL.js → outline-panel-rpjpMDYX.js} +1 -1
  48. package/dist/assets/{packages-panel-DTfKez6I.js → packages-panel-gXdFgCro.js} +1 -1
  49. package/dist/assets/{panel-accordion-state-BZHex38W.js → panel-accordion-state-CImIsdc8.js} +1 -1
  50. package/dist/assets/panels-CiR0Zzhg.js +105 -0
  51. package/dist/assets/{process-output-CrDc_O1D.js → process-output-BClx23Jv.js} +1 -1
  52. package/dist/assets/{readonly-code-display-BR-_jUj8.js → readonly-code-display-JC_YsLL_.js} +1 -1
  53. package/dist/assets/{readonly-python-code-D7jcy9ds.js → readonly-python-code-DeBED4OB.js} +1 -1
  54. package/dist/assets/{reveal-component-BWu6kkxZ.js → reveal-component-D2xhcDzw.js} +1 -1
  55. package/dist/assets/{run-page-BJq9MULx.js → run-page-B9Jb5LMt.js} +1 -1
  56. package/dist/assets/{runtime-details-state-hb7A1jgR.js → runtime-details-state-7JHj3efu.js} +1 -1
  57. package/dist/assets/{scratchpad-panel-OHQ7Z0RY.js → scratchpad-panel-B_xrdEvJ.js} +1 -1
  58. package/dist/assets/{session-panel-DmDjaPsN.js → session-panel-DwuCGJU1.js} +1 -1
  59. package/dist/assets/{snippets-panel-o9KL5jMi.js → snippets-panel-BNjtDbnR.js} +1 -1
  60. package/dist/assets/{state-BkAqJoW7.js → state-DUP4ToPd.js} +1 -1
  61. package/dist/assets/{state-Os7LSUUZ.js → state-DhaPGGBO.js} +1 -1
  62. package/dist/assets/{terminal-BI85khrS.js → terminal-BwLC9HS5.js} +1 -1
  63. package/dist/assets/{tracing-AsNGuDsh.js → tracing-Ch4LR62-.js} +1 -1
  64. package/dist/assets/{tracing-panel-CBcmea6L.js → tracing-panel-B8fX4PhX.js} +2 -2
  65. package/dist/assets/{tree-actions-Cx5EDpA6.js → tree-actions-Zg3Ycse4.js} +1 -1
  66. package/dist/assets/{useAddCell-uJJYtgwa.js → useAddCell-BX9lq_0J.js} +1 -1
  67. package/dist/assets/{useBoolean-8wlrTeus.js → useBoolean-WABMLM83.js} +1 -1
  68. package/dist/assets/{useCellActionButton-CmQ-OcPa.js → useCellActionButton-D7Ruyes-.js} +1 -1
  69. package/dist/assets/{useDeleteCell-CXgOCDqY.js → useDeleteCell-AbkQI6-c.js} +1 -1
  70. package/dist/assets/{useDependencyPanelTab-Ch6gm7uM.js → useDependencyPanelTab-B0b2KkvS.js} +1 -1
  71. package/dist/assets/{useInstallPackage-CYQoMdMQ.js → useInstallPackage-CWcPxSLP.js} +1 -1
  72. package/dist/assets/{useNotebookActions-CqXa3U6u.js → useNotebookActions-DNuGk3m2.js} +1 -1
  73. package/dist/assets/{useRunCells-DAfDmOMC.js → useRunCells-CV81rge-.js} +1 -1
  74. package/dist/assets/{useSplitCell-NLULgcq7.js → useSplitCell-BTwCB3JS.js} +1 -1
  75. package/dist/assets/{vega-component-BKJU3uaB.js → vega-component-CY6HmM06.js} +1 -1
  76. package/dist/index.html +24 -24
  77. package/package.json +1 -1
  78. package/src/core/host/AGENTS.md +6 -5
  79. package/src/core/host/connection.ts +1 -40
  80. package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
  81. package/src/core/runtime/__tests__/runtime.test.ts +13 -5
  82. package/src/core/runtime/runtime.ts +23 -27
  83. package/src/core/wasm/bridge.ts +0 -8
  84. package/src/core/websocket/__tests__/connection-state.test.ts +0 -12
  85. package/src/core/websocket/connection-state.ts +0 -10
  86. package/dist/assets/config-BQT0uYUo.js +0 -1
  87. package/dist/assets/feedback-button-BAcClv7c.js +0 -1
  88. package/dist/assets/panels-Cnu2Flfw.js +0 -105
  89. package/dist/assets/ws-BXWY541V.js +0 -22
  90. package/src/core/host/__tests__/events.test.ts +0 -49
  91. package/src/core/host/events.ts +0 -68
  92. package/src/core/websocket/__tests__/createConnectionTransport.test.ts +0 -49
  93. package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
  94. package/src/core/websocket/transports/__tests__/sse.test.ts +0 -352
  95. package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
  96. package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
  97. package/src/core/websocket/transports/basic.ts +0 -102
  98. package/src/core/websocket/transports/create.ts +0 -31
  99. package/src/core/websocket/transports/sse.ts +0 -320
  100. package/src/core/websocket/transports/transport.ts +0 -50
  101. package/src/core/websocket/transports/ws.ts +0 -98
@@ -1,308 +0,0 @@
1
- /* Copyright 2026 Marimo. All rights reserved. */
2
-
3
- import { describe, expect, it, vi } from "vitest";
4
- import { ConnectionSubscriptions } from "../transport";
5
-
6
- describe("ConnectionSubscriptions", () => {
7
- describe("addSubscription", () => {
8
- it("should add a subscription for an event", () => {
9
- const subscriptions = new ConnectionSubscriptions();
10
- const callback = vi.fn();
11
-
12
- subscriptions.addSubscription("open", callback);
13
-
14
- // Verify by notifying
15
- subscriptions.notify("open", new Event("open"));
16
- expect(callback).toHaveBeenCalledTimes(1);
17
- });
18
-
19
- it("should add multiple subscriptions for the same event", () => {
20
- const subscriptions = new ConnectionSubscriptions();
21
- const callback1 = vi.fn();
22
- const callback2 = vi.fn();
23
- const callback3 = vi.fn();
24
-
25
- subscriptions.addSubscription("message", callback1);
26
- subscriptions.addSubscription("message", callback2);
27
- subscriptions.addSubscription("message", callback3);
28
-
29
- const messageEvent = new MessageEvent("message", { data: "test" });
30
- subscriptions.notify("message", messageEvent);
31
-
32
- expect(callback1).toHaveBeenCalledWith(messageEvent);
33
- expect(callback2).toHaveBeenCalledWith(messageEvent);
34
- expect(callback3).toHaveBeenCalledWith(messageEvent);
35
- });
36
-
37
- it("should add subscriptions for different event types", () => {
38
- const subscriptions = new ConnectionSubscriptions();
39
- const openCallback = vi.fn();
40
- const closeCallback = vi.fn();
41
- const messageCallback = vi.fn();
42
-
43
- subscriptions.addSubscription("open", openCallback);
44
- subscriptions.addSubscription("close", closeCallback);
45
- subscriptions.addSubscription("message", messageCallback);
46
-
47
- subscriptions.notify("open", new Event("open"));
48
- expect(openCallback).toHaveBeenCalledTimes(1);
49
- expect(closeCallback).not.toHaveBeenCalled();
50
- expect(messageCallback).not.toHaveBeenCalled();
51
- });
52
-
53
- it("should not add duplicate subscriptions for the same callback", () => {
54
- const subscriptions = new ConnectionSubscriptions();
55
- const callback = vi.fn();
56
-
57
- subscriptions.addSubscription("close", callback);
58
- subscriptions.addSubscription("close", callback);
59
- subscriptions.addSubscription("close", callback);
60
-
61
- subscriptions.notify("close", new Event("close"));
62
-
63
- // Set only stores unique callbacks, so should be called once
64
- expect(callback).toHaveBeenCalledTimes(1);
65
- });
66
- });
67
-
68
- describe("removeSubscription", () => {
69
- it("should remove a subscription", () => {
70
- const subscriptions = new ConnectionSubscriptions();
71
- const callback = vi.fn();
72
-
73
- subscriptions.addSubscription("message", callback);
74
- subscriptions.removeSubscription("message", callback);
75
-
76
- const messageEvent = new MessageEvent("message", { data: "test" });
77
- subscriptions.notify("message", messageEvent);
78
-
79
- expect(callback).not.toHaveBeenCalled();
80
- });
81
-
82
- it("should only remove the specific callback", () => {
83
- const subscriptions = new ConnectionSubscriptions();
84
- const callback1 = vi.fn();
85
- const callback2 = vi.fn();
86
- const callback3 = vi.fn();
87
-
88
- subscriptions.addSubscription("close", callback1);
89
- subscriptions.addSubscription("close", callback2);
90
- subscriptions.addSubscription("close", callback3);
91
-
92
- subscriptions.removeSubscription("close", callback2);
93
-
94
- subscriptions.notify("close", new Event("close"));
95
-
96
- expect(callback1).toHaveBeenCalledTimes(1);
97
- expect(callback2).not.toHaveBeenCalled();
98
- expect(callback3).toHaveBeenCalledTimes(1);
99
- });
100
-
101
- it("should handle removing subscription from non-existent event type", () => {
102
- const subscriptions = new ConnectionSubscriptions();
103
- const callback = vi.fn();
104
-
105
- subscriptions.addSubscription("open", callback);
106
-
107
- expect(() =>
108
- subscriptions.removeSubscription("close", callback),
109
- ).not.toThrow();
110
-
111
- subscriptions.notify("open", new Event("open"));
112
- expect(callback).toHaveBeenCalledTimes(1);
113
- });
114
- });
115
-
116
- describe("notify", () => {
117
- it("should notify all subscribed callbacks for an event", () => {
118
- const subscriptions = new ConnectionSubscriptions();
119
- const callback1 = vi.fn();
120
- const callback2 = vi.fn();
121
-
122
- subscriptions.addSubscription("open", callback1);
123
- subscriptions.addSubscription("open", callback2);
124
-
125
- const openEvent = new Event("open");
126
- subscriptions.notify("open", openEvent);
127
-
128
- expect(callback1).toHaveBeenCalledWith(openEvent);
129
- expect(callback2).toHaveBeenCalledWith(openEvent);
130
- });
131
-
132
- it("should pass the correct data to callbacks", () => {
133
- const subscriptions = new ConnectionSubscriptions();
134
- const callback = vi.fn();
135
-
136
- subscriptions.addSubscription("message", callback);
137
-
138
- const messageEvent = new MessageEvent("message", {
139
- data: "test data",
140
- });
141
- subscriptions.notify("message", messageEvent);
142
-
143
- expect(callback).toHaveBeenCalledWith(messageEvent);
144
- expect(callback.mock.calls[0][0].data).toBe("test data");
145
- });
146
-
147
- it("should not notify callbacks for different event types", () => {
148
- const subscriptions = new ConnectionSubscriptions();
149
- const openCallback = vi.fn();
150
- const closeCallback = vi.fn();
151
-
152
- subscriptions.addSubscription("open", openCallback);
153
- subscriptions.addSubscription("close", closeCallback);
154
-
155
- subscriptions.notify("open", new Event("open"));
156
-
157
- expect(openCallback).toHaveBeenCalledTimes(1);
158
- expect(closeCallback).not.toHaveBeenCalled();
159
- });
160
-
161
- it("should handle exceptions in callbacks without affecting other callbacks", () => {
162
- const subscriptions = new ConnectionSubscriptions();
163
- const throwingCallback = vi.fn(() => {
164
- throw new Error("Callback error");
165
- });
166
- const normalCallback = vi.fn();
167
-
168
- subscriptions.addSubscription("message", throwingCallback);
169
- subscriptions.addSubscription("message", normalCallback);
170
-
171
- const messageEvent = new MessageEvent("message", { data: "test" });
172
-
173
- expect(() => subscriptions.notify("message", messageEvent)).toThrow(
174
- "Callback error",
175
- );
176
-
177
- expect(throwingCallback).toHaveBeenCalledWith(messageEvent);
178
- expect(normalCallback).not.toHaveBeenCalled();
179
- });
180
- });
181
-
182
- describe("Integration scenarios", () => {
183
- it("should handle complete subscription lifecycle", () => {
184
- const subscriptions = new ConnectionSubscriptions();
185
- const callback = vi.fn();
186
-
187
- subscriptions.addSubscription("open", callback);
188
-
189
- subscriptions.notify("open", new Event("open"));
190
- expect(callback).toHaveBeenCalledTimes(1);
191
-
192
- subscriptions.removeSubscription("open", callback);
193
-
194
- subscriptions.notify("open", new Event("open"));
195
- expect(callback).toHaveBeenCalledTimes(1);
196
- });
197
-
198
- it("should handle multiple events with multiple callbacks", () => {
199
- const subscriptions = new ConnectionSubscriptions();
200
-
201
- const openCallback1 = vi.fn();
202
- const openCallback2 = vi.fn();
203
- const closeCallback1 = vi.fn();
204
- const messageCallback = vi.fn();
205
-
206
- subscriptions.addSubscription("open", openCallback1);
207
- subscriptions.addSubscription("open", openCallback2);
208
- subscriptions.addSubscription("close", closeCallback1);
209
- subscriptions.addSubscription("message", messageCallback);
210
-
211
- subscriptions.notify("open", new Event("open"));
212
- expect(openCallback1).toHaveBeenCalledTimes(1);
213
- expect(openCallback2).toHaveBeenCalledTimes(1);
214
- expect(closeCallback1).not.toHaveBeenCalled();
215
- expect(messageCallback).not.toHaveBeenCalled();
216
-
217
- const messageEvent = new MessageEvent("message", { data: "test" });
218
- subscriptions.notify("message", messageEvent);
219
- expect(openCallback1).toHaveBeenCalledTimes(1);
220
- expect(openCallback2).toHaveBeenCalledTimes(1);
221
- expect(closeCallback1).not.toHaveBeenCalled();
222
- expect(messageCallback).toHaveBeenCalledTimes(1);
223
- expect(messageCallback).toHaveBeenCalledWith(messageEvent);
224
-
225
- subscriptions.notify("close", new Event("close"));
226
- expect(openCallback1).toHaveBeenCalledTimes(1);
227
- expect(openCallback2).toHaveBeenCalledTimes(1);
228
- expect(closeCallback1).toHaveBeenCalledTimes(1);
229
- expect(messageCallback).toHaveBeenCalledTimes(1);
230
- });
231
-
232
- it("should handle add and remove operations interleaved", () => {
233
- const subscriptions = new ConnectionSubscriptions();
234
- const callback1 = vi.fn();
235
- const callback2 = vi.fn();
236
- const callback3 = vi.fn();
237
-
238
- subscriptions.addSubscription("message", callback1);
239
- subscriptions.addSubscription("message", callback2);
240
-
241
- const msg1 = new MessageEvent("message", { data: "msg1" });
242
- subscriptions.notify("message", msg1);
243
-
244
- expect(callback1).toHaveBeenCalledWith(msg1);
245
- expect(callback2).toHaveBeenCalledWith(msg1);
246
- expect(callback3).not.toHaveBeenCalled();
247
-
248
- subscriptions.removeSubscription("message", callback1);
249
- subscriptions.addSubscription("message", callback3);
250
-
251
- const msg2 = new MessageEvent("message", { data: "msg2" });
252
- subscriptions.notify("message", msg2);
253
-
254
- expect(callback1).toHaveBeenCalledTimes(1);
255
- expect(callback2).toHaveBeenCalledWith(msg2);
256
- expect(callback3).toHaveBeenCalledWith(msg2);
257
- });
258
-
259
- it("should maintain separate subscription lists per event type", () => {
260
- const subscriptions = new ConnectionSubscriptions();
261
- const callback = vi.fn();
262
-
263
- subscriptions.addSubscription("open", callback);
264
- subscriptions.addSubscription("close", callback);
265
- subscriptions.addSubscription("message", callback);
266
-
267
- subscriptions.removeSubscription("close", callback);
268
-
269
- subscriptions.notify("open", new Event("open"));
270
- expect(callback).toHaveBeenCalledTimes(1);
271
-
272
- subscriptions.notify(
273
- "message",
274
- new MessageEvent("message", { data: "test" }),
275
- );
276
- expect(callback).toHaveBeenCalledTimes(2);
277
-
278
- subscriptions.notify("close", new Event("close"));
279
- expect(callback).toHaveBeenCalledTimes(2);
280
- });
281
- });
282
-
283
- describe("Edge cases", () => {
284
- it("should handle callback that modifies subscriptions", () => {
285
- const subscriptions = new ConnectionSubscriptions();
286
- const callback1 = vi.fn();
287
- const callback2 = vi.fn(() => {
288
- subscriptions.removeSubscription("message", callback1);
289
- });
290
-
291
- subscriptions.addSubscription("message", callback1);
292
- subscriptions.addSubscription("message", callback2);
293
-
294
- const messageEvent = new MessageEvent("message", { data: "test" });
295
- subscriptions.notify("message", messageEvent);
296
-
297
- expect(callback1).toHaveBeenCalledTimes(1);
298
- expect(callback2).toHaveBeenCalledTimes(1);
299
-
300
- callback1.mockClear();
301
- callback2.mockClear();
302
-
303
- subscriptions.notify("message", messageEvent);
304
- expect(callback1).not.toHaveBeenCalled();
305
- expect(callback2).toHaveBeenCalledTimes(1);
306
- });
307
- });
308
- });
@@ -1,125 +0,0 @@
1
- /* Copyright 2026 Marimo. All rights reserved. */
2
-
3
- import { beforeEach, describe, expect, it, vi } from "vitest";
4
- import { MAX_RETRIES, WsTransport, TRANSPORT_EXHAUSTED_REASON } from "../ws";
5
-
6
- let innerListeners: Record<string, ((e: unknown) => void)[]>;
7
-
8
- vi.mock("partysocket/ws", () => {
9
- class FakeReconnectingWebSocket {
10
- public retryCount = 0;
11
- public readyState = WebSocket.CONNECTING;
12
- public constructor() {
13
- innerListeners = { open: [], close: [], message: [], error: [] };
14
- }
15
- public addEventListener(event: string, cb: (e: unknown) => void) {
16
- innerListeners[event].push(cb);
17
- }
18
- public removeEventListener(event: string, cb: (e: unknown) => void) {
19
- innerListeners[event] = innerListeners[event].filter((c) => c !== cb);
20
- }
21
- public reconnect() {}
22
- public close() {}
23
- public send() {}
24
- }
25
- return { default: FakeReconnectingWebSocket };
26
- });
27
-
28
- interface FakeReconnectingWebSocket {
29
- retryCount: number;
30
- readyState: number;
31
- }
32
-
33
- function dispatchClose(reason = "") {
34
- const evt = new CloseEvent("close", { reason, code: 1006 });
35
- for (const cb of innerListeners.close) {
36
- cb(evt);
37
- }
38
- }
39
-
40
- describe("WsTransport", () => {
41
- let transport: WsTransport;
42
- let inner: FakeReconnectingWebSocket;
43
-
44
- beforeEach(() => {
45
- transport = new WsTransport(() => "ws://example.invalid/ws");
46
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
- inner = (transport as any).inner;
48
- });
49
-
50
- describe("close event reason", () => {
51
- it("forwards the original reason before retry budget is exhausted", () => {
52
- const seen: CloseEvent[] = [];
53
- transport.addEventListener("close", (e) => seen.push(e));
54
-
55
- inner.retryCount = MAX_RETRIES - 1;
56
- dispatchClose("");
57
-
58
- expect(seen).toHaveLength(1);
59
- expect(seen[0].reason).toBe("");
60
- });
61
-
62
- it("rewrites reason to MARIMO_TRANSPORT_EXHAUSTED on exhaustion", () => {
63
- const seen: CloseEvent[] = [];
64
- transport.addEventListener("close", (e) => seen.push(e));
65
-
66
- inner.retryCount = MAX_RETRIES;
67
- dispatchClose("");
68
-
69
- expect(seen).toHaveLength(1);
70
- expect(seen[0].reason).toBe(TRANSPORT_EXHAUSTED_REASON);
71
- });
72
-
73
- it("does not rewrite a server-sent reason on exhaustion", () => {
74
- // If partysocket happens to deliver a real MARIMO_* reason at the same
75
- // moment the retry budget exhausts, the exhausted-state rewrite still
76
- // wins because the wrapper keys off retryCount, not the original reason.
77
- // Document the behavior so a future change is deliberate.
78
- const seen: CloseEvent[] = [];
79
- transport.addEventListener("close", (e) => seen.push(e));
80
-
81
- inner.retryCount = MAX_RETRIES;
82
- dispatchClose("MARIMO_SHUTDOWN");
83
-
84
- expect(seen[0].reason).toBe(TRANSPORT_EXHAUSTED_REASON);
85
- });
86
- });
87
-
88
- describe("addEventListener dedupe", () => {
89
- it("does not double-fire when the same close listener is added twice", () => {
90
- const cb = vi.fn();
91
- transport.addEventListener("close", cb);
92
- transport.addEventListener("close", cb);
93
-
94
- dispatchClose("");
95
-
96
- expect(cb).toHaveBeenCalledTimes(1);
97
- });
98
-
99
- it("a single removeEventListener fully unregisters a duplicated add", () => {
100
- const cb = vi.fn();
101
- transport.addEventListener("close", cb);
102
- transport.addEventListener("close", cb);
103
- transport.removeEventListener("close", cb);
104
-
105
- dispatchClose("");
106
-
107
- expect(cb).not.toHaveBeenCalled();
108
- // Inner socket has no orphaned wrappers left.
109
- expect(innerListeners.close).toHaveLength(0);
110
- });
111
- });
112
-
113
- describe("removeEventListener", () => {
114
- it("unregisters the right wrapper for close listeners", () => {
115
- const cb = vi.fn();
116
- transport.addEventListener("close", cb);
117
- transport.removeEventListener("close", cb);
118
-
119
- inner.retryCount = MAX_RETRIES;
120
- dispatchClose("");
121
-
122
- expect(cb).not.toHaveBeenCalled();
123
- });
124
- });
125
- });
@@ -1,102 +0,0 @@
1
- /* Copyright 2026 Marimo. All rights reserved. */
2
-
3
- import {
4
- type ConnectionEvent,
5
- ConnectionSubscriptions,
6
- type ConnectionTransportCallback,
7
- type IConnectionTransport,
8
- } from "./transport";
9
-
10
- type MessageProducer = (callback: (message: MessageEvent) => void) => void;
11
-
12
- export class BasicTransport implements IConnectionTransport {
13
- private subscriptions = new ConnectionSubscriptions();
14
- private producer?: MessageProducer;
15
-
16
- public static withProducerCallback(
17
- producer: MessageProducer,
18
- ): IConnectionTransport {
19
- return new BasicTransport(producer);
20
- }
21
-
22
- public static empty(): IConnectionTransport {
23
- return new BasicTransport();
24
- }
25
-
26
- private constructor(producer?: MessageProducer) {
27
- this.producer = producer;
28
- }
29
-
30
- private startProducer() {
31
- if (this.producer) {
32
- this.producer((message) => {
33
- this.subscriptions.notify("message", message);
34
- });
35
- }
36
- }
37
-
38
- private connect(): Promise<void> {
39
- return new Promise<void>((resolve) => setTimeout(resolve, 0)).then(() => {
40
- this.subscriptions.notify("open", new Event("open"));
41
- });
42
- }
43
-
44
- public get readyState(): WebSocket["readyState"] {
45
- return WebSocket.OPEN;
46
- }
47
-
48
- public reconnect(
49
- _code?: number | undefined,
50
- _reason?: string | undefined,
51
- ): void {
52
- this.close();
53
- this.connect();
54
- return;
55
- }
56
-
57
- public close(): void {
58
- this.subscriptions.notify("close", new Event("close"));
59
- }
60
-
61
- public send(
62
- data: string | ArrayBuffer | Blob | ArrayBufferView,
63
- ): Promise<void> {
64
- this.subscriptions.notify(
65
- "message",
66
- new MessageEvent("message", {
67
- data,
68
- }),
69
- );
70
- return Promise.resolve();
71
- }
72
-
73
- public addEventListener<T extends ConnectionEvent>(
74
- event: T,
75
- callback: ConnectionTransportCallback<T>,
76
- ): void {
77
- this.subscriptions.addSubscription(
78
- event,
79
- callback as ConnectionTransportCallback<ConnectionEvent>,
80
- );
81
-
82
- // Call open right away
83
- if (event === "open") {
84
- (callback as ConnectionTransportCallback<"open">)(new Event("open"));
85
- }
86
-
87
- // Start the producer when we have one consumer
88
- if (event === "message") {
89
- this.startProducer();
90
- }
91
- }
92
-
93
- public removeEventListener<T extends ConnectionEvent>(
94
- event: T,
95
- callback: ConnectionTransportCallback<T>,
96
- ): void {
97
- this.subscriptions.removeSubscription(
98
- event,
99
- callback as ConnectionTransportCallback<ConnectionEvent>,
100
- );
101
- }
102
- }
@@ -1,31 +0,0 @@
1
- /* Copyright 2026 Marimo. All rights reserved. */
2
-
3
- import { createPyodideConnection } from "../../wasm/bridge";
4
- import { isWasm } from "../../wasm/utils";
5
- import { BasicTransport } from "./basic";
6
- import { SseTransport } from "./sse";
7
- import { WsTransport } from "./ws";
8
- import type { IConnectionTransport } from "./transport";
9
-
10
- export interface ConnectionTransportOptions {
11
- url: () => string;
12
- static: boolean;
13
- transportType: "websocket" | "sse";
14
- headers: () => Record<string, string>;
15
- }
16
-
17
- export function createConnectionTransport(
18
- options: ConnectionTransportOptions,
19
- ): IConnectionTransport {
20
- if (options.static) {
21
- return BasicTransport.empty();
22
- }
23
- if (isWasm()) {
24
- return createPyodideConnection();
25
- }
26
- // Read the URL lazily to follow runtime redirects.
27
- if (options.transportType === "sse") {
28
- return new SseTransport(options.url, options.headers);
29
- }
30
- return new WsTransport(options.url);
31
- }