@marimo-team/marimo-export 0.0.0 → 0.0.2

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.
@@ -0,0 +1,1561 @@
1
+ import { t as defineBlobAssetLoader } from "../loader-CDfnJ5Yb.mjs";
2
+ //#region ../loader-anywidget/src/runtime/value-types.ts
3
+ function isPrimitiveWithPrototype(value, prototype) {
4
+ const boxed = Object(value);
5
+ return boxed !== value && Object.getPrototypeOf(boxed) === prototype;
6
+ }
7
+ function isStringValue(value) {
8
+ return isPrimitiveWithPrototype(value, String.prototype);
9
+ }
10
+ function isNumberValue(value) {
11
+ return isPrimitiveWithPrototype(value, Number.prototype);
12
+ }
13
+ function isBooleanValue(value) {
14
+ return isPrimitiveWithPrototype(value, Boolean.prototype);
15
+ }
16
+ function isRecordValue(value) {
17
+ return value !== null && !Array.isArray(value) && Object.prototype.toString.call(value) === "[object Object]";
18
+ }
19
+ function isPropertyOwner(value) {
20
+ return value !== null && Object(value) === value;
21
+ }
22
+ function isCallableValue(value) {
23
+ try {
24
+ Function.prototype.toString.call(value);
25
+ return true;
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+ //#endregion
31
+ //#region ../loader-anywidget/src/payload.ts
32
+ const ANYWIDGET_SCHEMA = "marimo-export.anywidget.v1";
33
+ function embeddedFileKey(url) {
34
+ return url.startsWith("./@file/") ? url.slice(1) : url;
35
+ }
36
+ const UNSAFE_PATH_KEYS = /* @__PURE__ */ new Set([
37
+ "__proto__",
38
+ "constructor",
39
+ "prototype"
40
+ ]);
41
+ const MAX_DIAGNOSTIC_LENGTH = 2048;
42
+ const MAX_UNEXPECTED_FIELDS = 8;
43
+ const MAX_DATA_URL_MEDIA_TYPE_BYTES = 1024;
44
+ const MAX_EXTERNAL_ESM_URL_BYTES = 8192;
45
+ function parseAnyWidgetPayload(value) {
46
+ const payload = record(parseSnapshotValue(value), "AnyWidget payload");
47
+ exactKeys(payload, [
48
+ "schema",
49
+ "rootModelId",
50
+ "files",
51
+ "modelNotifications"
52
+ ], "payload");
53
+ if (payload.schema !== "marimo-export.anywidget.v1") throw new TypeError(`AnyWidget payload schema must be ${JSON.stringify(ANYWIDGET_SCHEMA)}.`);
54
+ const rootModelId = nonEmptyString(payload.rootModelId, "rootModelId");
55
+ if (rootModelId !== "model-0") throw new TypeError("AnyWidget payload rootModelId must be \"model-0\".");
56
+ const files = parseFiles(payload.files);
57
+ const notifications = array(payload.modelNotifications, "modelNotifications");
58
+ const models = /* @__PURE__ */ new Map();
59
+ for (const [index, value] of notifications.entries()) {
60
+ const notification = record(value, `modelNotifications[${index}]`);
61
+ exactKeys(notification, [
62
+ "op",
63
+ "model_id",
64
+ "message"
65
+ ], `modelNotifications[${index}]`);
66
+ if (notification.op !== "model-lifecycle") throw new TypeError(`modelNotifications[${index}].op must be "model-lifecycle".`);
67
+ const id = nonEmptyString(notification.model_id, `modelNotifications[${index}].model_id`);
68
+ const expectedId = `model-${index}`;
69
+ if (id !== expectedId) throw new TypeError(`modelNotifications[${index}].model_id must be ${JSON.stringify(expectedId)}.`);
70
+ const message = record(notification.message, `modelNotifications[${index}].message`);
71
+ exactKeys(message, [
72
+ "method",
73
+ "state",
74
+ "buffer_paths",
75
+ "buffers",
76
+ "esm_spec"
77
+ ], `modelNotifications[${index}].message`);
78
+ if (message.method !== "open") throw new TypeError(`AnyWidget model ${JSON.stringify(id)} must contain an open message.`);
79
+ const state = cloneJsonObject(record(message.state, `AnyWidget model ${JSON.stringify(id)} state`));
80
+ const bufferPaths = parseBufferPaths(message.buffer_paths, id);
81
+ const buffers = parseBuffers(message.buffers, id);
82
+ if (bufferPaths.length !== buffers.length) throw new TypeError(`AnyWidget model ${JSON.stringify(id)} has ${bufferPaths.length} buffer paths and ${buffers.length} buffers.`);
83
+ for (const [bufferIndex, path] of bufferPaths.entries()) setBuffer(state, path, buffers[bufferIndex], id);
84
+ const esmSpec = parseEsmSpec(message.esm_spec, files, id);
85
+ models.set(id, Object.freeze({
86
+ id,
87
+ state,
88
+ esmSpec
89
+ }));
90
+ }
91
+ const root = models.get(rootModelId);
92
+ if (root === void 0) throw new TypeError(`AnyWidget root model ${JSON.stringify(rootModelId)} is missing.`);
93
+ if (root.esmSpec === void 0) throw new TypeError(`AnyWidget root model ${JSON.stringify(rootModelId)} has no ESM spec.`);
94
+ const reachable = collectReachableModels(rootModelId, models);
95
+ if (reachable.size !== models.size) {
96
+ const unrelated = [];
97
+ let unrelatedCount = 0;
98
+ for (const id of models.keys()) {
99
+ if (reachable.has(id)) continue;
100
+ unrelatedCount += 1;
101
+ if (unrelated.length < MAX_UNEXPECTED_FIELDS) unrelated.push(id);
102
+ }
103
+ throw new TypeError(truncateDiagnostic(`AnyWidget payload contains models outside the root closure: ${renderUnexpectedFields(unrelated, unrelatedCount)}.`));
104
+ }
105
+ return Object.freeze({
106
+ rootModelId,
107
+ files,
108
+ models
109
+ });
110
+ }
111
+ function cloneModelState(state) {
112
+ return structuredClone(state);
113
+ }
114
+ function readonlyModelState(state) {
115
+ return deepFreeze(cloneModelState(state));
116
+ }
117
+ function parseDataUrl(value, path) {
118
+ const comma = value.indexOf(",");
119
+ if (comma === -1) throw new TypeError(`${path} is a malformed data URL.`);
120
+ const mediaTypeEnd = dataUrlMediaTypeEnd(value, comma, path);
121
+ return {
122
+ body: value.slice(comma + 1),
123
+ isBase64: hasBase64Parameter(value, mediaTypeEnd + 1, comma),
124
+ mediaType: value.slice(5, mediaTypeEnd) || "text/plain"
125
+ };
126
+ }
127
+ function parseFiles(value) {
128
+ const input = record(value, "files");
129
+ const files = Object.create(null);
130
+ for (const [path, dataUrl] of Object.entries(input)) {
131
+ if (path.length === 0) throw new TypeError("AnyWidget file paths must be non-empty strings.");
132
+ const fileLabel = `AnyWidget file ${quoteField(path)}`;
133
+ if (!isStringValue(dataUrl) || !dataUrl.startsWith("data:")) throw new TypeError(`${fileLabel} must contain a data URL.`);
134
+ validateDataUrl(dataUrl, fileLabel);
135
+ files[path] = dataUrl;
136
+ }
137
+ return Object.freeze(files);
138
+ }
139
+ function parseEsmSpec(value, files, modelId) {
140
+ if (value === null) return void 0;
141
+ const spec = record(value, `AnyWidget model ${JSON.stringify(modelId)} ESM spec`);
142
+ exactKeys(spec, ["url", "hash"], `AnyWidget model ${JSON.stringify(modelId)} ESM spec`);
143
+ const url = nonEmptyString(spec.url, `AnyWidget model ${JSON.stringify(modelId)} ESM URL`);
144
+ const hash = nonEmptyString(spec.hash, `AnyWidget model ${JSON.stringify(modelId)} ESM hash`);
145
+ if (!Object.hasOwn(files, embeddedFileKey(url))) {
146
+ if (hasDataUrlPrefix(url)) {
147
+ validateDataUrl(url, `AnyWidget model ${JSON.stringify(modelId)} ESM URL`);
148
+ return Object.freeze({
149
+ url,
150
+ hash
151
+ });
152
+ }
153
+ if (!hasUtf8ByteLengthAtMost(url, MAX_EXTERNAL_ESM_URL_BYTES)) throw new TypeError(`AnyWidget model ${JSON.stringify(modelId)} contains an invalid ESM URL ${quoteField(url)}.`);
154
+ let parsed;
155
+ try {
156
+ parsed = new URL(url);
157
+ } catch (error) {
158
+ throw new TypeError(`AnyWidget model ${JSON.stringify(modelId)} references missing virtual file ${quoteField(url)}.`, { cause: error });
159
+ }
160
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new TypeError(`AnyWidget model ${JSON.stringify(modelId)} uses incompatible ESM URL protocol ${quoteField(parsed.protocol)}.`);
161
+ }
162
+ return Object.freeze({
163
+ url,
164
+ hash
165
+ });
166
+ }
167
+ function parseBufferPaths(value, modelId) {
168
+ const input = array(value, `AnyWidget model ${JSON.stringify(modelId)} buffer_paths`);
169
+ const seen = /* @__PURE__ */ new Set();
170
+ return Object.freeze(input.map((value, index) => {
171
+ const path = array(value, `AnyWidget model ${JSON.stringify(modelId)} buffer path ${index}`);
172
+ if (path.length === 0) throw new TypeError(`AnyWidget model ${JSON.stringify(modelId)} has an empty buffer path.`);
173
+ const parsed = path.map((token) => {
174
+ if (isNumberValue(token) && Number.isSafeInteger(token) && token >= 0) return token;
175
+ if (isStringValue(token) && !UNSAFE_PATH_KEYS.has(token)) return token;
176
+ throw new TypeError(`AnyWidget model ${JSON.stringify(modelId)} has an invalid buffer path token.`);
177
+ });
178
+ const identity = JSON.stringify(parsed);
179
+ if (seen.has(identity)) throw new TypeError(`AnyWidget model ${JSON.stringify(modelId)} repeats buffer path ${quoteField(identity)}.`);
180
+ seen.add(identity);
181
+ return Object.freeze(parsed);
182
+ }));
183
+ }
184
+ function parseBuffers(value, modelId) {
185
+ return Object.freeze(array(value, `AnyWidget model ${JSON.stringify(modelId)} buffers`).map((buffer, index) => {
186
+ if (!isStringValue(buffer) || !isCanonicalBase64(buffer)) throw new TypeError(`AnyWidget model ${JSON.stringify(modelId)} buffer ${index} is not canonical base64.`);
187
+ return base64ToDataView(buffer);
188
+ }));
189
+ }
190
+ function base64ToDataView(value) {
191
+ const binary = atob(value);
192
+ const bytes = new Uint8Array(binary.length);
193
+ for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
194
+ return new DataView(bytes.buffer);
195
+ }
196
+ function validateDataUrl(value, path) {
197
+ const { body, isBase64 } = parseDataUrl(value, path);
198
+ if (isBase64) {
199
+ if (!isCanonicalBase64(body)) throw new TypeError(`${path} contains malformed base64 data.`);
200
+ return;
201
+ }
202
+ if (!isValidPercentData(body)) throw new TypeError(`${path} contains malformed percent-encoded data.`);
203
+ }
204
+ function dataUrlMediaTypeEnd(value, comma, path) {
205
+ let byteLength = 0;
206
+ for (let index = 5; index < comma; index += 1) {
207
+ const codePoint = value.codePointAt(index);
208
+ if (codePoint === 59) return index;
209
+ byteLength += utf8CodePointBytes(codePoint);
210
+ if (byteLength > MAX_DATA_URL_MEDIA_TYPE_BYTES) throw new TypeError(`${path} data URL media type exceeds ${MAX_DATA_URL_MEDIA_TYPE_BYTES} UTF-8 bytes.`);
211
+ if (codePoint > 65535) index += 1;
212
+ }
213
+ return comma;
214
+ }
215
+ function hasBase64Parameter(value, start, end) {
216
+ let segmentStart = start;
217
+ for (let index = start; index <= end; index += 1) {
218
+ if (index < end && value.charCodeAt(index) !== 59) continue;
219
+ if (isBase64Parameter(value, segmentStart, index)) return true;
220
+ segmentStart = index + 1;
221
+ }
222
+ return false;
223
+ }
224
+ function isBase64Parameter(value, start, end) {
225
+ return end - start === 6 && (value.charCodeAt(start) | 32) === 98 && (value.charCodeAt(start + 1) | 32) === 97 && (value.charCodeAt(start + 2) | 32) === 115 && (value.charCodeAt(start + 3) | 32) === 101 && value.charCodeAt(start + 4) === 54 && value.charCodeAt(start + 5) === 52;
226
+ }
227
+ function isCanonicalBase64(value) {
228
+ if (value.length % 4 !== 0) return false;
229
+ let contentEnd = value.length;
230
+ if (contentEnd > 0 && value.charCodeAt(contentEnd - 1) === 61) {
231
+ contentEnd -= 1;
232
+ if (contentEnd > 0 && value.charCodeAt(contentEnd - 1) === 61) contentEnd -= 1;
233
+ }
234
+ for (let index = 0; index < contentEnd; index += 1) {
235
+ const codeUnit = value.charCodeAt(index);
236
+ if ((codeUnit < 65 || codeUnit > 90) && (codeUnit < 97 || codeUnit > 122) && (codeUnit < 48 || codeUnit > 57) && codeUnit !== 43 && codeUnit !== 47) return false;
237
+ }
238
+ const padding = value.length - contentEnd;
239
+ if (padding > 0) {
240
+ if ((base64Value(value.charCodeAt(contentEnd - 1)) & (padding === 2 ? 15 : 3)) !== 0) return false;
241
+ }
242
+ return true;
243
+ }
244
+ function base64Value(codeUnit) {
245
+ if (codeUnit >= 65 && codeUnit <= 90) return codeUnit - 65;
246
+ if (codeUnit >= 97 && codeUnit <= 122) return codeUnit - 97 + 26;
247
+ if (codeUnit >= 48 && codeUnit <= 57) return codeUnit - 48 + 52;
248
+ return codeUnit === 43 ? 62 : 63;
249
+ }
250
+ function isValidPercentData(value) {
251
+ if (value.indexOf("%") === -1) return true;
252
+ let index = 0;
253
+ let remaining = 0;
254
+ let continuationMin = 128;
255
+ let continuationMax = 191;
256
+ while (index < value.length) {
257
+ const codePoint = value.codePointAt(index);
258
+ if (codePoint > 127) {
259
+ if (remaining > 0) return false;
260
+ index += codePoint > 65535 ? 2 : 1;
261
+ continue;
262
+ }
263
+ let byte;
264
+ if (codePoint === 37) {
265
+ if (index + 2 >= value.length) return false;
266
+ const high = hexValue(value.charCodeAt(index + 1));
267
+ const low = hexValue(value.charCodeAt(index + 2));
268
+ if (high < 0 || low < 0) return false;
269
+ byte = high * 16 + low;
270
+ index += 3;
271
+ } else {
272
+ byte = codePoint;
273
+ index += 1;
274
+ }
275
+ if (remaining > 0) {
276
+ if (byte < continuationMin || byte > continuationMax) return false;
277
+ remaining -= 1;
278
+ continuationMin = 128;
279
+ continuationMax = 191;
280
+ continue;
281
+ }
282
+ if (byte <= 127) continue;
283
+ if (byte >= 194 && byte <= 223) remaining = 1;
284
+ else if (byte === 224) {
285
+ remaining = 2;
286
+ continuationMin = 160;
287
+ } else if (byte >= 225 && byte <= 236 || byte >= 238 && byte <= 239) remaining = 2;
288
+ else if (byte === 237) {
289
+ remaining = 2;
290
+ continuationMax = 159;
291
+ } else if (byte === 240) {
292
+ remaining = 3;
293
+ continuationMin = 144;
294
+ } else if (byte >= 241 && byte <= 243) remaining = 3;
295
+ else if (byte === 244) {
296
+ remaining = 3;
297
+ continuationMax = 143;
298
+ } else return false;
299
+ }
300
+ return remaining === 0;
301
+ }
302
+ function hexValue(codeUnit) {
303
+ if (codeUnit >= 48 && codeUnit <= 57) return codeUnit - 48;
304
+ if (codeUnit >= 65 && codeUnit <= 70) return codeUnit - 55;
305
+ if (codeUnit >= 97 && codeUnit <= 102) return codeUnit - 87;
306
+ return -1;
307
+ }
308
+ function hasDataUrlPrefix(value) {
309
+ return value.length >= 5 && (value.charCodeAt(0) | 32) === 100 && (value.charCodeAt(1) | 32) === 97 && (value.charCodeAt(2) | 32) === 116 && (value.charCodeAt(3) | 32) === 97 && value.charCodeAt(4) === 58;
310
+ }
311
+ function hasUtf8ByteLengthAtMost(value, maximum) {
312
+ let byteLength = 0;
313
+ for (let index = 0; index < value.length; index += 1) {
314
+ const codePoint = value.codePointAt(index);
315
+ byteLength += utf8CodePointBytes(codePoint);
316
+ if (byteLength > maximum) return false;
317
+ if (codePoint > 65535) index += 1;
318
+ }
319
+ return true;
320
+ }
321
+ function utf8CodePointBytes(codePoint) {
322
+ if (codePoint <= 127) return 1;
323
+ if (codePoint <= 2047) return 2;
324
+ if (codePoint <= 65535) return 3;
325
+ return 4;
326
+ }
327
+ function setBuffer(state, path, buffer, modelId) {
328
+ let target = state;
329
+ for (const token of path.slice(0, -1)) target = ownChild(target, token, modelId, path);
330
+ const finalToken = path.at(-1);
331
+ if (isNumberValue(finalToken)) {
332
+ if (!Array.isArray(target) || finalToken >= target.length || !Object.hasOwn(target, finalToken)) invalidBufferPath(modelId, path);
333
+ defineBuffer(target, finalToken, buffer);
334
+ return;
335
+ }
336
+ if (!isModelState(target)) invalidBufferPath(modelId, path);
337
+ defineBuffer(target, finalToken, buffer);
338
+ }
339
+ function defineBuffer(target, token, buffer) {
340
+ Object.defineProperty(target, token, {
341
+ value: buffer,
342
+ configurable: true,
343
+ enumerable: true,
344
+ writable: true
345
+ });
346
+ }
347
+ function ownChild(target, token, modelId, path) {
348
+ if (isNumberValue(token)) {
349
+ if (!Array.isArray(target) || token >= target.length || !Object.hasOwn(target, token)) invalidBufferPath(modelId, path);
350
+ const child = target[token];
351
+ if (child === void 0) invalidBufferPath(modelId, path);
352
+ return child;
353
+ }
354
+ if (!isModelState(target) || !Object.hasOwn(target, token)) invalidBufferPath(modelId, path);
355
+ const child = target[token];
356
+ if (child === void 0) invalidBufferPath(modelId, path);
357
+ return child;
358
+ }
359
+ function invalidBufferPath(modelId, _path) {
360
+ throw new TypeError(`AnyWidget model ${JSON.stringify(modelId)} has a buffer path that does not target existing state.`);
361
+ }
362
+ function collectReachableModels(rootModelId, models) {
363
+ const reachable = /* @__PURE__ */ new Set();
364
+ const pending = [rootModelId];
365
+ while (pending.length > 0) {
366
+ const id = pending.pop();
367
+ if (reachable.has(id)) continue;
368
+ const model = models.get(id);
369
+ if (model === void 0) throw new TypeError(`AnyWidget model reference ${quoteField(id)} is unresolved.`);
370
+ reachable.add(id);
371
+ for (const reference of findWidgetReferences(model.state)) pending.push(reference);
372
+ }
373
+ return reachable;
374
+ }
375
+ function findWidgetReferences(value) {
376
+ const references = [];
377
+ const visit = (value) => {
378
+ if (isModelString(value)) {
379
+ for (const prefix of ["anywidget:", "IPY_MODEL_"]) {
380
+ if (!value.startsWith(prefix)) continue;
381
+ const id = value.slice(prefix.length);
382
+ if (id.length === 0) throw new TypeError("AnyWidget state contains an empty model reference.");
383
+ references.push(id);
384
+ return;
385
+ }
386
+ return;
387
+ }
388
+ if (Array.isArray(value)) {
389
+ for (const child of value) visit(child);
390
+ return;
391
+ }
392
+ if (isModelState(value) && !(value instanceof DataView)) for (const child of Object.values(value)) visit(child);
393
+ };
394
+ visit(value);
395
+ return references;
396
+ }
397
+ function cloneJsonObject(value) {
398
+ return structuredClone(value);
399
+ }
400
+ function deepFreeze(value) {
401
+ if (ArrayBuffer.isView(value)) return value;
402
+ if (Array.isArray(value)) {
403
+ for (const child of value) deepFreeze(child);
404
+ return Object.freeze(value);
405
+ }
406
+ if (isModelState(value)) {
407
+ for (const child of Object.values(value)) deepFreeze(child);
408
+ return Object.freeze(value);
409
+ }
410
+ return value;
411
+ }
412
+ function exactKeys(value, expected, path) {
413
+ const allowed = new Set(expected);
414
+ const unexpected = [];
415
+ let unexpectedCount = 0;
416
+ for (const key of Object.keys(value)) {
417
+ if (allowed.has(key)) continue;
418
+ unexpectedCount += 1;
419
+ if (unexpected.length < MAX_UNEXPECTED_FIELDS) unexpected.push(key);
420
+ }
421
+ const missing = expected.filter((key) => !Object.hasOwn(value, key));
422
+ if (unexpectedCount > 0 || missing.length > 0) {
423
+ const missingText = missing.length === 0 ? "none" : missing.map(quoteField).join(", ");
424
+ const unexpectedText = renderUnexpectedFields(unexpected, unexpectedCount);
425
+ throw new TypeError(truncateDiagnostic(`${path} fields are invalid. Missing: ${missingText}. Unexpected: ${unexpectedText}.`));
426
+ }
427
+ }
428
+ function renderUnexpectedFields(fields, total) {
429
+ if (total === 0) return "none";
430
+ const rendered = fields.map(quoteField).join(", ");
431
+ const omitted = total - fields.length;
432
+ return omitted === 0 ? rendered : `${rendered}, ... (+${omitted} more)`;
433
+ }
434
+ function quoteField(value) {
435
+ let body = "";
436
+ const bodyLimit = 123;
437
+ let truncated = false;
438
+ for (let index = 0; index < value.length; index += 1) {
439
+ const codeUnit = value.charCodeAt(index);
440
+ let token;
441
+ if (codeUnit === 34 || codeUnit === 92) token = `\\${value[index]}`;
442
+ else if (codeUnit <= 31 || codeUnit >= 127 && codeUnit <= 159 || codeUnit >= 55296 && codeUnit <= 57343) {
443
+ if (codeUnit >= 55296 && codeUnit <= 56319 && index + 1 < value.length && value.charCodeAt(index + 1) >= 56320 && value.charCodeAt(index + 1) <= 57343) {
444
+ token = value.slice(index, index + 2);
445
+ index += 1;
446
+ } else token = `\\u${codeUnit.toString(16).padStart(4, "0")}`;
447
+ } else token = value[index];
448
+ if (body.length + token.length > bodyLimit) {
449
+ truncated = true;
450
+ break;
451
+ }
452
+ body += token;
453
+ }
454
+ return `"${body}${truncated ? "..." : ""}"`;
455
+ }
456
+ function truncateDiagnostic(value) {
457
+ if (value.length <= MAX_DIAGNOSTIC_LENGTH) return value;
458
+ return `${value.slice(0, 2045)}...`;
459
+ }
460
+ function nonEmptyString(value, path) {
461
+ if (!isStringValue(value) || value.length === 0) throw new TypeError(`${path} must be a non-empty string.`);
462
+ return value;
463
+ }
464
+ function array(value, path) {
465
+ if (!Array.isArray(value)) throw new TypeError(`${path} must be an array.`);
466
+ return value;
467
+ }
468
+ function record(value, path) {
469
+ if (!isModelState(value)) throw new TypeError(`${path} must be an object.`);
470
+ return value;
471
+ }
472
+ function isModelState(value) {
473
+ return isRecordValue(value) && !(value instanceof DataView);
474
+ }
475
+ function isModelString(value) {
476
+ return isStringValue(value);
477
+ }
478
+ function parseSnapshotValue(value) {
479
+ if (value === null) return null;
480
+ if (isBooleanValue(value) || isNumberValue(value) || isStringValue(value)) return value;
481
+ if (Array.isArray(value)) return value.map(parseSnapshotValue);
482
+ if (!isRecordValue(value)) throw new TypeError("AnyWidget payload must contain JSON values.");
483
+ const parsed = {};
484
+ for (const [key, child] of Object.entries(value)) parsed[key] = parseSnapshotValue(child);
485
+ return parsed;
486
+ }
487
+ //#endregion
488
+ //#region ../loader-anywidget/src/runtime/abort.ts
489
+ function combineAbortSignals(signals) {
490
+ const sources = [...new Set(signals)];
491
+ const candidate = Object.getOwnPropertyDescriptor(AbortSignal, "any")?.value;
492
+ if (isCallableValue(candidate)) return candidate(sources);
493
+ const controller = new AbortController();
494
+ const listeners = /* @__PURE__ */ new Map();
495
+ const settle = (signal) => {
496
+ for (const [source, listener] of listeners) source.removeEventListener("abort", listener);
497
+ listeners.clear();
498
+ controller.abort(signal.reason);
499
+ };
500
+ for (const signal of sources) {
501
+ if (signal.aborted) {
502
+ settle(signal);
503
+ break;
504
+ }
505
+ const listener = () => settle(signal);
506
+ listeners.set(signal, listener);
507
+ signal.addEventListener("abort", listener, { once: true });
508
+ }
509
+ return controller.signal;
510
+ }
511
+ async function raceAbort(task, signal, message) {
512
+ if (signal.aborted) throw abortReason(signal, message);
513
+ let onAbort;
514
+ const aborted = new Promise((_resolve, reject) => {
515
+ onAbort = () => reject(abortReason(signal, message));
516
+ signal.addEventListener("abort", onAbort, { once: true });
517
+ });
518
+ try {
519
+ return await Promise.race([task, aborted]);
520
+ } finally {
521
+ if (onAbort !== void 0) signal.removeEventListener("abort", onAbort);
522
+ }
523
+ }
524
+ function abortError(message) {
525
+ return Object.assign(new Error(message), { name: "AbortError" });
526
+ }
527
+ function abortReason(signal, message) {
528
+ return signal.reason instanceof Error ? signal.reason : abortError(message);
529
+ }
530
+ function throwIfAborted(signal, message) {
531
+ if (signal.aborted) throw abortReason(signal, message);
532
+ }
533
+ //#endregion
534
+ //#region ../loader-anywidget/src/runtime/model-proxy.ts
535
+ function modelProxy(model, signal) {
536
+ const events = modelEvents(model);
537
+ const listeners = /* @__PURE__ */ new Set();
538
+ return {
539
+ get(key) {
540
+ return model.get(key);
541
+ },
542
+ set(key, value) {
543
+ model.set(key, value);
544
+ },
545
+ save_changes() {
546
+ model.save_changes();
547
+ },
548
+ send: model.send.bind(model),
549
+ on(name, callback) {
550
+ if (signal.aborted) return;
551
+ let registration;
552
+ const onAbort = () => {
553
+ listeners.delete(registration);
554
+ events.off(name, callback);
555
+ };
556
+ registration = {
557
+ name,
558
+ callback,
559
+ onAbort
560
+ };
561
+ listeners.add(registration);
562
+ signal.addEventListener("abort", onAbort, { once: true });
563
+ try {
564
+ events.on(name, callback);
565
+ } catch (error) {
566
+ signal.removeEventListener("abort", onAbort);
567
+ listeners.delete(registration);
568
+ throw error;
569
+ }
570
+ },
571
+ off(name, callback) {
572
+ const normalizedName = name ?? null;
573
+ const normalizedCallback = callback ?? null;
574
+ try {
575
+ events.off(normalizedName, normalizedCallback);
576
+ } finally {
577
+ for (const listener of listeners) {
578
+ const matchesName = normalizedName === null || listener.name === normalizedName;
579
+ const matchesCallback = normalizedCallback === null || listener.callback === normalizedCallback;
580
+ if (!matchesName || !matchesCallback) continue;
581
+ signal.removeEventListener("abort", listener.onAbort);
582
+ listeners.delete(listener);
583
+ }
584
+ }
585
+ },
586
+ widget_manager: { async get_model(modelId) {
587
+ return modelProxy(await model.widget_manager.get_model(modelId), signal);
588
+ } }
589
+ };
590
+ }
591
+ function modelEvents(model) {
592
+ return model;
593
+ }
594
+ //#endregion
595
+ //#region ../loader-anywidget/src/runtime/binding.ts
596
+ const experimental = { async invoke() {
597
+ throw new Error("Exported AnyWidgets cannot invoke Python.");
598
+ } };
599
+ function resolveAnyWidgetModule(module, moduleUrl) {
600
+ if (!isPropertyOwner(module) || !("default" in module)) throw invalidModule(moduleUrl);
601
+ if (isAnyWidget(module.default)) return module.default;
602
+ if (module.default !== void 0) throw invalidModule(moduleUrl);
603
+ const render = callableMember(module, "render");
604
+ const initialize = callableMember(module, "initialize");
605
+ if (render === void 0 && initialize === void 0) throw invalidModule(moduleUrl);
606
+ return widgetDefinition({
607
+ render,
608
+ initialize
609
+ });
610
+ }
611
+ var WidgetBinding = class WidgetBinding {
612
+ #controller;
613
+ #widget;
614
+ #model;
615
+ #createHost;
616
+ #exports;
617
+ #cleanupTasks = /* @__PURE__ */ new Set();
618
+ #cleanupErrors = [];
619
+ #viewTasks = /* @__PURE__ */ new Set();
620
+ #destroyPromise;
621
+ #destroyed = false;
622
+ constructor(options) {
623
+ this.#controller = options.controller;
624
+ this.#widget = options.widget;
625
+ this.#model = options.model;
626
+ this.#createHost = options.createHost;
627
+ this.#exports = options.exports;
628
+ }
629
+ static async create(options) {
630
+ const { controller, model, createHost } = options;
631
+ const signal = controller.signal;
632
+ const widget = await raceAbort(Promise.resolve(isWidgetFactory(options.widget) ? options.widget() : options.widget), signal, "AnyWidget binding was disposed.");
633
+ let lateInitializeCleanup;
634
+ const settleLateInitializeCleanup = (cleanup) => {
635
+ lateInitializeCleanup ??= settleCleanup(cleanup);
636
+ return lateInitializeCleanup;
637
+ };
638
+ const initializeTask = Promise.resolve(widget.initialize?.({
639
+ model: modelProxy(model, signal),
640
+ experimental,
641
+ signal
642
+ }));
643
+ initializeTask.then(async (result) => {
644
+ if (signal.aborted && isCleanup(result)) await settleLateInitializeCleanup(result);
645
+ }).catch((cause) => {
646
+ if (signal.aborted) reportLateFailure("initialize", cause);
647
+ });
648
+ const initializeResult = await raceAbort(initializeTask, signal, "AnyWidget binding was disposed.");
649
+ if (signal.aborted) {
650
+ if (isCleanup(initializeResult)) await settleLateInitializeCleanup(initializeResult);
651
+ throw abortReason(signal, "AnyWidget binding was disposed.");
652
+ }
653
+ let exports;
654
+ let initializeCleanup;
655
+ if (isCleanup(initializeResult)) initializeCleanup = initializeResult;
656
+ else if (isPropertyOwner(initializeResult)) exports = initializeResult;
657
+ const binding = new WidgetBinding({
658
+ controller,
659
+ widget,
660
+ model,
661
+ createHost,
662
+ exports
663
+ });
664
+ if (initializeCleanup !== void 0) signal.addEventListener("abort", () => binding.#trackCleanup(initializeCleanup, "initialize"), { once: true });
665
+ return binding;
666
+ }
667
+ get exports() {
668
+ return this.#exports;
669
+ }
670
+ async createView(element, signal) {
671
+ const task = this.#createView(element, signal);
672
+ this.#viewTasks.add(task);
673
+ try {
674
+ await task;
675
+ } finally {
676
+ this.#viewTasks.delete(task);
677
+ }
678
+ }
679
+ destroy() {
680
+ this.#destroyPromise ??= this.#destroy();
681
+ return this.#destroyPromise;
682
+ }
683
+ async #createView(element, signal) {
684
+ const renderSignal = combineAbortSignals([signal, this.#controller.signal]);
685
+ if (renderSignal.aborted) throw abortReason(renderSignal, "AnyWidget view was disposed.");
686
+ element.replaceChildren();
687
+ let renderCleanupTask;
688
+ const settleRenderCleanup = (cleanup) => {
689
+ renderCleanupTask ??= this.#trackCleanup(cleanup, "render");
690
+ return renderCleanupTask;
691
+ };
692
+ const renderTask = Promise.resolve().then(() => this.#widget.render?.({
693
+ model: modelProxy(this.#model, renderSignal),
694
+ el: element,
695
+ experimental,
696
+ signal: renderSignal,
697
+ host: this.#createHost(renderSignal)
698
+ }));
699
+ renderTask.then((cleanup) => {
700
+ if (renderSignal.aborted && isCleanup(cleanup)) return settleRenderCleanup(cleanup);
701
+ }).catch((cause) => {
702
+ if (renderSignal.aborted) reportLateFailure("render", cause);
703
+ });
704
+ let cleanup;
705
+ try {
706
+ cleanup = await raceAbort(renderTask, renderSignal, "AnyWidget view was disposed.");
707
+ } catch (error) {
708
+ element.replaceChildren();
709
+ throw error;
710
+ }
711
+ const clear = () => element.replaceChildren();
712
+ if (!isCleanup(cleanup)) {
713
+ if (renderSignal.aborted) clear();
714
+ else renderSignal.addEventListener("abort", clear, { once: true });
715
+ return;
716
+ }
717
+ const release = () => {
718
+ settleRenderCleanup(cleanup).finally(clear);
719
+ };
720
+ if (renderSignal.aborted) {
721
+ release();
722
+ return;
723
+ }
724
+ renderSignal.addEventListener("abort", release, { once: true });
725
+ }
726
+ #trackCleanup(cleanup, phase) {
727
+ const task = settleCleanup(cleanup).catch((error) => {
728
+ const failure = new Error(`AnyWidget ${phase} cleanup failed.`, { cause: error });
729
+ if (this.#destroyed) reportLateFailure(phase, failure);
730
+ else this.#cleanupErrors.push(failure);
731
+ });
732
+ this.#cleanupTasks.add(task);
733
+ task.finally(() => this.#cleanupTasks.delete(task));
734
+ return task;
735
+ }
736
+ async #destroy() {
737
+ this.#controller.abort();
738
+ await Promise.allSettled(this.#viewTasks);
739
+ while (this.#cleanupTasks.size > 0) await Promise.all(this.#cleanupTasks);
740
+ this.#destroyed = true;
741
+ if (this.#cleanupErrors.length > 0) throw new AggregateError(this.#cleanupErrors, "AnyWidget cleanup failed.");
742
+ }
743
+ };
744
+ function reportLateFailure(phase, cause) {
745
+ console.error(`AnyWidget ${phase} settled after its mount was disposed.`, cause);
746
+ }
747
+ function isAnyWidget(value) {
748
+ return isCallableValue(value) || isPropertyOwner(value) && (callableMember(value, "render") !== void 0 || callableMember(value, "initialize") !== void 0);
749
+ }
750
+ function invalidModule(url) {
751
+ return /* @__PURE__ */ new Error(`AnyWidget module ${quoteDiagnostic(url)} must default-export a factory or an object with render or initialize.`);
752
+ }
753
+ function quoteDiagnostic(value) {
754
+ let body = "";
755
+ for (let index = 0; index < value.length; index += 1) {
756
+ const codeUnit = value.charCodeAt(index);
757
+ let token;
758
+ if (codeUnit === 34 || codeUnit === 92) token = `\\${value[index]}`;
759
+ else if (codeUnit <= 31 || codeUnit >= 127 && codeUnit <= 159 || codeUnit >= 55296 && codeUnit <= 57343) {
760
+ if (codeUnit >= 55296 && codeUnit <= 56319 && index + 1 < value.length && value.charCodeAt(index + 1) >= 56320 && value.charCodeAt(index + 1) <= 57343) {
761
+ token = value.slice(index, index + 2);
762
+ index += 1;
763
+ } else token = `\\u${codeUnit.toString(16).padStart(4, "0")}`;
764
+ } else token = value[index];
765
+ if (body.length + token.length > 123) return `"${body}..."`;
766
+ body += token;
767
+ }
768
+ return `"${body}"`;
769
+ }
770
+ function isCleanup(value) {
771
+ return isCallableValue(value);
772
+ }
773
+ async function settleCleanup(cleanup) {
774
+ await cleanup();
775
+ }
776
+ function callableMember(value, name) {
777
+ const member = Object.getOwnPropertyDescriptor(value, name)?.value;
778
+ return isCallableValue(member) ? member : void 0;
779
+ }
780
+ function isWidgetFactory(value) {
781
+ return isCallableValue(value);
782
+ }
783
+ function widgetDefinition(value) {
784
+ return value;
785
+ }
786
+ //#endregion
787
+ //#region ../loader-anywidget/src/runtime/widget-ref.ts
788
+ function parseWidgetRef(value) {
789
+ if (isStringValue(value) && value.startsWith("anywidget:")) {
790
+ const modelId = value.slice(10);
791
+ if (modelId.length > 0) return modelId;
792
+ }
793
+ throw new Error(`Invalid AnyWidget model reference: ${JSON.stringify(value)}.`);
794
+ }
795
+ //#endregion
796
+ //#region ../loader-anywidget/src/runtime/host.ts
797
+ function createHost(resolver, parentSignal) {
798
+ return {
799
+ async getModel(ref) {
800
+ throwIfAborted(parentSignal, "AnyWidget parent view was disposed.");
801
+ const model = await resolver.getModel(parseWidgetRef(ref));
802
+ throwIfAborted(parentSignal, "AnyWidget parent view was disposed.");
803
+ return modelProxy(model, parentSignal);
804
+ },
805
+ async getWidget(ref) {
806
+ throwIfAborted(parentSignal, "AnyWidget parent view was disposed.");
807
+ const widget = await resolver.getWidget(parseWidgetRef(ref));
808
+ throwIfAborted(parentSignal, "AnyWidget parent view was disposed.");
809
+ return {
810
+ exports: widget.exports,
811
+ async render({ el, signal }) {
812
+ const renderSignal = signal === void 0 || signal === parentSignal ? parentSignal : combineAbortSignals([parentSignal, signal]);
813
+ throwIfAborted(renderSignal, "AnyWidget parent view was disposed.");
814
+ await widget.render({
815
+ el,
816
+ signal: renderSignal
817
+ });
818
+ }
819
+ };
820
+ }
821
+ };
822
+ }
823
+ //#endregion
824
+ //#region ../loader-anywidget/src/runtime/model.ts
825
+ var StaticModel = class {
826
+ #resolver;
827
+ #dirtyFields = /* @__PURE__ */ new Map();
828
+ #data;
829
+ #listeners = /* @__PURE__ */ new Map();
830
+ #changeQueued = false;
831
+ constructor(data, resolver, signal) {
832
+ this.#data = data;
833
+ this.#resolver = resolver;
834
+ signal.addEventListener("abort", () => {
835
+ this.#listeners.clear();
836
+ this.#dirtyFields.clear();
837
+ }, { once: true });
838
+ }
839
+ get(key) {
840
+ if (Object.hasOwn(this.#data, key)) return this.#data[key];
841
+ }
842
+ set(key, value) {
843
+ this.#data = {
844
+ ...this.#data,
845
+ [key]: value
846
+ };
847
+ this.#dirtyFields.set(key, value);
848
+ this.#emit(`change:${String(key)}`, value);
849
+ this.#queueAnyChange();
850
+ }
851
+ save_changes() {
852
+ this.#dirtyFields.clear();
853
+ }
854
+ send = (_content, callbacks) => {
855
+ if (isCallableValue(callbacks)) queueMicrotask(callbacks);
856
+ };
857
+ widget_manager = { get_model: async (modelId) => {
858
+ return await this.#resolver.getModel(widgetManagerModelId(modelId));
859
+ } };
860
+ on(eventName, callback, options) {
861
+ if (options?.signal?.aborted === true) return;
862
+ const listeners = this.#listeners.get(eventName) ?? /* @__PURE__ */ new Set();
863
+ listeners.add(callback);
864
+ this.#listeners.set(eventName, listeners);
865
+ options?.signal?.addEventListener("abort", () => this.off(eventName, callback), { once: true });
866
+ }
867
+ off(eventName, callback) {
868
+ if (!eventName) {
869
+ this.#listeners.clear();
870
+ return;
871
+ }
872
+ if (!callback) {
873
+ this.#listeners.delete(eventName);
874
+ return;
875
+ }
876
+ this.#listeners.get(eventName)?.delete(callback);
877
+ }
878
+ #emit(eventName, ...args) {
879
+ const listeners = this.#listeners.get(eventName);
880
+ if (listeners === void 0) return;
881
+ for (const listener of [...listeners]) try {
882
+ listener(...args);
883
+ } catch (error) {
884
+ console.error(`AnyWidget model listener for ${JSON.stringify(eventName)} failed.`, error);
885
+ }
886
+ }
887
+ #queueAnyChange() {
888
+ if (this.#changeQueued) return;
889
+ this.#changeQueued = true;
890
+ queueMicrotask(() => {
891
+ this.#changeQueued = false;
892
+ this.#emit("change");
893
+ });
894
+ }
895
+ };
896
+ function widgetManagerModelId(value) {
897
+ for (const prefix of ["IPY_MODEL_", "anywidget:"]) if (value.startsWith(prefix) && value.length > prefix.length) return value.slice(prefix.length);
898
+ return value;
899
+ }
900
+ //#endregion
901
+ //#region ../loader-anywidget/src/runtime/module-cache.ts
902
+ const MAX_PAGE_MODULES = 1024;
903
+ const PAGE_MODULE_CACHE_SYMBOL = Symbol.for("@marimo-team/marimo-export:anywidget-module-cache:v1");
904
+ const PAGE_MODULE_CACHE_VERSION = 1;
905
+ const encoder = new TextEncoder();
906
+ async function loadPageAnyWidget(spec, files) {
907
+ const source = await moduleSource(spec, files);
908
+ const modules = pageModuleCache().modules;
909
+ const existing = modules.get(source.cacheKey);
910
+ if (existing !== void 0) return existing;
911
+ if (modules.size >= 1024) throw new Error(`AnyWidget page module cache exceeds ${MAX_PAGE_MODULES} unique modules.`);
912
+ let promise;
913
+ promise = startImport(source, spec.url).catch((cause) => {
914
+ if (modules.get(source.cacheKey) === promise) modules.delete(source.cacheKey);
915
+ throw cause;
916
+ });
917
+ modules.set(source.cacheKey, promise);
918
+ return promise;
919
+ }
920
+ async function moduleSource(spec, files) {
921
+ const key = embeddedFileKey(spec.url);
922
+ const embedded = files[key];
923
+ if (embedded !== void 0) return {
924
+ cacheKey: await digestIdentity([
925
+ spec.hash,
926
+ "embedded",
927
+ key,
928
+ embedded
929
+ ]),
930
+ importUrl: spec.url,
931
+ embedded
932
+ };
933
+ const importUrl = new URL(spec.url).href;
934
+ const kind = importUrl.startsWith("data:") ? "data" : "url";
935
+ return {
936
+ cacheKey: await digestIdentity([
937
+ spec.hash,
938
+ kind,
939
+ importUrl
940
+ ]),
941
+ importUrl,
942
+ embedded: void 0
943
+ };
944
+ }
945
+ function startImport(source, diagnosticUrl) {
946
+ if (source.embedded === void 0) return resolveImportedWidget(importModule(source.importUrl), diagnosticUrl);
947
+ return importEmbeddedWidget(URL.createObjectURL(dataUrlToBlob(source.embedded)), diagnosticUrl);
948
+ }
949
+ async function importEmbeddedWidget(objectUrl, diagnosticUrl) {
950
+ try {
951
+ return resolveAnyWidgetModule(await importModule(objectUrl), diagnosticUrl);
952
+ } finally {
953
+ URL.revokeObjectURL(objectUrl);
954
+ }
955
+ }
956
+ async function resolveImportedWidget(task, diagnosticUrl) {
957
+ return resolveAnyWidgetModule(await task, diagnosticUrl);
958
+ }
959
+ function importModule(url) {
960
+ return import(
961
+ /* @vite-ignore */
962
+ /* webpackIgnore: true */
963
+ /* turbopackIgnore: true */
964
+ url
965
+ );
966
+ }
967
+ function dataUrlToBlob(dataUrl) {
968
+ const { body, isBase64, mediaType } = parseDataUrl(dataUrl, "AnyWidget ESM data URL");
969
+ const bytes = isBase64 ? base64Bytes(body) : new TextEncoder().encode(decodeURIComponent(body));
970
+ return new Blob([bytes], { type: mediaType });
971
+ }
972
+ function base64Bytes(value) {
973
+ const binary = atob(value);
974
+ const bytes = new Uint8Array(binary.length);
975
+ for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
976
+ return bytes;
977
+ }
978
+ async function digestIdentity(parts) {
979
+ const bytes = encoder.encode(JSON.stringify(parts));
980
+ const digest = await crypto.subtle.digest("SHA-256", new Uint8Array(bytes));
981
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
982
+ }
983
+ function pageModuleCache() {
984
+ const existing = Object.getOwnPropertyDescriptor(globalThis, PAGE_MODULE_CACHE_SYMBOL)?.value;
985
+ if (existing !== void 0) {
986
+ if (isPageModuleCache(existing)) return existing;
987
+ throw new Error("AnyWidget page module cache has an incompatible value.");
988
+ }
989
+ const created = Object.freeze({
990
+ version: PAGE_MODULE_CACHE_VERSION,
991
+ modules: /* @__PURE__ */ new Map()
992
+ });
993
+ Object.defineProperty(globalThis, PAGE_MODULE_CACHE_SYMBOL, {
994
+ configurable: false,
995
+ enumerable: false,
996
+ value: created,
997
+ writable: false
998
+ });
999
+ return created;
1000
+ }
1001
+ function isPageModuleCache(value) {
1002
+ if (!isPropertyOwner(value) || !("version" in value) || !("modules" in value)) return false;
1003
+ return value.version === PAGE_MODULE_CACHE_VERSION && value.modules instanceof Map;
1004
+ }
1005
+ //#endregion
1006
+ //#region ../loader-anywidget/src/runtime/registry.ts
1007
+ const ACTIVE_MOUNTS = /* @__PURE__ */ new WeakSet();
1008
+ async function mountSnapshot(snapshot, element, options = {}) {
1009
+ assertBrowserElement(element);
1010
+ options.signal?.throwIfAborted();
1011
+ if (ACTIVE_MOUNTS.has(element)) throw new Error("Dispose the existing AnyWidget mount before reusing its element.");
1012
+ const registry = new StaticRegistry(snapshot);
1013
+ ACTIVE_MOUNTS.add(element);
1014
+ let disposePromise;
1015
+ const dispose = () => {
1016
+ disposePromise ??= registry.dispose().finally(() => ACTIVE_MOUNTS.delete(element));
1017
+ return disposePromise;
1018
+ };
1019
+ const onAbort = () => {
1020
+ dispose().catch(() => void 0);
1021
+ };
1022
+ options.signal?.addEventListener("abort", onAbort, { once: true });
1023
+ try {
1024
+ const binding = await registry.getBinding(snapshot.rootModelId);
1025
+ await registry.createView(snapshot.rootModelId, element, registry.signal);
1026
+ if (registry.signal.aborted) throw abortError("AnyWidget mount was disposed.");
1027
+ options.signal?.throwIfAborted();
1028
+ const model = await registry.getModel(snapshot.rootModelId);
1029
+ if (registry.signal.aborted) throw abortError("AnyWidget mount was disposed.");
1030
+ options.signal?.throwIfAborted();
1031
+ return Object.freeze({
1032
+ model,
1033
+ exports: binding.exports,
1034
+ async dispose() {
1035
+ options.signal?.removeEventListener("abort", onAbort);
1036
+ await dispose();
1037
+ }
1038
+ });
1039
+ } catch (error) {
1040
+ options.signal?.removeEventListener("abort", onAbort);
1041
+ try {
1042
+ await dispose();
1043
+ } catch (cleanupError) {
1044
+ throw new AggregateError([error, cleanupError], "AnyWidget mount and cleanup failed.");
1045
+ }
1046
+ throw error;
1047
+ }
1048
+ }
1049
+ var StaticRegistry = class {
1050
+ #snapshot;
1051
+ #controller = new AbortController();
1052
+ #runtimes = /* @__PURE__ */ new Map();
1053
+ #bindings = /* @__PURE__ */ new Map();
1054
+ #bindingOrder = [];
1055
+ #disposePromise;
1056
+ #disposing = false;
1057
+ constructor(snapshot) {
1058
+ this.#snapshot = snapshot;
1059
+ for (const model of snapshot.models.values()) {
1060
+ const state = cloneModelState(model.state);
1061
+ const runtime = {
1062
+ model: new StaticModel(state, this, this.#controller.signal),
1063
+ esmSpec: model.esmSpec,
1064
+ styleMounts: /* @__PURE__ */ new Map(),
1065
+ css: isStringValue(state._css) ? state._css : ""
1066
+ };
1067
+ runtime.model.on("change:_css", () => {
1068
+ const css = runtime.model.get("_css");
1069
+ runtime.css = isStringValue(css) ? css : "";
1070
+ for (const mount of runtime.styleMounts.values()) mount.update(runtime.css);
1071
+ }, { signal: this.#controller.signal });
1072
+ this.#runtimes.set(model.id, runtime);
1073
+ }
1074
+ }
1075
+ get signal() {
1076
+ return this.#controller.signal;
1077
+ }
1078
+ async getModel(modelId) {
1079
+ return this.#runtime(modelId).model;
1080
+ }
1081
+ async getWidget(modelId) {
1082
+ return {
1083
+ exports: (await this.getBinding(modelId)).exports,
1084
+ render: async ({ el, signal }) => {
1085
+ await this.createView(modelId, el, signal ?? this.#controller.signal);
1086
+ }
1087
+ };
1088
+ }
1089
+ getBinding(modelId) {
1090
+ if (this.#disposing) return Promise.reject(abortError("AnyWidget registry was disposed."));
1091
+ const existing = this.#bindings.get(modelId);
1092
+ if (existing !== void 0) return existing.promise;
1093
+ const runtime = this.#runtime(modelId);
1094
+ if (runtime.esmSpec === void 0) return Promise.reject(/* @__PURE__ */ new Error(`AnyWidget model ${JSON.stringify(modelId)} has no ESM spec.`));
1095
+ const controller = new AbortController();
1096
+ const onDispose = () => controller.abort(this.#controller.signal.reason);
1097
+ if (this.#controller.signal.aborted) onDispose();
1098
+ else this.#controller.signal.addEventListener("abort", onDispose, { once: true });
1099
+ const state = {};
1100
+ const promise = raceAbort(this.#loadWidget(runtime.esmSpec), controller.signal, "AnyWidget binding was disposed.").then((widget) => WidgetBinding.create({
1101
+ widget,
1102
+ model: runtime.model,
1103
+ createHost: (signal) => createHost(this, signal),
1104
+ controller
1105
+ })).then((binding) => {
1106
+ state.binding = binding;
1107
+ this.#bindingOrder.push(binding);
1108
+ return binding;
1109
+ }).catch((cause) => {
1110
+ controller.abort();
1111
+ this.#bindings.delete(modelId);
1112
+ throw cause;
1113
+ }).finally(() => this.#controller.signal.removeEventListener("abort", onDispose));
1114
+ this.#bindings.set(modelId, {
1115
+ controller,
1116
+ promise,
1117
+ state
1118
+ });
1119
+ return promise;
1120
+ }
1121
+ async createView(modelId, element, signal) {
1122
+ if (signal.aborted || this.#controller.signal.aborted) throw abortError("AnyWidget view was disposed.");
1123
+ const runtime = this.#runtime(modelId);
1124
+ const controller = new AbortController();
1125
+ const viewSignal = combineAbortSignals([
1126
+ signal,
1127
+ this.#controller.signal,
1128
+ controller.signal
1129
+ ]);
1130
+ try {
1131
+ this.#mountStyle(runtime, element, viewSignal);
1132
+ await (await this.getBinding(modelId)).createView(element, viewSignal);
1133
+ } catch (error) {
1134
+ controller.abort(error);
1135
+ throw error;
1136
+ }
1137
+ }
1138
+ dispose() {
1139
+ if (this.#disposePromise === void 0) {
1140
+ this.#disposing = true;
1141
+ const entries = [...this.#bindings.values()];
1142
+ for (const entry of entries) if (entry.state.binding === void 0) entry.controller.abort();
1143
+ this.#disposePromise = this.#dispose(entries);
1144
+ }
1145
+ return this.#disposePromise;
1146
+ }
1147
+ async #loadWidget(spec) {
1148
+ if (this.#disposing) throw abortError("AnyWidget registry was disposed.");
1149
+ return loadPageAnyWidget(spec, this.#snapshot.files);
1150
+ }
1151
+ #runtime(modelId) {
1152
+ const runtime = this.#runtimes.get(modelId);
1153
+ if (runtime === void 0) throw new Error(`AnyWidget model ${JSON.stringify(modelId)} is missing from this mount.`);
1154
+ return runtime;
1155
+ }
1156
+ #mountStyle(runtime, element, signal) {
1157
+ const root = element.getRootNode();
1158
+ if (!isDocument(root) && !isShadowRoot(root)) return;
1159
+ let mount = runtime.styleMounts.get(root);
1160
+ if (mount === void 0) {
1161
+ mount = createStyleMount(root, runtime.css);
1162
+ runtime.styleMounts.set(root, mount);
1163
+ }
1164
+ mount.references += 1;
1165
+ let released = false;
1166
+ signal.addEventListener("abort", () => {
1167
+ if (released) return;
1168
+ released = true;
1169
+ const current = runtime.styleMounts.get(root);
1170
+ if (current === void 0) return;
1171
+ current.references -= 1;
1172
+ if (current.references === 0) {
1173
+ current.dispose();
1174
+ runtime.styleMounts.delete(root);
1175
+ }
1176
+ }, { once: true });
1177
+ }
1178
+ async #dispose(entries) {
1179
+ const errors = [];
1180
+ await Promise.allSettled(entries.map((entry) => entry.promise));
1181
+ for (const binding of [...this.#bindingOrder].reverse()) try {
1182
+ await binding.destroy();
1183
+ } catch (error) {
1184
+ errors.push(error);
1185
+ }
1186
+ this.#controller.abort();
1187
+ for (const runtime of this.#runtimes.values()) {
1188
+ for (const mount of runtime.styleMounts.values()) mount.dispose();
1189
+ runtime.styleMounts.clear();
1190
+ }
1191
+ if (errors.length > 0) throw new AggregateError(errors, "AnyWidget cleanup failed.");
1192
+ }
1193
+ };
1194
+ function createStyleMount(root, css) {
1195
+ const style = (isDocument(root) ? root : root.ownerDocument).createElement("style");
1196
+ style.textContent = css;
1197
+ if (isDocument(root)) root.head.append(style);
1198
+ else root.append(style);
1199
+ return {
1200
+ references: 0,
1201
+ update(nextCss) {
1202
+ style.textContent = nextCss;
1203
+ },
1204
+ dispose() {
1205
+ style.remove();
1206
+ }
1207
+ };
1208
+ }
1209
+ function isDocument(node) {
1210
+ return node.nodeType === 9 && "head" in node;
1211
+ }
1212
+ function isShadowRoot(node) {
1213
+ return node.nodeType === 11 && "host" in node;
1214
+ }
1215
+ function assertBrowserElement(element) {
1216
+ if (globalThis.document === void 0 || globalThis.window === void 0 || !isPropertyOwner(element) || element.nodeType !== 1 || !("replaceChildren" in element)) throw new TypeError("AnyWidget mount requires a browser element.");
1217
+ }
1218
+ //#endregion
1219
+ //#region ../loader-anywidget/src/runtime/prepared-graph.ts
1220
+ var PreparedWidgetGraphReplacementError = class extends Error {
1221
+ remount = true;
1222
+ constructor(cause) {
1223
+ super("Prepared AnyWidget graph replacement requires a full remount", { cause });
1224
+ this.name = "PreparedWidgetGraphReplacementError";
1225
+ }
1226
+ };
1227
+ const emptyGraph = () => ({
1228
+ files: Object.freeze({}),
1229
+ records: /* @__PURE__ */ new Map()
1230
+ });
1231
+ const graphFailure = (cause) => cause instanceof Error ? cause : new Error(String(cause));
1232
+ const sameFiles = (left, right) => {
1233
+ const leftNames = Object.keys(left);
1234
+ const rightNames = Object.keys(right);
1235
+ return leftNames.length === rightNames.length && leftNames.every((name) => Object.hasOwn(right, name) && left[name] === right[name]);
1236
+ };
1237
+ const snapshotCopy = (snapshot, port) => {
1238
+ const records = /* @__PURE__ */ new Map();
1239
+ snapshot.records.forEach((record, key) => {
1240
+ const id = port.id(record);
1241
+ if (id !== key) throw new Error(`Prepared AnyWidget graph record ${JSON.stringify(key)} has identity ${JSON.stringify(id)}`);
1242
+ records.set(id, record);
1243
+ });
1244
+ return Object.freeze({
1245
+ files: Object.freeze({ ...snapshot.files }),
1246
+ records
1247
+ });
1248
+ };
1249
+ const changesFor = (previous, next, port) => {
1250
+ const additions = [];
1251
+ const stableUpdates = [];
1252
+ const replacements = [];
1253
+ next.records.forEach((record, id) => {
1254
+ if (!port.active(record)) return;
1255
+ const current = previous.records.get(id);
1256
+ if (current === void 0 || !port.active(current)) {
1257
+ additions.push(record);
1258
+ return;
1259
+ }
1260
+ if (port.same(current, record)) return;
1261
+ if (port.changesModule(current, record)) replacements.push(record);
1262
+ else stableUpdates.push(record);
1263
+ });
1264
+ return {
1265
+ additions,
1266
+ stableUpdates,
1267
+ replacements,
1268
+ removals: []
1269
+ };
1270
+ };
1271
+ const currentRecordActive = (next, id, port) => {
1272
+ const record = next.records.get(id);
1273
+ return record !== void 0 && port.active(record);
1274
+ };
1275
+ const plannedChanges = (previous, next, port) => {
1276
+ return {
1277
+ ...changesFor(previous, next, port),
1278
+ removals: [...previous.records.entries()].filter(([id, record]) => port.active(record) && !currentRecordActive(next, id, port)).map(([, record]) => record)
1279
+ };
1280
+ };
1281
+ const captureStates = (changes, port) => {
1282
+ const capture = (records) => new Map(records.map((record) => [port.id(record), port.capture(port.id(record))]));
1283
+ return {
1284
+ stable: capture(changes.stableUpdates),
1285
+ replacements: capture(changes.replacements),
1286
+ removals: capture(changes.removals)
1287
+ };
1288
+ };
1289
+ const attempt = async (errors, action) => {
1290
+ try {
1291
+ await action();
1292
+ } catch (error) {
1293
+ errors.push(graphFailure(error));
1294
+ }
1295
+ };
1296
+ const eachSequential = async (values, action) => {
1297
+ for (const value of values) await action(value);
1298
+ };
1299
+ const throwCleanup = (errors, message) => {
1300
+ if (errors.length === 1) throw errors[0];
1301
+ if (errors.length > 1) throw new AggregateError(errors, message);
1302
+ };
1303
+ const settledReplacement = (mutated, remount, settle) => {
1304
+ let committed = false;
1305
+ let commitFailure;
1306
+ let commitTask;
1307
+ let rollbackTask;
1308
+ return Object.freeze({
1309
+ mutated,
1310
+ remount,
1311
+ commit() {
1312
+ if (rollbackTask !== void 0) return rollbackTask.then(() => void 0);
1313
+ commitTask ??= Promise.resolve().then(() => settle(true)).then((result) => {
1314
+ committed = true;
1315
+ return result;
1316
+ }).catch((cause) => {
1317
+ commitFailure = graphFailure(cause);
1318
+ throw cause;
1319
+ });
1320
+ return commitTask;
1321
+ },
1322
+ rollback() {
1323
+ rollbackTask ??= (commitTask ?? Promise.resolve()).catch(() => {}).then(async () => {
1324
+ if (committed) return;
1325
+ try {
1326
+ await settle(false);
1327
+ } catch (error) {
1328
+ throw new PreparedWidgetGraphReplacementError(commitFailure === void 0 ? graphFailure(error) : new AggregateError([commitFailure, graphFailure(error)], "Prepared AnyWidget graph commit and rollback failed"));
1329
+ }
1330
+ });
1331
+ return rollbackTask;
1332
+ }
1333
+ });
1334
+ };
1335
+ var PreparedWidgetGraph = class {
1336
+ #checkpoints = /* @__PURE__ */ new WeakMap();
1337
+ #port;
1338
+ #current;
1339
+ #active;
1340
+ #operation;
1341
+ #pending;
1342
+ #disposed = false;
1343
+ #disposal;
1344
+ constructor(port, initial = emptyGraph()) {
1345
+ this.#port = port;
1346
+ this.#current = snapshotCopy(initial, port);
1347
+ this.#port.setFiles(this.#current.files);
1348
+ }
1349
+ checkpoint() {
1350
+ this.#requireIdle();
1351
+ const records = new Map(this.#current.records);
1352
+ this.#current.records.forEach((record, id) => {
1353
+ if (this.#port.active(record)) records.set(id, this.#port.merge(record, this.#port.capture(id)));
1354
+ });
1355
+ const snapshot = snapshotCopy({
1356
+ files: this.#current.files,
1357
+ records
1358
+ }, this.#port);
1359
+ const checkpoint = Object.freeze({});
1360
+ this.#checkpoints.set(checkpoint, {
1361
+ snapshot,
1362
+ source: this.#current
1363
+ });
1364
+ return checkpoint;
1365
+ }
1366
+ replace(target, signal) {
1367
+ if (signal?.aborted) return Promise.reject(abortReason(signal, "Prepared AnyWidget graph replacement aborted"));
1368
+ if (this.#disposed) return Promise.resolve(settledReplacement(false, false, () => void 0));
1369
+ this.#requireIdle();
1370
+ const controller = new AbortController();
1371
+ const abort = () => {
1372
+ if (signal !== void 0) controller.abort(abortReason(signal, "Prepared AnyWidget graph replacement aborted"));
1373
+ };
1374
+ signal?.addEventListener("abort", abort, { once: true });
1375
+ if (signal?.aborted) abort();
1376
+ this.#active = controller;
1377
+ const operation = this.#replace(target, controller.signal);
1378
+ this.#operation = operation;
1379
+ return operation.finally(() => {
1380
+ signal?.removeEventListener("abort", abort);
1381
+ if (this.#active === controller) this.#active = void 0;
1382
+ if (this.#operation === operation) this.#operation = void 0;
1383
+ });
1384
+ }
1385
+ dispose() {
1386
+ this.#disposal ??= this.#dispose();
1387
+ return this.#disposal;
1388
+ }
1389
+ async #replace(target, signal) {
1390
+ const checkpoint = this.#checkpoints.get(target);
1391
+ const value = checkpoint === void 0 ? target : checkpoint.snapshot;
1392
+ const next = snapshotCopy(value, this.#port);
1393
+ const adopt = checkpoint?.source ?? next;
1394
+ const previous = this.#current;
1395
+ const changes = plannedChanges(previous, next, this.#port);
1396
+ if (!(!sameFiles(previous.files, next.files) || changes.additions.length > 0 || changes.stableUpdates.length > 0 || changes.replacements.length > 0 || changes.removals.length > 0)) return this.#replacement(false, false, () => {
1397
+ this.#current = adopt;
1398
+ return adopt;
1399
+ });
1400
+ return this.#stage(previous, next, adopt, changes, signal);
1401
+ }
1402
+ async #stage(previous, next, adopt, changes, signal) {
1403
+ this.#port.setFiles({
1404
+ ...previous.files,
1405
+ ...next.files
1406
+ });
1407
+ try {
1408
+ await this.#preflight(changes, signal);
1409
+ } catch (error) {
1410
+ this.#port.setFiles(previous.files);
1411
+ throw error;
1412
+ }
1413
+ let live;
1414
+ try {
1415
+ live = captureStates(changes, this.#port);
1416
+ } catch (error) {
1417
+ this.#port.setFiles(previous.files);
1418
+ throw error;
1419
+ }
1420
+ const added = /* @__PURE__ */ new Set();
1421
+ const replaced = /* @__PURE__ */ new Set();
1422
+ const removed = /* @__PURE__ */ new Set();
1423
+ const rollback = () => this.#rollback(previous, changes, live, added, replaced, removed);
1424
+ let remount = false;
1425
+ try {
1426
+ await eachSequential(changes.additions, async (record) => {
1427
+ signal.throwIfAborted();
1428
+ added.add(this.#port.id(record));
1429
+ await this.#port.replay(record, signal);
1430
+ });
1431
+ await eachSequential(changes.stableUpdates, async (record) => {
1432
+ signal.throwIfAborted();
1433
+ await this.#port.replay(record, signal);
1434
+ });
1435
+ await eachSequential(changes.replacements, async (record) => {
1436
+ signal.throwIfAborted();
1437
+ const id = this.#port.id(record);
1438
+ const state = live.replacements.get(id);
1439
+ replaced.add(id);
1440
+ remount = true;
1441
+ await this.#port.close(id);
1442
+ await this.#port.replay(this.#port.merge(record, state), signal);
1443
+ });
1444
+ signal.throwIfAborted();
1445
+ } catch (error) {
1446
+ const failure = graphFailure(error);
1447
+ try {
1448
+ await rollback();
1449
+ } catch (rollbackError) {
1450
+ throw new PreparedWidgetGraphReplacementError(new AggregateError([failure, graphFailure(rollbackError)], "Prepared AnyWidget graph replacement and rollback failed"));
1451
+ }
1452
+ if (remount) throw new PreparedWidgetGraphReplacementError(failure);
1453
+ throw failure;
1454
+ }
1455
+ return this.#replacement(true, changes.replacements.length > 0, async (commit) => {
1456
+ if (!commit) {
1457
+ await rollback();
1458
+ return;
1459
+ }
1460
+ await eachSequential(changes.removals, async (record) => {
1461
+ const id = this.#port.id(record);
1462
+ removed.add(id);
1463
+ await this.#port.close(id);
1464
+ });
1465
+ this.#port.setFiles(next.files);
1466
+ this.#current = adopt;
1467
+ return adopt;
1468
+ });
1469
+ }
1470
+ async #preflight(changes, signal) {
1471
+ await eachSequential([
1472
+ ...changes.additions,
1473
+ ...changes.stableUpdates,
1474
+ ...changes.replacements
1475
+ ], async (record) => {
1476
+ signal.throwIfAborted();
1477
+ await this.#port.validate?.(record, signal);
1478
+ });
1479
+ await eachSequential([...changes.additions, ...changes.replacements], async (record) => {
1480
+ signal.throwIfAborted();
1481
+ await this.#port.preflight?.(record, signal);
1482
+ });
1483
+ signal.throwIfAborted();
1484
+ }
1485
+ async #rollback(previous, changes, live, added, replaced, removed) {
1486
+ this.#port.setFiles(previous.files);
1487
+ const errors = [];
1488
+ await eachSequential(added, (id) => attempt(errors, () => this.#port.close(id)));
1489
+ await eachSequential(live.stable, ([id, state]) => attempt(errors, () => this.#port.restore(id, state)));
1490
+ await this.#replayPrevious(errors, previous, live.replacements, replaced);
1491
+ await this.#replayPrevious(errors, previous, live.removals, removed);
1492
+ throwCleanup(errors, "Prepared AnyWidget graph rollback failed");
1493
+ }
1494
+ async #replayPrevious(errors, previous, states, ids) {
1495
+ await eachSequential(ids, async (id) => {
1496
+ const record = previous.records.get(id);
1497
+ const state = states.get(id);
1498
+ if (record === void 0 || state === void 0) return;
1499
+ await attempt(errors, () => this.#port.close(id));
1500
+ await attempt(errors, () => this.#port.replay(this.#port.merge(record, state)));
1501
+ });
1502
+ }
1503
+ #replacement(mutated, remount, settle) {
1504
+ const replacement = settledReplacement(mutated, remount, async (commit) => {
1505
+ let settled = false;
1506
+ try {
1507
+ const result = await settle(commit);
1508
+ settled = true;
1509
+ return result;
1510
+ } finally {
1511
+ if (settled && this.#pending === replacement) this.#pending = void 0;
1512
+ }
1513
+ });
1514
+ this.#pending = replacement;
1515
+ return replacement;
1516
+ }
1517
+ #requireIdle() {
1518
+ if (this.#disposed) throw new Error("Prepared AnyWidget graph is disposed");
1519
+ if (this.#operation !== void 0 || this.#pending !== void 0) throw new Error("A prepared AnyWidget graph replacement is already active");
1520
+ }
1521
+ async #dispose() {
1522
+ this.#disposed = true;
1523
+ this.#active?.abort(new DOMException("Prepared AnyWidget graph disposed", "AbortError"));
1524
+ const errors = [];
1525
+ if (this.#operation !== void 0) await attempt(errors, () => this.#operation?.then(() => {}));
1526
+ if (this.#pending !== void 0) await attempt(errors, () => this.#pending?.rollback());
1527
+ await eachSequential(this.#current.records, ([id, record]) => this.#port.active(record) ? attempt(errors, () => this.#port.close(id)) : void 0);
1528
+ await attempt(errors, () => this.#port.setFiles({}));
1529
+ this.#current = emptyGraph();
1530
+ throwCleanup(errors, "Prepared AnyWidget graph disposal failed");
1531
+ }
1532
+ };
1533
+ //#endregion
1534
+ //#region ../loader-anywidget/src/index.ts
1535
+ /** Decode exported AnyWidget bytes and prepare them for browser mounting. */
1536
+ async function loadAnyWidget(data, signal) {
1537
+ signal?.throwIfAborted();
1538
+ const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(data));
1539
+ signal?.throwIfAborted();
1540
+ const snapshot = parseAnyWidgetPayload(value);
1541
+ const initialState = specializeState(readonlyModelState(snapshot.models.get(snapshot.rootModelId).state));
1542
+ return Object.freeze({
1543
+ initialState,
1544
+ async mount(element, options = {}) {
1545
+ return mountSnapshot(snapshot, element, options);
1546
+ }
1547
+ });
1548
+ }
1549
+ function specializeState(state) {
1550
+ return state;
1551
+ }
1552
+ //#endregion
1553
+ //#region src/loader/anywidget.ts
1554
+ const MEDIA_TYPE = "application/vnd.marimo-export.anywidget.v1+json";
1555
+ /** Decode an exported AnyWidget value and prepare it for browser mounting. */
1556
+ const anyWidgetLoader = () => defineBlobAssetLoader({
1557
+ mediaTypes: MEDIA_TYPE,
1558
+ load: ({ payload, signal }) => loadAnyWidget(payload.data, signal)
1559
+ });
1560
+ //#endregion
1561
+ export { PreparedWidgetGraph, PreparedWidgetGraphReplacementError, anyWidgetLoader };