@marimo-team/islands 0.25.3-dev140 → 0.25.3-dev142

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/{ErrorBoundary-CTJ1U1fx.js → ErrorBoundary-DxdeETpm.js} +1 -1
  2. package/dist/{code-visibility-C1jZfwPW.js → code-visibility-CO8abjLo.js} +1 -1
  3. package/dist/{common-CNv_7gvI.js → common-CbFJQhaI.js} +1 -1
  4. package/dist/{data-grid-overlay-editor-t0t17DPI.js → data-grid-overlay-editor-DBjVUS2K.js} +1 -1
  5. package/dist/{feedback-button-JzaiyZZm.js → feedback-button-CWQoIUKQ.js} +2 -2
  6. package/dist/{glide-data-editor-B3eq-1SC.js → glide-data-editor-BxT8eGip.js} +3 -3
  7. package/dist/{globals-BW8TOLYQ.js → globals-f0UxWZkr.js} +1 -1
  8. package/dist/main.js +9 -8
  9. package/dist/{number-overlay-editor-DoUnDbPE.js → number-overlay-editor-Dso2H7DV.js} +1 -1
  10. package/dist/{reveal-component-ClNn1CDl.js → reveal-component-DMglhlng.js} +2 -2
  11. package/package.json +1 -1
  12. package/src/core/host/__tests__/fixtures.ts +9 -1
  13. package/src/core/host/__tests__/static.test.ts +18 -12
  14. package/src/core/host/__tests__/wasm.test.ts +63 -60
  15. package/src/core/host/backend.ts +1 -1
  16. package/src/core/host/static.ts +10 -5
  17. package/src/core/host/wasm.ts +2 -12
  18. package/src/core/saving/save-component.tsx +12 -3
  19. package/src/core/wasm/__tests__/bridge.test.ts +177 -55
  20. package/src/core/wasm/__tests__/worker.test.ts +6 -1
  21. package/src/core/wasm/bridge.ts +48 -38
  22. package/src/core/wasm/rpc.ts +15 -2
  23. package/src/core/wasm/worker/save-worker.ts +9 -10
  24. package/src/core/wasm/worker/types.ts +6 -4
  25. package/src/core/wasm/worker/worker.ts +29 -17
  26. package/src/core/websocket/__tests__/backend.test.ts +10 -3
  27. package/src/core/websocket/backend.ts +9 -10
@@ -12,6 +12,7 @@ import type { UserConfig } from "@/core/config/config-schema";
12
12
  import type {
13
13
  ListPackagesResponse,
14
14
  PackageOperationResponse,
15
+ NotebookDocumentTransactionRequest,
15
16
  SaveNotebookRequest,
16
17
  } from "@/core/network/types";
17
18
  import { decodeUtf8 } from "@/utils/strings";
@@ -83,14 +84,6 @@ async function getNotebook() {
83
84
  }
84
85
 
85
86
  const requestHandler = createRPCRequestHandler({
86
- notebookInfo: async () => {
87
- const notebook = await getNotebook();
88
- try {
89
- return { id: notebook.id };
90
- } finally {
91
- notebook.destroy();
92
- }
93
- },
94
87
  notebookSubscribe: (request: { id: string; cursor: string | null }) => {
95
88
  let canceled = false;
96
89
  const subscribing = (async () => {
@@ -172,6 +165,17 @@ const requestHandler = createRPCRequestHandler({
172
165
  queryParameters: opts.queryParameters,
173
166
  userConfig: opts.userConfig,
174
167
  });
168
+ const notebook = session.bridge.notebook;
169
+ try {
170
+ notebook.set_persistence(async (contents, filename) => {
171
+ // Keep file operations in the execution worker from syncing stale source.
172
+ self.pyodide.FS.writeFile(filename, contents);
173
+ self.pyodide.FS.writeFile(WasmFileSystem.NOTEBOOK_FILENAME, contents);
174
+ await rpc.proxy.request.persistNotebook({ filename, contents });
175
+ });
176
+ } finally {
177
+ notebook.destroy();
178
+ }
175
179
  sessionReady.resolve(session);
176
180
  rpc.send.initializingMessage({
177
181
  message: "Initializing notebook...",
@@ -298,13 +302,21 @@ const requestHandler = createRPCRequestHandler({
298
302
  };
299
303
  },
300
304
 
301
- /**
302
- * Save the notebook
303
- */
304
- saveNotebook: async (opts: SaveNotebookRequest) => {
305
- await pyodideReadyPromise; // Make sure loading is done
306
- const { bridge } = await sessionReady.promise;
307
- await bridge.save(JSON.stringify(opts));
305
+ documentTransaction: async (request: NotebookDocumentTransactionRequest) => {
306
+ const notebook = await getNotebook();
307
+ try {
308
+ await notebook.transaction(JSON.stringify(request));
309
+ } finally {
310
+ notebook.destroy();
311
+ }
312
+ },
313
+ setNotebookLayout: async (layout: SaveNotebookRequest["layout"]) => {
314
+ const notebook = await getNotebook();
315
+ try {
316
+ await notebook.set_layout(JSON.stringify(layout));
317
+ } finally {
318
+ notebook.destroy();
319
+ }
308
320
  },
309
321
 
310
322
  /**
@@ -403,6 +415,8 @@ const rpc = createRPC<WorkerSchema, ParentSchema>({
403
415
  transportId: TRANSPORT_ID,
404
416
  }),
405
417
  requestHandler,
418
+ // Persistence can include bootstrapping the storage worker.
419
+ maxRequestTime: Infinity,
406
420
  });
407
421
 
408
422
  rpc.addMessageListener("bootstrap", (config) => runtimeConfig.resolve(config));
@@ -410,8 +424,6 @@ rpc.addMessageListener("bootstrap", (config) => runtimeConfig.resolve(config));
410
424
  rpc.send("ready", {});
411
425
 
412
426
  const namesThatRequireSync = new Set<keyof RawBridge>([
413
- "save",
414
- "save_app_config",
415
427
  "rename_file",
416
428
  "create_file_or_directory",
417
429
  "delete_file_or_directory",
@@ -5,7 +5,11 @@ import { createStore } from "jotai/vanilla";
5
5
  import { afterEach, describe, expect, it, onTestFinished, vi } from "vitest";
6
6
  import { Deferred } from "@/utils/Deferred";
7
7
  import { ConnectionState } from "../../host/connection";
8
- import { type FakeEdit, fakeBackend } from "../../host/__tests__/fixtures";
8
+ import {
9
+ type FakeEdit,
10
+ type FakeSave,
11
+ fakeBackend,
12
+ } from "../../host/__tests__/fixtures";
9
13
  import { createNotebookReplica } from "../../host/replica";
10
14
  import { createEditorBackend } from "../backend";
11
15
 
@@ -119,11 +123,12 @@ describe("createEditorBackend", () => {
119
123
  expect(fetch).not.toHaveBeenCalled();
120
124
  });
121
125
 
122
- it("edits the WASM notebook through the worker once its stream starts", async () => {
126
+ it("routes WASM commands through the worker once its stream starts", async () => {
123
127
  wasm.enabled = true;
124
128
  const edit = vi.fn<FakeEdit>();
129
+ const save = vi.fn<FakeSave>();
125
130
  const transport = vi.fn(async () => undefined);
126
- wasm.create.mockResolvedValue(fakeBackend({ transport, edit }));
131
+ wasm.create.mockResolvedValue(fakeBackend({ transport, edit, save }));
127
132
  const backend = createEditorBackend(options);
128
133
  expect(wasm.create).not.toHaveBeenCalled();
129
134
  expect(() => backend.notebook("n1")).toThrow("has not started");
@@ -136,6 +141,8 @@ describe("createEditorBackend", () => {
136
141
  expect(transport).toHaveBeenCalledWith(attempt, handlers);
137
142
  await backend.notebook("n1").edit([], { idempotencyKey: "edit-1" });
138
143
  expect(edit).toHaveBeenCalledWith("n1", [], { idempotencyKey: "edit-1" });
144
+ await backend.notebook("n2").save({ idempotencyKey: "save-1" });
145
+ expect(save).toHaveBeenCalledWith("n2", { idempotencyKey: "save-1" });
139
146
  });
140
147
 
141
148
  it("stops following WASM when the worker does not start", async () => {
@@ -92,17 +92,16 @@ function wasm(): NotebookBackend {
92
92
 
93
93
  /** Commands for a notebook the editor cannot change through its backend. */
94
94
  function uneditable(): ReturnType<NotebookBackend["notebook"]> {
95
- return {
96
- edit: async () => ({
97
- ok: false,
95
+ const unsupported: NotebookClient["save"] = async () => ({
96
+ ok: false,
97
+ status: 501,
98
+ problem: {
99
+ type: "tag:marimo.io,2026:host/unsupported-operation",
100
+ title: "Document mutations are unavailable on this connection",
98
101
  status: 501,
99
- problem: {
100
- type: "tag:marimo.io,2026:host/unsupported-operation",
101
- title: "Document editing is unavailable on this connection",
102
- status: 501,
103
- },
104
- }),
105
- };
102
+ },
103
+ });
104
+ return { edit: () => unsupported(), save: unsupported };
106
105
  }
107
106
 
108
107
  /** Choose the backend for the editor's notebook. */