@marimo-team/marimo-export 0.0.0 → 0.0.1
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/LICENSE +202 -0
- package/README.md +113 -2
- package/dist/export-6118xT3K.mjs +823 -0
- package/dist/index-Ca3yEJUD.d.mts +15 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.mjs +4 -0
- package/dist/loader/anywidget.d.mts +76 -0
- package/dist/loader/anywidget.mjs +1561 -0
- package/dist/loader/arrow.d.mts +10 -0
- package/dist/loader/arrow.mjs +38 -0
- package/dist/loader/html.d.mts +6 -0
- package/dist/loader/html.mjs +10 -0
- package/dist/loader/json.d.mts +6 -0
- package/dist/loader/json.mjs +15 -0
- package/dist/loader/marimo-cell.d.mts +7 -0
- package/dist/loader/marimo-cell.mjs +16 -0
- package/dist/loader/marimo-output.d.mts +7 -0
- package/dist/loader/marimo-output.mjs +16 -0
- package/dist/loader/numpy.d.mts +19 -0
- package/dist/loader/numpy.mjs +168 -0
- package/dist/loader/parquet.d.mts +14 -0
- package/dist/loader/parquet.mjs +43 -0
- package/dist/loader/text.d.mts +6 -0
- package/dist/loader/text.mjs +10 -0
- package/dist/loader/vegalite.d.mts +18 -0
- package/dist/loader/vegalite.mjs +98 -0
- package/dist/loader-CDfnJ5Yb.mjs +441 -0
- package/dist/marimo-snapshot--nNjqcq_.mjs +280 -0
- package/dist/marimo-snapshot-Df66Xan0.d.mts +74 -0
- package/dist/prepared/index.d.mts +116 -0
- package/dist/prepared/index.mjs +703 -0
- package/dist/schema-SJ_aIgcO.mjs +713 -0
- package/dist/types-D52GHn1Q.d.mts +201 -0
- package/dist/utf8-DI-jI2Nm.mjs +17 -0
- package/package.json +112 -5
|
@@ -0,0 +1,823 @@
|
|
|
1
|
+
import { c as isNotebookExportError, g as portableJsonObject, h as isStringValue, i as resolveOutputLoader, l as isCallableValue, m as isRecordValue, o as parseMediaType, p as isPropertyOwner, s as NotebookExportError } from "./loader-CDfnJ5Yb.mjs";
|
|
2
|
+
import { a as parseStrictJson, n as compareUnicodeScalarStrings, r as parseExportIndex, t as canonicalJson } from "./schema-SJ_aIgcO.mjs";
|
|
3
|
+
import { decode } from "@msgpack/msgpack";
|
|
4
|
+
//#region src/abort.ts
|
|
5
|
+
function isAbortError(cause) {
|
|
6
|
+
if (!isPropertyOwner(cause) || !("name" in cause)) return false;
|
|
7
|
+
try {
|
|
8
|
+
return cause.name === "AbortError";
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/strict-msgpack.ts
|
|
15
|
+
const decoder$1 = new TextDecoder("utf-8", {
|
|
16
|
+
fatal: true,
|
|
17
|
+
ignoreBOM: true
|
|
18
|
+
});
|
|
19
|
+
const MAX_DEPTH = 256;
|
|
20
|
+
const MAX_VALUES = 1e5;
|
|
21
|
+
function validateCanonicalMessagePack(bytes) {
|
|
22
|
+
const scanner = new MessagePackScanner(bytes);
|
|
23
|
+
scanner.value(0);
|
|
24
|
+
scanner.end();
|
|
25
|
+
}
|
|
26
|
+
var MessagePackScanner = class {
|
|
27
|
+
#bytes;
|
|
28
|
+
#view;
|
|
29
|
+
#offset = 0;
|
|
30
|
+
#values = 0;
|
|
31
|
+
constructor(bytes) {
|
|
32
|
+
this.#bytes = bytes;
|
|
33
|
+
this.#view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
34
|
+
}
|
|
35
|
+
value(depth) {
|
|
36
|
+
this.#values += 1;
|
|
37
|
+
if (this.#values > MAX_VALUES) throw new RangeError("MessagePack exceeds its value limit.");
|
|
38
|
+
if (depth > MAX_DEPTH) throw new RangeError("MessagePack exceeds its nesting limit.");
|
|
39
|
+
const head = this.u8();
|
|
40
|
+
if (head <= 127) return head;
|
|
41
|
+
if (head >= 224) return head - 256;
|
|
42
|
+
if (head >= 160 && head <= 191) return this.stringPayload(head & 31);
|
|
43
|
+
if (head >= 144 && head <= 159) return this.array(head & 15, depth);
|
|
44
|
+
if (head >= 128 && head <= 143) return this.map(head & 15, depth);
|
|
45
|
+
switch (head) {
|
|
46
|
+
case 192: return null;
|
|
47
|
+
case 194: return false;
|
|
48
|
+
case 195: return true;
|
|
49
|
+
case 196: return this.binary(this.u8());
|
|
50
|
+
case 197: {
|
|
51
|
+
const length = this.u16();
|
|
52
|
+
if (length <= 255) throw nonCanonical();
|
|
53
|
+
return this.binary(length);
|
|
54
|
+
}
|
|
55
|
+
case 198: {
|
|
56
|
+
const length = this.u32();
|
|
57
|
+
if (length <= 65535) throw nonCanonical();
|
|
58
|
+
return this.binary(length);
|
|
59
|
+
}
|
|
60
|
+
case 202: throw new RangeError("Canonical BlobAsset metadata uses float64.");
|
|
61
|
+
case 203: {
|
|
62
|
+
const value = this.f64();
|
|
63
|
+
if (!Number.isFinite(value)) throw new RangeError("MessagePack float must be finite.");
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
case 204: {
|
|
67
|
+
const value = this.u8();
|
|
68
|
+
if (value <= 127) throw nonCanonical();
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
case 205: {
|
|
72
|
+
const value = this.u16();
|
|
73
|
+
if (value <= 255) throw nonCanonical();
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
case 206: {
|
|
77
|
+
const value = this.u32();
|
|
78
|
+
if (value <= 65535) throw nonCanonical();
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
case 207: {
|
|
82
|
+
const value = this.u64();
|
|
83
|
+
if (value <= 4294967295n) throw nonCanonical();
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
case 208: {
|
|
87
|
+
const value = this.i8();
|
|
88
|
+
if (value >= -32) throw nonCanonical();
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
case 209: {
|
|
92
|
+
const value = this.i16();
|
|
93
|
+
if (value >= -128) throw nonCanonical();
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
case 210: {
|
|
97
|
+
const value = this.i32();
|
|
98
|
+
if (value >= -32768) throw nonCanonical();
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
case 211: {
|
|
102
|
+
const value = this.i64();
|
|
103
|
+
if (value >= -2147483648n) throw nonCanonical();
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
case 217: {
|
|
107
|
+
const length = this.u8();
|
|
108
|
+
if (length <= 31) throw nonCanonical();
|
|
109
|
+
return this.stringPayload(length);
|
|
110
|
+
}
|
|
111
|
+
case 218: {
|
|
112
|
+
const length = this.u16();
|
|
113
|
+
if (length <= 255) throw nonCanonical();
|
|
114
|
+
return this.stringPayload(length);
|
|
115
|
+
}
|
|
116
|
+
case 219: {
|
|
117
|
+
const length = this.u32();
|
|
118
|
+
if (length <= 65535) throw nonCanonical();
|
|
119
|
+
return this.stringPayload(length);
|
|
120
|
+
}
|
|
121
|
+
case 220: {
|
|
122
|
+
const length = this.u16();
|
|
123
|
+
if (length <= 15) throw nonCanonical();
|
|
124
|
+
return this.array(length, depth);
|
|
125
|
+
}
|
|
126
|
+
case 221: {
|
|
127
|
+
const length = this.u32();
|
|
128
|
+
if (length <= 65535) throw nonCanonical();
|
|
129
|
+
return this.array(length, depth);
|
|
130
|
+
}
|
|
131
|
+
case 222: {
|
|
132
|
+
const length = this.u16();
|
|
133
|
+
if (length <= 15) throw nonCanonical();
|
|
134
|
+
return this.map(length, depth);
|
|
135
|
+
}
|
|
136
|
+
case 223: {
|
|
137
|
+
const length = this.u32();
|
|
138
|
+
if (length <= 65535) throw nonCanonical();
|
|
139
|
+
return this.map(length, depth);
|
|
140
|
+
}
|
|
141
|
+
default: throw new RangeError("BlobAsset contains an unsupported MessagePack token.");
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
array(length, depth) {
|
|
145
|
+
const values = [];
|
|
146
|
+
for (let index = 0; index < length; index += 1) values.push(this.value(depth + 1));
|
|
147
|
+
return values;
|
|
148
|
+
}
|
|
149
|
+
map(length, depth) {
|
|
150
|
+
const values = /* @__PURE__ */ new Map();
|
|
151
|
+
for (let index = 0; index < length; index += 1) {
|
|
152
|
+
const key = this.value(depth + 1);
|
|
153
|
+
if (!isMessagePackString(key)) throw new RangeError("BlobAsset map keys must be strings.");
|
|
154
|
+
if (values.has(key)) throw new RangeError("BlobAsset map keys must be unique.");
|
|
155
|
+
values.set(key, this.value(depth + 1));
|
|
156
|
+
}
|
|
157
|
+
return values;
|
|
158
|
+
}
|
|
159
|
+
binary(length) {
|
|
160
|
+
return this.payload(length);
|
|
161
|
+
}
|
|
162
|
+
stringPayload(length) {
|
|
163
|
+
return decoder$1.decode(this.payload(length));
|
|
164
|
+
}
|
|
165
|
+
end() {
|
|
166
|
+
if (this.#offset !== this.#bytes.byteLength) throw new RangeError("BlobAsset MessagePack contains trailing data.");
|
|
167
|
+
}
|
|
168
|
+
payload(length) {
|
|
169
|
+
this.ensure(length);
|
|
170
|
+
const start = this.#offset;
|
|
171
|
+
this.#offset += length;
|
|
172
|
+
return this.#bytes.subarray(start, this.#offset);
|
|
173
|
+
}
|
|
174
|
+
u8() {
|
|
175
|
+
this.ensure(1);
|
|
176
|
+
return this.#view.getUint8(this.#offset++);
|
|
177
|
+
}
|
|
178
|
+
i8() {
|
|
179
|
+
this.ensure(1);
|
|
180
|
+
return this.#view.getInt8(this.#offset++);
|
|
181
|
+
}
|
|
182
|
+
u16() {
|
|
183
|
+
this.ensure(2);
|
|
184
|
+
const value = this.#view.getUint16(this.#offset);
|
|
185
|
+
this.#offset += 2;
|
|
186
|
+
return value;
|
|
187
|
+
}
|
|
188
|
+
i16() {
|
|
189
|
+
this.ensure(2);
|
|
190
|
+
const value = this.#view.getInt16(this.#offset);
|
|
191
|
+
this.#offset += 2;
|
|
192
|
+
return value;
|
|
193
|
+
}
|
|
194
|
+
u32() {
|
|
195
|
+
this.ensure(4);
|
|
196
|
+
const value = this.#view.getUint32(this.#offset);
|
|
197
|
+
this.#offset += 4;
|
|
198
|
+
return value;
|
|
199
|
+
}
|
|
200
|
+
i32() {
|
|
201
|
+
this.ensure(4);
|
|
202
|
+
const value = this.#view.getInt32(this.#offset);
|
|
203
|
+
this.#offset += 4;
|
|
204
|
+
return value;
|
|
205
|
+
}
|
|
206
|
+
u64() {
|
|
207
|
+
this.ensure(8);
|
|
208
|
+
const value = this.#view.getBigUint64(this.#offset);
|
|
209
|
+
this.#offset += 8;
|
|
210
|
+
return value;
|
|
211
|
+
}
|
|
212
|
+
i64() {
|
|
213
|
+
this.ensure(8);
|
|
214
|
+
const value = this.#view.getBigInt64(this.#offset);
|
|
215
|
+
this.#offset += 8;
|
|
216
|
+
return value;
|
|
217
|
+
}
|
|
218
|
+
f64() {
|
|
219
|
+
this.ensure(8);
|
|
220
|
+
const value = this.#view.getFloat64(this.#offset);
|
|
221
|
+
this.#offset += 8;
|
|
222
|
+
return value;
|
|
223
|
+
}
|
|
224
|
+
ensure(length) {
|
|
225
|
+
if (length > this.#bytes.byteLength - this.#offset) throw new RangeError("BlobAsset MessagePack is truncated.");
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
function isMessagePackString(value) {
|
|
229
|
+
return Object.prototype.toString.call(value) === "[object String]";
|
|
230
|
+
}
|
|
231
|
+
function nonCanonical() {
|
|
232
|
+
return /* @__PURE__ */ new RangeError("BlobAsset MessagePack is not canonical.");
|
|
233
|
+
}
|
|
234
|
+
//#endregion
|
|
235
|
+
//#region src/blob-asset.ts
|
|
236
|
+
const EXPECTED_FIELDS = [
|
|
237
|
+
"data",
|
|
238
|
+
"media_type",
|
|
239
|
+
"filename",
|
|
240
|
+
"metadata"
|
|
241
|
+
];
|
|
242
|
+
function decodeBlobAsset(bytes, descriptor) {
|
|
243
|
+
try {
|
|
244
|
+
validateCanonicalMessagePack(bytes);
|
|
245
|
+
const decoded = decode(bytes);
|
|
246
|
+
if (!isRecordValue(decoded)) throw new TypeError("BlobAsset must be a map.");
|
|
247
|
+
const fields = Object.keys(decoded);
|
|
248
|
+
if (fields.length !== EXPECTED_FIELDS.length || fields.some((field, index) => field !== EXPECTED_FIELDS[index])) throw new TypeError("BlobAsset must use the native four-field envelope.");
|
|
249
|
+
if (!("data" in decoded) || !(decoded.data instanceof Uint8Array)) throw new TypeError("BlobAsset.data must be binary.");
|
|
250
|
+
if (!("media_type" in decoded) || !isStringValue(decoded.media_type)) throw new TypeError("BlobAsset.media_type must be a string.");
|
|
251
|
+
const mediaType = parseMediaType(decoded.media_type);
|
|
252
|
+
if (!("filename" in decoded)) throw new TypeError("BlobAsset.filename must be present.");
|
|
253
|
+
const filename = decoded.filename;
|
|
254
|
+
if (filename !== null && !isStringValue(filename)) throw new TypeError("BlobAsset.filename must be a string or null.");
|
|
255
|
+
if (!("metadata" in decoded)) throw new TypeError("BlobAsset.metadata must be present.");
|
|
256
|
+
const metadata = portableJsonObject(decoded.metadata, "BlobAsset.metadata");
|
|
257
|
+
if (decoded.media_type !== descriptor.mediaType) throw new TypeError("BlobAsset.media_type does not match its descriptor.");
|
|
258
|
+
if (filename !== descriptor.filename) throw new TypeError("BlobAsset.filename does not match its descriptor.");
|
|
259
|
+
if (canonicalJson(metadata) !== canonicalJson(descriptor.metadata)) throw new TypeError("BlobAsset.metadata does not match its descriptor.");
|
|
260
|
+
return Object.freeze({
|
|
261
|
+
data: decoded.data,
|
|
262
|
+
mediaType,
|
|
263
|
+
filename,
|
|
264
|
+
metadata
|
|
265
|
+
});
|
|
266
|
+
} catch (error) {
|
|
267
|
+
if (isNotebookExportError(error) && error.code === "asset_invalid") throw error;
|
|
268
|
+
throw new NotebookExportError("asset_invalid", "BlobAsset envelope validation failed.", {
|
|
269
|
+
cause: error,
|
|
270
|
+
details: {
|
|
271
|
+
outputCodec: descriptor.codec,
|
|
272
|
+
mediaType: descriptor.mediaType
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
//#endregion
|
|
278
|
+
//#region src/integrity.ts
|
|
279
|
+
const NPY_MAGIC = new Uint8Array([
|
|
280
|
+
147,
|
|
281
|
+
78,
|
|
282
|
+
85,
|
|
283
|
+
77,
|
|
284
|
+
80,
|
|
285
|
+
89
|
|
286
|
+
]);
|
|
287
|
+
const ARROW_MAGIC = new TextEncoder().encode("ARROW1");
|
|
288
|
+
const ARROW_CONTINUATION = new Uint8Array([
|
|
289
|
+
255,
|
|
290
|
+
255,
|
|
291
|
+
255,
|
|
292
|
+
255
|
|
293
|
+
]);
|
|
294
|
+
const ARROW_STREAM_END = new Uint8Array([
|
|
295
|
+
255,
|
|
296
|
+
255,
|
|
297
|
+
255,
|
|
298
|
+
255,
|
|
299
|
+
0,
|
|
300
|
+
0,
|
|
301
|
+
0,
|
|
302
|
+
0
|
|
303
|
+
]);
|
|
304
|
+
async function sha256Hex(bytes) {
|
|
305
|
+
const subtle = globalThis.crypto?.subtle;
|
|
306
|
+
if (subtle === void 0) throw new NotebookExportError("integrity_failed", "Export verification requires Web Crypto SHA-256.");
|
|
307
|
+
try {
|
|
308
|
+
const digest = await subtle.digest("SHA-256", new Uint8Array(bytes));
|
|
309
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
310
|
+
} catch (error) {
|
|
311
|
+
throw new NotebookExportError("integrity_failed", "SHA-256 verification failed.", { cause: error });
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
async function verifyBytes(bytes, expected) {
|
|
315
|
+
if (bytes.byteLength !== expected.size) throw new NotebookExportError("integrity_failed", "Export asset size does not match.", { details: {
|
|
316
|
+
expectedSize: expected.size,
|
|
317
|
+
observedSize: bytes.byteLength
|
|
318
|
+
} });
|
|
319
|
+
const actual = await sha256Hex(bytes);
|
|
320
|
+
if (actual !== expected.sha256) throw new NotebookExportError("integrity_failed", "Export asset SHA-256 does not match.", { details: {
|
|
321
|
+
expectedSha256: expected.sha256,
|
|
322
|
+
observedSha256: actual
|
|
323
|
+
} });
|
|
324
|
+
}
|
|
325
|
+
function validateNativeFile(codec, bytes) {
|
|
326
|
+
if (codec === "numpy.npy.v1" && !hasBytes(bytes, NPY_MAGIC, 0)) throw new NotebookExportError("decode_failed", "NumPy asset has an invalid NPY header.");
|
|
327
|
+
if (codec === "apache.arrow.file.v1" && !isArrowFile(bytes) && !isArrowStream(bytes)) throw new NotebookExportError("decode_failed", "Arrow asset has invalid IPC framing.");
|
|
328
|
+
}
|
|
329
|
+
function isArrowFile(bytes) {
|
|
330
|
+
return bytes.byteLength >= 16 && hasBytes(bytes, ARROW_MAGIC, 0) && hasBytes(bytes, ARROW_MAGIC, bytes.byteLength - ARROW_MAGIC.byteLength);
|
|
331
|
+
}
|
|
332
|
+
function isArrowStream(bytes) {
|
|
333
|
+
return bytes.byteLength >= 16 && hasBytes(bytes, ARROW_CONTINUATION, 0) && hasBytes(bytes, ARROW_STREAM_END, bytes.byteLength - ARROW_STREAM_END.byteLength);
|
|
334
|
+
}
|
|
335
|
+
function hasBytes(value, expected, offset) {
|
|
336
|
+
if (offset < 0 || offset + expected.byteLength > value.byteLength) return false;
|
|
337
|
+
return expected.every((byte, index) => value[offset + index] === byte);
|
|
338
|
+
}
|
|
339
|
+
//#endregion
|
|
340
|
+
//#region src/transport.ts
|
|
341
|
+
const MAX_DIAGNOSTIC_PATH = 256;
|
|
342
|
+
function normalizeBase(base) {
|
|
343
|
+
let url;
|
|
344
|
+
if (base instanceof URL) url = new URL(base.href);
|
|
345
|
+
else {
|
|
346
|
+
const documentBase = globalThis.document?.baseURI;
|
|
347
|
+
if (documentBase === void 0) try {
|
|
348
|
+
url = new URL(base);
|
|
349
|
+
} catch (error) {
|
|
350
|
+
throw new TypeError("A string export base must be absolute outside a document.", { cause: error });
|
|
351
|
+
}
|
|
352
|
+
else url = new URL(base, documentBase);
|
|
353
|
+
}
|
|
354
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") throw new TypeError("Export base URL must use HTTP or HTTPS.");
|
|
355
|
+
if (url.username !== "" || url.password !== "") throw new TypeError("Export base URL must not contain user information.");
|
|
356
|
+
if (url.hash !== "") throw new TypeError("Export base URL must not contain a fragment.");
|
|
357
|
+
if (!url.pathname.endsWith("/")) url.pathname += "/";
|
|
358
|
+
return url;
|
|
359
|
+
}
|
|
360
|
+
function resolveExportUrl(base, path) {
|
|
361
|
+
if (path.length === 0 || path.includes("?") || path.includes("#")) throw new TypeError("Export object path must be a non-empty path without a query or fragment.");
|
|
362
|
+
const normalized = base instanceof URL ? base : new URL(base);
|
|
363
|
+
const url = new URL(path, normalized);
|
|
364
|
+
url.search = normalized.search;
|
|
365
|
+
return url;
|
|
366
|
+
}
|
|
367
|
+
async function fetchBytes(fetcher, url, path, options) {
|
|
368
|
+
throwIfAborted$1(options.signal);
|
|
369
|
+
let response;
|
|
370
|
+
try {
|
|
371
|
+
const request = {};
|
|
372
|
+
if (options.cache !== void 0) request.cache = options.cache;
|
|
373
|
+
if (options.signal !== void 0) request.signal = options.signal;
|
|
374
|
+
response = await fetcher(url, request);
|
|
375
|
+
} catch (error) {
|
|
376
|
+
throwReadError(error, options.signal, path);
|
|
377
|
+
}
|
|
378
|
+
if (!response.ok) {
|
|
379
|
+
cancelBody(response, /* @__PURE__ */ new Error("Unsuccessful response"));
|
|
380
|
+
throw new NotebookExportError("read_failed", `Export file ${quotePath(path)} was not found.`, { details: {
|
|
381
|
+
path: boundedPath(path),
|
|
382
|
+
status: response.status
|
|
383
|
+
} });
|
|
384
|
+
}
|
|
385
|
+
const declared = decodedContentLength(response);
|
|
386
|
+
if (declared !== void 0) {
|
|
387
|
+
if (declared > options.maxBytes) {
|
|
388
|
+
cancelBody(response, /* @__PURE__ */ new Error("Response exceeds byte limit"));
|
|
389
|
+
throw limitError(path, options.maxBytes, declared);
|
|
390
|
+
}
|
|
391
|
+
if (options.expectedBytes !== void 0 && declared !== options.expectedBytes) {
|
|
392
|
+
cancelBody(response, /* @__PURE__ */ new Error("Response size differs from descriptor"));
|
|
393
|
+
throw new NotebookExportError("integrity_failed", `Export file ${quotePath(path)} has an unexpected declared size.`, { details: {
|
|
394
|
+
path: boundedPath(path),
|
|
395
|
+
expectedSize: options.expectedBytes,
|
|
396
|
+
observedSize: declared
|
|
397
|
+
} });
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
try {
|
|
401
|
+
const bytes = await readBody(response, path, options.maxBytes, options.signal);
|
|
402
|
+
if (options.expectedBytes !== void 0 && bytes.byteLength !== options.expectedBytes) throw new NotebookExportError("integrity_failed", `Export file ${quotePath(path)} has an unexpected size.`, { details: {
|
|
403
|
+
path: boundedPath(path),
|
|
404
|
+
expectedSize: options.expectedBytes,
|
|
405
|
+
observedSize: bytes.byteLength
|
|
406
|
+
} });
|
|
407
|
+
return bytes;
|
|
408
|
+
} catch (error) {
|
|
409
|
+
if (isNotebookExportError(error)) throw error;
|
|
410
|
+
throwReadError(error, options.signal, path);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
async function readBody(response, path, maxBytes, signal) {
|
|
414
|
+
if (response.body === null) throw new NotebookExportError("read_failed", `Export file ${quotePath(path)} has no readable response body.`, { details: { path: boundedPath(path) } });
|
|
415
|
+
const reader = response.body.getReader();
|
|
416
|
+
let buffer = new Uint8Array(Math.min(65536, maxBytes));
|
|
417
|
+
let size = 0;
|
|
418
|
+
let pendingRead;
|
|
419
|
+
let deferredRelease = false;
|
|
420
|
+
try {
|
|
421
|
+
while (true) {
|
|
422
|
+
throwIfAborted$1(signal);
|
|
423
|
+
pendingRead = reader.read();
|
|
424
|
+
const next = await waitForRead(pendingRead, reader, signal);
|
|
425
|
+
pendingRead = void 0;
|
|
426
|
+
if (next.done) break;
|
|
427
|
+
const chunk = next.value;
|
|
428
|
+
if (chunk.byteLength > maxBytes - size) throw limitError(path, maxBytes, size + chunk.byteLength);
|
|
429
|
+
const required = size + chunk.byteLength;
|
|
430
|
+
if (required > buffer.byteLength) {
|
|
431
|
+
let capacity = Math.max(buffer.byteLength, 1);
|
|
432
|
+
while (capacity < required) capacity = Math.min(maxBytes, Math.max(required, capacity * 2));
|
|
433
|
+
const grown = new Uint8Array(capacity);
|
|
434
|
+
grown.set(buffer);
|
|
435
|
+
buffer = grown;
|
|
436
|
+
}
|
|
437
|
+
buffer.set(chunk, size);
|
|
438
|
+
size = required;
|
|
439
|
+
}
|
|
440
|
+
} catch (error) {
|
|
441
|
+
reader.cancel(error).catch(() => void 0);
|
|
442
|
+
if (pendingRead !== void 0) {
|
|
443
|
+
deferredRelease = true;
|
|
444
|
+
pendingRead.finally(() => {
|
|
445
|
+
reader.releaseLock();
|
|
446
|
+
}).catch(() => void 0);
|
|
447
|
+
}
|
|
448
|
+
throw error;
|
|
449
|
+
} finally {
|
|
450
|
+
if (!deferredRelease) reader.releaseLock();
|
|
451
|
+
}
|
|
452
|
+
throwIfAborted$1(signal);
|
|
453
|
+
return buffer.slice(0, size);
|
|
454
|
+
}
|
|
455
|
+
async function waitForRead(read, reader, signal) {
|
|
456
|
+
if (signal === void 0) return read;
|
|
457
|
+
throwIfAborted$1(signal);
|
|
458
|
+
let abort;
|
|
459
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
460
|
+
abort = () => {
|
|
461
|
+
const error = new NotebookExportError("abort", "Export operation was aborted.", { cause: signal.reason });
|
|
462
|
+
reader.cancel(signal.reason).catch(() => void 0);
|
|
463
|
+
reject(error);
|
|
464
|
+
};
|
|
465
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
466
|
+
});
|
|
467
|
+
try {
|
|
468
|
+
return await Promise.race([read, aborted]);
|
|
469
|
+
} finally {
|
|
470
|
+
if (abort !== void 0) signal.removeEventListener("abort", abort);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
function decodedContentLength(response) {
|
|
474
|
+
const encoding = response.headers.get("Content-Encoding");
|
|
475
|
+
if (encoding !== null && encoding.trim().toLowerCase() !== "identity") return void 0;
|
|
476
|
+
const header = response.headers.get("Content-Length");
|
|
477
|
+
if (header === null || !/^\d+$/u.test(header)) return void 0;
|
|
478
|
+
const value = Number(header);
|
|
479
|
+
return Number.isSafeInteger(value) ? value : void 0;
|
|
480
|
+
}
|
|
481
|
+
function limitError(path, maxBytes, observed) {
|
|
482
|
+
return new NotebookExportError("read_limit_exceeded", `Export file ${quotePath(path)} exceeds its byte limit.`, { details: {
|
|
483
|
+
path: boundedPath(path),
|
|
484
|
+
maxBytes,
|
|
485
|
+
observedBytes: observed
|
|
486
|
+
} });
|
|
487
|
+
}
|
|
488
|
+
function throwIfAborted$1(signal) {
|
|
489
|
+
if (signal?.aborted) throw new NotebookExportError("abort", "Export operation was aborted.", { cause: signal.reason });
|
|
490
|
+
}
|
|
491
|
+
function throwReadError(cause, signal, path) {
|
|
492
|
+
if (signal?.aborted || isAbortError(cause)) throw new NotebookExportError("abort", "Export operation was aborted.", { cause });
|
|
493
|
+
throw new NotebookExportError("read_failed", `Failed to read export object ${quotePath(path)}.`, {
|
|
494
|
+
cause,
|
|
495
|
+
details: { path: boundedPath(path) }
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
function cancelBody(response, cause) {
|
|
499
|
+
if (response.body !== null) response.body.cancel(cause).catch(() => void 0);
|
|
500
|
+
}
|
|
501
|
+
function boundedPath(path) {
|
|
502
|
+
return path.length <= MAX_DIAGNOSTIC_PATH ? path : `${path.slice(0, 253)}...`;
|
|
503
|
+
}
|
|
504
|
+
function quotePath(path) {
|
|
505
|
+
return JSON.stringify(boundedPath(path));
|
|
506
|
+
}
|
|
507
|
+
//#endregion
|
|
508
|
+
//#region src/export.ts
|
|
509
|
+
const INDEX_MAX_BYTES = 16777216;
|
|
510
|
+
const INDEX_MAX_VALUES = 2e6;
|
|
511
|
+
const DEFAULT_ASSET_MAX_BYTES = 536870912;
|
|
512
|
+
const HARD_ASSET_MAX_BYTES = 2147483647;
|
|
513
|
+
const DEFAULT_VERIFY_MAX_BYTES = 2147483648;
|
|
514
|
+
const encoder = new TextEncoder();
|
|
515
|
+
const decoder = new TextDecoder("utf-8", {
|
|
516
|
+
fatal: true,
|
|
517
|
+
ignoreBOM: true
|
|
518
|
+
});
|
|
519
|
+
async function openExport(base, options = {}) {
|
|
520
|
+
throwIfAborted(options.signal);
|
|
521
|
+
const normalized = normalizeBase(base);
|
|
522
|
+
const fetcher = options.fetch ?? globalThis.fetch;
|
|
523
|
+
if (!isCallableValue(fetcher)) throw new TypeError("A fetch implementation is required.");
|
|
524
|
+
const bytes = await fetchBytes(fetcher, resolveExportUrl(normalized, "index.json"), "index.json", assetReadOptions(INDEX_MAX_BYTES, options.signal));
|
|
525
|
+
const wire = decodeCanonicalIndex(bytes);
|
|
526
|
+
const parsed = parseExportIndex(wire);
|
|
527
|
+
const identity = await sha256Hex(bytes);
|
|
528
|
+
await validateFingerprints(parsed, options.signal);
|
|
529
|
+
return new NotebookExportValue(normalized, identity, parsed, fetcher);
|
|
530
|
+
}
|
|
531
|
+
function decodeCanonicalIndex(bytes) {
|
|
532
|
+
let value;
|
|
533
|
+
try {
|
|
534
|
+
value = parseStrictJson(decoder.decode(bytes), INDEX_MAX_VALUES);
|
|
535
|
+
} catch (error) {
|
|
536
|
+
throw new NotebookExportError("export_invalid", "Export index must be strict UTF-8 JSON.", { cause: error });
|
|
537
|
+
}
|
|
538
|
+
let canonical;
|
|
539
|
+
try {
|
|
540
|
+
canonical = encoder.encode(canonicalJson(value));
|
|
541
|
+
} catch (error) {
|
|
542
|
+
if (isNotebookExportError(error)) throw error;
|
|
543
|
+
throw new NotebookExportError("export_invalid", "Export index JSON is invalid.", { cause: error });
|
|
544
|
+
}
|
|
545
|
+
if (!equalBytes(bytes, canonical)) throw new NotebookExportError("export_noncanonical", "Export index is not canonical JSON.");
|
|
546
|
+
return value;
|
|
547
|
+
}
|
|
548
|
+
async function validateFingerprints(index, signal) {
|
|
549
|
+
for (const [fingerprint, state] of Object.entries(index.states)) {
|
|
550
|
+
throwIfAborted(signal);
|
|
551
|
+
const actual = await sha256Hex(encoder.encode(canonicalJson(state.inputs)));
|
|
552
|
+
throwIfAborted(signal);
|
|
553
|
+
if (actual !== fingerprint) throw new NotebookExportError("export_invalid", `State fingerprint ${fingerprint} does not match its inputs.`, { details: { fingerprint } });
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
var NotebookExportValue = class {
|
|
557
|
+
#baseHref;
|
|
558
|
+
identity;
|
|
559
|
+
specSha256;
|
|
560
|
+
defaultState;
|
|
561
|
+
notebook;
|
|
562
|
+
producer;
|
|
563
|
+
inputNames;
|
|
564
|
+
controlBindings;
|
|
565
|
+
outputNames;
|
|
566
|
+
#states;
|
|
567
|
+
#statesByAlias;
|
|
568
|
+
#statesByInputs;
|
|
569
|
+
#reader;
|
|
570
|
+
constructor(base, identity, index, fetcher) {
|
|
571
|
+
this.#baseHref = base.href;
|
|
572
|
+
this.identity = identity;
|
|
573
|
+
this.specSha256 = index.specSha256;
|
|
574
|
+
this.notebook = index.notebook;
|
|
575
|
+
this.producer = index.producer;
|
|
576
|
+
this.inputNames = index.inputs;
|
|
577
|
+
this.controlBindings = index.controlBindings;
|
|
578
|
+
this.outputNames = index.outputs;
|
|
579
|
+
this.#reader = new AssetReader(this.#baseHref, fetcher);
|
|
580
|
+
const aliasesByFingerprint = new Map(Object.keys(index.states).map((fingerprint) => [fingerprint, []]));
|
|
581
|
+
for (const [alias, fingerprint] of Object.entries(index.aliases).sort(([left], [right]) => compareUnicodeScalarStrings(left, right))) aliasesByFingerprint.get(fingerprint).push(alias);
|
|
582
|
+
this.#states = Object.freeze(Object.entries(index.states).sort(([left], [right]) => compareUnicodeScalarStrings(left, right)).map(([fingerprint, state]) => new ExportStateValue(this, fingerprint, Object.freeze(aliasesByFingerprint.get(fingerprint)), state, this.#reader)));
|
|
583
|
+
const statesByFingerprint = new Map(this.#states.map((state) => [state.fingerprint, state]));
|
|
584
|
+
this.defaultState = statesByFingerprint.get(index.defaultState);
|
|
585
|
+
this.#statesByAlias = new Map(Object.entries(index.aliases).map(([alias, fingerprint]) => [alias, statesByFingerprint.get(fingerprint)]));
|
|
586
|
+
this.#statesByInputs = new Map(this.#states.map((state) => [canonicalJson(state.inputs), state]));
|
|
587
|
+
Object.freeze(this);
|
|
588
|
+
}
|
|
589
|
+
get base() {
|
|
590
|
+
return new URL(this.#baseHref);
|
|
591
|
+
}
|
|
592
|
+
states() {
|
|
593
|
+
return this.#states;
|
|
594
|
+
}
|
|
595
|
+
state(alias) {
|
|
596
|
+
const state = this.#statesByAlias.get(alias);
|
|
597
|
+
if (state === void 0) throw new NotebookExportError("state_not_found", `State alias ${JSON.stringify(alias)} was not found.`, { details: {
|
|
598
|
+
requested: String(alias).slice(0, 255),
|
|
599
|
+
available: [...this.#statesByAlias.keys()].slice(0, 16)
|
|
600
|
+
} });
|
|
601
|
+
return state;
|
|
602
|
+
}
|
|
603
|
+
resolve(inputs) {
|
|
604
|
+
const normalized = normalizeResolutionObject(inputs, "inputs");
|
|
605
|
+
requireCompleteInputs(normalized, this.inputNames);
|
|
606
|
+
return this.resolveNormalized(normalized);
|
|
607
|
+
}
|
|
608
|
+
resolveNormalized(inputs) {
|
|
609
|
+
const state = this.#statesByInputs.get(canonicalJson(inputs));
|
|
610
|
+
if (state === void 0) throw new NotebookExportError("state_unavailable", "The requested input vector is absent from this export.", { details: { fingerprint: "unavailable" } });
|
|
611
|
+
return state;
|
|
612
|
+
}
|
|
613
|
+
async verify(options = {}) {
|
|
614
|
+
const maxBytes = assetLimit(options.maxBytes);
|
|
615
|
+
const maxTotalBytes = totalLimit(options.maxTotalBytes);
|
|
616
|
+
const assets = uniqueAssets(this.#states);
|
|
617
|
+
const total = assets.reduce((sum, item) => sum + item.descriptor.asset.size, 0);
|
|
618
|
+
if (total > maxTotalBytes) throw new NotebookExportError("read_limit_exceeded", "Export assets exceed the verification byte limit.", { details: {
|
|
619
|
+
declaredBytes: total,
|
|
620
|
+
maxTotalBytes
|
|
621
|
+
} });
|
|
622
|
+
for (const item of assets) {
|
|
623
|
+
throwIfAborted(options.signal);
|
|
624
|
+
await this.#reader.payload(item.descriptor, assetReadOptions(maxBytes, options.signal));
|
|
625
|
+
}
|
|
626
|
+
return Object.freeze({
|
|
627
|
+
states: this.#states.length,
|
|
628
|
+
outputs: this.#states.length * this.outputNames.length,
|
|
629
|
+
assets: assets.length,
|
|
630
|
+
bytesVerified: total
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
var ExportStateValue = class {
|
|
635
|
+
notebookExport;
|
|
636
|
+
fingerprint;
|
|
637
|
+
aliases;
|
|
638
|
+
inputs;
|
|
639
|
+
#outputs;
|
|
640
|
+
#outputsByName;
|
|
641
|
+
constructor(notebookExport, fingerprint, aliases, state, reader) {
|
|
642
|
+
this.notebookExport = notebookExport;
|
|
643
|
+
this.fingerprint = fingerprint;
|
|
644
|
+
this.aliases = aliases;
|
|
645
|
+
this.inputs = state.inputs;
|
|
646
|
+
this.#outputs = Object.freeze(notebookExport.outputNames.map((outputName) => new ExportOutputValue(this, outputName, state.outputs[outputName], reader)));
|
|
647
|
+
this.#outputsByName = new Map(this.#outputs.map((output) => [output.name, output]));
|
|
648
|
+
Object.freeze(this);
|
|
649
|
+
}
|
|
650
|
+
outputs() {
|
|
651
|
+
return this.#outputs;
|
|
652
|
+
}
|
|
653
|
+
output(name) {
|
|
654
|
+
const output = this.#outputsByName.get(name);
|
|
655
|
+
if (output === void 0) throw new NotebookExportError("output_not_found", `Output ${JSON.stringify(name)} was not found.`, { details: {
|
|
656
|
+
requested: String(name).slice(0, 255),
|
|
657
|
+
available: this.#outputs.slice(0, 16).map((item) => item.name)
|
|
658
|
+
} });
|
|
659
|
+
return output;
|
|
660
|
+
}
|
|
661
|
+
resolve(patch) {
|
|
662
|
+
const normalized = normalizeResolutionObject(patch, "patch");
|
|
663
|
+
const keys = Object.keys(normalized);
|
|
664
|
+
if (keys.length === 0) return this;
|
|
665
|
+
const allowed = new Set(this.notebookExport.inputNames);
|
|
666
|
+
if (keys.some((key) => !allowed.has(key))) throw new NotebookExportError("state_input_invalid", "State patch contains an unknown input name.", { details: { keys: keys.slice(0, 16) } });
|
|
667
|
+
const merged = Object.freeze(Object.fromEntries(this.notebookExport.inputNames.map((name) => [name, Object.hasOwn(normalized, name) ? normalized[name] : this.inputs[name]])));
|
|
668
|
+
return this.notebookExport.resolveNormalized(merged);
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
var ExportOutputValue = class {
|
|
672
|
+
state;
|
|
673
|
+
name;
|
|
674
|
+
codec;
|
|
675
|
+
mediaType;
|
|
676
|
+
descriptor;
|
|
677
|
+
#reader;
|
|
678
|
+
constructor(state, name, descriptor, reader) {
|
|
679
|
+
this.state = state;
|
|
680
|
+
this.name = name;
|
|
681
|
+
this.codec = descriptor.codec;
|
|
682
|
+
this.mediaType = parseMediaType(descriptor.mediaType);
|
|
683
|
+
this.descriptor = descriptor;
|
|
684
|
+
this.#reader = reader;
|
|
685
|
+
Object.freeze(this);
|
|
686
|
+
}
|
|
687
|
+
async load(loader, options = {}) {
|
|
688
|
+
const selected = resolveOutputLoader(this, [anyOutputLoader(loader)]);
|
|
689
|
+
const payload = await this.#reader.payload(this.descriptor, assetReadOptions(assetLimit(options.maxBytes), options.signal));
|
|
690
|
+
throwIfAborted(options.signal);
|
|
691
|
+
const call = selectedLoader(selected);
|
|
692
|
+
try {
|
|
693
|
+
const input = {
|
|
694
|
+
descriptor: this.descriptor,
|
|
695
|
+
mediaType: this.mediaType,
|
|
696
|
+
payload
|
|
697
|
+
};
|
|
698
|
+
if (options.signal !== void 0) input.signal = options.signal;
|
|
699
|
+
return await waitForLoader(Promise.resolve(call.load(input)), options.signal);
|
|
700
|
+
} catch (error) {
|
|
701
|
+
throwLoaderError(error, options.signal, this);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
var AssetReader = class {
|
|
706
|
+
#baseHref;
|
|
707
|
+
#fetch;
|
|
708
|
+
constructor(baseHref, fetcher) {
|
|
709
|
+
this.#baseHref = baseHref;
|
|
710
|
+
this.#fetch = fetcher;
|
|
711
|
+
}
|
|
712
|
+
async payload(descriptor, options) {
|
|
713
|
+
throwIfAborted(options.signal);
|
|
714
|
+
if (descriptor.codec === "marimo.scalar.v1") return descriptor.value;
|
|
715
|
+
if (descriptor.codec === "marimo.json.v1") return descriptor.value;
|
|
716
|
+
if (descriptor.asset.size > options.maxBytes) throw new NotebookExportError("read_limit_exceeded", "Export asset exceeds the caller byte limit.", { details: {
|
|
717
|
+
declaredBytes: descriptor.asset.size,
|
|
718
|
+
maxBytes: options.maxBytes
|
|
719
|
+
} });
|
|
720
|
+
const path = assetPath(descriptor.codec, descriptor.asset.sha256);
|
|
721
|
+
const fetchOptions = {
|
|
722
|
+
cache: "force-cache",
|
|
723
|
+
maxBytes: options.maxBytes,
|
|
724
|
+
expectedBytes: descriptor.asset.size
|
|
725
|
+
};
|
|
726
|
+
if (options.signal !== void 0) fetchOptions.signal = options.signal;
|
|
727
|
+
const bytes = await fetchBytes(this.#fetch, resolveExportUrl(this.#baseHref, path), path, fetchOptions);
|
|
728
|
+
throwIfAborted(options.signal);
|
|
729
|
+
await verifyBytes(bytes, descriptor.asset);
|
|
730
|
+
throwIfAborted(options.signal);
|
|
731
|
+
validateNativeFile(descriptor.codec, bytes);
|
|
732
|
+
if (descriptor.codec === "marimo.blob-asset.msgpack.v1") return decodeBlobAsset(bytes, descriptor);
|
|
733
|
+
return bytes;
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
function assetPath(codec, digest) {
|
|
737
|
+
return `assets/${digest}.${(() => {
|
|
738
|
+
if (codec === "marimo.output.v1") return "output.json";
|
|
739
|
+
if (codec === "marimo.cell.v1") return "cell.json";
|
|
740
|
+
if (codec === "numpy.npy.v1") return "npy";
|
|
741
|
+
if (codec === "apache.arrow.file.v1") return "arrow";
|
|
742
|
+
return "bin";
|
|
743
|
+
})()}`;
|
|
744
|
+
}
|
|
745
|
+
function uniqueAssets(states) {
|
|
746
|
+
const values = /* @__PURE__ */ new Map();
|
|
747
|
+
for (const state of states) for (const output of state.outputs()) {
|
|
748
|
+
if (output.descriptor.codec === "marimo.scalar.v1" || output.descriptor.codec === "marimo.json.v1") continue;
|
|
749
|
+
const descriptor = output.descriptor;
|
|
750
|
+
values.set(`${descriptor.codec}\0${descriptor.asset.sha256}`, { descriptor });
|
|
751
|
+
}
|
|
752
|
+
return Object.freeze([...values.values()]);
|
|
753
|
+
}
|
|
754
|
+
function normalizeResolutionObject(input, label) {
|
|
755
|
+
try {
|
|
756
|
+
return portableJsonObject(input, label);
|
|
757
|
+
} catch (error) {
|
|
758
|
+
throw new NotebookExportError("state_input_invalid", `State ${label} is invalid.`, { cause: error });
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
function requireCompleteInputs(inputs, names) {
|
|
762
|
+
const expected = new Set(names);
|
|
763
|
+
const keys = Object.keys(inputs);
|
|
764
|
+
if (keys.length !== names.length || keys.some((key) => !expected.has(key)) || names.some((name) => !Object.hasOwn(inputs, name))) throw new NotebookExportError("state_input_invalid", "State inputs must equal the export input name set.", { details: {
|
|
765
|
+
expected: names.slice(0, 16),
|
|
766
|
+
actual: keys.slice(0, 16)
|
|
767
|
+
} });
|
|
768
|
+
}
|
|
769
|
+
function assetLimit(value) {
|
|
770
|
+
if (value === void 0) return DEFAULT_ASSET_MAX_BYTES;
|
|
771
|
+
if (!Number.isSafeInteger(value) || value <= 0 || value > HARD_ASSET_MAX_BYTES) throw new TypeError(`maxBytes must be an integer from 1 through ${HARD_ASSET_MAX_BYTES}.`);
|
|
772
|
+
return value;
|
|
773
|
+
}
|
|
774
|
+
function totalLimit(value) {
|
|
775
|
+
if (value === void 0) return DEFAULT_VERIFY_MAX_BYTES;
|
|
776
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new TypeError("maxTotalBytes must be a non-negative safe integer.");
|
|
777
|
+
return value;
|
|
778
|
+
}
|
|
779
|
+
async function waitForLoader(promise, signal) {
|
|
780
|
+
if (signal === void 0) return promise;
|
|
781
|
+
throwIfAborted(signal);
|
|
782
|
+
let abort;
|
|
783
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
784
|
+
abort = () => reject(new NotebookExportError("abort", "Export output loading was aborted.", { cause: signal.reason }));
|
|
785
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
786
|
+
});
|
|
787
|
+
try {
|
|
788
|
+
return await Promise.race([promise, aborted]);
|
|
789
|
+
} finally {
|
|
790
|
+
if (abort !== void 0) signal.removeEventListener("abort", abort);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
function throwLoaderError(cause, signal, output) {
|
|
794
|
+
if (isNotebookExportError(cause)) throw cause;
|
|
795
|
+
if (signal?.aborted || isAbortError(cause)) throw new NotebookExportError("abort", "Export output loading was aborted.", { cause: signal?.aborted ? signal.reason : cause });
|
|
796
|
+
throw new NotebookExportError("decode_failed", "OutputLoader decoding failed.", {
|
|
797
|
+
cause,
|
|
798
|
+
details: {
|
|
799
|
+
output: output.name,
|
|
800
|
+
codec: output.codec,
|
|
801
|
+
mediaType: output.mediaType.raw
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
function assetReadOptions(maxBytes, signal) {
|
|
806
|
+
const options = { maxBytes };
|
|
807
|
+
if (signal !== void 0) options.signal = signal;
|
|
808
|
+
return options;
|
|
809
|
+
}
|
|
810
|
+
function selectedLoader(loader) {
|
|
811
|
+
return loader;
|
|
812
|
+
}
|
|
813
|
+
function anyOutputLoader(loader) {
|
|
814
|
+
return loader;
|
|
815
|
+
}
|
|
816
|
+
function throwIfAborted(signal) {
|
|
817
|
+
if (signal?.aborted) throw new NotebookExportError("abort", "Export operation was aborted.", { cause: signal.reason });
|
|
818
|
+
}
|
|
819
|
+
function equalBytes(left, right) {
|
|
820
|
+
return left.byteLength === right.byteLength && left.every((byte, index) => byte === right[index]);
|
|
821
|
+
}
|
|
822
|
+
//#endregion
|
|
823
|
+
export { normalizeBase as n, openExport as t };
|