@marimo-team/frontend 0.25.3-dev2 → 0.25.3-dev20
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-CLF_VUTW.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-CMx5iSyu.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-B_jlikjE.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-CkP22x9D.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 +1 -1
- package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
- 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 +5 -0
- package/src/core/host/__tests__/document.test.ts +124 -0
- package/src/core/host/__tests__/events.test.ts +46 -0
- package/src/core/host/__tests__/fixtures.ts +39 -0
- package/src/core/host/__tests__/notebook-client.test.ts +303 -0
- package/src/core/host/__tests__/stream.test.ts +211 -0
- package/src/core/host/connection.ts +81 -0
- package/src/core/host/events.ts +60 -0
- package/src/core/host/notebook-client.ts +272 -0
- package/src/core/host/resources/document.ts +101 -0
- package/src/core/host/resources/resource.ts +59 -0
- package/src/core/host/stream.ts +213 -0
- package/src/core/islands/__tests__/bootstrap.test.ts +26 -0
- package/src/core/islands/bootstrap.ts +1 -0
- package/src/core/network/connection.ts +4 -2
- 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
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type * as Protocol from "../protocol";
|
|
4
|
+
import type { Resource } from "./resource";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The notebook's content at one revision.
|
|
8
|
+
*
|
|
9
|
+
* The revision record carries the cell order, app config, etags, and
|
|
10
|
+
* version. Each cell in its order has a record in `cells`.
|
|
11
|
+
*/
|
|
12
|
+
export interface Document {
|
|
13
|
+
readonly revision: Protocol.Document;
|
|
14
|
+
readonly cells: ReadonlyMap<Protocol.CellId, Protocol.Cell>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** The records of a revision still being received. */
|
|
18
|
+
interface PendingDocument {
|
|
19
|
+
/** Null until the `document` event that closes the group arrives. */
|
|
20
|
+
readonly revision: Protocol.Document | null;
|
|
21
|
+
readonly cells: ReadonlyMap<Protocol.CellId, Protocol.Cell>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The confirmed document and the revision being assembled after it.
|
|
26
|
+
*
|
|
27
|
+
* A snapshot's records are pending until `ready`. A live revision is pending
|
|
28
|
+
* from its first cell until its `document` event.
|
|
29
|
+
*/
|
|
30
|
+
export interface DocumentState {
|
|
31
|
+
readonly current: Document | null;
|
|
32
|
+
readonly pending: PendingDocument | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function pendingOf(state: DocumentState): PendingDocument {
|
|
36
|
+
return (
|
|
37
|
+
state.pending ?? {
|
|
38
|
+
revision: state.current?.revision ?? null,
|
|
39
|
+
cells: state.current?.cells ?? new Map(),
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Confirm a revision whose records are complete. */
|
|
45
|
+
function publish(
|
|
46
|
+
revision: Protocol.Document,
|
|
47
|
+
cells: ReadonlyMap<Protocol.CellId, Protocol.Cell>,
|
|
48
|
+
): DocumentState {
|
|
49
|
+
const order = new Set(revision.order);
|
|
50
|
+
if (
|
|
51
|
+
order.size !== revision.order.length ||
|
|
52
|
+
order.size !== cells.size ||
|
|
53
|
+
[...order].some((id) => !cells.has(id)) ||
|
|
54
|
+
[...cells.values()].some((cell) => cell.version > revision.version)
|
|
55
|
+
) {
|
|
56
|
+
throw new Error("Document records do not match its revision and order");
|
|
57
|
+
}
|
|
58
|
+
return { current: { revision, cells }, pending: null };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const document: Resource<DocumentState> = {
|
|
62
|
+
initial: { current: null, pending: null },
|
|
63
|
+
on: {
|
|
64
|
+
cell: (state, { data }) => {
|
|
65
|
+
const pending = pendingOf(state);
|
|
66
|
+
return {
|
|
67
|
+
...state,
|
|
68
|
+
pending: {
|
|
69
|
+
revision: pending.revision,
|
|
70
|
+
cells: new Map(pending.cells).set(data.cell.id, data.cell),
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
"cell.removed": (state, { data }) => {
|
|
75
|
+
const pending = pendingOf(state);
|
|
76
|
+
if (!pending.cells.has(data.id)) {
|
|
77
|
+
return state;
|
|
78
|
+
}
|
|
79
|
+
const cells = new Map(pending.cells);
|
|
80
|
+
cells.delete(data.id);
|
|
81
|
+
return { ...state, pending: { ...pending, cells } };
|
|
82
|
+
},
|
|
83
|
+
document: (state, { data }, { phase }) => {
|
|
84
|
+
const { cells } = pendingOf(state);
|
|
85
|
+
return phase === "snapshot"
|
|
86
|
+
? { ...state, pending: { revision: data.document, cells } }
|
|
87
|
+
: publish(data.document, cells);
|
|
88
|
+
},
|
|
89
|
+
ready: (state, _event, { phase }) => {
|
|
90
|
+
if (state.pending === null) {
|
|
91
|
+
return state;
|
|
92
|
+
}
|
|
93
|
+
if (phase === "live") {
|
|
94
|
+
throw new Error("Stream ended with an incomplete document update");
|
|
95
|
+
}
|
|
96
|
+
return state.pending.revision === null
|
|
97
|
+
? { current: null, pending: null }
|
|
98
|
+
: publish(state.pending.revision, state.pending.cells);
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type * as Protocol from "../protocol";
|
|
4
|
+
|
|
5
|
+
/** An event name from the protocol. */
|
|
6
|
+
export type EventName = Protocol.EventOutput["event"];
|
|
7
|
+
|
|
8
|
+
/** The decoded event with the given name. */
|
|
9
|
+
export type EventOf<Name extends EventName> = Extract<
|
|
10
|
+
Protocol.EventOutput,
|
|
11
|
+
{ event: Name }
|
|
12
|
+
>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Events the stream handles itself.
|
|
16
|
+
*
|
|
17
|
+
* They identify the notebook, discard state, or carry a native
|
|
18
|
+
* notification. Resources do not receive them.
|
|
19
|
+
*/
|
|
20
|
+
export type StreamEventName =
|
|
21
|
+
| "reset"
|
|
22
|
+
| "notebook"
|
|
23
|
+
| "notebook.removed"
|
|
24
|
+
| "marimo";
|
|
25
|
+
|
|
26
|
+
/** Events a resource may handle. `ready` marks the end of the snapshot. */
|
|
27
|
+
export type ResourceEventName = Exclude<EventName, StreamEventName>;
|
|
28
|
+
|
|
29
|
+
/** `snapshot` until `ready` arrives and `live` after it. */
|
|
30
|
+
export type StreamPhase = "snapshot" | "live";
|
|
31
|
+
|
|
32
|
+
/** What a handler may know about the stream beyond the event. */
|
|
33
|
+
export interface HandlerContext {
|
|
34
|
+
readonly phase: StreamPhase;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A pure handler for each event the resource reads.
|
|
39
|
+
*
|
|
40
|
+
* A handler returns `state` when the event does not apply to it.
|
|
41
|
+
*/
|
|
42
|
+
export type EventHandlers<State> = {
|
|
43
|
+
readonly [Name in ResourceEventName]?: (
|
|
44
|
+
state: State,
|
|
45
|
+
event: EventOf<Name>,
|
|
46
|
+
context: HandlerContext,
|
|
47
|
+
) => State;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* One kind of object assembled from its events.
|
|
52
|
+
*
|
|
53
|
+
* The stream returns a resource to `initial` on `reset` and when the
|
|
54
|
+
* notebook is removed.
|
|
55
|
+
*/
|
|
56
|
+
export interface Resource<State> {
|
|
57
|
+
readonly initial: State;
|
|
58
|
+
readonly on: EventHandlers<State>;
|
|
59
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { NotebookEvent } from "./events";
|
|
4
|
+
import type * as Protocol from "./protocol";
|
|
5
|
+
import type {
|
|
6
|
+
EventOf,
|
|
7
|
+
HandlerContext,
|
|
8
|
+
Resource,
|
|
9
|
+
ResourceEventName,
|
|
10
|
+
StreamPhase,
|
|
11
|
+
} from "./resources/resource";
|
|
12
|
+
|
|
13
|
+
/** A resource for each entry of a state map under the same name. */
|
|
14
|
+
export type Resources<States> = {
|
|
15
|
+
readonly [Name in keyof States]: Resource<States[Name]>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The state of one notebook stream.
|
|
20
|
+
*
|
|
21
|
+
* Holds the phase, the notebook identity, the resume cursor, and the state
|
|
22
|
+
* of each resource.
|
|
23
|
+
*/
|
|
24
|
+
export interface StreamState<States> {
|
|
25
|
+
readonly phase: StreamPhase;
|
|
26
|
+
/**
|
|
27
|
+
* The notebook the stream describes.
|
|
28
|
+
*
|
|
29
|
+
* Retained across `reset`.
|
|
30
|
+
*/
|
|
31
|
+
readonly notebookId: Protocol.NotebookIdOutput | null;
|
|
32
|
+
/**
|
|
33
|
+
* The id of the last live event.
|
|
34
|
+
*
|
|
35
|
+
* Null before `ready` and after `reset`.
|
|
36
|
+
*/
|
|
37
|
+
readonly cursor: string | null;
|
|
38
|
+
/** The state of each resource by name. */
|
|
39
|
+
readonly resources: States;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A reducer over the events of one notebook stream. */
|
|
43
|
+
export interface Stream<States> {
|
|
44
|
+
readonly initial: StreamState<States>;
|
|
45
|
+
/**
|
|
46
|
+
* Apply one event.
|
|
47
|
+
*
|
|
48
|
+
* Throws on a live event without a cursor or on an object from another
|
|
49
|
+
* notebook.
|
|
50
|
+
*/
|
|
51
|
+
reduce(state: StreamState<States>, event: NotebookEvent): StreamState<States>;
|
|
52
|
+
/**
|
|
53
|
+
* The state to hold while reconnecting.
|
|
54
|
+
*
|
|
55
|
+
* A live stream keeps its state and resumes from `cursor`. An incomplete
|
|
56
|
+
* snapshot starts over.
|
|
57
|
+
*/
|
|
58
|
+
resume(state: StreamState<States>): StreamState<States>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Events that may arrive without a cursor after `ready`. */
|
|
62
|
+
const UNCURSORED = new Set<NotebookEvent["event"]>([
|
|
63
|
+
"reset",
|
|
64
|
+
"marimo",
|
|
65
|
+
"unknown",
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The notebook the event's object belongs to.
|
|
70
|
+
*
|
|
71
|
+
* Undefined for objects that do not name one.
|
|
72
|
+
*/
|
|
73
|
+
function notebookIdOf(
|
|
74
|
+
event: EventOf<ResourceEventName>,
|
|
75
|
+
): Protocol.NotebookIdOutput | undefined {
|
|
76
|
+
switch (event.event) {
|
|
77
|
+
case "document":
|
|
78
|
+
return event.data.document.notebook_id;
|
|
79
|
+
case "cell":
|
|
80
|
+
return event.data.cell.notebook_id;
|
|
81
|
+
case "analysis":
|
|
82
|
+
return event.data.analysis.notebook_id;
|
|
83
|
+
case "runtime":
|
|
84
|
+
return event.data.runtime.notebook_id;
|
|
85
|
+
case "attachment":
|
|
86
|
+
return event.data.attachment.notebook_id;
|
|
87
|
+
case "run":
|
|
88
|
+
return event.data.run.notebook_id;
|
|
89
|
+
default:
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Compose resources into one stream. */
|
|
95
|
+
export function createStream<States extends object>(
|
|
96
|
+
resources: Resources<States>,
|
|
97
|
+
): Stream<States> {
|
|
98
|
+
const names = Object.keys(resources) as (keyof States & string)[];
|
|
99
|
+
const initialResources = Object.fromEntries(
|
|
100
|
+
names.map((name) => [name, resources[name].initial]),
|
|
101
|
+
) as States;
|
|
102
|
+
const initial: StreamState<States> = {
|
|
103
|
+
phase: "snapshot",
|
|
104
|
+
notebookId: null,
|
|
105
|
+
cursor: null,
|
|
106
|
+
resources: initialResources,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
function identify(
|
|
110
|
+
state: StreamState<States>,
|
|
111
|
+
notebookId: Protocol.NotebookIdOutput,
|
|
112
|
+
): StreamState<States> {
|
|
113
|
+
if (state.notebookId === notebookId) {
|
|
114
|
+
return state;
|
|
115
|
+
}
|
|
116
|
+
if (state.notebookId !== null) {
|
|
117
|
+
throw new Error("Notebook stream changed identity");
|
|
118
|
+
}
|
|
119
|
+
return { ...state, notebookId };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function clear(state: StreamState<States>): StreamState<States> {
|
|
123
|
+
return state.resources === initialResources
|
|
124
|
+
? state
|
|
125
|
+
: { ...state, resources: initialResources };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function fanOut(
|
|
129
|
+
state: StreamState<States>,
|
|
130
|
+
event: EventOf<ResourceEventName>,
|
|
131
|
+
): StreamState<States> {
|
|
132
|
+
const context: HandlerContext = { phase: state.phase };
|
|
133
|
+
let next = state.resources;
|
|
134
|
+
for (const name of names) {
|
|
135
|
+
type State = States[typeof name];
|
|
136
|
+
// Handlers are keyed by the event name they accept.
|
|
137
|
+
const handler = resources[name].on[event.event] as
|
|
138
|
+
| ((
|
|
139
|
+
state: State,
|
|
140
|
+
event: Protocol.EventOutput,
|
|
141
|
+
context: HandlerContext,
|
|
142
|
+
) => State)
|
|
143
|
+
| undefined;
|
|
144
|
+
if (handler === undefined) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const before = next[name];
|
|
148
|
+
const after = handler(before, event, context);
|
|
149
|
+
if (after !== before) {
|
|
150
|
+
next = { ...next, [name]: after };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return next === state.resources ? state : { ...state, resources: next };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Record the position of a live event. */
|
|
157
|
+
function advance(
|
|
158
|
+
state: StreamState<States>,
|
|
159
|
+
id: string | undefined,
|
|
160
|
+
): StreamState<States> {
|
|
161
|
+
return state.phase === "live" && id !== undefined && id !== state.cursor
|
|
162
|
+
? { ...state, cursor: id }
|
|
163
|
+
: state;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
initial,
|
|
168
|
+
reduce(state, event) {
|
|
169
|
+
if (
|
|
170
|
+
state.phase === "live" &&
|
|
171
|
+
event.id === undefined &&
|
|
172
|
+
!UNCURSORED.has(event.event)
|
|
173
|
+
) {
|
|
174
|
+
throw new Error("Live notebook event is missing its cursor");
|
|
175
|
+
}
|
|
176
|
+
switch (event.event) {
|
|
177
|
+
case "marimo":
|
|
178
|
+
case "unknown":
|
|
179
|
+
return advance(state, event.id);
|
|
180
|
+
case "reset":
|
|
181
|
+
return { ...initial, notebookId: state.notebookId };
|
|
182
|
+
case "ready":
|
|
183
|
+
// Handlers see the phase that ended, so a snapshot's ready differs
|
|
184
|
+
// from one that ends a replay.
|
|
185
|
+
return { ...fanOut(state, event), phase: "live", cursor: event.id };
|
|
186
|
+
case "notebook":
|
|
187
|
+
return advance(identify(state, event.data.notebook.id), event.id);
|
|
188
|
+
case "notebook.removed":
|
|
189
|
+
return advance(
|
|
190
|
+
state.notebookId !== null && event.data.id !== state.notebookId
|
|
191
|
+
? state
|
|
192
|
+
: clear(state),
|
|
193
|
+
event.id,
|
|
194
|
+
);
|
|
195
|
+
default: {
|
|
196
|
+
const notebookId = notebookIdOf(event);
|
|
197
|
+
return advance(
|
|
198
|
+
fanOut(
|
|
199
|
+
notebookId === undefined ? state : identify(state, notebookId),
|
|
200
|
+
event,
|
|
201
|
+
),
|
|
202
|
+
event.id,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
resume(state) {
|
|
208
|
+
return state.phase === "snapshot"
|
|
209
|
+
? { ...initial, notebookId: state.notebookId }
|
|
210
|
+
: state;
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { createStore } from "jotai";
|
|
4
4
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
5
|
import { RuntimeState } from "@/core/kernel/RuntimeState";
|
|
6
|
+
import { attachmentsAtom } from "@/core/attachments/state";
|
|
7
|
+
import { Logger } from "@/utils/Logger";
|
|
6
8
|
import { initializeIslands } from "../bootstrap";
|
|
7
9
|
import type { IslandsPyodideBridge } from "../bridge";
|
|
8
10
|
import { islandsHydratedAtom, islandsPendingInitialRunsAtom } from "../state";
|
|
@@ -48,6 +50,30 @@ describe("initializeIslands", () => {
|
|
|
48
50
|
expect(store.get(islandsHydratedAtom)).toBe(false);
|
|
49
51
|
store.set(islandsPendingInitialRunsAtom, new Set([1, 2]));
|
|
50
52
|
|
|
53
|
+
const errors = vi.spyOn(Logger, "error");
|
|
54
|
+
consumeMessage?.({
|
|
55
|
+
sessionGeneration: 1,
|
|
56
|
+
message: JSON.stringify({
|
|
57
|
+
op: "attachments",
|
|
58
|
+
data: {
|
|
59
|
+
op: "attachments",
|
|
60
|
+
attachments: [
|
|
61
|
+
{
|
|
62
|
+
id: "a1",
|
|
63
|
+
kind: "agent",
|
|
64
|
+
name: "Pi",
|
|
65
|
+
since: "2026-10-14T00:00:00Z",
|
|
66
|
+
notebook_id: "nb-test",
|
|
67
|
+
identity: "pi-thread",
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
}) as IslandsKernelMessage["message"],
|
|
72
|
+
});
|
|
73
|
+
expect(errors).not.toHaveBeenCalled();
|
|
74
|
+
expect(store.get(attachmentsAtom)).toEqual([]);
|
|
75
|
+
expect(store.get(islandsPendingInitialRunsAtom)).toEqual(new Set([1, 2]));
|
|
76
|
+
|
|
51
77
|
consumeMessage?.(completedRun(1));
|
|
52
78
|
expect(store.get(islandsHydratedAtom)).toBe(false);
|
|
53
79
|
|
|
@@ -7,8 +7,10 @@ import { waitFor } from "../state/jotai";
|
|
|
7
7
|
import { type ConnectionStatus, ConnectionState } from "../websocket/types";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Shared application state, not the actual event-stream status.
|
|
11
|
+
*
|
|
12
|
+
* TODO(MO-8051) Expose read-only connection state and derive UI availability
|
|
13
|
+
* separately.
|
|
12
14
|
*/
|
|
13
15
|
export const connectionAtom = atom<ConnectionStatus>({
|
|
14
16
|
state: ConnectionState.NOT_STARTED,
|
package/src/core/run-app.tsx
CHANGED
|
@@ -22,7 +22,7 @@ import { getSessionId } from "./kernel/session";
|
|
|
22
22
|
import { connectionAtom } from "./network/connection";
|
|
23
23
|
import { useRequestClient } from "./network/requests";
|
|
24
24
|
import { isAppConnecting } from "./websocket/connection-utils";
|
|
25
|
-
import {
|
|
25
|
+
import { useNotebookSession } from "./websocket/useNotebookSession";
|
|
26
26
|
|
|
27
27
|
interface AppProps {
|
|
28
28
|
appConfig: AppConfig;
|
|
@@ -51,7 +51,7 @@ export const RunApp: React.FC<AppProps> = ({ appConfig, hideHeader }) => {
|
|
|
51
51
|
};
|
|
52
52
|
}, []);
|
|
53
53
|
|
|
54
|
-
const { connection, reconnect } =
|
|
54
|
+
const { connection, reconnect } = useNotebookSession({
|
|
55
55
|
autoInstantiate: true,
|
|
56
56
|
setCells: setCells,
|
|
57
57
|
sessionId: getSessionId(),
|
|
@@ -2,14 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
4
|
import { Mocks } from "@/__mocks__/common";
|
|
5
|
-
import
|
|
5
|
+
import {
|
|
6
|
+
generateSessionId,
|
|
7
|
+
isSessionId,
|
|
8
|
+
type SessionId,
|
|
9
|
+
} from "@/core/kernel/session";
|
|
6
10
|
import { Logger } from "@/utils/Logger";
|
|
7
11
|
import { RuntimeManager } from "../runtime";
|
|
8
12
|
import type { RuntimeConfig } from "../types";
|
|
9
13
|
|
|
10
14
|
// Mock the session module
|
|
11
|
-
vi.mock("@/core/kernel/session", () => ({
|
|
12
|
-
|
|
15
|
+
vi.mock("@/core/kernel/session", async (importOriginal) => ({
|
|
16
|
+
...(await importOriginal<typeof import("@/core/kernel/session")>()),
|
|
17
|
+
getSessionId: () => "test-session-id",
|
|
13
18
|
}));
|
|
14
19
|
|
|
15
20
|
vi.mock("@/utils/Logger", () => ({ Logger: Mocks.quietLogger() }));
|
|
@@ -51,6 +56,38 @@ describe("RuntimeManager", () => {
|
|
|
51
56
|
});
|
|
52
57
|
|
|
53
58
|
describe("getWsURL", () => {
|
|
59
|
+
it("uses host notebook events with the existing session and auth parameters", () => {
|
|
60
|
+
const original = window.location.href;
|
|
61
|
+
window.history.replaceState({}, "", "/?types=run");
|
|
62
|
+
try {
|
|
63
|
+
const runtime = new RuntimeManager({
|
|
64
|
+
...mockConfig,
|
|
65
|
+
url: "https://example.com/nested?file=notebook.py",
|
|
66
|
+
notebookId: "nb_123",
|
|
67
|
+
});
|
|
68
|
+
const sessionId = "s_123456";
|
|
69
|
+
if (!isSessionId(sessionId)) {
|
|
70
|
+
throw new Error("Invalid test session ID");
|
|
71
|
+
}
|
|
72
|
+
const url = runtime.getWsURL(sessionId);
|
|
73
|
+
expect({
|
|
74
|
+
endpoint: `${url.origin}${url.pathname}`,
|
|
75
|
+
params: Object.fromEntries(url.searchParams),
|
|
76
|
+
}).toEqual({
|
|
77
|
+
endpoint:
|
|
78
|
+
"wss://example.com/nested/api/marimo/v1/notebooks/nb_123/events/ws",
|
|
79
|
+
params: {
|
|
80
|
+
file: "notebook.py",
|
|
81
|
+
types: "marimo",
|
|
82
|
+
session_id: sessionId,
|
|
83
|
+
access_token: "test-token",
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
} finally {
|
|
87
|
+
window.history.replaceState({}, "", original);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
54
91
|
it("should return WebSocket URL with session ID for https", () => {
|
|
55
92
|
const runtime = new RuntimeManager(mockConfig);
|
|
56
93
|
const sessionId = "1234" as SessionId;
|
|
@@ -59,6 +96,7 @@ describe("RuntimeManager", () => {
|
|
|
59
96
|
expect(url.protocol).toBe("wss:");
|
|
60
97
|
expect(url.hostname).toBe("example.com");
|
|
61
98
|
expect(url.pathname).toBe("/ws");
|
|
99
|
+
expect(url.searchParams.has("types")).toBe(false);
|
|
62
100
|
expect(url.searchParams.get("session_id")).toBe(sessionId);
|
|
63
101
|
});
|
|
64
102
|
|
|
@@ -219,14 +257,20 @@ describe("RuntimeManager", () => {
|
|
|
219
257
|
});
|
|
220
258
|
|
|
221
259
|
describe("getSseURL", () => {
|
|
222
|
-
it("
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
260
|
+
it.each([undefined, "nb_123"])(
|
|
261
|
+
"uses the legacy session stream when notebookId is %s",
|
|
262
|
+
(notebookId) => {
|
|
263
|
+
const runtime = new RuntimeManager({
|
|
264
|
+
...mockConfig,
|
|
265
|
+
url: "https://example.com/nested?file=notebook.py",
|
|
266
|
+
notebookId,
|
|
267
|
+
});
|
|
268
|
+
const sessionId = generateSessionId();
|
|
269
|
+
expect(runtime.getSseURL(sessionId).toString()).toBe(
|
|
270
|
+
`https://example.com/nested/sse?file=notebook.py&session_id=${sessionId}`,
|
|
271
|
+
);
|
|
272
|
+
},
|
|
273
|
+
);
|
|
230
274
|
|
|
231
275
|
it("should never put the auth token in the URL, even cross-origin", () => {
|
|
232
276
|
// SSE requests can send headers, so auth travels in the
|
|
@@ -9,6 +9,8 @@ import { isStaticNotebook } from "../static/static-state";
|
|
|
9
9
|
import { isWasm } from "../wasm/utils";
|
|
10
10
|
import type { RuntimeConfig } from "./types";
|
|
11
11
|
|
|
12
|
+
const HOST_EVENT_TYPES = ["marimo"];
|
|
13
|
+
|
|
12
14
|
export class RuntimeManager {
|
|
13
15
|
private initialHealthyCheck = new Deferred<void>();
|
|
14
16
|
private config: RuntimeConfig;
|
|
@@ -154,7 +156,18 @@ export class RuntimeManager {
|
|
|
154
156
|
* The WebSocket URL of the runtime.
|
|
155
157
|
*/
|
|
156
158
|
public getWsURL(sessionId: SessionId): URL {
|
|
157
|
-
|
|
159
|
+
const searchParams = this.getSessionSearchParams(sessionId);
|
|
160
|
+
// TODO(MO-8039): Rediscover hosts and resolve notebooks on reconnect.
|
|
161
|
+
const notebookId = this.config.notebookId;
|
|
162
|
+
const path = notebookId
|
|
163
|
+
? `/api/marimo/v1/notebooks/${encodeURIComponent(notebookId)}/events/ws`
|
|
164
|
+
: "/ws";
|
|
165
|
+
const url = this.formatWsURL(path, searchParams);
|
|
166
|
+
if (notebookId) {
|
|
167
|
+
// TODO(MO-8034): Separate host options from notebook query params.
|
|
168
|
+
url.searchParams.set("types", HOST_EVENT_TYPES.join(","));
|
|
169
|
+
}
|
|
170
|
+
return url;
|
|
158
171
|
}
|
|
159
172
|
|
|
160
173
|
/**
|
package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts}
RENAMED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
4
4
|
import { Logger } from "@/utils/Logger";
|
|
5
5
|
import { WebSocketClosedReason, ConnectionState } from "../types";
|
|
6
|
-
import { classifyCloseEvent } from "../
|
|
6
|
+
import { classifyCloseEvent } from "../connection-state";
|
|
7
7
|
|
|
8
8
|
function classify(reason: string | undefined) {
|
|
9
9
|
return classifyCloseEvent({ reason });
|
|
@@ -13,8 +13,10 @@ describe("classifyCloseEvent", () => {
|
|
|
13
13
|
describe("transient closes (default branch)", () => {
|
|
14
14
|
it("retries on empty/undefined reason", () => {
|
|
15
15
|
const decision = classify(undefined);
|
|
16
|
-
expect(decision
|
|
17
|
-
|
|
16
|
+
expect(decision).toEqual({
|
|
17
|
+
kind: "retry",
|
|
18
|
+
status: { state: ConnectionState.CONNECTING },
|
|
19
|
+
});
|
|
18
20
|
});
|
|
19
21
|
|
|
20
22
|
it("treats unknown reason strings as transient and logs a warning", () => {
|
|
@@ -40,29 +42,25 @@ describe("classifyCloseEvent", () => {
|
|
|
40
42
|
// terminal or a rejected client would reconnect forever.
|
|
41
43
|
"MARIMO_UNAUTHORIZED",
|
|
42
44
|
"MARIMO_KIOSK_NOT_ALLOWED",
|
|
43
|
-
])("%s → terminal with KERNEL_DISCONNECTED
|
|
45
|
+
])("%s → terminal with KERNEL_DISCONNECTED", (reason) => {
|
|
44
46
|
const decision = classify(reason);
|
|
45
|
-
expect(decision.kind).toBe("terminal");
|
|
46
|
-
expect(decision.status).toMatchObject({
|
|
47
|
-
state: ConnectionState.CLOSED,
|
|
48
|
-
code: WebSocketClosedReason.KERNEL_DISCONNECTED,
|
|
49
|
-
});
|
|
50
47
|
expect(decision).toMatchObject({
|
|
51
48
|
kind: "terminal",
|
|
52
|
-
|
|
49
|
+
status: {
|
|
50
|
+
state: ConnectionState.CLOSED,
|
|
51
|
+
code: WebSocketClosedReason.KERNEL_DISCONNECTED,
|
|
52
|
+
},
|
|
53
53
|
});
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
-
it("MARIMO_KERNEL_STARTUP_ERROR → terminal
|
|
56
|
+
it("MARIMO_KERNEL_STARTUP_ERROR → terminal", () => {
|
|
57
57
|
const decision = classify("MARIMO_KERNEL_STARTUP_ERROR");
|
|
58
|
-
expect(decision.kind).toBe("terminal");
|
|
59
|
-
expect(decision.status).toMatchObject({
|
|
60
|
-
state: ConnectionState.CLOSED,
|
|
61
|
-
code: WebSocketClosedReason.KERNEL_STARTUP_ERROR,
|
|
62
|
-
});
|
|
63
58
|
expect(decision).toMatchObject({
|
|
64
59
|
kind: "terminal",
|
|
65
|
-
|
|
60
|
+
status: {
|
|
61
|
+
state: ConnectionState.CLOSED,
|
|
62
|
+
code: WebSocketClosedReason.KERNEL_STARTUP_ERROR,
|
|
63
|
+
},
|
|
66
64
|
});
|
|
67
65
|
});
|
|
68
66
|
});
|
package/src/core/websocket/__tests__/{useWebSocket.test.ts → createConnectionTransport.test.ts}
RENAMED
|
@@ -4,7 +4,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
|
|
4
4
|
import { BasicTransport } from "../transports/basic";
|
|
5
5
|
import { SseTransport } from "../transports/sse";
|
|
6
6
|
import { WsTransport } from "../transports/ws";
|
|
7
|
-
import { createConnectionTransport } from "../
|
|
7
|
+
import { createConnectionTransport } from "../transports/create";
|
|
8
8
|
|
|
9
9
|
vi.mock("../../wasm/utils", () => ({
|
|
10
10
|
isWasm: () => false,
|