@marimo-team/islands 0.23.7-dev55 → 0.23.7-dev56
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/{chat-ui-DCyW3OUK.js → chat-ui-D3XBept8.js} +3 -3
- package/dist/{code-visibility-CJ7U5FE0.js → code-visibility-nPfbiA_L.js} +4 -4
- package/dist/{formats-CpgZM9BM.js → formats-Dsy9kkZu.js} +1 -1
- package/dist/{html-to-image-40ZXSWP-.js → html-to-image-CpggM7u1.js} +1 -1
- package/dist/main.js +8 -8
- package/dist/{process-output-CCeeXIBd.js → process-output-X8TR20AK.js} +1 -1
- package/dist/{reveal-component-Bopa1DsA.js → reveal-component-DuRlSS4j.js} +3 -3
- package/dist/{toDate-CJWlVNGD.js → toDate-CIpC_34u.js} +30 -17
- package/dist/{vega-component-BtvQ-Kc4.js → vega-component-cSdqoAxe.js} +2 -2
- package/package.json +1 -1
- package/src/components/editor/chrome/wrapper/footer-items/backend-status.tsx +1 -1
- package/src/core/runtime/__tests__/runtime.test.ts +38 -17
- package/src/core/runtime/runtime.ts +57 -34
- package/src/core/websocket/__tests__/useMarimoKernelConnection.hook.test.tsx +5 -4
- package/src/core/websocket/__tests__/useMarimoKernelConnection.test.ts +18 -54
- package/src/core/websocket/transports/__tests__/ws.test.ts +125 -0
- package/src/core/websocket/transports/basic.ts +1 -3
- package/src/core/websocket/transports/transport.ts +0 -1
- package/src/core/websocket/transports/ws.ts +94 -0
- package/src/core/websocket/useMarimoKernelConnection.tsx +30 -26
- package/src/core/websocket/useWebSocket.tsx +3 -18
|
@@ -6,13 +6,13 @@ import { _ as Logger, c as Objects, g as cn, l as useEventListener, t as Button
|
|
|
6
6
|
import { t as require_react } from "./react-DA-nE2FX.js";
|
|
7
7
|
import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
|
|
8
8
|
import { r as toast } from "./copy-DLf4aN7I.js";
|
|
9
|
-
import { C as AccordionContent, D as ChatBubbleIcon, En as Trash2, Ft as jotaiJsonStorage, G as cellErrorsAtom, Ht as allTablesAtom, In as ExternalLink, It as variablesAtom, Kt as getRequestClient, Lt as PluralWord, Nn as Info, Ot as moveToEndOfEditor, P as base64ToDataURL, Pn as FileText, S as Accordion, T as AccordionTrigger, Tn as Wrench, Tt as createVariableInfoElement, Ut as dataSourceConnectionsAtom, V as renderHTML, Wt as getTableType, Xt as singleFacet, Y as notebookAtom, _ as Boosts, _n as atomWithStorage, b as AIContextProvider, c as Popover, d as PopoverTrigger, f as isOutputEmpty, h as DatasourceContextProvider, jn as LoaderCircle, n as MarkdownRenderer, on as CellOutputId, s as Spinner, t as toPng, tn as ZodLocalStorage, u as PopoverContent, v as Sections, vt as displayCellName, w as AccordionItem, x as AIContextRegistry, y as contextToXml, yn as Anchor2, zn as CircleX } from "./html-to-image-
|
|
9
|
+
import { C as AccordionContent, D as ChatBubbleIcon, En as Trash2, Ft as jotaiJsonStorage, G as cellErrorsAtom, Ht as allTablesAtom, In as ExternalLink, It as variablesAtom, Kt as getRequestClient, Lt as PluralWord, Nn as Info, Ot as moveToEndOfEditor, P as base64ToDataURL, Pn as FileText, S as Accordion, T as AccordionTrigger, Tn as Wrench, Tt as createVariableInfoElement, Ut as dataSourceConnectionsAtom, V as renderHTML, Wt as getTableType, Xt as singleFacet, Y as notebookAtom, _ as Boosts, _n as atomWithStorage, b as AIContextProvider, c as Popover, d as PopoverTrigger, f as isOutputEmpty, h as DatasourceContextProvider, jn as LoaderCircle, n as MarkdownRenderer, on as CellOutputId, s as Spinner, t as toPng, tn as ZodLocalStorage, u as PopoverContent, v as Sections, vt as displayCellName, w as AccordionItem, x as AIContextRegistry, y as contextToXml, yn as Anchor2, zn as CircleX } from "./html-to-image-CpggM7u1.js";
|
|
10
10
|
import "./chunk-5FQGJX7Z-BOg95xG5.js";
|
|
11
11
|
import { u as createLucideIcon } from "./dist-D3ZI9nhS.js";
|
|
12
12
|
import { F as X, L as ChevronDown, S as logNever, t as Strings } from "./strings-BiIhGaI8.js";
|
|
13
13
|
import { a as NumberField, b as DropdownMenuTrigger, d as DropdownMenuContent, p as DropdownMenuItem, r as Input, u as DropdownMenu } from "./input-D4kjoQUB.js";
|
|
14
|
-
import { p as isUrl, v as CircleQuestionMark } from "./toDate-
|
|
15
|
-
import { a as MarimoIncomingMessageEvent, d as File$1, n as blobToString, t as processOutput, u as deserializeBlob } from "./process-output-
|
|
14
|
+
import { p as isUrl, v as CircleQuestionMark } from "./toDate-CIpC_34u.js";
|
|
15
|
+
import { a as MarimoIncomingMessageEvent, d as File$1, n as blobToString, t as processOutput, u as deserializeBlob } from "./process-output-X8TR20AK.js";
|
|
16
16
|
import "./react-dom-BWRJ_g_k.js";
|
|
17
17
|
import { t as require_jsx_runtime } from "./jsx-runtime-COBk7ree.js";
|
|
18
18
|
import { A as looseObject, B as union, C as any, D as discriminatedUnion, E as custom, H as safeParseAsync, I as record, L as strictObject, M as never, N as number, O as lazy, P as object$1, R as string, S as _null, T as boolean, V as unknown, W as toJSONSchema, b as _enum, k as literal, w as array$1, x as _instanceof } from "./zod-BWkcDORu.js";
|
|
@@ -6,14 +6,14 @@ import { _ as Logger, c as Objects, g as cn, h as Events, m as useComposedRefs,
|
|
|
6
6
|
import { t as require_react } from "./react-DA-nE2FX.js";
|
|
7
7
|
import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
|
|
8
8
|
import { n as Copy, r as toast, t as copyToClipboard } from "./copy-DLf4aN7I.js";
|
|
9
|
-
import { $ as useCellActions, $t as getTracebackInfo, A as ChevronRightIcon, An as Minus, C as AccordionContent, En as Trash2, Ft as jotaiJsonStorage, Hn as esm_default, I as base64ToUint8Array, In as ExternalLink, Jt as useRequestClient, Kt as getRequestClient, Lt as PluralWord, M as PinLeftIcon, Mt as useChromeActions, N as PinRightIcon, O as CheckIcon, Pn as FileText, Pt as adaptForLocalStorage, Qt as extractAllTracebackInfo, R as extractBase64FromDataURL, S as Accordion, St as Checkbox, T as AccordionTrigger, Tn as Wrench, Un as import_lib, V as renderHTML, Vn as Braces, Y as notebookAtom, Zt as elementContainsMarimoCellFile, _n as atomWithStorage, _t as getCellDomProps, at as AnsiUp, bn as Close$1, c as Popover, cn as SCRATCH_CELL_ID, ct as kioskModeAtom, d as PopoverTrigger, dt as outputIsLoading, et as useCellIds, g as getDatasourceContext, gt as DATA_CELL_ID, hn as jsonToTSV, ht as sanitizeHtml, j as DotFilledIcon, jn as LoaderCircle, kn as NotebookPen, kt as goToCellLine, l as PopoverClose$1, lt as useInstallAllowed, m as useExpandedOutput, mn as jsonToMarkdown, n as MarkdownRenderer, nn as filenameAtom, on as CellOutputId, pn as jsonParseWithSpecialChar, q as getCellEditorView, s as Spinner, sn as HTMLCellId, t as toPng, tt as useCellNames, u as PopoverContent, ut as viewStateAtom, vt as displayCellName, w as AccordionItem, z as isDataURLString, zn as CircleX, zt as DATA_TYPE_ICON, __tla as __tla_0 } from "./html-to-image-
|
|
9
|
+
import { $ as useCellActions, $t as getTracebackInfo, A as ChevronRightIcon, An as Minus, C as AccordionContent, En as Trash2, Ft as jotaiJsonStorage, Hn as esm_default, I as base64ToUint8Array, In as ExternalLink, Jt as useRequestClient, Kt as getRequestClient, Lt as PluralWord, M as PinLeftIcon, Mt as useChromeActions, N as PinRightIcon, O as CheckIcon, Pn as FileText, Pt as adaptForLocalStorage, Qt as extractAllTracebackInfo, R as extractBase64FromDataURL, S as Accordion, St as Checkbox, T as AccordionTrigger, Tn as Wrench, Un as import_lib, V as renderHTML, Vn as Braces, Y as notebookAtom, Zt as elementContainsMarimoCellFile, _n as atomWithStorage, _t as getCellDomProps, at as AnsiUp, bn as Close$1, c as Popover, cn as SCRATCH_CELL_ID, ct as kioskModeAtom, d as PopoverTrigger, dt as outputIsLoading, et as useCellIds, g as getDatasourceContext, gt as DATA_CELL_ID, hn as jsonToTSV, ht as sanitizeHtml, j as DotFilledIcon, jn as LoaderCircle, kn as NotebookPen, kt as goToCellLine, l as PopoverClose$1, lt as useInstallAllowed, m as useExpandedOutput, mn as jsonToMarkdown, n as MarkdownRenderer, nn as filenameAtom, on as CellOutputId, pn as jsonParseWithSpecialChar, q as getCellEditorView, s as Spinner, sn as HTMLCellId, t as toPng, tt as useCellNames, u as PopoverContent, ut as viewStateAtom, vt as displayCellName, w as AccordionItem, z as isDataURLString, zn as CircleX, zt as DATA_TYPE_ICON, __tla as __tla_0 } from "./html-to-image-CpggM7u1.js";
|
|
10
10
|
import { o as useSize, u as createLucideIcon } from "./dist-D3ZI9nhS.js";
|
|
11
11
|
import { i as createReducerAndAtoms, r as Badge } from "./useLifecycle-BF6-z62y.js";
|
|
12
12
|
import { a as ListFilter, i as Table$1, o as ChartPie, t as $896ba0a80a8f4d36$export$85fd5fdf27bacc79 } from "./useDateFormatter-B_9k85Ex.js";
|
|
13
13
|
import { t as Check } from "./check-BcUIXnUT.js";
|
|
14
14
|
import { C as $a916eb452884faea$export$b7a616150fdb9f44, F as X, L as ChevronDown, M as usePrevious$1, N as useDirection, S as logNever, T as $18f2051aff69b9bf$export$43bb16f9c6d9e3f7, _ as menuItemVariants, b as menuSubTriggerVariants, g as menuControlVariants, h as menuControlCheckVariants, i as SelectContent, l as SelectTrigger, m as menuContentCommon, o as SelectItem, p as MENU_ITEM_DISABLED, r as Select, t as Strings, u as SelectValue, v as menuLabelVariants, x as assertNever, y as menuSeparatorVariants } from "./strings-BiIhGaI8.js";
|
|
15
15
|
import { $t as Portal, G as marked, Gt as Anchor2, Jt as Content2$1, Kt as Arrow2, Qt as Label, Xt as Item2$2, Yt as Group, Zt as ItemIndicator, _ as DropdownMenuSub, a as NumberField, an as SubContent, b as DropdownMenuTrigger, c as prettyNumber, cn as Item, d as DropdownMenuContent, dn as Search, en as RadioGroup$2, fn as Circle, g as DropdownMenuSeparator, h as DropdownMenuPortal, in as Sub, l as prettyScientificNumber, ln as Root$2, m as DropdownMenuLabel, nn as Root3, o as maxFractionalDigits, on as SubTrigger, p as DropdownMenuItem, pn as ChevronRight, qt as CheckboxItem, r as Input, rn as Separator$1, s as prettyEngineeringNumber, sn as createMenuScope, tn as RadioItem, u as DropdownMenu, un as createRovingFocusGroupScope, v as DropdownMenuSubContent, y as DropdownMenuSubTrigger } from "./input-D4kjoQUB.js";
|
|
16
|
-
import { _ as isWasm, a as millisecondsInMinute, c as asRemoteURL, d as isStaticNotebook, i as millisecondsInHour, n as constructFrom, o as millisecondsInSecond, p as isUrl, r as millisecondsInDay, s as millisecondsInWeek, t as toDate } from "./toDate-
|
|
16
|
+
import { _ as isWasm, a as millisecondsInMinute, c as asRemoteURL, d as isStaticNotebook, i as millisecondsInHour, n as constructFrom, o as millisecondsInSecond, p as isUrl, r as millisecondsInDay, s as millisecondsInWeek, t as toDate } from "./toDate-CIpC_34u.js";
|
|
17
17
|
import { n as clsx_default } from "./clsx-CyyyQ8Ue.js";
|
|
18
18
|
import { t as require_react_dom } from "./react-dom-BWRJ_g_k.js";
|
|
19
19
|
import { t as require_jsx_runtime } from "./jsx-runtime-COBk7ree.js";
|
|
@@ -28,7 +28,7 @@ import { t as invariant } from "./invariant-UcGKQEhF.js";
|
|
|
28
28
|
import { c as uniqueBy, t as Arrays } from "./arrays-CldYf7p7.js";
|
|
29
29
|
import { n as once, t as memoizeLastValue } from "./once-rJImu7SE.js";
|
|
30
30
|
import { a as parser } from "./dist-nuW5EDYT.js";
|
|
31
|
-
import { a as isValid, i as AlertTitle, n as Alert, t as arrow } from "./formats-
|
|
31
|
+
import { a as isValid, i as AlertTitle, n as Alert, t as arrow } from "./formats-Dsy9kkZu.js";
|
|
32
32
|
import { n as memoize$1, t as isPropValid } from "./emotion-is-prop-valid.esm-DtW2o230.js";
|
|
33
33
|
import { t as ErrorBoundary } from "./ErrorBoundary-D3wrPNma.js";
|
|
34
34
|
import { n as formats } from "./vega-loader.browser-3_z8GoFC.js";
|
|
@@ -26003,7 +26003,7 @@ ${_}`,
|
|
|
26003
26003
|
return Logger.warn("Failed to get version from mount config"), null;
|
|
26004
26004
|
}
|
|
26005
26005
|
}
|
|
26006
|
-
marimoVersionAtom = atom(getVersionFromMountConfig() || "0.23.7-
|
|
26006
|
+
marimoVersionAtom = atom(getVersionFromMountConfig() || "0.23.7-dev56");
|
|
26007
26007
|
showCodeInRunModeAtom = atom(true);
|
|
26008
26008
|
atom(null);
|
|
26009
26009
|
var import_compiler_runtime = require_compiler_runtime();
|
|
@@ -2,7 +2,7 @@ import { s as __toESM } from "./chunk-BNovOVIE.js";
|
|
|
2
2
|
import { g as cn, r as cva } from "./button-Dj4BTre0.js";
|
|
3
3
|
import { t as require_react } from "./react-DA-nE2FX.js";
|
|
4
4
|
import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
|
|
5
|
-
import { t as toDate } from "./toDate-
|
|
5
|
+
import { t as toDate } from "./toDate-CIpC_34u.js";
|
|
6
6
|
import { t as require_jsx_runtime } from "./jsx-runtime-COBk7ree.js";
|
|
7
7
|
import { i as tableFromIPC } from "./loader-Dr8Qem8p.js";
|
|
8
8
|
function isDate(e) {
|
|
@@ -12,7 +12,7 @@ import { a as Type, c as Calendar, i as createReducerAndAtoms, o as ToggleLeft,
|
|
|
12
12
|
import { t as Check } from "./check-BcUIXnUT.js";
|
|
13
13
|
import { F as X, L as ChevronDown, M as usePrevious, N as useDirection, P as createCollection, S as logNever, a as SelectGroup, c as SelectSeparator, i as SelectContent, l as SelectTrigger, o as SelectItem, r as Select, s as SelectLabel, t as Strings, u as SelectValue, x as assertNever } from "./strings-BiIhGaI8.js";
|
|
14
14
|
import { G as marked, W as useNonce, mt as $f6c31cce2adf654f$export$45712eceda6fad21, q as useDebouncedCallback } from "./input-D4kjoQUB.js";
|
|
15
|
-
import { _ as isWasm, g as Deferred, h as waitForConnectionOpen, l as getRuntimeManager, t as toDate, v as CircleQuestionMark } from "./toDate-
|
|
15
|
+
import { _ as isWasm, g as Deferred, h as waitForConnectionOpen, l as getRuntimeManager, t as toDate, v as CircleQuestionMark } from "./toDate-CIpC_34u.js";
|
|
16
16
|
import { t as require_react_dom } from "./react-dom-BWRJ_g_k.js";
|
|
17
17
|
import { t as require_jsx_runtime } from "./jsx-runtime-COBk7ree.js";
|
|
18
18
|
import { $ as StyleNamespace, B as union, N as number, P as object, R as string, T as boolean, X as withFullScreenAsRoot, Y as MAX_HEIGHT_OFFSET, Z as withSmartCollisionBoundary, _t as Primitive, at as FocusScope, ct as Root$1, dt as Presence, et as hideOthers, ft as useControllableState, gt as createContextScope, it as Portal, k as literal, lt as useCallbackRef, mt as composeEventHandlers, ot as Branch, pt as useLayoutEffect2, rt as useFocusGuards, st as DismissableLayer, tt as Combination_default, ut as useId, vt as dispatchDiscreteCustomEvent, w as array } from "./zod-BWkcDORu.js";
|
package/dist/main.js
CHANGED
|
@@ -22,18 +22,18 @@ import { _ as Logger, c as Objects, g as cn, h as Events, i as NOT_SET, l as use
|
|
|
22
22
|
import { t as require_react } from "./react-DA-nE2FX.js";
|
|
23
23
|
import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
|
|
24
24
|
import { n as Copy, r as toast, t as copyToClipboard } from "./copy-DLf4aN7I.js";
|
|
25
|
-
import { $ as useCellActions, At as DeferredRequestRegistry, B as safeExtractSetUIElementMessageBuffers, Bn as CircleAlert, Bt as getDataTypeColor, C as AccordionContent, Cn as Root2$1, Ct as customPythonLanguageSupport, Dn as Table2, Dt as Paths, E as BorderAllIcon, En as Trash2, Et as PathBuilder, F as base64ToDataView, Fn as Eye, Ft as jotaiJsonStorage, Gt as convertStatsName, H as getMarimoExportContext, J as getCellNames, Jt as useRequestClient, K as createActions, Kt as getRequestClient, L as dataViewToBase64, Ln as Database, Mn as Layers, Nn as Info, Nt as repl, On as PaintRoller, Pn as FileText, Q as reducer, Rn as Columns2, Rt as PluralWords, S as Accordion, Sn as Item$1, St as Checkbox, T as AccordionTrigger, U as hasTrustedExportContext, V as renderHTML, Vn as Braces, Vt as require_client, W as hasRunAnyCellAtom, X as notebookOutline, Y as notebookAtom, Yt as isUninstantiated, Z as numColumnsAtom, _n as atomWithStorage, a as useLastFocusedCellId, an as parseInitialValue, bt as isInternalCellName, ct as kioskModeAtom, dn as OBJECT_ID_ATTR, dt as outputIsLoading, en as NotebookScopedLocalStorage, et as useCellIds, f as isOutputEmpty, fn as RANDOM_ID_ATTR, ft as outputIsStale, gn as atomWithReducer, i as useCellFocusActions, in as parseDataset, jn as LoaderCircle, jt as generateUUID, k as ChevronDownIcon, ln as UIElementId, mt as headingToIdentifier, nt as createCell, o as maybeAddAltairImport, ot as getInitialAppMode, p as useExpandedConsoleOutput, pn as jsonParseWithSpecialChar, pt as isErrorMime, qt as requestClientAtom, r as LazyAnyLanguageCodeMirror, rn as parseAttrValue, s as Spinner, sn as HTMLCellId, st as initialModeAtom, un as findCellId, vn as selectAtom, w as AccordionItem, wn as Trigger2, wt as MarkdownLanguageAdapter, xn as Content2, xt as normalizeName, yt as getValidName, zt as DATA_TYPE_ICON, __tla as __tla_0 } from "./html-to-image-
|
|
25
|
+
import { $ as useCellActions, At as DeferredRequestRegistry, B as safeExtractSetUIElementMessageBuffers, Bn as CircleAlert, Bt as getDataTypeColor, C as AccordionContent, Cn as Root2$1, Ct as customPythonLanguageSupport, Dn as Table2, Dt as Paths, E as BorderAllIcon, En as Trash2, Et as PathBuilder, F as base64ToDataView, Fn as Eye, Ft as jotaiJsonStorage, Gt as convertStatsName, H as getMarimoExportContext, J as getCellNames, Jt as useRequestClient, K as createActions, Kt as getRequestClient, L as dataViewToBase64, Ln as Database, Mn as Layers, Nn as Info, Nt as repl, On as PaintRoller, Pn as FileText, Q as reducer, Rn as Columns2, Rt as PluralWords, S as Accordion, Sn as Item$1, St as Checkbox, T as AccordionTrigger, U as hasTrustedExportContext, V as renderHTML, Vn as Braces, Vt as require_client, W as hasRunAnyCellAtom, X as notebookOutline, Y as notebookAtom, Yt as isUninstantiated, Z as numColumnsAtom, _n as atomWithStorage, a as useLastFocusedCellId, an as parseInitialValue, bt as isInternalCellName, ct as kioskModeAtom, dn as OBJECT_ID_ATTR, dt as outputIsLoading, en as NotebookScopedLocalStorage, et as useCellIds, f as isOutputEmpty, fn as RANDOM_ID_ATTR, ft as outputIsStale, gn as atomWithReducer, i as useCellFocusActions, in as parseDataset, jn as LoaderCircle, jt as generateUUID, k as ChevronDownIcon, ln as UIElementId, mt as headingToIdentifier, nt as createCell, o as maybeAddAltairImport, ot as getInitialAppMode, p as useExpandedConsoleOutput, pn as jsonParseWithSpecialChar, pt as isErrorMime, qt as requestClientAtom, r as LazyAnyLanguageCodeMirror, rn as parseAttrValue, s as Spinner, sn as HTMLCellId, st as initialModeAtom, un as findCellId, vn as selectAtom, w as AccordionItem, wn as Trigger2, wt as MarkdownLanguageAdapter, xn as Content2, xt as normalizeName, yt as getValidName, zt as DATA_TYPE_ICON, __tla as __tla_0 } from "./html-to-image-CpggM7u1.js";
|
|
26
26
|
import { __tla as __tla_1 } from "./chunk-5FQGJX7Z-BOg95xG5.js";
|
|
27
27
|
import { o as useSize, s as Root$2, u as createLucideIcon } from "./dist-D3ZI9nhS.js";
|
|
28
28
|
import { A as SquareFunction, C as DEFAULT_COLOR_SCHEME, D as SCALE_TYPE_DESCRIPTIONS, E as EMPTY_VALUE$1, O as TIME_UNIT_DESCRIPTIONS, S as DEFAULT_AGGREGATION, T as DEFAULT_TIME_UNIT, _ as AGGREGATION_TYPE_DESCRIPTIONS, a as AGGREGATION_FNS$1, b as COLOR_SCHEMES, c as COLOR_BY_FIELDS, d as NONE_VALUE, f as SELECTABLE_DATA_TYPES, g as TIME_UNITS, h as STRING_AGGREGATION_FNS, i as convertDataTypeToSelectable, j as ChartColumn, k as escapeFieldName, l as COMBINED_TIME_UNITS, m as SORT_TYPES, n as createSpecWithoutData, o as BIN_AGGREGATION, p as SINGLE_TIME_UNITS, r as isFieldSet, s as CHART_TYPES, t as augmentSpecWithData, u as ChartType, v as AGGREGATION_TYPE_ICON, w as DEFAULT_MAX_BINS_FACET, x as COUNT_FIELD, y as CHART_TYPE_ICON } from "./spec-hVaaZsY5.js";
|
|
29
|
-
import { $ as filtersToFilterGroup, A as contextAwarePanelOwner, At as Funnel, B as TableCell, Bt as ChevronLeft, C as prettifyRowCount, Ct as useOverflowDetection, D as ContextAwarePanelItem, Dt as EmotionCacheProvider, E as ComboboxItem, Et as HtmlOutput, F as Toggle, Ft as Code, G as generateColumns, H as TableHeader, I as Fill, It as ChevronsUpDown, J as ColumnChartContext, K as inferFieldTypes, L as Provider$1, Lt as ChevronsRight, M as isCellAwareAtom, N as SlotNames, Nt as Ellipsis, O as PANEL_TYPES, Ot as TextWrap, P as slotsController, Pt as Download, Q as usePrevious$1, R as Table, Rt as ChevronsLeft, S as prettifyRowColumnCount, St as LazyVegaEmbed, T as Combobox, Tt as Kbd, U as TableRow, V as TableHead, Vt as ArrowDownWideNarrow, W as NAMELESS_COLUMN_PREFIX, X as DelayMount, Y as ColumnChartSpecModel, Z as useIntersectionObserver, _ as downloadBlob, _t as TabsList, at as TOO_MANY_ROWS, b as Progress, bt as ChartInfoState, c as Slide, ct as Command, d as JsonOutput, dt as CommandItem, et as getPageIndexForRow, f as OutputArea, ft as CommandList, g as ADD_PRINTING_CLASS, gt as TabsContent, h as InstallPackageButton, ht as Tabs, it as SELECT_COLUMN_ID, j as contextAwarePanelType, jt as EyeOff, k as contextAwarePanelOpen, kt as GripHorizontal, l as RadioGroup, lt as CommandEmpty, m as DataTable, mt as Maps, n as marimoVersionAtom, nt as loadTableData, o as SLIDE_TYPE_OPTIONS_BY_VALUE, ot as toFieldTypes, p as OutputRenderer, pt as CommandSeparator, q as renderCellValue, r as showCodeInRunModeAtom, rt as INDEX_COLUMN_NAME, st as getMimeValues, t as useNotebookCodeAvailable, tt as loadTableAndRawData, u as RadioGroupItem, ut as CommandInput, v as downloadByURL, vt as TabsTrigger, w as useInternalStateWithSync, wt as RenderTextWithLinks, x as Filenames, xt as ChartLoadingState, y as downloadHTMLAsImage, yt as ChartErrorState, z as TableBody, zt as ChevronsDownUp, __tla as __tla_2 } from "./code-visibility-
|
|
29
|
+
import { $ as filtersToFilterGroup, A as contextAwarePanelOwner, At as Funnel, B as TableCell, Bt as ChevronLeft, C as prettifyRowCount, Ct as useOverflowDetection, D as ContextAwarePanelItem, Dt as EmotionCacheProvider, E as ComboboxItem, Et as HtmlOutput, F as Toggle, Ft as Code, G as generateColumns, H as TableHeader, I as Fill, It as ChevronsUpDown, J as ColumnChartContext, K as inferFieldTypes, L as Provider$1, Lt as ChevronsRight, M as isCellAwareAtom, N as SlotNames, Nt as Ellipsis, O as PANEL_TYPES, Ot as TextWrap, P as slotsController, Pt as Download, Q as usePrevious$1, R as Table, Rt as ChevronsLeft, S as prettifyRowColumnCount, St as LazyVegaEmbed, T as Combobox, Tt as Kbd, U as TableRow, V as TableHead, Vt as ArrowDownWideNarrow, W as NAMELESS_COLUMN_PREFIX, X as DelayMount, Y as ColumnChartSpecModel, Z as useIntersectionObserver, _ as downloadBlob, _t as TabsList, at as TOO_MANY_ROWS, b as Progress, bt as ChartInfoState, c as Slide, ct as Command, d as JsonOutput, dt as CommandItem, et as getPageIndexForRow, f as OutputArea, ft as CommandList, g as ADD_PRINTING_CLASS, gt as TabsContent, h as InstallPackageButton, ht as Tabs, it as SELECT_COLUMN_ID, j as contextAwarePanelType, jt as EyeOff, k as contextAwarePanelOpen, kt as GripHorizontal, l as RadioGroup, lt as CommandEmpty, m as DataTable, mt as Maps, n as marimoVersionAtom, nt as loadTableData, o as SLIDE_TYPE_OPTIONS_BY_VALUE, ot as toFieldTypes, p as OutputRenderer, pt as CommandSeparator, q as renderCellValue, r as showCodeInRunModeAtom, rt as INDEX_COLUMN_NAME, st as getMimeValues, t as useNotebookCodeAvailable, tt as loadTableAndRawData, u as RadioGroupItem, ut as CommandInput, v as downloadByURL, vt as TabsTrigger, w as useInternalStateWithSync, wt as RenderTextWithLinks, x as Filenames, xt as ChartLoadingState, y as downloadHTMLAsImage, yt as ChartErrorState, z as TableBody, zt as ChevronsDownUp, __tla as __tla_2 } from "./code-visibility-nPfbiA_L.js";
|
|
30
30
|
import { c as Calendar, i as createReducerAndAtoms, n as useOnUnmount, o as ToggleLeft, t as useOnMount } from "./useLifecycle-BF6-z62y.js";
|
|
31
31
|
import { n as $fb18d541ea1ad717$export$ad991b66133851cf, r as $5a387cc49350e6db$export$722debc0e56fea39, t as $896ba0a80a8f4d36$export$85fd5fdf27bacc79 } from "./useDateFormatter-B_9k85Ex.js";
|
|
32
32
|
import { t as Check } from "./check-BcUIXnUT.js";
|
|
33
33
|
import { A as Trigger$1, C as $a916eb452884faea$export$b7a616150fdb9f44, D as $b5e257d569688ac6$export$535bd6ca7f90a273, E as $18f2051aff69b9bf$export$a54013f0d02a8f82, F as X, L as ChevronDown, M as usePrevious$2, N as useDirection, P as createCollection, S as logNever, T as $18f2051aff69b9bf$export$43bb16f9c6d9e3f7, a as SelectGroup, c as SelectSeparator, d as NativeSelect, f as selectStyles, i as SelectContent, j as clamp$2, k as Icon, l as SelectTrigger, n as capitalize, o as SelectItem, r as Select, s as SelectLabel, t as Strings, u as SelectValue, w as $488c6ddbf4ef74c2$export$cc77c4ff7e8673c5, x as assertNever } from "./strings-BiIhGaI8.js";
|
|
34
34
|
import { $ as $e5be200c675c3b3a$export$aca958c65c314e6c, A as $d2b4bc8c273e7be6$export$24d547caef80ccd1, At as $c87311424ea30a05$export$fedb369cb70207f1, B as $64fa3d84918910a7$export$c62b8e45d58ddad9, Bt as $431fbd86ca7dc216$export$f21a1ffae260145a, C as $a049562f99e7db0e$export$eb2fcfdbd7ba97d4, Ct as $8ae05eaa5c114e9c$export$7f54fc3180508a52, D as $ee014567cb39d3f0$export$ff05c3ac10437e03, Dt as $c87311424ea30a05$export$78551043582a6a98, E as $ee014567cb39d3f0$export$f551688fc98f2e09, Et as $c87311424ea30a05$export$6446a186d09e379e, F as $64fa3d84918910a7$export$2881499e37b75b9a, Ft as $d4ee10de306f2510$export$b4f377a2b6254582, H as $64fa3d84918910a7$export$ef03459518577ad4, Ht as $bdb11010cef70236$export$b4cc09c592e8fdb8, I as $64fa3d84918910a7$export$29f1550f4b0d4415, It as $d4ee10de306f2510$export$cd4e5573fbe2b576, J as $d2e8511e6f209edf$export$e908e06f4b8e3402, K as useDebounceControlledState, L as $64fa3d84918910a7$export$4d86445c2cf5e3, Lt as $d4ee10de306f2510$export$e58f029f0fbfdb29, M as $01b77f81d0f07f68$export$75b6ee27786ba447, Mt as $65484d02dcb7eb3e$export$457c3d6518dd4c6f, N as $01b77f81d0f07f68$export$b04be29aa201d4f5, Nt as $3ef42575df84b30b$export$9d1611c77c2fe928, O as $514c0188e459b4c0$export$5f1af8db9871e1d6, Ot as $c87311424ea30a05$export$9ac100e40613ea10, P as $f39a9eba43920ace$export$b5d7cc18bb8d2b59, Pt as $d4ee10de306f2510$export$4282f70798064fe0, Q as $e5be200c675c3b3a$export$a763b9476acd3eb, R as $64fa3d84918910a7$export$9d4c57ee4c6ffdd8, Rt as $f4e2df6bd15f8569$export$98658e8c59125e6a, S as $3985021b0ad6602f$export$f5b8910cec6cf069, St as $e9faafb641e167db$export$90fc3a17d93f704c, T as $d3e0e05bdfcf66bd$export$c24727297075ec6a, Tt as $313b98861ee5dd6c$export$d6875122194c7b44, U as $64fa3d84918910a7$export$fabf2dc03a41866e, Ut as $bdb11010cef70236$export$f680877a34711e37, V as $64fa3d84918910a7$export$df3a06d6289f983e, Vt as $ff5963eb1fccf552$export$e08e3b67e392101e, Wt as $f0a04ccd8dbdd83b$export$e5c5a5f917a5871c, X as $e93e671b31057976$export$b8473d3665f3a75a, Y as $2baaea4c71418dea$export$294aa081a6c6f55d, Z as $e5be200c675c3b3a$export$75ee7c75d68f5b0e, _t as $9446cca9a3875146$export$7d15b64cf5a3a4c4, a as NumberField, at as $6c7bd7858deea686$export$cd11ab140839f11d, b as DropdownMenuTrigger, bt as $b4b717babfbb907b$export$bebd5a1431fec25d, c as prettyNumber, ct as $6db58dc88e78b024$export$2f817fcdc4b89ae0, d as DropdownMenuContent, dt as $9ab94262bd0047c7$export$420e68273165f4ec, et as $e5be200c675c3b3a$export$dad6ae84456c676a, f as DropdownMenuGroup, fn as Circle, ft as $3ad3f6e1647bc98d$export$80f3e147d781571c, g as DropdownMenuSeparator, gt as $ae1eeba8b9eafd08$export$5165eccb35aaadb5, ht as _class_private_field_init, i as OnBlurredInput, it as $701a24aa0da5b062$export$ea18c227d4417cc3, j as $d2b4bc8c273e7be6$export$353f5b6fc5456de1, jt as $7215afc6de606d6b$export$de79e2c695e052f3, k as $514c0188e459b4c0$export$9afb8bc826b033ea, kt as $c87311424ea30a05$export$a11b0059900ceec8, l as prettyScientificNumber, lt as $5b160d28a433310d$export$c17fa47878dc55b6, m as DropdownMenuLabel, mt as $f6c31cce2adf654f$export$45712eceda6fad21, n as DebouncedNumberInput, nt as $319e236875307eab$export$a9b970dcc4ae71a9, ot as $fca6afa0e843324b$export$87b761675e8eaa10, p as DropdownMenuItem, pn as ChevronRight, pt as $507fabe10e71c6fb$export$630ff653c5ada6a9, q as useDebouncedCallback, r as Input, rt as $f7dceffc5ad7768b$export$4e328f61c538687f, st as $fca6afa0e843324b$export$f12b703ca79dfbb1, t as DebouncedInput, tt as $e5be200c675c3b3a$export$fc1a364ae1f3ff10, u as DropdownMenu, ut as $6179b936705e76d3$export$ae780daf29e6d456, vt as $458b0a5536c1a7cf$export$40bfa8c7b0832715, w as $a049562f99e7db0e$export$f9c6924e160136d1, wt as $df56164dff5785e2$export$4338b53315abf666, x as $3985021b0ad6602f$export$37fb8590cf2c088c, xt as $99facab73266f662$export$5add1d006293d136, yt as $b4b717babfbb907b$export$4c063cf1350e6fed, z as $64fa3d84918910a7$export$c245e6201fed2f75, zt as $431fbd86ca7dc216$export$b204af158042fbac } from "./input-D4kjoQUB.js";
|
|
35
|
-
import { _ as isWasm, c as asRemoteURL, d as isStaticNotebook, f as appendQueryParams, g as Deferred, m as require_cuid2, u as getStaticVirtualFiles, v as CircleQuestionMark } from "./toDate-
|
|
36
|
-
import { a as MarimoIncomingMessageEvent, c as MarimoValueUpdateEvent, d as File, i as PythonIcon, l as createInputEvent, n as blobToString, o as MarimoValueInputEvent, r as filesToBase64, s as MarimoValueReadyEvent, t as processOutput, u as deserializeBlob } from "./process-output-
|
|
35
|
+
import { _ as isWasm, c as asRemoteURL, d as isStaticNotebook, f as appendQueryParams, g as Deferred, m as require_cuid2, u as getStaticVirtualFiles, v as CircleQuestionMark } from "./toDate-CIpC_34u.js";
|
|
36
|
+
import { a as MarimoIncomingMessageEvent, c as MarimoValueUpdateEvent, d as File, i as PythonIcon, l as createInputEvent, n as blobToString, o as MarimoValueInputEvent, r as filesToBase64, s as MarimoValueReadyEvent, t as processOutput, u as deserializeBlob } from "./process-output-X8TR20AK.js";
|
|
37
37
|
import { i as Pencil, n as Trash, r as Plus, t as BulkEdit } from "./types-Dzuoc3LN.js";
|
|
38
38
|
import { t as require_react_dom } from "./react-dom-BWRJ_g_k.js";
|
|
39
39
|
import { t as require_jsx_runtime } from "./jsx-runtime-COBk7ree.js";
|
|
@@ -59,7 +59,7 @@ import "./dist-DGAfI2rB.js";
|
|
|
59
59
|
import { r as python } from "./dist-nuW5EDYT.js";
|
|
60
60
|
import { n as minimalSetup, t as esm_default } from "./esm-CWp0KQeK.js";
|
|
61
61
|
import "./purify.es-DT70lfR0.js";
|
|
62
|
-
import { i as AlertTitle, n as Alert, r as AlertDescription } from "./formats-
|
|
62
|
+
import { i as AlertTitle, n as Alert, r as AlertDescription } from "./formats-Dsy9kkZu.js";
|
|
63
63
|
import { n as require_prop_types, t as ErrorBoundary } from "./ErrorBoundary-D3wrPNma.js";
|
|
64
64
|
import "./vega-loader.browser-3_z8GoFC.js";
|
|
65
65
|
import { a as getContainerWidth, n as vegaLoadData, o as getVegaFieldTypes, s as tooltipHandler } from "./loader-Dr8Qem8p.js";
|
|
@@ -13569,7 +13569,7 @@ let __tla = Promise.all([
|
|
|
13569
13569
|
};
|
|
13570
13570
|
}
|
|
13571
13571
|
};
|
|
13572
|
-
var LazyChatbot = import_react.lazy(() => import("./chat-ui-
|
|
13572
|
+
var LazyChatbot = import_react.lazy(() => import("./chat-ui-D3XBept8.js").then((e) => ({
|
|
13573
13573
|
default: e.Chatbot
|
|
13574
13574
|
}))), messageSchema = array(object({
|
|
13575
13575
|
id: string(),
|
|
@@ -35036,7 +35036,7 @@ ${c}
|
|
|
35036
35036
|
return true;
|
|
35037
35037
|
}
|
|
35038
35038
|
}
|
|
35039
|
-
var LazyVegaComponent = import_react.lazy(() => import("./vega-component-
|
|
35039
|
+
var LazyVegaComponent = import_react.lazy(() => import("./vega-component-cSdqoAxe.js")), VegaPlugin = class {
|
|
35040
35040
|
constructor() {
|
|
35041
35041
|
__publicField(this, "tagName", "marimo-vega");
|
|
35042
35042
|
__publicField(this, "validator", object({
|
|
@@ -44606,7 +44606,7 @@ ${c}
|
|
|
44606
44606
|
if (l && l !== "slide") return l;
|
|
44607
44607
|
if (c == null ? void 0 : c.has(e)) return "skip";
|
|
44608
44608
|
}
|
|
44609
|
-
var LazySlidesComponent = import_react.lazy(() => import("./reveal-component-
|
|
44609
|
+
var LazySlidesComponent = import_react.lazy(() => import("./reveal-component-DuRlSS4j.js"));
|
|
44610
44610
|
const SlidesLayoutPlugin = {
|
|
44611
44611
|
type: "slides",
|
|
44612
44612
|
name: "Slides",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { s as __toESM } from "./chunk-BNovOVIE.js";
|
|
2
2
|
import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
|
|
3
|
-
import { it as parseHtmlContent, rt as ansiToPlainText } from "./html-to-image-
|
|
3
|
+
import { it as parseHtmlContent, rt as ansiToPlainText } from "./html-to-image-CpggM7u1.js";
|
|
4
4
|
import { u as createLucideIcon } from "./dist-D3ZI9nhS.js";
|
|
5
5
|
import { t as Strings } from "./strings-BiIhGaI8.js";
|
|
6
6
|
import { t as require_jsx_runtime } from "./jsx-runtime-COBk7ree.js";
|
|
@@ -6,11 +6,11 @@ import { s as __toESM } from "./chunk-BNovOVIE.js";
|
|
|
6
6
|
import { _ as Logger, g as cn, h as Events, l as useEventListener, t as Button } from "./button-Dj4BTre0.js";
|
|
7
7
|
import { t as require_react } from "./react-DA-nE2FX.js";
|
|
8
8
|
import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
|
|
9
|
-
import "./html-to-image-
|
|
9
|
+
import "./html-to-image-CpggM7u1.js";
|
|
10
10
|
import "./chunk-5FQGJX7Z-BOg95xG5.js";
|
|
11
|
-
import { Ft as Code, Mt as Expand, a as DEFAULT_SLIDE_TYPE, c as Slide, i as DEFAULT_DECK_TRANSITION, jt as EyeOff, s as SlideSidebar, t as useNotebookCodeAvailable } from "./code-visibility-
|
|
11
|
+
import { Ft as Code, Mt as Expand, a as DEFAULT_SLIDE_TYPE, c as Slide, i as DEFAULT_DECK_TRANSITION, jt as EyeOff, s as SlideSidebar, t as useNotebookCodeAvailable } from "./code-visibility-nPfbiA_L.js";
|
|
12
12
|
import "./input-D4kjoQUB.js";
|
|
13
|
-
import "./toDate-
|
|
13
|
+
import "./toDate-CIpC_34u.js";
|
|
14
14
|
import "./react-dom-BWRJ_g_k.js";
|
|
15
15
|
import { t as require_jsx_runtime } from "./jsx-runtime-COBk7ree.js";
|
|
16
16
|
import "./zod-BWkcDORu.js";
|
|
@@ -512,15 +512,21 @@ var RuntimeManager = class {
|
|
|
512
512
|
get isSameOrigin() {
|
|
513
513
|
return this.httpURL.origin === window.location.origin;
|
|
514
514
|
}
|
|
515
|
-
|
|
516
|
-
|
|
515
|
+
get isServerless() {
|
|
516
|
+
return isWasm() || isIslands() || isStaticNotebook();
|
|
517
|
+
}
|
|
518
|
+
formatHttpURL({ path: e = "", searchParams: b, restrictToKnownQueryParams: x = true }) {
|
|
517
519
|
let S = this.httpURL, w = new URLSearchParams(window.location.search);
|
|
518
520
|
if (b) for (let [e2, x2] of b.entries()) S.searchParams.set(e2, x2);
|
|
519
521
|
for (let [e2, b2] of w.entries()) x && !Object.values(KnownQueryParams).includes(e2) || S.searchParams.set(e2, b2);
|
|
520
522
|
return S.pathname = `${S.pathname.replace(/\/$/, "")}/${e.replace(/^\//, "")}`, S.hash = "", S;
|
|
521
523
|
}
|
|
522
524
|
formatWsURL(e, b) {
|
|
523
|
-
let x = this.formatHttpURL(
|
|
525
|
+
let x = this.formatHttpURL({
|
|
526
|
+
path: e,
|
|
527
|
+
searchParams: b,
|
|
528
|
+
restrictToKnownQueryParams: false
|
|
529
|
+
});
|
|
524
530
|
return !this.isSameOrigin && this.config.authToken && x.searchParams.set(KnownQueryParams.accessToken, this.config.authToken), asWsUrl(x.toString());
|
|
525
531
|
}
|
|
526
532
|
getWsURL(e) {
|
|
@@ -546,26 +552,33 @@ var RuntimeManager = class {
|
|
|
546
552
|
return this.formatWsURL(`/lsp/${e}`);
|
|
547
553
|
}
|
|
548
554
|
getAiURL(e) {
|
|
549
|
-
return this.formatHttpURL(`/api/ai/${e}`);
|
|
555
|
+
return this.formatHttpURL({ path: `/api/ai/${e}` });
|
|
550
556
|
}
|
|
551
557
|
healthURL() {
|
|
552
|
-
return this.formatHttpURL("/health");
|
|
558
|
+
return this.formatHttpURL({ path: "/health" });
|
|
553
559
|
}
|
|
554
|
-
async
|
|
555
|
-
if (isWasm() || isIslands() || isStaticNotebook()) return true;
|
|
560
|
+
async fetchHealth() {
|
|
556
561
|
try {
|
|
557
|
-
|
|
558
|
-
if (e.redirected) {
|
|
559
|
-
Logger.debug(`Runtime redirected to ${e.url}`);
|
|
560
|
-
let x2 = new URL(e.url);
|
|
561
|
-
x2.pathname = x2.pathname.replace(/\/health$/, ""), this.config.url = x2.toString();
|
|
562
|
-
}
|
|
563
|
-
let x = e.ok;
|
|
564
|
-
return x && this.setDOMBaseUri(this.config.url), x;
|
|
562
|
+
return await fetch(this.healthURL().toString());
|
|
565
563
|
} catch (e) {
|
|
566
|
-
return Logger.error(`Failed to check health: ${e instanceof Error ? e.message : "Unknown error"}`, { cause: e }),
|
|
564
|
+
return Logger.error(`Failed to check health: ${e instanceof Error ? e.message : "Unknown error"}`, { cause: e }), null;
|
|
567
565
|
}
|
|
568
566
|
}
|
|
567
|
+
async reconcileFromHealth() {
|
|
568
|
+
if (this.isServerless) return true;
|
|
569
|
+
let e = await this.fetchHealth();
|
|
570
|
+
if (!e) return false;
|
|
571
|
+
if (e.redirected) {
|
|
572
|
+
Logger.debug(`Runtime redirected to ${e.url}`);
|
|
573
|
+
let x = new URL(e.url);
|
|
574
|
+
x.pathname = x.pathname.replace(/\/health$/, ""), this.config.url = x.toString();
|
|
575
|
+
}
|
|
576
|
+
return e.ok && this.setDOMBaseUri(this.config.url), e.ok;
|
|
577
|
+
}
|
|
578
|
+
async probeHealth() {
|
|
579
|
+
var _a;
|
|
580
|
+
return this.isServerless ? true : ((_a = await this.fetchHealth()) == null ? void 0 : _a.ok) ?? false;
|
|
581
|
+
}
|
|
569
582
|
setDOMBaseUri(e) {
|
|
570
583
|
e = e.split("?", 1)[0], e.endsWith("/") || (e += "/");
|
|
571
584
|
let b = document.querySelector("base");
|
|
@@ -574,7 +587,7 @@ var RuntimeManager = class {
|
|
|
574
587
|
async init(e) {
|
|
575
588
|
Logger.debug("Initializing runtime...");
|
|
576
589
|
let x = 0;
|
|
577
|
-
for (; !await this.
|
|
590
|
+
for (; !await this.reconcileFromHealth(); ) {
|
|
578
591
|
if (x >= 25) {
|
|
579
592
|
Logger.error("Failed to connect after 25 retries"), this.initialHealthyCheck.reject(/* @__PURE__ */ Error("Failed to connect after 25 retries"));
|
|
580
593
|
return;
|
|
@@ -2,7 +2,7 @@ import { s as __toESM } from "./chunk-BNovOVIE.js";
|
|
|
2
2
|
import { _ as Logger, c as Objects, g as cn, h as Events } from "./button-Dj4BTre0.js";
|
|
3
3
|
import { t as require_react } from "./react-DA-nE2FX.js";
|
|
4
4
|
import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
|
|
5
|
-
import { c as asRemoteURL, v as CircleQuestionMark } from "./toDate-
|
|
5
|
+
import { c as asRemoteURL, v as CircleQuestionMark } from "./toDate-CIpC_34u.js";
|
|
6
6
|
import "./react-dom-BWRJ_g_k.js";
|
|
7
7
|
import { t as require_jsx_runtime } from "./jsx-runtime-COBk7ree.js";
|
|
8
8
|
import "./zod-BWkcDORu.js";
|
|
@@ -11,7 +11,7 @@ import { t as Tooltip } from "./tooltip-DRaMBu06.js";
|
|
|
11
11
|
import { i as debounce_default } from "./constants-D0gkYoE2.js";
|
|
12
12
|
import { n as useTheme, w as useEvent_default } from "./useTheme-DykuNHR2.js";
|
|
13
13
|
import { s as uniq } from "./arrays-CldYf7p7.js";
|
|
14
|
-
import { a as isValid, i as AlertTitle, n as Alert, t as arrow } from "./formats-
|
|
14
|
+
import { a as isValid, i as AlertTitle, n as Alert, t as arrow } from "./formats-Dsy9kkZu.js";
|
|
15
15
|
import { n as formats } from "./vega-loader.browser-3_z8GoFC.js";
|
|
16
16
|
import { a as getContainerWidth, n as vegaLoadData, s as tooltipHandler } from "./loader-Dr8Qem8p.js";
|
|
17
17
|
import { t as useAsyncData } from "./useAsyncData-C56Khv_R.js";
|
package/package.json
CHANGED
|
@@ -275,14 +275,14 @@ describe("RuntimeManager", () => {
|
|
|
275
275
|
});
|
|
276
276
|
});
|
|
277
277
|
|
|
278
|
-
describe("
|
|
278
|
+
describe("probeHealth", () => {
|
|
279
279
|
it("should return true for successful health check", async () => {
|
|
280
280
|
global.fetch = vi.fn().mockResolvedValue({
|
|
281
281
|
ok: true,
|
|
282
282
|
});
|
|
283
283
|
|
|
284
284
|
const runtime = new RuntimeManager(mockConfig);
|
|
285
|
-
const result = await runtime.
|
|
285
|
+
const result = await runtime.probeHealth();
|
|
286
286
|
|
|
287
287
|
expect(result).toBe(true);
|
|
288
288
|
expect(fetch).toHaveBeenCalledWith("https://example.com/health");
|
|
@@ -294,7 +294,7 @@ describe("RuntimeManager", () => {
|
|
|
294
294
|
});
|
|
295
295
|
|
|
296
296
|
const runtime = new RuntimeManager(mockConfig);
|
|
297
|
-
const result = await runtime.
|
|
297
|
+
const result = await runtime.probeHealth();
|
|
298
298
|
|
|
299
299
|
expect(result).toBe(false);
|
|
300
300
|
});
|
|
@@ -304,11 +304,32 @@ describe("RuntimeManager", () => {
|
|
|
304
304
|
global.fetch = vi.fn().mockRejectedValue(error);
|
|
305
305
|
|
|
306
306
|
const runtime = new RuntimeManager(mockConfig);
|
|
307
|
-
const result = await runtime.
|
|
307
|
+
const result = await runtime.probeHealth();
|
|
308
308
|
|
|
309
309
|
expect(result).toBe(false);
|
|
310
310
|
});
|
|
311
311
|
|
|
312
|
+
it("should not mutate config.url on redirect", async () => {
|
|
313
|
+
global.fetch = vi.fn().mockResolvedValue({
|
|
314
|
+
ok: true,
|
|
315
|
+
redirected: true,
|
|
316
|
+
url: "https://sandbox.example.com/health?some_value=abc123",
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
const runtime = new RuntimeManager(
|
|
320
|
+
{
|
|
321
|
+
...mockConfig,
|
|
322
|
+
url: "https://backend.example.com/lazy?some_value=abc123",
|
|
323
|
+
},
|
|
324
|
+
true,
|
|
325
|
+
);
|
|
326
|
+
const result = await runtime.probeHealth();
|
|
327
|
+
expect(result).toBe(true);
|
|
328
|
+
expect(runtime.httpURL.hostname).toBe("backend.example.com");
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
describe("reconcileFromHealth", () => {
|
|
312
333
|
it("should update config.url on redirect, stripping /health from pathname", async () => {
|
|
313
334
|
global.fetch = vi.fn().mockResolvedValue({
|
|
314
335
|
ok: true,
|
|
@@ -323,7 +344,7 @@ describe("RuntimeManager", () => {
|
|
|
323
344
|
},
|
|
324
345
|
true, // lazy — don't call init() in constructor
|
|
325
346
|
);
|
|
326
|
-
const result = await runtime.
|
|
347
|
+
const result = await runtime.reconcileFromHealth();
|
|
327
348
|
|
|
328
349
|
expect(result).toBe(true);
|
|
329
350
|
// Should strip /health from pathname but preserve query params
|
|
@@ -342,7 +363,7 @@ describe("RuntimeManager", () => {
|
|
|
342
363
|
it("should resolve immediately if healthy", async () => {
|
|
343
364
|
const runtime = new RuntimeManager(mockConfig, true);
|
|
344
365
|
|
|
345
|
-
vi.spyOn(runtime, "
|
|
366
|
+
vi.spyOn(runtime, "reconcileFromHealth").mockResolvedValue(true);
|
|
346
367
|
runtime.init();
|
|
347
368
|
|
|
348
369
|
await expect(runtime.waitForHealthy()).resolves.toBeUndefined();
|
|
@@ -351,7 +372,7 @@ describe("RuntimeManager", () => {
|
|
|
351
372
|
it("should retry and eventually succeed", async () => {
|
|
352
373
|
const runtime = new RuntimeManager(mockConfig, true);
|
|
353
374
|
const healthySpy = vi
|
|
354
|
-
.spyOn(runtime, "
|
|
375
|
+
.spyOn(runtime, "reconcileFromHealth")
|
|
355
376
|
.mockResolvedValueOnce(false)
|
|
356
377
|
.mockResolvedValueOnce(false)
|
|
357
378
|
.mockResolvedValueOnce(true);
|
|
@@ -364,7 +385,7 @@ describe("RuntimeManager", () => {
|
|
|
364
385
|
|
|
365
386
|
it("should throw after max retries", async () => {
|
|
366
387
|
const runtime = new RuntimeManager(mockConfig, true);
|
|
367
|
-
vi.spyOn(runtime, "
|
|
388
|
+
vi.spyOn(runtime, "reconcileFromHealth").mockResolvedValue(false);
|
|
368
389
|
runtime.init({ disableRetryDelay: true });
|
|
369
390
|
|
|
370
391
|
await expect(runtime.waitForHealthy()).rejects.toThrow(
|
|
@@ -431,7 +452,7 @@ describe("RuntimeManager", () => {
|
|
|
431
452
|
// Mock failed health check
|
|
432
453
|
global.fetch = vi.fn().mockResolvedValue({ ok: false });
|
|
433
454
|
|
|
434
|
-
await runtime.
|
|
455
|
+
await runtime.reconcileFromHealth();
|
|
435
456
|
|
|
436
457
|
baseElement = document.querySelector("base");
|
|
437
458
|
expect(baseElement).toBeNull();
|
|
@@ -443,7 +464,7 @@ describe("RuntimeManager", () => {
|
|
|
443
464
|
// Mock successful health check
|
|
444
465
|
global.fetch = vi.fn().mockResolvedValue({ ok: true });
|
|
445
466
|
|
|
446
|
-
await runtime.
|
|
467
|
+
await runtime.reconcileFromHealth();
|
|
447
468
|
|
|
448
469
|
const baseElement = document.querySelector("base");
|
|
449
470
|
expect(baseElement).toBeTruthy();
|
|
@@ -461,7 +482,7 @@ describe("RuntimeManager", () => {
|
|
|
461
482
|
// Mock successful health check
|
|
462
483
|
global.fetch = vi.fn().mockResolvedValue({ ok: true });
|
|
463
484
|
|
|
464
|
-
await runtime.
|
|
485
|
+
await runtime.reconcileFromHealth();
|
|
465
486
|
|
|
466
487
|
const baseElement = document.querySelector("base");
|
|
467
488
|
expect(baseElement).toBe(existingBase); // Should be the same element
|
|
@@ -483,7 +504,7 @@ describe("RuntimeManager", () => {
|
|
|
483
504
|
// Mock successful health check
|
|
484
505
|
global.fetch = vi.fn().mockResolvedValue({ ok: true });
|
|
485
506
|
|
|
486
|
-
await runtime.
|
|
507
|
+
await runtime.reconcileFromHealth();
|
|
487
508
|
|
|
488
509
|
const baseElement = document.querySelector("base");
|
|
489
510
|
expect(baseElement).toBeTruthy();
|
|
@@ -524,11 +545,11 @@ describe("RuntimeManager", () => {
|
|
|
524
545
|
});
|
|
525
546
|
|
|
526
547
|
const wsUrl = runtime.getWsURL("test" as SessionId);
|
|
527
|
-
const httpUrl = runtime.formatHttpURL(
|
|
528
|
-
"api/test",
|
|
529
|
-
new URLSearchParams(),
|
|
530
|
-
false,
|
|
531
|
-
);
|
|
548
|
+
const httpUrl = runtime.formatHttpURL({
|
|
549
|
+
path: "api/test",
|
|
550
|
+
searchParams: new URLSearchParams(),
|
|
551
|
+
restrictToKnownQueryParams: false,
|
|
552
|
+
});
|
|
532
553
|
|
|
533
554
|
// Should preserve base URL query params
|
|
534
555
|
expect(wsUrl.searchParams.get("base_param")).toBe("existing");
|
|
@@ -44,17 +44,22 @@ export class RuntimeManager {
|
|
|
44
44
|
return this.httpURL.origin === window.location.origin;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
private get isServerless(): boolean {
|
|
48
|
+
return isWasm() || isIslands() || isStaticNotebook();
|
|
49
|
+
}
|
|
50
|
+
|
|
47
51
|
/**
|
|
48
52
|
* The base URL of the runtime.
|
|
49
53
|
*/
|
|
50
|
-
formatHttpURL(
|
|
51
|
-
path
|
|
52
|
-
searchParams
|
|
54
|
+
formatHttpURL({
|
|
55
|
+
path = "",
|
|
56
|
+
searchParams,
|
|
53
57
|
restrictToKnownQueryParams = true,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
}: {
|
|
59
|
+
path?: string;
|
|
60
|
+
searchParams?: URLSearchParams;
|
|
61
|
+
restrictToKnownQueryParams?: boolean;
|
|
62
|
+
}): URL {
|
|
58
63
|
// URL may be something like "http://localhost:8000?auth=123"
|
|
59
64
|
const baseUrl = this.httpURL;
|
|
60
65
|
const currentParams = new URLSearchParams(window.location.search);
|
|
@@ -84,11 +89,11 @@ export class RuntimeManager {
|
|
|
84
89
|
formatWsURL(path: string, searchParams?: URLSearchParams): URL {
|
|
85
90
|
// We don't restrict to known query parameters, since mo.query_params()
|
|
86
91
|
// can accept arbitrary parameters.
|
|
87
|
-
const url = this.formatHttpURL(
|
|
92
|
+
const url = this.formatHttpURL({
|
|
88
93
|
path,
|
|
89
94
|
searchParams,
|
|
90
|
-
|
|
91
|
-
);
|
|
95
|
+
restrictToKnownQueryParams: false,
|
|
96
|
+
});
|
|
92
97
|
|
|
93
98
|
// For cross-origin runtimes, pass the auth token as a query parameter.
|
|
94
99
|
// WebSocket connections cannot send custom headers (no Authorization
|
|
@@ -168,45 +173,63 @@ export class RuntimeManager {
|
|
|
168
173
|
}
|
|
169
174
|
|
|
170
175
|
getAiURL(path: "completion" | "chat"): URL {
|
|
171
|
-
return this.formatHttpURL(`/api/ai/${path}`);
|
|
176
|
+
return this.formatHttpURL({ path: `/api/ai/${path}` });
|
|
172
177
|
}
|
|
173
178
|
|
|
174
179
|
/**
|
|
175
180
|
* The URL of the health check endpoint.
|
|
176
181
|
*/
|
|
177
182
|
healthURL(): URL {
|
|
178
|
-
return this.formatHttpURL("/health");
|
|
183
|
+
return this.formatHttpURL({ path: "/health" });
|
|
179
184
|
}
|
|
180
185
|
|
|
181
|
-
async
|
|
182
|
-
// Always healthy if WASM, Islands, or a static notebook (no server)
|
|
183
|
-
if (isWasm() || isIslands() || isStaticNotebook()) {
|
|
184
|
-
return true;
|
|
185
|
-
}
|
|
186
|
-
|
|
186
|
+
private async fetchHealth(): Promise<Response | null> {
|
|
187
187
|
try {
|
|
188
|
-
|
|
189
|
-
// If there is a redirect, update the URL in the config
|
|
190
|
-
if (response.redirected) {
|
|
191
|
-
Logger.debug(`Runtime redirected to ${response.url}`);
|
|
192
|
-
// strip /health from the URL, using URL parsing to handle query params
|
|
193
|
-
const redirected = new URL(response.url);
|
|
194
|
-
redirected.pathname = redirected.pathname.replace(/\/health$/, "");
|
|
195
|
-
this.config.url = redirected.toString();
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const success = response.ok;
|
|
199
|
-
if (success) {
|
|
200
|
-
this.setDOMBaseUri(this.config.url);
|
|
201
|
-
}
|
|
202
|
-
return success;
|
|
188
|
+
return await fetch(this.healthURL().toString());
|
|
203
189
|
} catch (error) {
|
|
204
190
|
Logger.error(
|
|
205
191
|
`Failed to check health: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
206
192
|
{ cause: error },
|
|
207
193
|
);
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async reconcileFromHealth(): Promise<boolean> {
|
|
199
|
+
// Always healthy if WASM, Islands, or a static notebook (no server)
|
|
200
|
+
if (this.isServerless) {
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const response = await this.fetchHealth();
|
|
205
|
+
|
|
206
|
+
if (!response) {
|
|
208
207
|
return false;
|
|
209
208
|
}
|
|
209
|
+
|
|
210
|
+
if (response.redirected) {
|
|
211
|
+
Logger.debug(`Runtime redirected to ${response.url}`);
|
|
212
|
+
// strip /health from the URL, using URL parsing to handle query params
|
|
213
|
+
const redirected = new URL(response.url);
|
|
214
|
+
redirected.pathname = redirected.pathname.replace(/\/health$/, "");
|
|
215
|
+
this.config.url = redirected.toString();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (response.ok) {
|
|
219
|
+
this.setDOMBaseUri(this.config.url);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return response.ok;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async probeHealth(): Promise<boolean> {
|
|
226
|
+
// Always healthy if WASM, Islands, or a static notebook (no server)
|
|
227
|
+
if (this.isServerless) {
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const response = await this.fetchHealth();
|
|
232
|
+
return response?.ok ?? false;
|
|
210
233
|
}
|
|
211
234
|
|
|
212
235
|
/**
|
|
@@ -251,7 +274,7 @@ export class RuntimeManager {
|
|
|
251
274
|
const growthFactor = 1.2;
|
|
252
275
|
const maxDelay = 2000;
|
|
253
276
|
|
|
254
|
-
while (!(await this.
|
|
277
|
+
while (!(await this.reconcileFromHealth())) {
|
|
255
278
|
if (retries >= maxRetries) {
|
|
256
279
|
Logger.error(`Failed to connect after ${maxRetries} retries`);
|
|
257
280
|
this.initialHealthyCheck.reject(
|
|
@@ -35,7 +35,6 @@ import { useConnectionTransport } from "../useWebSocket";
|
|
|
35
35
|
|
|
36
36
|
interface MockTransport {
|
|
37
37
|
readyState: 0 | 1 | 2 | 3;
|
|
38
|
-
retryCount: number;
|
|
39
38
|
reconnect: ReturnType<typeof vi.fn>;
|
|
40
39
|
close: ReturnType<typeof vi.fn>;
|
|
41
40
|
send: ReturnType<typeof vi.fn>;
|
|
@@ -48,7 +47,6 @@ function makeTransport(
|
|
|
48
47
|
): MockTransport {
|
|
49
48
|
return {
|
|
50
49
|
readyState,
|
|
51
|
-
retryCount: 0,
|
|
52
50
|
reconnect: vi.fn(),
|
|
53
51
|
close: vi.fn(),
|
|
54
52
|
send: vi.fn(),
|
|
@@ -57,9 +55,12 @@ function makeTransport(
|
|
|
57
55
|
};
|
|
58
56
|
}
|
|
59
57
|
|
|
60
|
-
function makeRuntimeManager(
|
|
58
|
+
function makeRuntimeManager(
|
|
59
|
+
reconcileFromHealth = vi.fn().mockResolvedValue(true),
|
|
60
|
+
) {
|
|
61
61
|
return {
|
|
62
|
-
|
|
62
|
+
reconcileFromHealth,
|
|
63
|
+
probeHealth: vi.fn().mockResolvedValue(true),
|
|
63
64
|
getWsURL: () => new URL("ws://localhost/ws"),
|
|
64
65
|
waitForHealthy: vi.fn().mockResolvedValue(undefined),
|
|
65
66
|
isSameOrigin: true,
|
|
@@ -4,50 +4,22 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
|
|
4
4
|
import { Logger } from "@/utils/Logger";
|
|
5
5
|
import { WebSocketClosedReason, WebSocketState } from "../types";
|
|
6
6
|
import { classifyCloseEvent } from "../useMarimoKernelConnection";
|
|
7
|
-
import { MAX_RETRIES } from "../useWebSocket";
|
|
8
7
|
|
|
9
|
-
function classify(
|
|
10
|
-
|
|
11
|
-
retryCount = 0,
|
|
12
|
-
maxRetries = MAX_RETRIES,
|
|
13
|
-
) {
|
|
14
|
-
return classifyCloseEvent({ reason }, { retryCount, maxRetries });
|
|
8
|
+
function classify(reason: string | undefined) {
|
|
9
|
+
return classifyCloseEvent({ reason });
|
|
15
10
|
}
|
|
16
11
|
|
|
17
12
|
describe("classifyCloseEvent", () => {
|
|
18
13
|
describe("transient closes (default branch)", () => {
|
|
19
|
-
it("retries
|
|
20
|
-
const decision = classify(undefined
|
|
14
|
+
it("retries on empty/undefined reason", () => {
|
|
15
|
+
const decision = classify(undefined);
|
|
21
16
|
expect(decision.kind).toBe("retry");
|
|
22
17
|
expect(decision.status).toEqual({ state: WebSocketState.CONNECTING });
|
|
23
18
|
});
|
|
24
19
|
|
|
25
|
-
it("retries on each intermediate close event during a retry storm", () => {
|
|
26
|
-
for (let n = 0; n < MAX_RETRIES; n++) {
|
|
27
|
-
const decision = classify(undefined, n);
|
|
28
|
-
expect(decision.kind).toBe("retry");
|
|
29
|
-
expect(decision.status).toEqual({ state: WebSocketState.CONNECTING });
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("transitions to CLOSED when retryCount reaches maxRetries", () => {
|
|
34
|
-
const decision = classify(undefined, MAX_RETRIES);
|
|
35
|
-
expect(decision.kind).toBe("gave-up");
|
|
36
|
-
expect(decision.status).toEqual({
|
|
37
|
-
state: WebSocketState.CLOSED,
|
|
38
|
-
code: WebSocketClosedReason.KERNEL_DISCONNECTED,
|
|
39
|
-
reason: "kernel not found",
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("transitions to CLOSED when retryCount exceeds maxRetries", () => {
|
|
44
|
-
const decision = classify(undefined, MAX_RETRIES + 5);
|
|
45
|
-
expect(decision.kind).toBe("gave-up");
|
|
46
|
-
});
|
|
47
|
-
|
|
48
20
|
it("treats unknown reason strings as transient and logs a warning", () => {
|
|
49
21
|
const logger = vi.spyOn(Logger, "warn").mockImplementation(() => {});
|
|
50
|
-
const decision = classify("something-else"
|
|
22
|
+
const decision = classify("something-else");
|
|
51
23
|
expect(decision.kind).toBe("retry");
|
|
52
24
|
expect(logger).toHaveBeenCalled();
|
|
53
25
|
logger.mockRestore();
|
|
@@ -60,7 +32,7 @@ describe("classifyCloseEvent", () => {
|
|
|
60
32
|
|
|
61
33
|
describe("terminal closes (server-initiated)", () => {
|
|
62
34
|
it("MARIMO_ALREADY_CONNECTED → terminal + closeTransport, with takeover", () => {
|
|
63
|
-
const decision = classify("MARIMO_ALREADY_CONNECTED"
|
|
35
|
+
const decision = classify("MARIMO_ALREADY_CONNECTED");
|
|
64
36
|
expect(decision.kind).toBe("terminal");
|
|
65
37
|
expect(decision.status).toMatchObject({
|
|
66
38
|
state: WebSocketState.CLOSED,
|
|
@@ -79,7 +51,7 @@ describe("classifyCloseEvent", () => {
|
|
|
79
51
|
"MARIMO_NO_SESSION",
|
|
80
52
|
"MARIMO_SHUTDOWN",
|
|
81
53
|
])("%s → terminal with KERNEL_DISCONNECTED, closes transport", (reason) => {
|
|
82
|
-
const decision = classify(reason
|
|
54
|
+
const decision = classify(reason);
|
|
83
55
|
expect(decision.kind).toBe("terminal");
|
|
84
56
|
expect(decision.status).toMatchObject({
|
|
85
57
|
state: WebSocketState.CLOSED,
|
|
@@ -91,7 +63,7 @@ describe("classifyCloseEvent", () => {
|
|
|
91
63
|
});
|
|
92
64
|
|
|
93
65
|
it("MARIMO_MALFORMED_QUERY → terminal but does NOT close transport", () => {
|
|
94
|
-
const decision = classify("MARIMO_MALFORMED_QUERY"
|
|
66
|
+
const decision = classify("MARIMO_MALFORMED_QUERY");
|
|
95
67
|
expect(decision.kind).toBe("terminal");
|
|
96
68
|
expect(decision.status).toMatchObject({
|
|
97
69
|
state: WebSocketState.CLOSED,
|
|
@@ -103,7 +75,7 @@ describe("classifyCloseEvent", () => {
|
|
|
103
75
|
});
|
|
104
76
|
|
|
105
77
|
it("MARIMO_KERNEL_STARTUP_ERROR → terminal + closeTransport", () => {
|
|
106
|
-
const decision = classify("MARIMO_KERNEL_STARTUP_ERROR"
|
|
78
|
+
const decision = classify("MARIMO_KERNEL_STARTUP_ERROR");
|
|
107
79
|
expect(decision.kind).toBe("terminal");
|
|
108
80
|
expect(decision.status).toMatchObject({
|
|
109
81
|
state: WebSocketState.CLOSED,
|
|
@@ -113,25 +85,17 @@ describe("classifyCloseEvent", () => {
|
|
|
113
85
|
expect(decision.closeTransport).toBe(true);
|
|
114
86
|
}
|
|
115
87
|
});
|
|
116
|
-
|
|
117
|
-
it("terminal closes ignore retryCount entirely", () => {
|
|
118
|
-
const decision = classify("MARIMO_SHUTDOWN", 99);
|
|
119
|
-
expect(decision.kind).toBe("terminal");
|
|
120
|
-
});
|
|
121
88
|
});
|
|
122
89
|
|
|
123
|
-
describe("
|
|
124
|
-
it("
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
...Array.from({ length: MAX_RETRIES }, () => "retry"),
|
|
133
|
-
"gave-up",
|
|
134
|
-
]);
|
|
90
|
+
describe("transport exhaustion", () => {
|
|
91
|
+
it("MARIMO_TRANSPORT_EXHAUSTED → gave-up with KERNEL_DISCONNECTED", () => {
|
|
92
|
+
const decision = classify("MARIMO_TRANSPORT_EXHAUSTED");
|
|
93
|
+
expect(decision.kind).toBe("gave-up");
|
|
94
|
+
expect(decision.status).toEqual({
|
|
95
|
+
state: WebSocketState.CLOSED,
|
|
96
|
+
code: WebSocketClosedReason.KERNEL_DISCONNECTED,
|
|
97
|
+
reason: "kernel not found",
|
|
98
|
+
});
|
|
135
99
|
});
|
|
136
100
|
});
|
|
137
101
|
});
|
|
@@ -0,0 +1,125 @@
|
|
|
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
|
+
retryCount = 0;
|
|
11
|
+
readyState = WebSocket.CONNECTING;
|
|
12
|
+
constructor() {
|
|
13
|
+
innerListeners = { open: [], close: [], message: [], error: [] };
|
|
14
|
+
}
|
|
15
|
+
addEventListener(event: string, cb: (e: unknown) => void) {
|
|
16
|
+
innerListeners[event].push(cb);
|
|
17
|
+
}
|
|
18
|
+
removeEventListener(event: string, cb: (e: unknown) => void) {
|
|
19
|
+
innerListeners[event] = innerListeners[event].filter((c) => c !== cb);
|
|
20
|
+
}
|
|
21
|
+
reconnect() {}
|
|
22
|
+
close() {}
|
|
23
|
+
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
|
+
});
|
|
@@ -43,9 +43,7 @@ export class BasicTransport implements IConnectionTransport {
|
|
|
43
43
|
return WebSocket.OPEN;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
reconnect(code?: number | undefined, reason?: string | undefined): void {
|
|
46
|
+
reconnect(_code?: number | undefined, _reason?: string | undefined): void {
|
|
49
47
|
this.close();
|
|
50
48
|
this.connect();
|
|
51
49
|
return;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import ReconnectingWebSocket from "partysocket/ws";
|
|
4
|
+
import type {
|
|
5
|
+
ConnectionEvent,
|
|
6
|
+
ConnectionTransportCallback,
|
|
7
|
+
IConnectionTransport,
|
|
8
|
+
} from "./transport";
|
|
9
|
+
|
|
10
|
+
// Per-`reconnect()` retry budget. After exhaustion, partysocket stops silently;
|
|
11
|
+
// the wrapper rewrites the close-event reason to surface the give-up.
|
|
12
|
+
export const MAX_RETRIES = 10;
|
|
13
|
+
|
|
14
|
+
export const TRANSPORT_EXHAUSTED_REASON = "MARIMO_TRANSPORT_EXHAUSTED";
|
|
15
|
+
|
|
16
|
+
export class WsTransport implements IConnectionTransport {
|
|
17
|
+
private inner: ReconnectingWebSocket;
|
|
18
|
+
private closeWrappers = new WeakMap<
|
|
19
|
+
ConnectionTransportCallback<"close">,
|
|
20
|
+
ConnectionTransportCallback<"close">
|
|
21
|
+
>();
|
|
22
|
+
|
|
23
|
+
constructor(urlProvider: () => string) {
|
|
24
|
+
this.inner = new ReconnectingWebSocket(urlProvider, undefined, {
|
|
25
|
+
maxRetries: MAX_RETRIES,
|
|
26
|
+
debug: false,
|
|
27
|
+
startClosed: true,
|
|
28
|
+
// long timeout — the server can become slow when many notebooks are open.
|
|
29
|
+
connectionTimeout: 10_000,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get readyState(): WebSocket["readyState"] {
|
|
34
|
+
return this.inner.readyState as WebSocket["readyState"];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
reconnect(code?: number, reason?: string): void {
|
|
38
|
+
this.inner.reconnect(code, reason);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
close(): void {
|
|
42
|
+
this.inner.close();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
send(data: string | ArrayBuffer | Blob | ArrayBufferView): void {
|
|
46
|
+
this.inner.send(data);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
addEventListener<T extends ConnectionEvent>(
|
|
50
|
+
event: T,
|
|
51
|
+
callback: ConnectionTransportCallback<T>,
|
|
52
|
+
): void {
|
|
53
|
+
if (event === "close") {
|
|
54
|
+
const userCb = callback as ConnectionTransportCallback<"close">;
|
|
55
|
+
// Match native EventTarget dedupe: a second addEventListener with the
|
|
56
|
+
// same listener is a no-op. Without this, repeated adds leak wrappers
|
|
57
|
+
// on the inner socket and double-fire on close.
|
|
58
|
+
if (this.closeWrappers.has(userCb)) return;
|
|
59
|
+
const wrapper: ConnectionTransportCallback<"close"> = (e) => {
|
|
60
|
+
if (this.inner.retryCount >= MAX_RETRIES) {
|
|
61
|
+
userCb(
|
|
62
|
+
new CloseEvent("close", {
|
|
63
|
+
code: e.code,
|
|
64
|
+
reason: TRANSPORT_EXHAUSTED_REASON,
|
|
65
|
+
wasClean: e.wasClean,
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
} else {
|
|
69
|
+
userCb(e);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
this.closeWrappers.set(userCb, wrapper);
|
|
73
|
+
this.inner.addEventListener("close", wrapper);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
this.inner.addEventListener(event, callback as never);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
removeEventListener<T extends ConnectionEvent>(
|
|
80
|
+
event: T,
|
|
81
|
+
callback: ConnectionTransportCallback<T>,
|
|
82
|
+
): void {
|
|
83
|
+
if (event === "close") {
|
|
84
|
+
const userCb = callback as ConnectionTransportCallback<"close">;
|
|
85
|
+
const wrapper = this.closeWrappers.get(userCb);
|
|
86
|
+
if (wrapper) {
|
|
87
|
+
this.closeWrappers.delete(userCb);
|
|
88
|
+
this.inner.removeEventListener("close", wrapper);
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
this.inner.removeEventListener(event, callback as never);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -11,10 +11,8 @@ import type {
|
|
|
11
11
|
NotificationMessageData,
|
|
12
12
|
NotificationPayload,
|
|
13
13
|
} from "@/core/kernel/messages";
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
useConnectionTransport,
|
|
17
|
-
} from "@/core/websocket/useWebSocket";
|
|
14
|
+
import { TRANSPORT_EXHAUSTED_REASON } from "@/core/websocket/transports/ws";
|
|
15
|
+
import { useConnectionTransport } from "@/core/websocket/useWebSocket";
|
|
18
16
|
import { renderHTML } from "@/plugins/core/RenderHTML";
|
|
19
17
|
import {
|
|
20
18
|
handleWidgetMessage,
|
|
@@ -80,16 +78,27 @@ import {
|
|
|
80
78
|
|
|
81
79
|
const SUPPORTS_LAZY_KERNELS = true;
|
|
82
80
|
|
|
81
|
+
// All MARIMO_* reasons except TRANSPORT_EXHAUSTED are emitted by the backend
|
|
82
|
+
// (marimo/_server/api/endpoints/ws_endpoint.py and ws/*.py). Keep in sync with
|
|
83
|
+
// the backend literals.
|
|
84
|
+
export type CloseReason =
|
|
85
|
+
| "MARIMO_ALREADY_CONNECTED"
|
|
86
|
+
| "MARIMO_WRONG_KERNEL_ID"
|
|
87
|
+
| "MARIMO_NO_FILE_KEY"
|
|
88
|
+
| "MARIMO_NO_SESSION_ID"
|
|
89
|
+
| "MARIMO_NO_SESSION"
|
|
90
|
+
| "MARIMO_SHUTDOWN"
|
|
91
|
+
| "MARIMO_MALFORMED_QUERY"
|
|
92
|
+
| "MARIMO_KERNEL_STARTUP_ERROR"
|
|
93
|
+
| typeof TRANSPORT_EXHAUSTED_REASON;
|
|
94
|
+
|
|
83
95
|
export type CloseDecision =
|
|
84
96
|
| { kind: "terminal"; status: ConnectionStatus; closeTransport: boolean }
|
|
85
97
|
| { kind: "gave-up"; status: ConnectionStatus }
|
|
86
98
|
| { kind: "retry"; status: ConnectionStatus };
|
|
87
99
|
|
|
88
|
-
export function classifyCloseEvent(
|
|
89
|
-
event
|
|
90
|
-
context: { retryCount: number; maxRetries: number },
|
|
91
|
-
): CloseDecision {
|
|
92
|
-
switch (event.reason) {
|
|
100
|
+
export function classifyCloseEvent(event: { reason?: string }): CloseDecision {
|
|
101
|
+
switch (event.reason as CloseReason | undefined) {
|
|
93
102
|
case "MARIMO_ALREADY_CONNECTED":
|
|
94
103
|
return {
|
|
95
104
|
kind: "terminal",
|
|
@@ -101,6 +110,15 @@ export function classifyCloseEvent(
|
|
|
101
110
|
},
|
|
102
111
|
closeTransport: true,
|
|
103
112
|
};
|
|
113
|
+
case TRANSPORT_EXHAUSTED_REASON:
|
|
114
|
+
return {
|
|
115
|
+
kind: "gave-up",
|
|
116
|
+
status: {
|
|
117
|
+
state: WebSocketState.CLOSED,
|
|
118
|
+
code: WebSocketClosedReason.KERNEL_DISCONNECTED,
|
|
119
|
+
reason: "kernel not found",
|
|
120
|
+
},
|
|
121
|
+
};
|
|
104
122
|
case "MARIMO_WRONG_KERNEL_ID":
|
|
105
123
|
case "MARIMO_NO_FILE_KEY":
|
|
106
124
|
case "MARIMO_NO_SESSION_ID":
|
|
@@ -144,18 +162,7 @@ export function classifyCloseEvent(
|
|
|
144
162
|
logNever(event.reason as never);
|
|
145
163
|
}
|
|
146
164
|
}
|
|
147
|
-
|
|
148
|
-
// CLOSED so callers can detect the give-up.
|
|
149
|
-
if (context.retryCount >= context.maxRetries) {
|
|
150
|
-
return {
|
|
151
|
-
kind: "gave-up",
|
|
152
|
-
status: {
|
|
153
|
-
state: WebSocketState.CLOSED,
|
|
154
|
-
code: WebSocketClosedReason.KERNEL_DISCONNECTED,
|
|
155
|
-
reason: "kernel not found",
|
|
156
|
-
},
|
|
157
|
-
};
|
|
158
|
-
}
|
|
165
|
+
|
|
159
166
|
return {
|
|
160
167
|
kind: "retry",
|
|
161
168
|
status: { state: WebSocketState.CONNECTING },
|
|
@@ -440,7 +447,7 @@ export function useMarimoKernelConnection(opts: {
|
|
|
440
447
|
}
|
|
441
448
|
shouldTryReconnecting.current = true;
|
|
442
449
|
setConnection({ state: WebSocketState.CONNECTING });
|
|
443
|
-
const healthy = await runtimeManager.
|
|
450
|
+
const healthy = await runtimeManager.reconcileFromHealth();
|
|
444
451
|
if (!healthy) {
|
|
445
452
|
shouldTryReconnecting.current = false;
|
|
446
453
|
setConnection({
|
|
@@ -512,10 +519,7 @@ export function useMarimoKernelConnection(opts: {
|
|
|
512
519
|
*/
|
|
513
520
|
onClose: (e) => {
|
|
514
521
|
Logger.warn("WebSocket closed", e.code, e.reason);
|
|
515
|
-
const decision = classifyCloseEvent(e
|
|
516
|
-
retryCount: ws.retryCount,
|
|
517
|
-
maxRetries: MAX_RETRIES,
|
|
518
|
-
});
|
|
522
|
+
const decision = classifyCloseEvent(e);
|
|
519
523
|
setConnection(decision.status);
|
|
520
524
|
if (decision.kind === "terminal" && decision.closeTransport) {
|
|
521
525
|
ws.close(); // close to prevent reconnecting
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import ReconnectingWebSocket from "partysocket/ws";
|
|
4
3
|
import { useEffect, useState } from "react";
|
|
5
4
|
import { Logger } from "@/utils/Logger";
|
|
6
5
|
import { createPyodideConnection } from "../wasm/bridge";
|
|
7
6
|
import { isWasm } from "../wasm/utils";
|
|
8
7
|
import { BasicTransport } from "./transports/basic";
|
|
8
|
+
import { WsTransport } from "./transports/ws";
|
|
9
9
|
import type { IConnectionTransport } from "./transports/transport";
|
|
10
10
|
|
|
11
11
|
interface UseConnectionTransportOptions {
|
|
@@ -18,10 +18,6 @@ interface UseConnectionTransportOptions {
|
|
|
18
18
|
onError: (event: WebSocketEventMap["error"]) => void;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
// Per-`reconnect()` retry budget for partysocket. After exhaustion, partysocket
|
|
22
|
-
// stops silently; treat `retryCount >= MAX_RETRIES` as the give-up signal.
|
|
23
|
-
export const MAX_RETRIES = 10;
|
|
24
|
-
|
|
25
21
|
function createConnectionTransport(
|
|
26
22
|
options: Pick<UseConnectionTransportOptions, "url" | "static">,
|
|
27
23
|
): IConnectionTransport {
|
|
@@ -31,19 +27,8 @@ function createConnectionTransport(
|
|
|
31
27
|
if (isWasm()) {
|
|
32
28
|
return createPyodideConnection();
|
|
33
29
|
}
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
const urlProvider = options.url; // We don't call the URL provider now since it may change (i.e. if the runtime redirects)
|
|
37
|
-
// Cast needed: ReconnectingWebSocket types readyState as `number`
|
|
38
|
-
// but IConnectionTransport expects `0 | 1 | 2 | 3`
|
|
39
|
-
return new ReconnectingWebSocket(urlProvider, undefined, {
|
|
40
|
-
maxRetries: MAX_RETRIES,
|
|
41
|
-
debug: false,
|
|
42
|
-
startClosed: true,
|
|
43
|
-
// long timeout -- the server can become slow when many notebooks
|
|
44
|
-
// are open.
|
|
45
|
-
connectionTimeout: 10_000,
|
|
46
|
-
}) as unknown as IConnectionTransport;
|
|
30
|
+
// urlProvider is passed lazily; it may change after a runtime redirect.
|
|
31
|
+
return new WsTransport(options.url);
|
|
47
32
|
}
|
|
48
33
|
|
|
49
34
|
/**
|