@marimo-team/frontend 0.22.1-dev3 → 0.22.1-dev4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/frontend",
3
- "version": "0.22.1-dev3",
3
+ "version": "0.22.1-dev4",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -10,11 +10,17 @@
10
10
  * everywhere.
11
11
  */
12
12
 
13
+ import type { components } from "@marimo-team/marimo-api";
13
14
  import type { CellId, UIElementId } from "@/core/cells/ids";
14
15
  import type { RequestId } from "@/core/network/DeferredRequestRegistry";
15
16
  import type { VariableName } from "@/core/variables/types";
16
17
 
18
+ type WidgetModelId = components["schemas"]["WidgetModelId"];
19
+ type Base64String = components["schemas"]["Base64String"];
20
+
17
21
  export const cellId = (s: string) => s as CellId;
18
22
  export const variableName = (s: string) => s as VariableName;
19
23
  export const requestId = (s: string) => s as RequestId;
20
24
  export const uiElementId = (s: string) => s as UIElementId;
25
+ export const widgetModelId = (s: string) => s as WidgetModelId;
26
+ export const base64String = (s: string) => s as Base64String;
@@ -18,6 +18,7 @@ import { python } from "@codemirror/lang-python";
18
18
  import { EditorState } from "@codemirror/state";
19
19
  import { EditorView } from "@codemirror/view";
20
20
  import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
21
+ import { cellId } from "@/__tests__/branded";
21
22
  import type { CellHandle } from "@/components/editor/notebook-cell";
22
23
  import { adaptiveLanguageConfiguration } from "@/core/codemirror/language/extension";
23
24
  import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys";
@@ -121,7 +122,7 @@ describe("applyTransactionChanges edge cases", () => {
121
122
  apply([
122
123
  {
123
124
  type: "create-cell",
124
- cellId: "new-cell",
125
+ cellId: cellId("new-cell"),
125
126
  code: "configured",
126
127
  name: "",
127
128
  config: { hide_code: true, disabled: true, column: 1 },
@@ -141,12 +142,12 @@ describe("applyTransactionChanges edge cases", () => {
141
142
  apply([
142
143
  {
143
144
  type: "create-cell",
144
- cellId: "new-cell",
145
+ cellId: cellId("new-cell"),
145
146
  code: "new",
146
147
  name: "",
147
148
  config: {},
148
149
  },
149
- { type: "move-cell", cellId: "new-cell", before: a },
150
+ { type: "move-cell", cellId: cellId("new-cell"), before: a },
150
151
  ]);
151
152
  expect(pretty(state)).toMatchInlineSnapshot(`
152
153
  "
@@ -162,12 +163,12 @@ describe("applyTransactionChanges edge cases", () => {
162
163
  apply([
163
164
  {
164
165
  type: "create-cell",
165
- cellId: "new-cell",
166
+ cellId: cellId("new-cell"),
166
167
  code: "initial",
167
168
  name: "",
168
169
  config: {},
169
170
  },
170
- { type: "set-code", cellId: "new-cell", code: "updated" },
171
+ { type: "set-code", cellId: cellId("new-cell"), code: "updated" },
171
172
  ]);
172
173
  expect(pretty(state)).toMatchInlineSnapshot(`
173
174
  "
@@ -182,12 +183,12 @@ describe("applyTransactionChanges edge cases", () => {
182
183
  apply([
183
184
  {
184
185
  type: "create-cell",
185
- cellId: "ephemeral",
186
+ cellId: cellId("ephemeral"),
186
187
  code: "tmp",
187
188
  name: "",
188
189
  config: {},
189
190
  },
190
- { type: "delete-cell", cellId: "ephemeral" },
191
+ { type: "delete-cell", cellId: cellId("ephemeral") },
191
192
  ]);
192
193
  expect(pretty(state)).toMatchInlineSnapshot(`
193
194
  "
@@ -228,7 +229,7 @@ describe("applyTransactionChanges edge cases", () => {
228
229
  it("move-cell with missing after anchor falls back to end", () => {
229
230
  setup("a", "b");
230
231
  const [a] = state.cellIds.inOrderIds;
231
- apply([{ type: "move-cell", cellId: a, after: "nonexistent" as CellId }]);
232
+ apply([{ type: "move-cell", cellId: a, after: cellId("nonexistent") }]);
232
233
  expect(pretty(state)).toMatchInlineSnapshot(`
233
234
  "
234
235
  1: 'b'
@@ -240,7 +241,7 @@ describe("applyTransactionChanges edge cases", () => {
240
241
  it("move-cell with missing before anchor falls back to start", () => {
241
242
  setup("a", "b");
242
243
  const [, b] = state.cellIds.inOrderIds;
243
- apply([{ type: "move-cell", cellId: b, before: "nonexistent" as CellId }]);
244
+ apply([{ type: "move-cell", cellId: b, before: cellId("nonexistent") }]);
244
245
  expect(pretty(state)).toMatchInlineSnapshot(`
245
246
  "
246
247
  1: 'b'
@@ -254,8 +255,8 @@ describe("applyTransactionChanges edge cases", () => {
254
255
  apply([
255
256
  {
256
257
  type: "move-cell",
257
- cellId: "nonexistent" as CellId,
258
- after: "0" as CellId,
258
+ cellId: cellId("nonexistent"),
259
+ after: cellId("0"),
259
260
  },
260
261
  ]);
261
262
  expect(pretty(state)).toMatchInlineSnapshot(`
@@ -425,7 +425,7 @@ export function fromDocumentChanges(
425
425
  cellId,
426
426
  before,
427
427
  code: change.code,
428
- newCellId: change.cellId as CellId,
428
+ newCellId: change.cellId,
429
429
  autoFocus: false,
430
430
  hideCode: change.config?.hide_code ?? false,
431
431
  },
@@ -433,14 +433,14 @@ export function fromDocumentChanges(
433
433
  if (change.name) {
434
434
  actions.push({
435
435
  type: "updateCellName",
436
- payload: { cellId: change.cellId as CellId, name: change.name },
436
+ payload: { cellId: change.cellId, name: change.name },
437
437
  });
438
438
  }
439
439
  if (change.config?.disabled != null || change.config?.column != null) {
440
440
  actions.push({
441
441
  type: "updateCellConfig",
442
442
  payload: {
443
- cellId: change.cellId as CellId,
443
+ cellId: change.cellId,
444
444
  config: {
445
445
  ...(change.config.disabled != null && {
446
446
  disabled: change.config.disabled,
@@ -460,7 +460,7 @@ export function fromDocumentChanges(
460
460
  case "delete-cell":
461
461
  actions.push({
462
462
  type: "deleteCell",
463
- payload: { cellId: change.cellId as CellId },
463
+ payload: { cellId: change.cellId },
464
464
  });
465
465
  break;
466
466
 
@@ -471,7 +471,7 @@ export function fromDocumentChanges(
471
471
  // missing. No-ops if the cell itself doesn't exist.
472
472
  case "move-cell": {
473
473
  const ids = [...getCurrentCellIds()];
474
- const cellId = change.cellId as CellId;
474
+ const cellId = change.cellId;
475
475
  const idx = ids.indexOf(cellId);
476
476
  if (idx < 0) {
477
477
  break;
@@ -507,7 +507,7 @@ export function fromDocumentChanges(
507
507
  case "reorder-cells":
508
508
  actions.push({
509
509
  type: "setCellIds",
510
- payload: { cellIds: change.cellIds as CellId[] },
510
+ payload: { cellIds: change.cellIds },
511
511
  });
512
512
  break;
513
513
 
@@ -518,7 +518,7 @@ export function fromDocumentChanges(
518
518
  actions.push({
519
519
  type: "setCellCodes",
520
520
  payload: {
521
- ids: [change.cellId as CellId],
521
+ ids: [change.cellId],
522
522
  codes: [change.code],
523
523
  codeIsStale: true,
524
524
  },
@@ -530,7 +530,7 @@ export function fromDocumentChanges(
530
530
  case "set-name":
531
531
  actions.push({
532
532
  type: "updateCellName",
533
- payload: { cellId: change.cellId as CellId, name: change.name },
533
+ payload: { cellId: change.cellId, name: change.name },
534
534
  });
535
535
  break;
536
536
 
@@ -542,7 +542,7 @@ export function fromDocumentChanges(
542
542
  actions.push({
543
543
  type: "updateCellConfig",
544
544
  payload: {
545
- cellId: change.cellId as CellId,
545
+ cellId: change.cellId,
546
546
  config: {
547
547
  ...(change.hideCode != null && { hide_code: change.hideCode }),
548
548
  ...(change.disabled != null && { disabled: change.disabled }),
@@ -1,5 +1,15 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import type { components } from "@marimo-team/marimo-api";
2
4
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5
+ import {
6
+ cellId,
7
+ requestId,
8
+ uiElementId,
9
+ widgetModelId,
10
+ } from "@/__tests__/branded";
11
+
12
+ type Base64String = components["schemas"]["Base64String"];
3
13
 
4
14
  // Mock browser APIs before any imports
5
15
  vi.stubGlobal(
@@ -89,7 +99,7 @@ describe("IslandsPyodideBridge", () => {
89
99
  describe("sendComponentValues", () => {
90
100
  it("should include type field and token in control request", async () => {
91
101
  const request = {
92
- objectIds: ["Hbol-0"],
102
+ objectIds: [uiElementId("Hbol-0")],
93
103
  values: [58],
94
104
  };
95
105
 
@@ -108,7 +118,7 @@ describe("IslandsPyodideBridge", () => {
108
118
 
109
119
  it("should preserve all request properties", async () => {
110
120
  const request = {
111
- objectIds: ["slider-1", "slider-2"],
121
+ objectIds: [uiElementId("slider-1"), uiElementId("slider-2")],
112
122
  values: [10, 20],
113
123
  };
114
124
 
@@ -128,7 +138,7 @@ describe("IslandsPyodideBridge", () => {
128
138
  describe("sendFunctionRequest", () => {
129
139
  it("should include type field in control request", async () => {
130
140
  const request = {
131
- functionCallId: "call-123",
141
+ functionCallId: requestId("call-123"),
132
142
  namespace: "test_namespace",
133
143
  functionName: "my_function",
134
144
  args: { x: 1, y: 2 },
@@ -152,7 +162,7 @@ describe("IslandsPyodideBridge", () => {
152
162
  describe("sendRun", () => {
153
163
  it("should include type field in control request", async () => {
154
164
  const request = {
155
- cellIds: ["cell-1", "cell-2"],
165
+ cellIds: [cellId("cell-1"), cellId("cell-2")],
156
166
  codes: ["print('hello')", "print('world')"],
157
167
  };
158
168
 
@@ -170,7 +180,7 @@ describe("IslandsPyodideBridge", () => {
170
180
 
171
181
  it("should call loadPackages before putControlRequest", async () => {
172
182
  const request = {
173
- cellIds: ["cell-1"],
183
+ cellIds: [cellId("cell-1")],
174
184
  codes: ["import pandas"],
175
185
  };
176
186
 
@@ -190,13 +200,13 @@ describe("IslandsPyodideBridge", () => {
190
200
  describe("sendModelValue", () => {
191
201
  it("should include type field in control request", async () => {
192
202
  const request = {
193
- modelId: "widget-1",
203
+ modelId: widgetModelId("widget-1"),
194
204
  message: {
195
205
  method: "update" as const,
196
206
  state: { value: 42 },
197
207
  bufferPaths: [],
198
208
  },
199
- buffers: [],
209
+ buffers: [] as Base64String[],
200
210
  };
201
211
 
202
212
  await bridge.sendModelValue(request);
@@ -222,16 +232,16 @@ describe("IslandsPyodideBridge", () => {
222
232
  // Test all methods to ensure they include the type field
223
233
  await bridge.sendComponentValues({ objectIds: [], values: [] });
224
234
  await bridge.sendFunctionRequest({
225
- functionCallId: "",
235
+ functionCallId: requestId(""),
226
236
  namespace: "",
227
237
  functionName: "",
228
238
  args: {},
229
239
  });
230
240
  await bridge.sendRun({ cellIds: [], codes: [] });
231
241
  await bridge.sendModelValue({
232
- modelId: "",
242
+ modelId: widgetModelId(""),
233
243
  message: { method: "update", state: {}, bufferPaths: [] },
234
- buffers: [],
244
+ buffers: [] as Base64String[],
235
245
  });
236
246
 
237
247
  // All calls should have the type field
@@ -1,6 +1,7 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
3
  import { beforeEach, describe, expect, it, vi } from "vitest";
4
+ import { cellId, requestId, uiElementId } from "@/__tests__/branded";
4
5
  import type { RuntimeManager } from "../../runtime/runtime";
5
6
  import { createLazyRequests } from "../requests-lazy";
6
7
  import type { EditRequests, RunRequests } from "../types";
@@ -59,7 +60,7 @@ describe("createLazyRequests", () => {
59
60
  mockGetRuntimeManager,
60
61
  );
61
62
 
62
- await lazyRequests.sendRun({ cellIds: ["cell1"], codes: ["code"] });
63
+ await lazyRequests.sendRun({ cellIds: [cellId("cell1")], codes: ["code"] });
63
64
 
64
65
  expect(mockInit).toHaveBeenCalledTimes(1);
65
66
  });
@@ -70,10 +71,13 @@ describe("createLazyRequests", () => {
70
71
  mockGetRuntimeManager,
71
72
  );
72
73
 
73
- await lazyRequests.sendRun({ cellIds: ["cell1"], codes: ["code"] });
74
- await lazyRequests.sendInstantiate({ objectIds: ["obj1"], values: [] });
74
+ await lazyRequests.sendRun({ cellIds: [cellId("cell1")], codes: ["code"] });
75
+ await lazyRequests.sendInstantiate({
76
+ objectIds: [uiElementId("obj1")],
77
+ values: [],
78
+ });
75
79
  await lazyRequests.sendFunctionRequest({
76
- functionCallId: "func1",
80
+ functionCallId: requestId("func1"),
77
81
  functionName: "testFunc",
78
82
  args: {},
79
83
  namespace: "test",
@@ -89,7 +93,7 @@ describe("createLazyRequests", () => {
89
93
  mockGetRuntimeManager,
90
94
  );
91
95
 
92
- await lazyRequests.sendRun({ cellIds: ["cell1"], codes: ["code"] });
96
+ await lazyRequests.sendRun({ cellIds: [cellId("cell1")], codes: ["code"] });
93
97
 
94
98
  expect(waitForConnectionOpen).toHaveBeenCalled();
95
99
  });
@@ -100,7 +104,7 @@ describe("createLazyRequests", () => {
100
104
  mockGetRuntimeManager,
101
105
  );
102
106
 
103
- const args = { cellIds: ["cell1"], codes: ["code"] };
107
+ const args = { cellIds: [cellId("cell1")], codes: ["code"] };
104
108
  await lazyRequests.sendRun(args);
105
109
 
106
110
  expect(mockDelegate.sendRun).toHaveBeenCalledWith(args);
@@ -113,7 +117,7 @@ describe("createLazyRequests", () => {
113
117
  );
114
118
 
115
119
  const result = await lazyRequests.sendFunctionRequest({
116
- functionCallId: "func1",
120
+ functionCallId: requestId("func1"),
117
121
  functionName: "testFunc",
118
122
  args: {},
119
123
  namespace: "test",
@@ -143,7 +147,7 @@ describe("createLazyRequests", () => {
143
147
  );
144
148
 
145
149
  await expect(
146
- lazyRequests.sendRun({ cellIds: ["cell1"], codes: ["code"] }),
150
+ lazyRequests.sendRun({ cellIds: [cellId("cell1")], codes: ["code"] }),
147
151
  ).rejects.toThrow("Init failed");
148
152
  });
149
153
 
@@ -157,7 +161,7 @@ describe("createLazyRequests", () => {
157
161
  );
158
162
 
159
163
  await expect(
160
- lazyRequests.sendRun({ cellIds: ["cell1"], codes: ["code"] }),
164
+ lazyRequests.sendRun({ cellIds: [cellId("cell1")], codes: ["code"] }),
161
165
  ).rejects.toThrow("Request failed");
162
166
  });
163
167
 
@@ -169,7 +173,10 @@ describe("createLazyRequests", () => {
169
173
  );
170
174
 
171
175
  // First request with first runtime manager
172
- await lazyRequests.sendRun({ cellIds: ["cell1"], codes: ["code"] });
176
+ await lazyRequests.sendRun({
177
+ cellIds: [cellId("cell1")],
178
+ codes: ["code"],
179
+ });
173
180
  expect(mockInit).toHaveBeenCalledTimes(1);
174
181
 
175
182
  // Create a new runtime manager
@@ -187,7 +194,10 @@ describe("createLazyRequests", () => {
187
194
  );
188
195
 
189
196
  // Second request with second runtime manager
190
- await lazyRequests2.sendRun({ cellIds: ["cell2"], codes: ["code2"] });
197
+ await lazyRequests2.sendRun({
198
+ cellIds: [cellId("cell2")],
199
+ codes: ["code2"],
200
+ });
191
201
 
192
202
  // Both inits should have been called
193
203
  expect(mockInit).toHaveBeenCalledTimes(1);
@@ -201,9 +211,15 @@ describe("createLazyRequests", () => {
201
211
  );
202
212
 
203
213
  // Multiple requests
204
- await lazyRequests.sendRun({ cellIds: ["cell1"], codes: ["code"] });
205
- await lazyRequests.sendDeleteCell({ cellId: "cell2" });
206
- await lazyRequests.sendInstantiate({ objectIds: ["obj1"], values: [] });
214
+ await lazyRequests.sendRun({
215
+ cellIds: [cellId("cell1")],
216
+ codes: ["code"],
217
+ });
218
+ await lazyRequests.sendDeleteCell({ cellId: cellId("cell2") });
219
+ await lazyRequests.sendInstantiate({
220
+ objectIds: [uiElementId("obj1")],
221
+ values: [],
222
+ });
207
223
 
208
224
  // Init should only be called once
209
225
  expect(mockInit).toHaveBeenCalledTimes(1);
@@ -186,7 +186,7 @@ export function useMarimoKernelConnection(opts: {
186
186
  return;
187
187
 
188
188
  case "completion-result":
189
- AUTOCOMPLETER.resolve(msg.data.completion_id as RequestId, msg.data);
189
+ AUTOCOMPLETER.resolve(msg.data.completion_id, msg.data);
190
190
  return;
191
191
  case "function-call-result":
192
192
  FUNCTIONS_REGISTRY.resolve(msg.data.function_call_id, msg.data);
@@ -207,20 +207,14 @@ export function useMarimoKernelConnection(opts: {
207
207
  case "variables":
208
208
  setVariables(
209
209
  msg.data.variables.map((v) => ({
210
- name: v.name as VariableName,
210
+ name: v.name,
211
211
  declaredBy: v.declared_by,
212
212
  usedBy: v.used_by,
213
213
  })),
214
214
  );
215
- filterDatasetsFromVariables(
216
- msg.data.variables.map((v) => v.name as VariableName),
217
- );
218
- filterDataSourcesFromVariables(
219
- msg.data.variables.map((v) => v.name as VariableName),
220
- );
221
- filterStorageFromVariables(
222
- msg.data.variables.map((v) => v.name as VariableName),
223
- );
215
+ filterDatasetsFromVariables(msg.data.variables.map((v) => v.name));
216
+ filterDataSourcesFromVariables(msg.data.variables.map((v) => v.name));
217
+ filterStorageFromVariables(msg.data.variables.map((v) => v.name));
224
218
  return;
225
219
  case "variable-values":
226
220
  setMetadata(
package/src/utils/time.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
 
3
- export type Milliseconds = number & { __type__: "milliseconds" };
3
+ import type { TypedNumber } from "./typed";
4
4
 
5
- export type Seconds = number & { __type__: "seconds" };
5
+ export type Milliseconds = TypedNumber<"milliseconds">;
6
+
7
+ export type Seconds = TypedNumber<"seconds">;
6
8
 
7
9
  export class Time {
8
10
  private readonly ms: Milliseconds;
@@ -5,13 +5,13 @@
5
5
  * This is a compile-time type only and does not exist at runtime.
6
6
  * It is used to distinguish between different types of numbers.
7
7
  */
8
- export type TypedNumber<T> = number & { __type__: T };
8
+ export type TypedNumber<T> = number & { __brand: T };
9
9
 
10
10
  /**
11
11
  * A typed string.
12
12
  * This is a compile-time type only and does not exist at runtime.
13
13
  * It is used to distinguish between different types of strings.
14
14
  */
15
- export type TypedString<T> = string & { __type__: T };
15
+ export type TypedString<T> = string & { __brand: T };
16
16
 
17
17
  export type Identified<T> = { id: string } & T;