@marimo-team/frontend 0.25.2 → 0.25.3-dev12
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.
- package/dist/assets/{CellStatus-BP2mavAq.js → CellStatus-DVOHpdeJ.js} +1 -1
- package/dist/assets/{ConnectedDataExplorerComponent-t8fH_TNc.js → ConnectedDataExplorerComponent-TprnxZeJ.js} +1 -1
- package/dist/assets/{ErrorBoundary-Gl5nCA8C.js → ErrorBoundary-CM13wrd8.js} +2 -2
- package/dist/assets/JsonOutput-Cx1iyibB.js +11 -0
- package/dist/assets/{LazyAnyLanguageCodeMirror-CKcCbG2G.js → LazyAnyLanguageCodeMirror-B4fCknq1.js} +2 -2
- package/dist/assets/MarimoErrorOutput-s_5GOX8k.js +10 -0
- package/dist/assets/{RenderHTML-BbOqmHy3.js → RenderHTML-DX6i99Op.js} +1 -1
- package/dist/assets/{RunButton-CK3LhnP5.js → RunButton-BKoh0zYG.js} +1 -1
- package/dist/assets/{add-cell-with-ai-8UBC0xwR.js → add-cell-with-ai-BpD_790A.js} +11 -11
- package/dist/assets/{add-connection-dialog-DXQnd6eB.js → add-connection-dialog-B1BXCjex.js} +2 -2
- package/dist/assets/{agent-panel-D7kMZLG-.js → agent-panel-CDFUc1uu.js} +5 -5
- package/dist/assets/ai-model-dropdown-i1vfGeVi.js +13 -0
- package/dist/assets/{any-language-editor-BLCAD3yx.js → any-language-editor-BdTWdMCI.js} +1 -1
- package/dist/assets/{app-config-button-BKEG4x9u.js → app-config-button-AdV-QDD7.js} +1 -1
- package/dist/assets/{blob-D5nfNHOo.js → blob-BG8yoQmx.js} +1 -1
- package/dist/assets/{cell-editor-ADVfycpN.js → cell-editor-BKJ06H5F.js} +9 -9
- package/dist/assets/cell-link-8Auigi8P.js +1 -0
- package/dist/assets/{cells-BRkCaSsh.js → cells-C6YGYGdi.js} +56 -56
- package/dist/assets/{chat-display-Dnagy4Nv.js → chat-display-DhgI2MS9.js} +1 -1
- package/dist/assets/{chat-panel-DQ6HkWEF.js → chat-panel-Cau71Ygr.js} +2 -2
- package/dist/assets/{chat-ui-Df8Vcj6n.js → chat-ui-C5ql1Up-.js} +1 -1
- package/dist/assets/code-visibility-BJImdpDi.js +1 -0
- package/dist/assets/coerce-DWjFeXOY.js +1 -0
- package/dist/assets/{column-preview-Cm2D6vyX.js → column-preview-BZ_iNhtJ.js} +1 -1
- package/dist/assets/{command-palette-DTSQ7gOC.js → command-palette-DDC3kBet.js} +1 -1
- package/dist/assets/{common-Cu6CDbuw.js → common-Sl3uthjg.js} +1 -1
- package/dist/assets/{components-CGw16R_N.js → components-3cou6Mka.js} +1 -1
- package/dist/assets/{components-CP_UoFtT.js → components-CskjK6xZ.js} +1 -1
- package/dist/assets/{components-CXfO-F47.js → components-YBmtV0p6.js} +1 -1
- package/dist/assets/{config-VPSq73n8.js → config-CA1V-wjh.js} +1 -1
- package/dist/assets/config-DRIpOKqh.js +1 -0
- package/dist/assets/config-Dy28fDwn.js +1 -0
- package/dist/assets/{datasource-Cq6x_5ML.js → datasource-DfbNb83e.js} +1 -1
- package/dist/assets/{dates-BbFQ2oE4.js → dates-CfmdTL6B.js} +1 -1
- package/dist/assets/{dependency-graph-panel--Kgtktcw.js → dependency-graph-panel-L5cBE5sl.js} +1 -1
- package/dist/assets/{documentation-panel-BHmbeznw.js → documentation-panel-D7S8JIc2.js} +1 -1
- package/dist/assets/{download-DCJXy66H.js → download-C03FOElq.js} +1 -1
- package/dist/assets/edit-page-D_Rg0gQO.js +9 -0
- package/dist/assets/{error-banner-Cd69vvyX.js → error-banner-COZ7SfYV.js} +1 -1
- package/dist/assets/{error-entries-Dk3VZ-OK.js → error-entries-CLnPDv7b.js} +1 -1
- package/dist/assets/{error-panel-BPRYmC5J.js → error-panel-D2xrYLue.js} +1 -1
- package/dist/assets/{errors-DCrsa1nH.js → errors-CNsgAw6m.js} +1 -1
- package/dist/assets/{feedback-button-Bik2-e33.js → feedback-button-DzPunIqU.js} +2 -2
- package/dist/assets/feedback-button-Kla7i5RT.js +1 -0
- package/dist/assets/{file-explorer-panel-BEEK5lcl.js → file-explorer-panel-Uk4_QIJQ.js} +1 -1
- package/dist/assets/{file-name-input-Cdc-TVqO.js → file-name-input-B4fRaQkI.js} +1 -1
- package/dist/assets/{floating-outline-XD9FAV-f.js → floating-outline-B5MaM1JS.js} +1 -1
- package/dist/assets/{gallery-page-Dm6mGwBi.js → gallery-page-BefvdsM7.js} +1 -1
- package/dist/assets/{glide-data-editor-J9CAb70d.js → glide-data-editor-C3eaYlXg.js} +1 -1
- package/dist/assets/{home-page-DMHnsRxm.js → home-page-hmvKlc1w.js} +1 -1
- package/dist/assets/{hooks-93IwHd2u.js → hooks-DzML9nez.js} +1 -1
- package/dist/assets/index-6b4mf8Ai.css +2 -0
- package/dist/assets/index-ptK1gxmo.js +30 -0
- package/dist/assets/{layout-DmcNEu3P.js → layout-Ck1RoL1B.js} +5 -5
- package/dist/assets/{logs-panel-ABM16htT.js → logs-panel-BRytaWaO.js} +1 -1
- package/dist/assets/{markdown-renderer-BG3sp2rs.js → markdown-renderer-DX0Xrrfy.js} +1 -1
- package/dist/assets/{mermaid-a3xYymTy.js → mermaid-C4EiVqcd.js} +1 -1
- package/dist/assets/{name-cell-input-BgEBmqyL.js → name-cell-input-DlpTvPLa.js} +1 -1
- package/dist/assets/{outline-panel-DCXQ3_F6.js → outline-panel-B2WIpz-1.js} +1 -1
- package/dist/assets/{packages-panel-BMGOmZkR.js → packages-panel-CI5ff9b-.js} +1 -1
- package/dist/assets/{panel-accordion-state-C1sdPp0m.js → panel-accordion-state-BqSH5ttk.js} +1 -1
- package/dist/assets/panels-BCcZ_yLK.js +107 -0
- package/dist/assets/{process-output-D0KGq04R.js → process-output-f3C3PD0Z.js} +1 -1
- package/dist/assets/{readonly-code-display-Do50PHI8.js → readonly-code-display-MnMEoYW5.js} +1 -1
- package/dist/assets/{readonly-python-code-DCSMIsTX.js → readonly-python-code-ggs4hAzC.js} +1 -1
- package/dist/assets/{renderShortcut-BkVJQ_Zh.js → renderShortcut-C1JwYcqW.js} +1 -1
- package/dist/assets/{reveal-component-ltaepAqw.js → reveal-component-PiV1eQk9.js} +1 -1
- package/dist/assets/run-page-AfZhtEh1.js +1 -0
- package/dist/assets/{runtime-details-state-CSPobEdR.js → runtime-details-state-CoL-p0MC.js} +1 -1
- package/dist/assets/{schemas-Dv42wLFq.js → schemas-ChRqMOTL.js} +1 -1
- package/dist/assets/{scratchpad-panel-Dwd6W5PE.js → scratchpad-panel-D2rWg4k8.js} +1 -1
- package/dist/assets/{secrets-panel-Bctbtkrp.js → secrets-panel-D7KN4tVs.js} +1 -1
- package/dist/assets/session-panel-CnANhcTo.js +1 -0
- package/dist/assets/{snippets-panel-Bh1oTw4m.js → snippets-panel-ClGHtEKQ.js} +1 -1
- package/dist/assets/state-CbjBtkdd.js +3 -0
- package/dist/assets/{state-XsPO8hNi.js → state-DtGB7xeJ.js} +1 -1
- package/dist/assets/{terminal-Ck_vK-34.js → terminal-wnSHYT8N.js} +1 -1
- package/dist/assets/{tracing-Di6pUmX8.js → tracing-Dz2rYOFL.js} +1 -1
- package/dist/assets/{tracing-panel-PeRXHD8_.js → tracing-panel-DqQY9V91.js} +2 -2
- package/dist/assets/{tree-actions-GM5E_bUs.js → tree-actions-DnUokmva.js} +1 -1
- package/dist/assets/{useAddCell-GR7eEQrT.js → useAddCell-IN24thff.js} +1 -1
- package/dist/assets/{useBoolean-BDD6_xQQ.js → useBoolean-d0Tty94N.js} +1 -1
- package/dist/assets/{useCellActionButton-B-9eG5z_.js → useCellActionButton-DmaFZ4oF.js} +1 -1
- package/dist/assets/{useDeleteCell-2_bMClHd.js → useDeleteCell-fQlM46Y2.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-k3C3M18S.js → useDependencyPanelTab-ImXYINm_.js} +1 -1
- package/dist/assets/{useInstallPackage-iX1eufdl.js → useInstallPackage-BW06eter.js} +1 -1
- package/dist/assets/{useNotebookActions-NHN6A-cN.js → useNotebookActions-DHt3s-ia.js} +1 -1
- package/dist/assets/{useRunCells-B2vUhREg.js → useRunCells-DSJPXE-j.js} +1 -1
- package/dist/assets/{useSplitCell-DEb03htp.js → useSplitCell-DQyMF5gd.js} +1 -1
- package/dist/assets/{useTheme-COhSHkCj.js → useTheme-n8Dj1zH7.js} +1 -1
- package/dist/assets/{vega-component-CnadTgoE.js → vega-component-IouMe13t.js} +1 -1
- package/dist/index.html +34 -35
- package/package.json +5 -2
- package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
- package/src/__tests__/host-imports.test.ts +100 -0
- package/src/__tests__/mount.test.ts +28 -8
- package/src/components/data-table/__tests__/context-menu.test.ts +176 -0
- package/src/components/data-table/__tests__/date-filter-inputs.test.tsx +24 -1
- package/src/components/data-table/__tests__/export-actions.test.tsx +329 -6
- package/src/components/data-table/context-menu.tsx +125 -26
- package/src/components/data-table/date-filter-inputs.tsx +15 -42
- package/src/components/data-table/export-actions.tsx +192 -116
- package/src/components/data-table/schemas.ts +14 -36
- package/src/components/data-table/utils.ts +18 -0
- package/src/components/editor/Output.tsx +9 -2
- package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +80 -252
- package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +186 -34
- package/src/components/editor/actions/pair-with-agent-commands.ts +42 -51
- package/src/components/editor/actions/pair-with-agent-modal.tsx +96 -36
- package/src/components/editor/chrome/panels/error-panel.tsx +1 -0
- package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +372 -0
- package/src/components/editor/chrome/wrapper/footer-item.tsx +32 -24
- package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +237 -0
- package/src/components/editor/chrome/wrapper/footer.tsx +2 -0
- package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +548 -0
- package/src/components/editor/errors/send-error-report-button.tsx +69 -0
- package/src/components/editor/output/MarimoErrorOutput.tsx +21 -5
- package/src/components/editor/output/MarimoTracebackOutput.tsx +35 -20
- package/src/components/editor/output/__tests__/ansi-reduce.test.ts +18 -0
- package/src/components/editor/output/ansi-reduce.ts +6 -0
- package/src/core/attachments/__tests__/toast.test.tsx +58 -0
- package/src/core/attachments/state.ts +21 -0
- package/src/core/attachments/toast.ts +15 -0
- package/src/core/cells/__tests__/cells.test.ts +52 -0
- package/src/core/cells/__tests__/error-report.test.ts +139 -0
- package/src/core/cells/__tests__/session.test.ts +50 -1
- package/src/core/cells/error-report.ts +63 -0
- package/src/core/cells/session.ts +28 -1
- package/src/core/config/pair.ts +5 -3
- package/src/core/edit-app.tsx +2 -2
- package/src/core/host/AGENTS.md +6 -0
- package/src/core/host/__tests__/events.test.ts +46 -0
- package/src/core/host/__tests__/notebook-client.test.ts +191 -0
- package/src/core/host/__tests__/protocol.test.ts +214 -0
- package/src/core/host/connection.ts +81 -0
- package/src/core/host/events.ts +60 -0
- package/src/core/host/notebook-client.ts +226 -0
- package/src/core/host/protocol.ts +1943 -0
- package/src/core/islands/__tests__/bootstrap.test.ts +26 -0
- package/src/core/islands/bootstrap.ts +1 -0
- package/src/core/run-app.tsx +2 -2
- package/src/core/runtime/__tests__/runtime.test.ts +55 -11
- package/src/core/runtime/runtime.ts +14 -1
- package/src/core/runtime/types.ts +2 -0
- package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -17
- package/src/core/websocket/__tests__/{useWebSocket.test.ts → createConnectionTransport.test.ts} +1 -1
- package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +140 -0
- package/src/core/websocket/__tests__/{useMarimoKernelConnection.hook.test.tsx → useNotebookSession.hook.test.tsx} +352 -65
- package/src/core/websocket/connection-state.ts +70 -0
- package/src/core/websocket/transports/__tests__/sse.test.ts +1 -4
- package/src/core/websocket/transports/create.ts +31 -0
- package/src/core/websocket/transports/transport.ts +12 -23
- package/src/core/websocket/types.ts +9 -39
- package/src/core/websocket/useNotebookClient.ts +80 -0
- package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +67 -225
- package/src/mount.tsx +7 -3
- package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +84 -0
- package/src/utils/__tests__/dates.test.ts +116 -0
- package/src/utils/dates.ts +94 -0
- package/dist/assets/JsonOutput-D6HIIsCb.js +0 -11
- package/dist/assets/MarimoErrorOutput-D60XkCny.js +0 -6
- package/dist/assets/ai-model-dropdown-B0z6-zhp.js +0 -5
- package/dist/assets/api-BeC_VSGv.js +0 -1
- package/dist/assets/cell-link-LSth_8hT.js +0 -1
- package/dist/assets/code-visibility-B-_GvwR5.js +0 -1
- package/dist/assets/coerce-CpE6kUdX.js +0 -1
- package/dist/assets/config-BOKzA3tF.js +0 -1
- package/dist/assets/config-DX8Y2WbJ.js +0 -1
- package/dist/assets/edit-page-ChcSgpke.js +0 -9
- package/dist/assets/feedback-button-B2gKC5cn.js +0 -1
- package/dist/assets/index-Bcdzex-3.css +0 -2
- package/dist/assets/index-s-M1OXjs.js +0 -29
- package/dist/assets/pair-CaQyK6En.js +0 -1
- package/dist/assets/panels-CxVG8-v2.js +0 -4
- package/dist/assets/run-page-DF3c5cDk.js +0 -1
- package/dist/assets/session-panel-VdDhEBa-.js +0 -1
- package/dist/assets/state-_Qai_QyX.js +0 -3
- package/src/core/websocket/useWebSocket.tsx +0 -88
|
@@ -21,14 +21,9 @@ import {
|
|
|
21
21
|
vi,
|
|
22
22
|
} from "vitest";
|
|
23
23
|
|
|
24
|
-
vi.mock("
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return {
|
|
28
|
-
...actual,
|
|
29
|
-
useConnectionTransport: vi.fn(),
|
|
30
|
-
};
|
|
31
|
-
});
|
|
24
|
+
vi.mock("../transports/create", () => ({
|
|
25
|
+
createConnectionTransport: vi.fn(),
|
|
26
|
+
}));
|
|
32
27
|
|
|
33
28
|
vi.mock("@/core/runtime/config", async () => {
|
|
34
29
|
const actual = await vi.importActual<typeof import("@/core/runtime/config")>(
|
|
@@ -41,21 +36,33 @@ vi.mock("@/core/runtime/config", async () => {
|
|
|
41
36
|
});
|
|
42
37
|
|
|
43
38
|
import { MockNotebook } from "@/__mocks__/notebook";
|
|
39
|
+
import * as Toast from "@/components/ui/use-toast";
|
|
40
|
+
import { Logger } from "@/utils/Logger";
|
|
41
|
+
import { Deferred } from "@/utils/Deferred";
|
|
44
42
|
import { cellId } from "@/__tests__/branded";
|
|
45
43
|
import { alertAtom, getPackageAlert } from "@/core/alerts/state";
|
|
44
|
+
import {
|
|
45
|
+
type Attachment,
|
|
46
|
+
attachmentsAtom,
|
|
47
|
+
agentAttachmentsAtom,
|
|
48
|
+
} from "@/core/attachments/state";
|
|
46
49
|
import { notebookAtom } from "@/core/cells/cells";
|
|
47
50
|
import { AppConfigSchema } from "@/core/config/config-schema";
|
|
48
51
|
import { ConnectionNotice } from "@/components/editor/alerts/connection-notice";
|
|
49
52
|
import { kernelStartupErrorAtom } from "@/core/errors/state";
|
|
53
|
+
import type { RunEvent } from "@/core/host/protocol";
|
|
50
54
|
import type { NotificationPayload } from "@/core/kernel/messages";
|
|
51
55
|
import { useRuntimeManager } from "@/core/runtime/config";
|
|
52
56
|
import { initialRunCompletedAtom } from "../../kernel/state";
|
|
53
57
|
import { connectionAtom, startupProgressAtom } from "../../network/connection";
|
|
54
|
-
import
|
|
58
|
+
import { generateSessionId } from "../../kernel/session";
|
|
59
|
+
import { RuntimeManager } from "../../runtime/runtime";
|
|
55
60
|
import { WebSocketClosedReason, ConnectionState } from "../types";
|
|
56
61
|
import type { IConnectionTransport } from "../transports/transport";
|
|
57
|
-
import {
|
|
58
|
-
import {
|
|
62
|
+
import { useNotebookSession } from "../useNotebookSession";
|
|
63
|
+
import { createConnectionTransport } from "../transports/create";
|
|
64
|
+
|
|
65
|
+
const SESSION_ID = generateSessionId();
|
|
59
66
|
|
|
60
67
|
interface MockTransport {
|
|
61
68
|
readyState: 0 | 1 | 2 | 3;
|
|
@@ -79,16 +86,47 @@ function makeTransport(
|
|
|
79
86
|
};
|
|
80
87
|
}
|
|
81
88
|
|
|
89
|
+
function getTransportHandlers() {
|
|
90
|
+
const result = vi.mocked(createConnectionTransport).mock.results.at(-1);
|
|
91
|
+
if (!result || result.type !== "return") {
|
|
92
|
+
throw new Error("No connection transport was created");
|
|
93
|
+
}
|
|
94
|
+
const transport = result.value;
|
|
95
|
+
function emit(
|
|
96
|
+
event: "open" | "close" | "error" | "message",
|
|
97
|
+
message: Event | CloseEvent | MessageEvent,
|
|
98
|
+
) {
|
|
99
|
+
const callback = vi
|
|
100
|
+
.mocked(transport.addEventListener)
|
|
101
|
+
.mock.calls.find(([name]) => name === event)?.[1];
|
|
102
|
+
callback?.(message);
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
onError: (event: Event) => emit("error", event),
|
|
106
|
+
onOpen: (event: Event) => emit("open", event),
|
|
107
|
+
onClose: (event: CloseEvent) => emit("close", event),
|
|
108
|
+
onMessage: (event: MessageEvent) => emit("message", event),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
82
112
|
function makeRuntimeManager(
|
|
83
113
|
reconcileFromHealth = vi.fn<() => Promise<boolean>>().mockResolvedValue(true),
|
|
84
114
|
) {
|
|
85
|
-
|
|
115
|
+
const runtime = new RuntimeManager(
|
|
116
|
+
{ url: "http://localhost", lazy: true },
|
|
117
|
+
true,
|
|
118
|
+
);
|
|
119
|
+
vi.spyOn(runtime, "reconcileFromHealth").mockImplementation(
|
|
86
120
|
reconcileFromHealth,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
121
|
+
);
|
|
122
|
+
vi.spyOn(runtime, "probeHealth").mockResolvedValue(true);
|
|
123
|
+
vi.spyOn(runtime, "getWsURL").mockReturnValue(new URL("ws://localhost/ws"));
|
|
124
|
+
// Keep the initial connection pending; these tests exercise session retries.
|
|
125
|
+
vi.spyOn(runtime, "waitForHealthy").mockReturnValue(
|
|
126
|
+
new Deferred<void>().promise,
|
|
127
|
+
);
|
|
128
|
+
vi.spyOn(runtime, "isSameOrigin", "get").mockReturnValue(false);
|
|
129
|
+
return runtime;
|
|
92
130
|
}
|
|
93
131
|
|
|
94
132
|
function renderConnectionHook(store: ReturnType<typeof createStore>) {
|
|
@@ -99,16 +137,16 @@ function renderConnectionHook(store: ReturnType<typeof createStore>) {
|
|
|
99
137
|
);
|
|
100
138
|
return renderHook(
|
|
101
139
|
() =>
|
|
102
|
-
|
|
103
|
-
sessionId:
|
|
140
|
+
useNotebookSession({
|
|
141
|
+
sessionId: SESSION_ID,
|
|
104
142
|
autoInstantiate: false,
|
|
105
|
-
setCells: ()
|
|
143
|
+
setCells: vi.fn(),
|
|
106
144
|
}),
|
|
107
145
|
{ wrapper },
|
|
108
146
|
);
|
|
109
147
|
}
|
|
110
148
|
|
|
111
|
-
describe("
|
|
149
|
+
describe("useNotebookSession.reconnect()", () => {
|
|
112
150
|
let transport: MockTransport;
|
|
113
151
|
let isHealthy: Mock<() => Promise<boolean>>;
|
|
114
152
|
let store: ReturnType<typeof createStore>;
|
|
@@ -122,12 +160,8 @@ describe("useMarimoKernelConnection.reconnect()", () => {
|
|
|
122
160
|
code: WebSocketClosedReason.KERNEL_DISCONNECTED,
|
|
123
161
|
reason: "kernel not found",
|
|
124
162
|
});
|
|
125
|
-
vi.mocked(
|
|
126
|
-
vi.mocked(useRuntimeManager).mockReturnValue(
|
|
127
|
-
makeRuntimeManager(isHealthy) as unknown as ReturnType<
|
|
128
|
-
typeof useRuntimeManager
|
|
129
|
-
>,
|
|
130
|
-
);
|
|
163
|
+
vi.mocked(createConnectionTransport).mockReturnValue(transport);
|
|
164
|
+
vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager(isHealthy));
|
|
131
165
|
});
|
|
132
166
|
|
|
133
167
|
function renderUseHook() {
|
|
@@ -183,31 +217,146 @@ describe("useMarimoKernelConnection.reconnect()", () => {
|
|
|
183
217
|
});
|
|
184
218
|
});
|
|
185
219
|
|
|
186
|
-
describe("
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
220
|
+
describe("useNotebookSession messages", () => {
|
|
221
|
+
let store: ReturnType<typeof createStore>;
|
|
222
|
+
|
|
223
|
+
beforeEach(() => {
|
|
224
|
+
store = createStore();
|
|
225
|
+
vi.mocked(createConnectionTransport).mockClear();
|
|
226
|
+
vi.mocked(createConnectionTransport).mockReturnValue(
|
|
191
227
|
makeTransport(WebSocket.OPEN),
|
|
192
228
|
);
|
|
193
|
-
vi.mocked(useRuntimeManager).mockReturnValue(
|
|
194
|
-
makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
|
|
195
|
-
);
|
|
229
|
+
vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
|
|
196
230
|
renderConnectionHook(store);
|
|
231
|
+
});
|
|
197
232
|
|
|
198
|
-
|
|
199
|
-
|
|
233
|
+
function receive(payload: unknown) {
|
|
234
|
+
const options = getTransportHandlers();
|
|
200
235
|
act(() => {
|
|
201
|
-
options
|
|
202
|
-
new MessageEvent("message", {
|
|
203
|
-
data: JSON.stringify({
|
|
204
|
-
op: "completed-run",
|
|
205
|
-
data: { op: "completed-run", run_id: null },
|
|
206
|
-
}),
|
|
207
|
-
}),
|
|
236
|
+
options.onMessage(
|
|
237
|
+
new MessageEvent("message", { data: JSON.stringify(payload) }),
|
|
208
238
|
);
|
|
209
239
|
});
|
|
210
|
-
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
it("replaces attachment snapshots, including the last detach", () => {
|
|
243
|
+
const agent: Attachment = {
|
|
244
|
+
id: "a1",
|
|
245
|
+
kind: "agent",
|
|
246
|
+
name: "Pi",
|
|
247
|
+
since: "2026-10-14T00:00:00Z",
|
|
248
|
+
notebook_id: "nb-test",
|
|
249
|
+
identity: "pi-thread",
|
|
250
|
+
};
|
|
251
|
+
const client: Attachment = {
|
|
252
|
+
id: "c1",
|
|
253
|
+
kind: "client",
|
|
254
|
+
name: null,
|
|
255
|
+
since: "2026-10-14T00:00:01Z",
|
|
256
|
+
notebook_id: "nb-test",
|
|
257
|
+
};
|
|
258
|
+
for (const attachments of [[agent, client], [client], []]) {
|
|
259
|
+
receive({ data: { op: "attachments", attachments } });
|
|
260
|
+
expect(store.get(attachmentsAtom)).toEqual(attachments);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it.each(["{broken", "null"])(
|
|
265
|
+
"reports a malformed legacy frame and continues applying notifications: %s",
|
|
266
|
+
(data) => {
|
|
267
|
+
const toast = vi.spyOn(Toast, "toast");
|
|
268
|
+
try {
|
|
269
|
+
receive({ data: { op: "reconnected" } });
|
|
270
|
+
act(() =>
|
|
271
|
+
getTransportHandlers().onMessage(
|
|
272
|
+
new MessageEvent("message", { data }),
|
|
273
|
+
),
|
|
274
|
+
);
|
|
275
|
+
expect(toast).toHaveBeenCalledExactlyOnceWith({
|
|
276
|
+
title: "Failed to handle message",
|
|
277
|
+
description: expect.any(String),
|
|
278
|
+
variant: "danger",
|
|
279
|
+
});
|
|
280
|
+
expect(store.get(connectionAtom)).toEqual({
|
|
281
|
+
state: ConnectionState.OPEN,
|
|
282
|
+
});
|
|
283
|
+
receive({
|
|
284
|
+
data: {
|
|
285
|
+
op: "completed-run",
|
|
286
|
+
run_id: "r1",
|
|
287
|
+
outcome: "succeeded",
|
|
288
|
+
error: null,
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
expect(store.get(initialRunCompletedAtom)).toBe(true);
|
|
292
|
+
} finally {
|
|
293
|
+
toast.mockRestore();
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
it.each(["session", "host"])(
|
|
299
|
+
"records completion of the initial run from the %s stream",
|
|
300
|
+
(stream) => {
|
|
301
|
+
const notification = {
|
|
302
|
+
op: "completed-run",
|
|
303
|
+
run_id: null,
|
|
304
|
+
outcome: "succeeded",
|
|
305
|
+
error: null,
|
|
306
|
+
} satisfies NotificationPayload["data"];
|
|
307
|
+
expect(store.get(initialRunCompletedAtom)).toBe(false);
|
|
308
|
+
receive(
|
|
309
|
+
stream === "host"
|
|
310
|
+
? { id: "42", event: "marimo", data: { marimo: notification } }
|
|
311
|
+
: { op: notification.op, data: notification },
|
|
312
|
+
);
|
|
313
|
+
expect(store.get(initialRunCompletedAtom)).toBe(true);
|
|
314
|
+
},
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
it("ignores other host events before handling the next notification", () => {
|
|
318
|
+
const errors = vi.spyOn(Logger, "error").mockImplementation(vi.fn());
|
|
319
|
+
try {
|
|
320
|
+
receive({ id: "1", event: "ready", data: {} });
|
|
321
|
+
receive({
|
|
322
|
+
id: "2",
|
|
323
|
+
event: "run",
|
|
324
|
+
data: {
|
|
325
|
+
run: {
|
|
326
|
+
id: "r1",
|
|
327
|
+
notebook_id: "n1",
|
|
328
|
+
caused_by: null,
|
|
329
|
+
status: "completed",
|
|
330
|
+
outcome: "succeeded",
|
|
331
|
+
started_at: "2026-10-19T12:00:00Z",
|
|
332
|
+
completed_at: "2026-10-19T12:00:01Z",
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
} satisfies RunEvent);
|
|
336
|
+
receive({
|
|
337
|
+
id: "3",
|
|
338
|
+
event: "future-event",
|
|
339
|
+
data: { marimo: { op: "completed-run", run_id: "r1" } },
|
|
340
|
+
});
|
|
341
|
+
receive({ id: "4", event: "another-event", data: null });
|
|
342
|
+
expect(store.get(initialRunCompletedAtom)).toBe(false);
|
|
343
|
+
receive({
|
|
344
|
+
id: "5",
|
|
345
|
+
event: "marimo",
|
|
346
|
+
data: {
|
|
347
|
+
marimo: {
|
|
348
|
+
op: "completed-run",
|
|
349
|
+
run_id: "r1",
|
|
350
|
+
outcome: "succeeded",
|
|
351
|
+
error: null,
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
expect(store.get(initialRunCompletedAtom)).toBe(true);
|
|
356
|
+
expect(errors).not.toHaveBeenCalled();
|
|
357
|
+
} finally {
|
|
358
|
+
errors.mockRestore();
|
|
359
|
+
}
|
|
211
360
|
});
|
|
212
361
|
});
|
|
213
362
|
|
|
@@ -216,15 +365,13 @@ it.each(["kernel-ready", "reconnected"])(
|
|
|
216
365
|
async (op) => {
|
|
217
366
|
const store = createStore();
|
|
218
367
|
store.set(connectionAtom, { state: ConnectionState.CONNECTING });
|
|
219
|
-
vi.mocked(
|
|
220
|
-
vi.mocked(
|
|
368
|
+
vi.mocked(createConnectionTransport).mockClear();
|
|
369
|
+
vi.mocked(createConnectionTransport).mockReturnValue(
|
|
221
370
|
makeTransport(WebSocket.OPEN),
|
|
222
371
|
);
|
|
223
|
-
vi.mocked(useRuntimeManager).mockReturnValue(
|
|
224
|
-
makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
|
|
225
|
-
);
|
|
372
|
+
vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
|
|
226
373
|
renderConnectionHook(store);
|
|
227
|
-
const options =
|
|
374
|
+
const options = getTransportHandlers();
|
|
228
375
|
|
|
229
376
|
await act(async () => {
|
|
230
377
|
await options.onOpen(new Event("open"));
|
|
@@ -261,6 +408,150 @@ it.each(["kernel-ready", "reconnected"])(
|
|
|
261
408
|
},
|
|
262
409
|
);
|
|
263
410
|
|
|
411
|
+
describe("attachment snapshots across browser connections", () => {
|
|
412
|
+
const agent: Attachment = {
|
|
413
|
+
id: "a1",
|
|
414
|
+
kind: "agent",
|
|
415
|
+
name: "Pi",
|
|
416
|
+
since: "2026-10-14T00:00:00Z",
|
|
417
|
+
notebook_id: "nb-test",
|
|
418
|
+
identity: "pi-thread",
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
function connectedSession() {
|
|
422
|
+
const store = createStore();
|
|
423
|
+
store.set(connectionAtom, { state: ConnectionState.OPEN });
|
|
424
|
+
store.set(attachmentsAtom, [agent]);
|
|
425
|
+
const transport = makeTransport(WebSocket.OPEN);
|
|
426
|
+
vi.mocked(createConnectionTransport).mockReturnValue(transport);
|
|
427
|
+
vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
|
|
428
|
+
renderConnectionHook(store);
|
|
429
|
+
const options = getTransportHandlers();
|
|
430
|
+
const receive = (data: NotificationPayload["data"]) =>
|
|
431
|
+
act(() => {
|
|
432
|
+
options.onMessage(
|
|
433
|
+
new MessageEvent("message", {
|
|
434
|
+
data: JSON.stringify({ op: data.op, data }),
|
|
435
|
+
}),
|
|
436
|
+
);
|
|
437
|
+
});
|
|
438
|
+
return { store, transport, options, receive };
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
it.each([
|
|
442
|
+
"MARIMO_NO_FILE_KEY",
|
|
443
|
+
"MARIMO_NO_SESSION_ID",
|
|
444
|
+
"MARIMO_NO_SESSION",
|
|
445
|
+
"MARIMO_SHUTDOWN",
|
|
446
|
+
"MARIMO_KERNEL_STARTUP_ERROR",
|
|
447
|
+
"MARIMO_UNAUTHORIZED",
|
|
448
|
+
"MARIMO_KIOSK_NOT_ALLOWED",
|
|
449
|
+
"MARIMO_TRANSPORT_EXHAUSTED",
|
|
450
|
+
])("clears cached attachments on %s", (reason) => {
|
|
451
|
+
const { store, transport, options } = connectedSession();
|
|
452
|
+
act(() => options.onClose(new CloseEvent("close", { reason })));
|
|
453
|
+
expect(store.get(attachmentsAtom)).toEqual([]);
|
|
454
|
+
expect(store.get(agentAttachmentsAtom)).toEqual([]);
|
|
455
|
+
expect(store.get(connectionAtom).state).toBe(ConnectionState.CLOSED);
|
|
456
|
+
expect(transport.reconnect).not.toHaveBeenCalled();
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it("clears the snapshot on transport error", () => {
|
|
460
|
+
const { store, options } = connectedSession();
|
|
461
|
+
act(() => options.onError(new Event("error")));
|
|
462
|
+
expect(store.get(attachmentsAtom)).toEqual([]);
|
|
463
|
+
expect(store.get(agentAttachmentsAtom)).toEqual([]);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it("waits for a fresh snapshot after a transient reconnect", async () => {
|
|
467
|
+
const { store, transport, options, receive } = connectedSession();
|
|
468
|
+
act(() => options.onClose(new CloseEvent("close")));
|
|
469
|
+
expect(store.get(attachmentsAtom)).toEqual([]);
|
|
470
|
+
expect(store.get(connectionAtom).state).toBe(ConnectionState.CONNECTING);
|
|
471
|
+
expect(transport.reconnect).toHaveBeenCalledOnce();
|
|
472
|
+
|
|
473
|
+
await act(async () => options.onOpen(new Event("open")));
|
|
474
|
+
receive({ op: "reconnected" });
|
|
475
|
+
expect(store.get(connectionAtom).state).toBe(ConnectionState.OPEN);
|
|
476
|
+
expect(store.get(agentAttachmentsAtom)).toEqual([]);
|
|
477
|
+
|
|
478
|
+
const refreshed = { ...agent, id: "a2", name: "Codex" };
|
|
479
|
+
receive({ op: "attachments", attachments: [refreshed] });
|
|
480
|
+
expect(store.get(agentAttachmentsAtom)).toEqual([refreshed]);
|
|
481
|
+
receive({ op: "attachments", attachments: [] });
|
|
482
|
+
expect(store.get(agentAttachmentsAtom)).toEqual([]);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it("keeps a fresh snapshot hidden until the session is ready", async () => {
|
|
486
|
+
const { store, options, receive } = connectedSession();
|
|
487
|
+
act(() => options.onClose(new CloseEvent("close")));
|
|
488
|
+
await act(async () => options.onOpen(new Event("open")));
|
|
489
|
+
receive({ op: "attachments", attachments: [agent] });
|
|
490
|
+
expect(store.get(attachmentsAtom)).toEqual([agent]);
|
|
491
|
+
expect(store.get(agentAttachmentsAtom)).toEqual([]);
|
|
492
|
+
receive({ op: "reconnected" });
|
|
493
|
+
expect(store.get(agentAttachmentsAtom)).toEqual([agent]);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it("invalidates an old snapshot when a new transport opens", async () => {
|
|
497
|
+
const { store, options } = connectedSession();
|
|
498
|
+
await act(async () => options.onOpen(new Event("open")));
|
|
499
|
+
expect(store.get(attachmentsAtom)).toEqual([]);
|
|
500
|
+
expect(store.get(agentAttachmentsAtom)).toEqual([]);
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it.each([true, false])(
|
|
505
|
+
"keeps attachments only when kernel-ready resumes the session: %s",
|
|
506
|
+
(resumed) => {
|
|
507
|
+
const store = createStore();
|
|
508
|
+
const attachments: Attachment[] = [
|
|
509
|
+
{
|
|
510
|
+
id: "a1",
|
|
511
|
+
kind: "agent",
|
|
512
|
+
name: "Pi",
|
|
513
|
+
since: "2026-10-14T00:00:00Z",
|
|
514
|
+
notebook_id: "nb-test",
|
|
515
|
+
identity: "pi-thread",
|
|
516
|
+
},
|
|
517
|
+
];
|
|
518
|
+
store.set(attachmentsAtom, attachments);
|
|
519
|
+
vi.mocked(createConnectionTransport).mockReturnValue(
|
|
520
|
+
makeTransport(WebSocket.OPEN),
|
|
521
|
+
);
|
|
522
|
+
vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
|
|
523
|
+
renderConnectionHook(store);
|
|
524
|
+
const options = getTransportHandlers();
|
|
525
|
+
act(() =>
|
|
526
|
+
options.onMessage(
|
|
527
|
+
new MessageEvent("message", {
|
|
528
|
+
data: JSON.stringify({
|
|
529
|
+
op: "kernel-ready",
|
|
530
|
+
data: {
|
|
531
|
+
op: "kernel-ready",
|
|
532
|
+
cell_ids: [],
|
|
533
|
+
codes: [],
|
|
534
|
+
names: [],
|
|
535
|
+
configs: [],
|
|
536
|
+
layout: null,
|
|
537
|
+
resumed,
|
|
538
|
+
ui_values: {},
|
|
539
|
+
last_executed_code: {},
|
|
540
|
+
last_execution_time: {},
|
|
541
|
+
app_config: { width: "normal" },
|
|
542
|
+
kiosk: false,
|
|
543
|
+
capabilities: { terminal: false },
|
|
544
|
+
auto_instantiated: true,
|
|
545
|
+
consumer_capabilities: { edit: true, interact: true },
|
|
546
|
+
},
|
|
547
|
+
}),
|
|
548
|
+
}),
|
|
549
|
+
),
|
|
550
|
+
);
|
|
551
|
+
expect(store.get(attachmentsAtom)).toEqual(resumed ? attachments : []);
|
|
552
|
+
},
|
|
553
|
+
);
|
|
554
|
+
|
|
264
555
|
describe("connection notice", () => {
|
|
265
556
|
beforeEach(() => vi.useFakeTimers());
|
|
266
557
|
afterEach(() => vi.useRealTimers());
|
|
@@ -269,15 +560,13 @@ describe("connection notice", () => {
|
|
|
269
560
|
const store = createStore();
|
|
270
561
|
store.set(connectionAtom, { state: ConnectionState.CONNECTING });
|
|
271
562
|
const transport = makeTransport(WebSocket.OPEN);
|
|
272
|
-
vi.mocked(
|
|
273
|
-
vi.mocked(useRuntimeManager).mockReturnValue(
|
|
274
|
-
makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
|
|
275
|
-
);
|
|
563
|
+
vi.mocked(createConnectionTransport).mockReturnValue(transport);
|
|
564
|
+
vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
|
|
276
565
|
const Connection = () => {
|
|
277
|
-
const { reconnect } =
|
|
278
|
-
sessionId:
|
|
566
|
+
const { reconnect } = useNotebookSession({
|
|
567
|
+
sessionId: SESSION_ID,
|
|
279
568
|
autoInstantiate: false,
|
|
280
|
-
setCells: ()
|
|
569
|
+
setCells: vi.fn(),
|
|
281
570
|
});
|
|
282
571
|
return (
|
|
283
572
|
<ConnectionNotice
|
|
@@ -293,7 +582,7 @@ describe("connection notice", () => {
|
|
|
293
582
|
</ErrorBoundary>
|
|
294
583
|
</JotaiProvider>,
|
|
295
584
|
);
|
|
296
|
-
const options =
|
|
585
|
+
const options = getTransportHandlers();
|
|
297
586
|
const send = (data: NotificationPayload["data"]) =>
|
|
298
587
|
act(() => {
|
|
299
588
|
options.onMessage(
|
|
@@ -482,15 +771,13 @@ describe("connection notice", () => {
|
|
|
482
771
|
|
|
483
772
|
it("replaces environment state from snapshots, then continues live progress", () => {
|
|
484
773
|
const store = createStore();
|
|
485
|
-
vi.mocked(
|
|
486
|
-
vi.mocked(
|
|
774
|
+
vi.mocked(createConnectionTransport).mockClear();
|
|
775
|
+
vi.mocked(createConnectionTransport).mockReturnValue(
|
|
487
776
|
makeTransport(WebSocket.OPEN),
|
|
488
777
|
);
|
|
489
|
-
vi.mocked(useRuntimeManager).mockReturnValue(
|
|
490
|
-
makeRuntimeManager() as unknown as ReturnType<typeof useRuntimeManager>,
|
|
491
|
-
);
|
|
778
|
+
vi.mocked(useRuntimeManager).mockReturnValue(makeRuntimeManager());
|
|
492
779
|
renderConnectionHook(store);
|
|
493
|
-
const options =
|
|
780
|
+
const options = getTransportHandlers();
|
|
494
781
|
function receive(data: NotificationPayload["data"]) {
|
|
495
782
|
act(() =>
|
|
496
783
|
options?.onMessage(
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { CloseDecision } from "../host/connection";
|
|
4
|
+
import { Logger } from "@/utils/Logger";
|
|
5
|
+
import { TRANSPORT_EXHAUSTED_REASON } from "./transports/ws";
|
|
6
|
+
import { ConnectionState, WebSocketClosedReason } from "./types";
|
|
7
|
+
|
|
8
|
+
// Keep server reasons in sync with ws_endpoint.py and ws/*.py.
|
|
9
|
+
// SSE carries the same reasons in close events. Unclassified reasons retry.
|
|
10
|
+
export function classifyCloseEvent(event: { reason?: string }): CloseDecision {
|
|
11
|
+
switch (event.reason) {
|
|
12
|
+
case TRANSPORT_EXHAUSTED_REASON:
|
|
13
|
+
return {
|
|
14
|
+
kind: "gave-up",
|
|
15
|
+
status: {
|
|
16
|
+
state: ConnectionState.CLOSED,
|
|
17
|
+
code: WebSocketClosedReason.KERNEL_DISCONNECTED,
|
|
18
|
+
reason: "kernel not found",
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
case "MARIMO_NO_FILE_KEY":
|
|
22
|
+
case "MARIMO_NO_SESSION_ID":
|
|
23
|
+
case "MARIMO_NO_SESSION":
|
|
24
|
+
case "MARIMO_SHUTDOWN":
|
|
25
|
+
return {
|
|
26
|
+
kind: "terminal",
|
|
27
|
+
status: {
|
|
28
|
+
state: ConnectionState.CLOSED,
|
|
29
|
+
code: WebSocketClosedReason.KERNEL_DISCONNECTED,
|
|
30
|
+
reason: "kernel not found",
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
case "MARIMO_UNAUTHORIZED":
|
|
34
|
+
return {
|
|
35
|
+
kind: "terminal",
|
|
36
|
+
status: {
|
|
37
|
+
state: ConnectionState.CLOSED,
|
|
38
|
+
code: WebSocketClosedReason.KERNEL_DISCONNECTED,
|
|
39
|
+
reason: "not authorized",
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
case "MARIMO_KIOSK_NOT_ALLOWED":
|
|
43
|
+
return {
|
|
44
|
+
kind: "terminal",
|
|
45
|
+
status: {
|
|
46
|
+
state: ConnectionState.CLOSED,
|
|
47
|
+
code: WebSocketClosedReason.KERNEL_DISCONNECTED,
|
|
48
|
+
reason: "kiosk mode is not available for this session",
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
case "MARIMO_KERNEL_STARTUP_ERROR":
|
|
52
|
+
return {
|
|
53
|
+
kind: "terminal",
|
|
54
|
+
status: {
|
|
55
|
+
state: ConnectionState.CLOSED,
|
|
56
|
+
code: WebSocketClosedReason.KERNEL_STARTUP_ERROR,
|
|
57
|
+
reason: "Failed to start kernel sandbox",
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
default:
|
|
61
|
+
if (event.reason) {
|
|
62
|
+
Logger.warn("Unrecognized connection close reason", event.reason);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
kind: "retry",
|
|
68
|
+
status: { state: ConnectionState.CONNECTING },
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -108,8 +108,6 @@ describe("SseTransport", () => {
|
|
|
108
108
|
transport.reconnect();
|
|
109
109
|
await vi.waitFor(() => expect(opens).toHaveLength(1));
|
|
110
110
|
|
|
111
|
-
// Stop retrying when the consumer treats the close as terminal,
|
|
112
|
-
// mirroring useMarimoKernelConnection's terminal path.
|
|
113
111
|
transport.addEventListener("close", () => transport.close());
|
|
114
112
|
push('event: close\ndata: {"code": 1000, "reason": "MARIMO_SHUTDOWN"}\n\n');
|
|
115
113
|
await vi.waitFor(() => expect(closes).toHaveLength(1));
|
|
@@ -146,8 +144,7 @@ describe("SseTransport", () => {
|
|
|
146
144
|
fetchMock
|
|
147
145
|
.mockResolvedValueOnce(sseResponse(first.stream))
|
|
148
146
|
.mockResolvedValueOnce(sseResponse(second.stream));
|
|
149
|
-
//
|
|
150
|
-
// synchronously from inside the close handler.
|
|
147
|
+
// Reconnect synchronously from the close handler.
|
|
151
148
|
transport.addEventListener("close", () => transport.reconnect());
|
|
152
149
|
|
|
153
150
|
transport.reconnect();
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
}
|