typst-wasm 0.0.1-alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,746 @@
1
+ import { loadWasmModule, toWasmCompileOptions } from "./wasm.js";
2
+ import { parseTarGzip } from "nanotar";
3
+
4
+ export * from "@typst-wasm/fonts"
5
+
6
+ //#region src/webassembly-jspi.ts
7
+ const getJspiWebAssembly = () => WebAssembly;
8
+
9
+ //#endregion
10
+ //#region src/backend-support.ts
11
+ const supportsWorkerBackend = () => typeof Worker !== "undefined" && typeof SharedArrayBuffer !== "undefined" && typeof Atomics !== "undefined" && typeof Atomics.wait === "function";
12
+ const supportsJspiBackend = () => {
13
+ const wasm = getJspiWebAssembly();
14
+ return typeof wasm.Suspending === "function" && typeof wasm.promising === "function";
15
+ };
16
+
17
+ //#endregion
18
+ //#region src/errors.ts
19
+ var TypstError = class extends Error {
20
+ constructor(message, options) {
21
+ super(message, options);
22
+ this.name = new.target.name;
23
+ }
24
+ };
25
+ var CompileError = class extends TypstError {
26
+ diagnostics;
27
+ constructor(message, options = {}) {
28
+ super(message, { cause: options.cause });
29
+ this.diagnostics = options.diagnostics ?? [];
30
+ }
31
+ };
32
+ var CompilerNotInitializedError = class extends TypstError {};
33
+ var CompilerDisposedError = class extends TypstError {};
34
+ var FontLoadError = class extends TypstError {
35
+ fontName;
36
+ constructor(fontName, cause) {
37
+ super(`Failed to load font "${fontName}"`, { cause });
38
+ this.fontName = fontName;
39
+ }
40
+ };
41
+ var FetchError = class extends TypstError {
42
+ path;
43
+ constructor(path, cause) {
44
+ super(`Failed to fetch "${path}"`, { cause });
45
+ this.path = path;
46
+ }
47
+ };
48
+ var PackageParseError = class extends TypstError {
49
+ spec;
50
+ constructor(spec, message) {
51
+ super(message);
52
+ this.spec = spec;
53
+ }
54
+ };
55
+ var PackageFetchError = class extends TypstError {
56
+ url;
57
+ constructor(url, cause) {
58
+ super(`Failed to fetch package: ${url}`, { cause });
59
+ this.url = url;
60
+ }
61
+ };
62
+ var FileNotFoundError = class extends TypstError {
63
+ filePath;
64
+ constructor(filePath) {
65
+ super(`File not found: ${filePath}`);
66
+ this.filePath = filePath;
67
+ }
68
+ };
69
+ var WorkerError = class extends TypstError {};
70
+
71
+ //#endregion
72
+ //#region src/direct-service.ts
73
+ const MAX_FETCH_ATTEMPTS = 3;
74
+ const textDecoder = new TextDecoder();
75
+ const retry = async (task, maxAttempts) => {
76
+ let lastError;
77
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) try {
78
+ return await task();
79
+ } catch (error) {
80
+ lastError = error;
81
+ }
82
+ throw lastError ?? /* @__PURE__ */ new Error("Unknown host fetch error");
83
+ };
84
+ var DirectService = class {
85
+ disposed = false;
86
+ initPromise = null;
87
+ compiler = null;
88
+ wasmExports = null;
89
+ compileAsync = null;
90
+ constructor(packageManager, fetchImpl = fetch) {
91
+ this.packageManager = packageManager;
92
+ this.fetchImpl = fetchImpl;
93
+ }
94
+ async init(moduleOrPath) {
95
+ this.assertNotDisposed();
96
+ this.initPromise ??= this.initDirect(moduleOrPath);
97
+ await this.initPromise;
98
+ }
99
+ async dispose() {
100
+ this.disposed = true;
101
+ if (this.compiler) this.compiler.free();
102
+ this.compiler = null;
103
+ this.wasmExports = null;
104
+ this.compileAsync = null;
105
+ }
106
+ async addFont(data) {
107
+ this.withCompiler((compiler) => void compiler.add_font(data), "add_font");
108
+ }
109
+ async addFile(path, data) {
110
+ this.withCompiler((compiler) => void compiler.add_file(path, data), "add_file");
111
+ }
112
+ async addSource(path, text) {
113
+ this.withCompiler((compiler) => void compiler.add_source(path, text), "add_source");
114
+ }
115
+ async removeFile(path) {
116
+ this.withCompiler((compiler) => void compiler.remove_file(path), "remove_file");
117
+ }
118
+ async clearFiles() {
119
+ this.withCompiler((compiler) => void compiler.clear_files(), "clear_files");
120
+ }
121
+ async listFiles() {
122
+ return this.withCompiler((compiler) => compiler.list_files(), "list_files");
123
+ }
124
+ async hasFile(path) {
125
+ return this.withCompiler((compiler) => compiler.has_file(path), "has_file");
126
+ }
127
+ async setMain(path) {
128
+ this.withCompiler((compiler) => void compiler.set_main(path), "set_main");
129
+ }
130
+ async compile(options) {
131
+ this.assertNotDisposed();
132
+ const compiler = this.compiler;
133
+ const wasmExports = this.wasmExports;
134
+ const compile = this.compileAsync;
135
+ if (!compiler || !wasmExports || !compile) throw new CompilerNotInitializedError("Compiler not initialized");
136
+ let ret;
137
+ try {
138
+ ret = await compile(compiler.__wbg_ptr, options);
139
+ } catch (cause) {
140
+ throw new WorkerError("Direct command failed: compile", { cause });
141
+ }
142
+ if (ret[2]) throw this.takeExternref(wasmExports, ret[1]);
143
+ return this.takeExternref(wasmExports, ret[0]);
144
+ }
145
+ async initDirect(moduleOrPath) {
146
+ if (!supportsJspiBackend()) throw new WorkerError("JSPI is unavailable in this runtime");
147
+ const wasmModule = await loadWasmModule();
148
+ const { Suspending, promising } = getJspiWebAssembly();
149
+ if (!Suspending || !promising) throw new WorkerError("JSPI is unavailable in this runtime");
150
+ const suspending = new Suspending(this.hostFetch);
151
+ const wasmExports = await wasmModule.default({
152
+ module_or_path: moduleOrPath,
153
+ imports: { bridge: { host_fetch: suspending } }
154
+ });
155
+ this.wasmExports = wasmExports;
156
+ this.compileAsync = promising(wasmExports.typstcompiler_compile);
157
+ this.compiler = new wasmModule.TypstCompiler();
158
+ }
159
+ hostFetch = async (pathPtr, pathLen, resultLenPtr) => {
160
+ const wasmExports = this.wasmExports;
161
+ if (!wasmExports) throw new Error("WASM exports not initialized");
162
+ const path = textDecoder.decode(new Uint8Array(wasmExports.memory.buffer, pathPtr, pathLen));
163
+ try {
164
+ const bytes = await retry(() => this.fetchBytes(path), MAX_FETCH_ATTEMPTS);
165
+ const resultPtr = wasmExports.__wbindgen_malloc(bytes.length, 1);
166
+ new Uint8Array(wasmExports.memory.buffer, resultPtr, bytes.length).set(bytes);
167
+ new DataView(wasmExports.memory.buffer).setUint32(resultLenPtr, bytes.length, true);
168
+ return resultPtr;
169
+ } catch {
170
+ new DataView(wasmExports.memory.buffer).setUint32(resultLenPtr, 0, true);
171
+ return 0;
172
+ }
173
+ };
174
+ async fetchBytes(path) {
175
+ if (path.startsWith("@")) return this.packageManager.getFile(path);
176
+ const response = await this.fetchImpl(path);
177
+ if (!response.ok) throw new Error(`Failed to fetch "${path}" with status ${response.status}`);
178
+ return new Uint8Array(await response.arrayBuffer());
179
+ }
180
+ withCompiler(run, name) {
181
+ this.assertNotDisposed();
182
+ if (!this.compiler) throw new CompilerNotInitializedError("Compiler not initialized");
183
+ try {
184
+ return run(this.compiler);
185
+ } catch (cause) {
186
+ throw new WorkerError(`Direct command failed: ${name}`, { cause });
187
+ }
188
+ }
189
+ assertNotDisposed() {
190
+ if (this.disposed) throw new CompilerDisposedError("Compiler has been disposed");
191
+ }
192
+ takeExternref(wasmExports, idx) {
193
+ const value = wasmExports.__wbindgen_externrefs.get(idx);
194
+ wasmExports.__externref_table_dealloc(idx);
195
+ return value;
196
+ }
197
+ };
198
+
199
+ //#endregion
200
+ //#region src/worker.ts?worker
201
+ function WorkerWrapper(options) {
202
+ return new Worker(new URL("worker.js", import.meta.url), {
203
+ type: "module",
204
+ name: options?.name
205
+ });
206
+ }
207
+
208
+ //#endregion
209
+ //#region src/messages.ts
210
+ const isRecord$1 = (value) => typeof value === "object" && value !== null;
211
+ const isRpcResponseMessage = (value) => {
212
+ if (!isRecord$1(value)) return false;
213
+ if (typeof value.requestId !== "number") return false;
214
+ return "result" in value !== "error" in value;
215
+ };
216
+ const isWorkerEventMessage = (value) => {
217
+ if (!isRecord$1(value)) return false;
218
+ if (typeof value.kind !== "string") return false;
219
+ if (value.kind === "web_fetch") {
220
+ if (!("payload" in value) || !isRecord$1(value.payload)) return false;
221
+ return typeof value.payload.path === "string";
222
+ }
223
+ return false;
224
+ };
225
+ const isWorkerToMainMessage = (value) => isRpcResponseMessage(value) || isWorkerEventMessage(value);
226
+
227
+ //#endregion
228
+ //#region src/rpc.ts
229
+ const isRecord = (value) => typeof value === "object" && value !== null;
230
+ const extractRpcErrorMessage = (error) => {
231
+ if (typeof error === "string") return error;
232
+ if (isRecord(error) && typeof error.message === "string") return error.message;
233
+ try {
234
+ return JSON.stringify(error);
235
+ } catch {
236
+ return String(error);
237
+ }
238
+ };
239
+ const makeRpcClient = (sender) => {
240
+ let requestIdCounter = 0;
241
+ const pending = /* @__PURE__ */ new Map();
242
+ const call = (kind, ...args) => {
243
+ const requestId = requestIdCounter;
244
+ requestIdCounter += 1;
245
+ return new Promise((resolve, reject) => {
246
+ pending.set(requestId, {
247
+ resolve,
248
+ reject,
249
+ kind
250
+ });
251
+ const payload = args[0];
252
+ try {
253
+ sender(payload === void 0 ? {
254
+ kind,
255
+ requestId
256
+ } : {
257
+ kind,
258
+ requestId,
259
+ payload
260
+ });
261
+ } catch (cause) {
262
+ pending.delete(requestId);
263
+ reject(new WorkerError(`Failed to send worker command "${String(kind)}"`, { cause }));
264
+ }
265
+ });
266
+ };
267
+ const receive = (response) => {
268
+ if (!isRpcResponseMessage(response)) return;
269
+ const req = pending.get(response.requestId);
270
+ if (!req) return;
271
+ pending.delete(response.requestId);
272
+ if ("result" in response) req.resolve(response.result);
273
+ else req.reject(new WorkerError(`Worker command failed: ${req.kind}`, { cause: response.error ?? extractRpcErrorMessage(response.error) }));
274
+ };
275
+ const rejectAll = (cause) => {
276
+ for (const [requestId, req] of pending) {
277
+ pending.delete(requestId);
278
+ req.reject(new WorkerError(`Worker command failed: ${req.kind}`, { cause }));
279
+ }
280
+ };
281
+ return {
282
+ call,
283
+ notify: sender,
284
+ receive,
285
+ rejectAll
286
+ };
287
+ };
288
+
289
+ //#endregion
290
+ //#region src/protocol.ts
291
+ const INITIAL_SAB_SIZE = 1024 * 1024;
292
+ const MAX_SAB_SIZE = 4 * 1024 * 1024 * 1024;
293
+ const DEFAULT_FETCH_TIMEOUT = 3e4;
294
+ const SharedMemoryCommunicationStatus = {
295
+ None: 0,
296
+ Pending: 1,
297
+ Error: 2,
298
+ Success: 3
299
+ };
300
+ var SharedMemoryCommunication = class SharedMemoryCommunication {
301
+ dataBuf;
302
+ statusBuf;
303
+ sizeBuf;
304
+ constructor() {
305
+ this.dataBuf = new SharedArrayBuffer(INITIAL_SAB_SIZE, { maxByteLength: MAX_SAB_SIZE });
306
+ this.statusBuf = new SharedArrayBuffer(4);
307
+ this.sizeBuf = new SharedArrayBuffer(4);
308
+ }
309
+ getStatus() {
310
+ return Atomics.load(new Int32Array(this.statusBuf), 0);
311
+ }
312
+ setStatus(status) {
313
+ const statusView = new Int32Array(this.statusBuf);
314
+ Atomics.store(statusView, 0, status);
315
+ Atomics.notify(statusView, 0, 1);
316
+ }
317
+ setBuffer(buf) {
318
+ const needed = buf.byteLength;
319
+ if (needed > MAX_SAB_SIZE) throw new Error(`File too large: ${needed} bytes. Maximum allowed: ${MAX_SAB_SIZE} bytes.`);
320
+ if (needed > this.dataBuf.byteLength) this.dataBuf.grow(needed);
321
+ new Uint8Array(this.dataBuf).set(buf);
322
+ Atomics.store(new Int32Array(this.sizeBuf), 0, needed);
323
+ }
324
+ getBuffer() {
325
+ const size = Atomics.load(new Int32Array(this.sizeBuf), 0);
326
+ return new Uint8Array(this.dataBuf, 0, size);
327
+ }
328
+ waitForStatusChange(expectedStatus, timeoutMs = DEFAULT_FETCH_TIMEOUT) {
329
+ return Atomics.wait(new Int32Array(this.statusBuf), 0, expectedStatus, timeoutMs) === "ok";
330
+ }
331
+ static hydrateObj(obj) {
332
+ const instantiation = new SharedMemoryCommunication();
333
+ instantiation.dataBuf = obj.dataBuf;
334
+ instantiation.statusBuf = obj.statusBuf;
335
+ instantiation.sizeBuf = obj.sizeBuf;
336
+ return instantiation;
337
+ }
338
+ };
339
+
340
+ //#endregion
341
+ //#region src/fetch-bridge.ts
342
+ const makeFetchBridge = (packageLoader, isDisposed, fetchImpl = fetch) => {
343
+ const sharedMemoryCommunication = new SharedMemoryCommunication();
344
+ const handleFetchRequest = async (path) => {
345
+ if (isDisposed()) return;
346
+ try {
347
+ const bytes = path.startsWith("@") ? await packageLoader.getFile(path) : await fetchPath(path, fetchImpl);
348
+ if (isDisposed()) return;
349
+ sharedMemoryCommunication.setBuffer(bytes);
350
+ sharedMemoryCommunication.setStatus(SharedMemoryCommunicationStatus.Success);
351
+ } catch {
352
+ if (!isDisposed()) sharedMemoryCommunication.setStatus(SharedMemoryCommunicationStatus.Error);
353
+ }
354
+ };
355
+ return {
356
+ sharedMemoryCommunication,
357
+ handleFetchRequest
358
+ };
359
+ };
360
+ const fetchPath = async (path, fetchImpl) => {
361
+ try {
362
+ const response = await fetchImpl(path);
363
+ if (!response.ok) throw new Error(`Status ${response.status}`);
364
+ return new Uint8Array(await response.arrayBuffer());
365
+ } catch (cause) {
366
+ throw new FetchError(path, cause);
367
+ }
368
+ };
369
+
370
+ //#endregion
371
+ //#region src/worker-transport.ts
372
+ const makeWorkerTransport = (worker, onMessage, onError) => {
373
+ worker.onmessage = (event) => {
374
+ if (isWorkerToMainMessage(event.data)) onMessage(event.data);
375
+ };
376
+ if ("onerror" in worker) worker.onerror = (event) => {
377
+ onError(event.error ?? event.message);
378
+ };
379
+ return {
380
+ post: (message) => {
381
+ worker.postMessage(message);
382
+ },
383
+ close: () => {
384
+ worker.onmessage = null;
385
+ if ("onerror" in worker) worker.onerror = null;
386
+ }
387
+ };
388
+ };
389
+
390
+ //#endregion
391
+ //#region src/worker-service.ts
392
+ var WorkerService = class {
393
+ disposed = false;
394
+ initPromise = null;
395
+ worker;
396
+ rpcClient;
397
+ transport;
398
+ constructor(packageManager, fetchImpl = fetch) {
399
+ this.worker = new WorkerWrapper();
400
+ const fetchBridge = makeFetchBridge(packageManager, () => this.disposed, fetchImpl);
401
+ this.rpcClient = makeRpcClient((msg) => {
402
+ this.transport.post(msg);
403
+ });
404
+ this.transport = makeWorkerTransport(this.worker, (msg) => {
405
+ this.handleMessage(msg, fetchBridge.handleFetchRequest);
406
+ }, (cause) => {
407
+ this.rpcClient.rejectAll(cause);
408
+ });
409
+ this.initWorker = async (moduleOrPath) => {
410
+ await this.rpcClient.call("init", {
411
+ sharedMemoryCommunication: fetchBridge.sharedMemoryCommunication,
412
+ moduleOrPath
413
+ });
414
+ };
415
+ }
416
+ initWorker;
417
+ async init(moduleOrPath) {
418
+ this.assertNotDisposed();
419
+ this.initPromise ??= this.initWorker(moduleOrPath);
420
+ try {
421
+ await this.initPromise;
422
+ } catch (error) {
423
+ this.initPromise = null;
424
+ throw error;
425
+ }
426
+ }
427
+ async dispose() {
428
+ if (this.disposed) return;
429
+ this.disposed = true;
430
+ this.rpcClient.rejectAll(new CompilerDisposedError("Compiler has been disposed"));
431
+ this.transport.close();
432
+ this.worker.terminate();
433
+ }
434
+ addFont(data) {
435
+ return this.rpcClient.call("add_font", { data });
436
+ }
437
+ addFile(path, data) {
438
+ return this.rpcClient.call("add_file", {
439
+ path,
440
+ data
441
+ });
442
+ }
443
+ addSource(path, text) {
444
+ return this.rpcClient.call("add_source", {
445
+ path,
446
+ text
447
+ });
448
+ }
449
+ removeFile(path) {
450
+ return this.rpcClient.call("remove_file", { path });
451
+ }
452
+ clearFiles() {
453
+ return this.rpcClient.call("clear_files");
454
+ }
455
+ listFiles() {
456
+ return this.rpcClient.call("list_files");
457
+ }
458
+ hasFile(path) {
459
+ return this.rpcClient.call("has_file", { path });
460
+ }
461
+ setMain(path) {
462
+ return this.rpcClient.call("set_main", { path });
463
+ }
464
+ compile(options) {
465
+ return this.rpcClient.call("compile", { options });
466
+ }
467
+ async handleMessage(msg, handleFetchRequest) {
468
+ if (isRpcResponseMessage(msg)) {
469
+ this.rpcClient.receive(msg);
470
+ return;
471
+ }
472
+ switch (msg.kind) {
473
+ case "web_fetch":
474
+ await handleFetchRequest(msg.payload.path);
475
+ return;
476
+ }
477
+ }
478
+ assertNotDisposed() {
479
+ if (this.disposed) throw new CompilerDisposedError("Compiler has been disposed");
480
+ }
481
+ };
482
+
483
+ //#endregion
484
+ //#region src/compiler-backend.ts
485
+ const selectAutomaticBackendKind = () => {
486
+ if (supportsWorkerBackend()) return "worker";
487
+ if (supportsJspiBackend()) return "jspi";
488
+ return "none";
489
+ };
490
+ const createCompilerBackend = (backend, options) => {
491
+ switch (backend === "auto" ? selectAutomaticBackendKind() : backend) {
492
+ case "worker": return new WorkerService(options.packageManager, options.fetch);
493
+ case "jspi": return new DirectService(options.packageManager, options.fetch);
494
+ case "none": throw new Error("No compatible typst-wasm backend available. Requires Worker+SharedArrayBuffer+Atomics.wait or JSPI.");
495
+ }
496
+ };
497
+
498
+ //#endregion
499
+ //#region src/cache-abstraction.ts
500
+ const makeBrowserCacheStorage = () => {
501
+ let cachePromise = null;
502
+ const openCache = async () => {
503
+ cachePromise ??= caches.open("typst-packages").catch(() => null);
504
+ return cachePromise;
505
+ };
506
+ return {
507
+ async get(key) {
508
+ const cache = await openCache();
509
+ if (!cache) return null;
510
+ try {
511
+ const response = await cache.match(key);
512
+ if (!response) return null;
513
+ return new Uint8Array(await response.arrayBuffer());
514
+ } catch {
515
+ return null;
516
+ }
517
+ },
518
+ async set(key, value) {
519
+ const cache = await openCache();
520
+ if (!cache) return;
521
+ try {
522
+ const response = new Response(value.slice(), { headers: { "Content-Type": "application/octet-stream" } });
523
+ await cache.put(key, response);
524
+ } catch {}
525
+ }
526
+ };
527
+ };
528
+ const makeMemoryCacheStorage = (capacity) => {
529
+ const storage = /* @__PURE__ */ new Map();
530
+ return {
531
+ async get(key) {
532
+ const value = storage.get(key);
533
+ if (!value) return null;
534
+ storage.delete(key);
535
+ storage.set(key, value);
536
+ return value;
537
+ },
538
+ async set(key, value) {
539
+ if (storage.has(key)) storage.delete(key);
540
+ storage.set(key, value);
541
+ while (storage.size > capacity) {
542
+ const oldest = storage.keys().next().value;
543
+ if (!oldest) break;
544
+ storage.delete(oldest);
545
+ }
546
+ }
547
+ };
548
+ };
549
+ const makeDefaultPackageCache = (capacity = 400) => typeof caches !== "undefined" ? makeBrowserCacheStorage() : makeMemoryCacheStorage(capacity);
550
+
551
+ //#endregion
552
+ //#region src/package-manager.ts
553
+ const parseSpec = (spec) => {
554
+ const match = spec.match(/^@([a-z0-9-]+)\/([a-z0-9_-]+):([0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9.-]+)?)\/(.+)$/);
555
+ if (!match) throw new PackageParseError(spec, "Expected format: @namespace/name:version/path where namespace is lowercase alphanumeric with hyphens, name is lowercase alphanumeric with hyphens/underscores, version is semver, and path is the file path.");
556
+ const [, namespace, name, version, filePath] = match;
557
+ if (namespace.startsWith("-") || namespace.endsWith("-")) throw new PackageParseError(spec, `Invalid package namespace: "${namespace}" cannot start or end with hyphen`);
558
+ if (name.startsWith("_") || name.endsWith("_")) throw new PackageParseError(spec, `Invalid package name: "${name}" cannot start or end with underscore`);
559
+ return {
560
+ namespace,
561
+ name,
562
+ version,
563
+ filePath
564
+ };
565
+ };
566
+ const getFileCacheKey = (spec, filePath) => `@${spec.namespace}/${spec.name}:${spec.version}/${filePath}`;
567
+ const getCacheKey = (spec) => getFileCacheKey(spec, spec.filePath);
568
+ const getPackageKey = (spec) => `@${spec.namespace}/${spec.name}:${spec.version}`;
569
+ var PackageManager = class {
570
+ fetchImpl;
571
+ packageBaseUrl;
572
+ cache;
573
+ loadedPackages = /* @__PURE__ */ new Set();
574
+ loadingPackages = /* @__PURE__ */ new Map();
575
+ constructor(options = {}) {
576
+ this.fetchImpl = options.fetch ?? fetch;
577
+ this.packageBaseUrl = options.packageBaseUrl ?? "https://packages.typst.org";
578
+ this.cache = options.cache ?? makeDefaultPackageCache(options.memoryPackageCacheCapacity);
579
+ }
580
+ async getFile(spec) {
581
+ const parsed = parseSpec(spec);
582
+ const cacheKey = getCacheKey(parsed);
583
+ const cached = await this.cache.get(cacheKey);
584
+ if (cached) return cached;
585
+ const packageKey = getPackageKey(parsed);
586
+ if (!this.loadedPackages.has(packageKey)) await this.loadPackageDeduped(parsed);
587
+ const file = await this.cache.get(cacheKey);
588
+ if (!file) throw new FileNotFoundError(parsed.filePath);
589
+ return file;
590
+ }
591
+ async loadPackageDeduped(spec) {
592
+ const packageKey = getPackageKey(spec);
593
+ const existing = this.loadingPackages.get(packageKey);
594
+ if (existing) {
595
+ await existing;
596
+ return;
597
+ }
598
+ const load = this.loadPackage(spec);
599
+ this.loadingPackages.set(packageKey, load);
600
+ try {
601
+ await load;
602
+ this.loadedPackages.add(packageKey);
603
+ } finally {
604
+ this.loadingPackages.delete(packageKey);
605
+ }
606
+ }
607
+ async loadPackage(spec) {
608
+ const url = `${this.packageBaseUrl}/${spec.namespace}/${spec.name}-${spec.version}.tar.gz`;
609
+ try {
610
+ const response = await this.fetchImpl(url);
611
+ if (!response.ok) throw new Error(`Status ${response.status}`);
612
+ const files = await parseTarGzip(new Uint8Array(await response.arrayBuffer()));
613
+ await Promise.all(files.map(async (file) => {
614
+ if (file.type === "file" && file.data) await this.cache.set(getFileCacheKey(spec, file.name), file.data);
615
+ }));
616
+ } catch (cause) {
617
+ throw new PackageFetchError(url, cause);
618
+ }
619
+ }
620
+ };
621
+
622
+ //#endregion
623
+ //#region src/index.ts
624
+ const hasErrorDiagnostics = (diagnostics) => diagnostics.some((diagnostic) => diagnostic.severity === "error");
625
+ const extractErrorMessage = (error) => {
626
+ if (error instanceof Error) return error.message;
627
+ if (typeof error === "string") return error;
628
+ try {
629
+ return JSON.stringify(error);
630
+ } catch {
631
+ return String(error);
632
+ }
633
+ };
634
+ const normalizeCompileResult = (result) => {
635
+ const diagnostics = result.diagnostics;
636
+ const deps = result.deps ?? void 0;
637
+ const timings = result.timings;
638
+ switch (result.format) {
639
+ case "pdf": return {
640
+ format: "pdf",
641
+ output: result.output_bytes ?? new Uint8Array(),
642
+ diagnostics,
643
+ deps,
644
+ timings
645
+ };
646
+ case "png": return {
647
+ format: "png",
648
+ pages: result.pages.map((page) => ({
649
+ page: page.page,
650
+ output: page.output_bytes ?? new Uint8Array()
651
+ })),
652
+ diagnostics,
653
+ deps,
654
+ timings
655
+ };
656
+ case "svg": return {
657
+ format: "svg",
658
+ pages: result.pages.map((page) => ({
659
+ page: page.page,
660
+ output: page.output_text ?? ""
661
+ })),
662
+ diagnostics,
663
+ deps,
664
+ timings
665
+ };
666
+ case "html": return {
667
+ format: "html",
668
+ output: result.output_text ?? "",
669
+ diagnostics,
670
+ deps,
671
+ timings
672
+ };
673
+ case "bundle": return {
674
+ format: "bundle",
675
+ files: result.files.map((file) => ({
676
+ path: file.path,
677
+ data: file.data,
678
+ mediaType: file.media_type ?? void 0
679
+ })),
680
+ diagnostics,
681
+ deps,
682
+ timings
683
+ };
684
+ }
685
+ };
686
+ var PromiseTypstCompiler = class {
687
+ constructor(backend) {
688
+ this.backend = backend;
689
+ }
690
+ addFont(data) {
691
+ return this.backend.addFont(data);
692
+ }
693
+ addFile(path, data) {
694
+ return this.backend.addFile(path, data);
695
+ }
696
+ addSource(path, text) {
697
+ return this.backend.addSource(path, text);
698
+ }
699
+ removeFile(path) {
700
+ return this.backend.removeFile(path);
701
+ }
702
+ clearFiles() {
703
+ return this.backend.clearFiles();
704
+ }
705
+ listFiles() {
706
+ return this.backend.listFiles();
707
+ }
708
+ hasFile(path) {
709
+ return this.backend.hasFile(path);
710
+ }
711
+ setMain(path) {
712
+ return this.backend.setMain(path);
713
+ }
714
+ async compile(options = {}) {
715
+ if (options.main) await this.setMain(options.main);
716
+ let rawResult;
717
+ try {
718
+ rawResult = await this.backend.compile(toWasmCompileOptions(options));
719
+ } catch (cause) {
720
+ throw new CompileError(extractErrorMessage(cause), { cause });
721
+ }
722
+ if (hasErrorDiagnostics(rawResult.diagnostics) || !rawResult.success) throw new CompileError(rawResult.internal_error ?? "Compilation failed", { diagnostics: rawResult.diagnostics });
723
+ return normalizeCompileResult(rawResult);
724
+ }
725
+ dispose() {
726
+ return this.backend.dispose();
727
+ }
728
+ };
729
+ const createTypstCompiler = async (options) => {
730
+ const packageManager = new PackageManager({
731
+ fetch: options.fetch,
732
+ packageBaseUrl: options.packageBaseUrl,
733
+ cache: options.packageCache,
734
+ memoryPackageCacheCapacity: options.memoryPackageCacheCapacity
735
+ });
736
+ const backend = createCompilerBackend(options.backend ?? "auto", {
737
+ packageManager,
738
+ fetch: options.fetch
739
+ });
740
+ await backend.init(options.moduleOrPath);
741
+ return new PromiseTypstCompiler(backend);
742
+ };
743
+
744
+ //#endregion
745
+ export { CompileError, CompilerDisposedError, CompilerNotInitializedError, FetchError, FileNotFoundError, FontLoadError, PackageFetchError, PackageManager, PackageParseError, SharedMemoryCommunication, TypstError, WorkerError, createTypstCompiler, makeBrowserCacheStorage, makeDefaultPackageCache, makeMemoryCacheStorage, selectAutomaticBackendKind, supportsJspiBackend, supportsWorkerBackend };
746
+ //# sourceMappingURL=index.js.map