@vedivad/typst-web-service 0.13.0 → 0.14.0

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.d.ts CHANGED
@@ -492,21 +492,28 @@ interface RenderedSvgPage {
492
492
  /**
493
493
  * Converts Typst vector artifacts to SVG strings.
494
494
  *
495
- * The renderer WASM module is loaded lazily on first use.
495
+ * Thin wrapper around `@myriaddreamin/typst.ts`'s renderer: we own the public
496
+ * API surface and WASM-URL convention; typst.ts owns the WASM session,
497
+ * sub-page rendering, and lifecycle. See `renderer.mts` in that package for
498
+ * the inner mechanics.
496
499
  *
497
500
  * const renderer = TypstRenderer.create();
498
501
  * const svg = await renderer.renderSvg(vector);
499
502
  */
500
503
  declare class TypstRenderer {
501
504
  #private;
502
- private wasmUrl;
503
- private instance;
505
+ private readonly wasmUrl;
506
+ private inner;
504
507
  private constructor();
505
508
  static create(options?: TypstRendererOptions): TypstRenderer;
506
- private getInstance;
507
- /** Free the underlying WASM renderer instance. */
508
- destroy(): Promise<void>;
509
- /** Render a Typst vector artifact to an SVG string. */
509
+ private getInner;
510
+ /**
511
+ * Drop the reference to the inner renderer. The upstream wrapper doesn't
512
+ * expose an explicit free(); the WASM module is reclaimed when the page
513
+ * unloads. Consumers may still call this to release the JS-side handle.
514
+ */
515
+ destroy(): void;
516
+ /** Render a Typst vector artifact to a single merged SVG string. */
510
517
  renderSvg(vector: Uint8Array): Promise<string>;
511
518
  /**
512
519
  * Render a Typst vector artifact into one self-contained SVG string per
@@ -517,6 +524,43 @@ declare class TypstRenderer {
517
524
  * the document has no page groups.
518
525
  */
519
526
  renderSvgPages(vector: Uint8Array): Promise<RenderedSvgPage[]>;
527
+ /**
528
+ * Mount an incremental canvas renderer into `root`. Each `update(vector)`
529
+ * call repaints the canvases in place. The win for image-heavy docs comes
530
+ * from the persistent WASM session: decoded image bitmaps are cached in
531
+ * the renderer, so subsequent paints skip the decode step. Canvas elements
532
+ * are recreated each update (typst.ts manages the DOM inside `root`), but
533
+ * canvas creation is cheap — the bottleneck the SVG path was solving was
534
+ * `<image>` data-URL decode, not DOM construction.
535
+ *
536
+ * Call `reset()` when switching to an unrelated document (next `update`
537
+ * starts fresh from the session). Call `dispose()` to free the session.
538
+ *
539
+ * Trade-off vs the SVG paths: output is rasterized — text in the rendered
540
+ * canvas isn't selectable, and zoom past `pixelPerPt` needs a re-render to
541
+ * stay crisp. typst.ts builds a `<canvas>` per page inside `root` and adds
542
+ * a text-semantic layer alongside it for screen readers / a11y.
543
+ */
544
+ createIncrementalCanvasRenderer(root: HTMLElement, options?: IncrementalCanvasOptions): IncrementalCanvasRenderer;
545
+ }
546
+ interface IncrementalCanvasOptions {
547
+ pixelPerPt?: number;
548
+ backgroundColor?: string;
549
+ }
550
+ interface IncrementalCanvasRenderer {
551
+ /**
552
+ * Apply a new vector and repaint. First call seeds the session.
553
+ *
554
+ * Concurrent calls are serialized: while a repaint is in flight, the most
555
+ * recent `update` becomes the next paint and intermediates are dropped.
556
+ * All overlapping callers share one returned promise that resolves when
557
+ * the chain catches up — your specific vector may have been superseded.
558
+ */
559
+ update(vector: Uint8Array): Promise<void>;
560
+ /** Force the next `update` to re-seed (use when the doc identity changes). */
561
+ reset(): void;
562
+ /** Free the WASM session. */
563
+ dispose(): void;
520
564
  }
521
565
 
522
566
  export { type AnalyzerUri, type CompileResult, type DiagnosticMessage, type DiagnosticRange, type FormatConfig, type FormatRangeResult, type LspCompletionItem, type LspCompletionList, type LspCompletionResponse, type LspDiagnostic, type LspHover, type LspHoverContents, type LspMarkupContent, type LspPosition, type LspRange, type Path, type RenderedSvgPage, TypstAnalyzer, type TypstAnalyzerOptions, TypstCompiler, type TypstCompilerOptions, TypstFormatter, TypstProject, type TypstProjectOptions, TypstRenderer, type TypstRendererOptions, normalizePath, normalizeRoot, pathToAnalyzerUri };
package/dist/index.js CHANGED
@@ -671,62 +671,46 @@ var TypstProject = class {
671
671
  };
672
672
 
673
673
  // src/renderer.ts
674
+ import {
675
+ createTypstRenderer
676
+ } from "@myriaddreamin/typst.ts";
674
677
  var DEFAULT_RENDERER_WASM_URL = `https://cdn.jsdelivr.net/npm/@myriaddreamin/typst-ts-renderer@${"0.7.0-rc2"}/pkg/typst_ts_renderer_bg.wasm`;
675
- var rendererModulePromise = null;
676
- function getRendererModule() {
677
- if (!rendererModulePromise) {
678
- rendererModulePromise = import("@myriaddreamin/typst-ts-renderer").catch(
679
- (err) => {
680
- rendererModulePromise = null;
681
- throw err;
682
- }
683
- );
684
- }
685
- return rendererModulePromise;
686
- }
687
678
  var TypstRenderer = class _TypstRenderer {
688
679
  wasmUrl;
689
- instance = null;
680
+ inner = null;
690
681
  constructor(options = {}) {
691
682
  this.wasmUrl = options.wasmUrl ?? DEFAULT_RENDERER_WASM_URL;
692
- getRendererModule().catch(() => {
693
- });
683
+ void this.getInner();
694
684
  }
695
685
  static create(options = {}) {
696
686
  return new _TypstRenderer(options);
697
687
  }
698
- getInstance() {
699
- if (!this.instance) {
700
- this.instance = this.#init().catch((err) => {
701
- this.instance = null;
688
+ getInner() {
689
+ if (!this.inner) {
690
+ this.inner = this.#initInner().catch((err) => {
691
+ this.inner = null;
702
692
  throw err;
703
693
  });
704
694
  }
705
- return this.instance;
706
- }
707
- async #init() {
708
- const mod = await getRendererModule();
709
- await mod.default({ module_or_path: this.wasmUrl });
710
- return new mod.TypstRendererBuilder().build();
711
- }
712
- /** Free the underlying WASM renderer instance. */
713
- async destroy() {
714
- const instance = this.instance;
715
- this.instance = null;
716
- if (instance) {
717
- (await instance).free();
718
- }
695
+ return this.inner;
696
+ }
697
+ async #initInner() {
698
+ const inner = createTypstRenderer();
699
+ await inner.init({ getModule: () => this.wasmUrl });
700
+ return inner;
701
+ }
702
+ /**
703
+ * Drop the reference to the inner renderer. The upstream wrapper doesn't
704
+ * expose an explicit free(); the WASM module is reclaimed when the page
705
+ * unloads. Consumers may still call this to release the JS-side handle.
706
+ */
707
+ destroy() {
708
+ this.inner = null;
719
709
  }
720
- /** Render a Typst vector artifact to an SVG string. */
710
+ /** Render a Typst vector artifact to a single merged SVG string. */
721
711
  async renderSvg(vector) {
722
- const renderer = await this.getInstance();
723
- const session = renderer.create_session();
724
- try {
725
- renderer.manipulate_data(session, "reset", vector);
726
- return renderer.svg_data(session);
727
- } finally {
728
- session.free();
729
- }
712
+ const inner = await this.getInner();
713
+ return inner.renderSvg({ format: "vector", artifactContent: vector });
730
714
  }
731
715
  /**
732
716
  * Render a Typst vector artifact into one self-contained SVG string per
@@ -739,6 +723,103 @@ var TypstRenderer = class _TypstRenderer {
739
723
  async renderSvgPages(vector) {
740
724
  return splitMergedSvgPages(await this.renderSvg(vector));
741
725
  }
726
+ /**
727
+ * Mount an incremental canvas renderer into `root`. Each `update(vector)`
728
+ * call repaints the canvases in place. The win for image-heavy docs comes
729
+ * from the persistent WASM session: decoded image bitmaps are cached in
730
+ * the renderer, so subsequent paints skip the decode step. Canvas elements
731
+ * are recreated each update (typst.ts manages the DOM inside `root`), but
732
+ * canvas creation is cheap — the bottleneck the SVG path was solving was
733
+ * `<image>` data-URL decode, not DOM construction.
734
+ *
735
+ * Call `reset()` when switching to an unrelated document (next `update`
736
+ * starts fresh from the session). Call `dispose()` to free the session.
737
+ *
738
+ * Trade-off vs the SVG paths: output is rasterized — text in the rendered
739
+ * canvas isn't selectable, and zoom past `pixelPerPt` needs a re-render to
740
+ * stay crisp. typst.ts builds a `<canvas>` per page inside `root` and adds
741
+ * a text-semantic layer alongside it for screen readers / a11y.
742
+ */
743
+ createIncrementalCanvasRenderer(root, options = {}) {
744
+ return new IncrementalCanvasImpl(root, () => this.getInner(), options);
745
+ }
746
+ };
747
+ async function openSession(inner) {
748
+ let close;
749
+ const closed = new Promise((resolve) => {
750
+ close = resolve;
751
+ });
752
+ let session;
753
+ await new Promise((ready, reject) => {
754
+ void inner.runWithSession(async (s) => {
755
+ session = s;
756
+ ready();
757
+ await closed;
758
+ }).catch(reject);
759
+ });
760
+ return { session, close };
761
+ }
762
+ function defaultPixelPerPt() {
763
+ const dpr = typeof globalThis !== "undefined" && typeof globalThis.devicePixelRatio === "number" ? globalThis.devicePixelRatio : 1;
764
+ return Math.max(dpr * 1.5, 3);
765
+ }
766
+ var IncrementalCanvasImpl = class {
767
+ constructor(root, getInner, options) {
768
+ this.root = root;
769
+ this.getInner = getInner;
770
+ this.options = options;
771
+ }
772
+ opened = null;
773
+ inflight = null;
774
+ pending = null;
775
+ firstUpdate = true;
776
+ disposed = false;
777
+ getOpened() {
778
+ this.opened ??= this.getInner().then(async (inner) => ({
779
+ ...await openSession(inner),
780
+ inner
781
+ }));
782
+ return this.opened;
783
+ }
784
+ update(vector) {
785
+ if (this.disposed) return Promise.resolve();
786
+ this.pending = vector;
787
+ this.inflight ??= this.drain();
788
+ return this.inflight;
789
+ }
790
+ async drain() {
791
+ try {
792
+ while (this.pending && !this.disposed) {
793
+ const vector = this.pending;
794
+ this.pending = null;
795
+ await this.paint(vector);
796
+ }
797
+ } finally {
798
+ this.inflight = null;
799
+ }
800
+ }
801
+ async paint(vector) {
802
+ const { session, inner } = await this.getOpened();
803
+ if (this.disposed) return;
804
+ const wasFirst = this.firstUpdate;
805
+ this.firstUpdate = false;
806
+ if (wasFirst) session.reset();
807
+ session.manipulateData({ action: "merge", data: vector });
808
+ await inner.renderToCanvas({
809
+ container: this.root,
810
+ renderSession: session,
811
+ pixelPerPt: this.options.pixelPerPt ?? defaultPixelPerPt(),
812
+ backgroundColor: this.options.backgroundColor ?? "#ffffff"
813
+ });
814
+ }
815
+ reset() {
816
+ this.firstUpdate = true;
817
+ }
818
+ dispose() {
819
+ if (this.disposed) return;
820
+ this.disposed = true;
821
+ void this.opened?.then(({ close }) => close());
822
+ }
742
823
  };
743
824
  function splitMergedSvgPages(svg) {
744
825
  const doc = new DOMParser().parseFromString(svg, "text/html");
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/analyzer.ts","../src/rpc.ts","../src/identifiers.ts","../src/compiler.ts","../src/formatter.ts","../src/compile-scheduler.ts","../src/project.ts","../src/renderer.ts"],"sourcesContent":["import * as Comlink from \"comlink\";\nimport type {\n LspCompletionResponse,\n LspHover,\n LspPosition,\n} from \"./analyzer-types.js\";\nimport type { AnalyzerWorker } from \"./analyzer-worker.js\";\nimport { createAnalyzerWorker } from \"./rpc.js\";\n\nexport interface TypstAnalyzerOptions {\n /**\n * Explicit Worker instance. When omitted, an inlined blob worker is created automatically.\n * Use this for Vite apps:\n * `await TypstAnalyzer.create({ worker: new Worker(new URL('typst-web-service/analyzer-worker', import.meta.url), { type: 'module' }) })`\n */\n worker?: Worker;\n /**\n * URL to the tinymist WASM binary.\n * Required — there is no default CDN URL for tinymist-web.\n */\n wasmUrl: string;\n}\n\n/**\n * Manages a tinymist language server in a Web Worker. Provides LSP-based\n * completion and hover for Typst documents.\n *\n * const analyzer = await TypstAnalyzer.create({ wasmUrl: '...' });\n */\nexport class TypstAnalyzer {\n private readonly proxy: Comlink.Remote<AnalyzerWorker>;\n private readonly worker: Worker;\n private versionCounter = 0;\n /**\n * Last content pushed to the worker per URI. Presence is the source of\n * truth for \"is this URI opened on the worker?\"; value drives own-RPC dedup.\n */\n private readonly content = new Map<string, string>();\n\n private constructor(worker: Worker, proxy: Comlink.Remote<AnalyzerWorker>) {\n this.worker = worker;\n this.proxy = proxy;\n }\n\n static async create(options: TypstAnalyzerOptions): Promise<TypstAnalyzer> {\n const worker = options.worker ?? createAnalyzerWorker();\n const proxy = Comlink.wrap<AnalyzerWorker>(worker);\n const absoluteWasmUrl = new URL(options.wasmUrl, globalThis.location?.href)\n .href;\n\n const analyzer = new TypstAnalyzer(worker, proxy);\n\n await proxy.init(absoluteWasmUrl);\n\n return analyzer;\n }\n\n async didOpen(uri: string, content: string): Promise<void> {\n await this.proxy.didOpen(uri, content);\n this.content.set(uri, content);\n }\n\n async didClose(uri: string): Promise<void> {\n if (!this.content.has(uri)) return;\n await this.proxy.didClose(uri);\n this.content.delete(uri);\n }\n\n /**\n * Notify the analyzer that a document has changed. Skips the RPC when the\n * content matches what the worker last saw.\n */\n async didChange(uri: string, content: string): Promise<void> {\n if (!this.content.has(uri)) {\n await this.didOpen(uri, content);\n return;\n }\n if (this.content.get(uri) === content) return;\n const version = ++this.versionCounter;\n await this.proxy.didChange(uri, version, content);\n this.content.set(uri, content);\n }\n\n /**\n * Batch document changes. Splits inputs into opens (first-time URIs) and\n * changes (already-open URIs) and sends them in a single worker roundtrip.\n * Skips unchanged documents; returns without an RPC if nothing is pending.\n */\n async didChangeMany(docs: Record<string, string>): Promise<void> {\n const opens: Array<{ uri: string; content: string }> = [];\n const changes: Array<{ uri: string; version: number; content: string }> =\n [];\n const updates: Array<[string, string]> = [];\n for (const [uri, content] of Object.entries(docs)) {\n if (!this.content.has(uri)) {\n opens.push({ uri, content });\n updates.push([uri, content]);\n } else if (this.content.get(uri) !== content) {\n changes.push({ uri, version: ++this.versionCounter, content });\n updates.push([uri, content]);\n }\n }\n if (opens.length === 0 && changes.length === 0) return;\n await this.proxy.didChangeMany(opens, changes);\n for (const [uri, content] of updates) this.content.set(uri, content);\n }\n\n /**\n * Batch document closes. Filters to currently-open URIs and sends the set\n * in a single worker roundtrip.\n */\n async didCloseMany(uris: string[]): Promise<void> {\n const toClose: string[] = [];\n for (const uri of uris) {\n if (this.content.has(uri)) toClose.push(uri);\n }\n if (toClose.length === 0) return;\n await this.proxy.didCloseMany(toClose);\n for (const uri of toClose) this.content.delete(uri);\n }\n\n /**\n * Request completion at `position` for `uri`, with `content` as the current\n * document state. Bundles the didChange notification with the request in one\n * worker roundtrip; degrades to a plain completion request when the worker\n * already has this exact content.\n */\n async completion(\n uri: string,\n content: string,\n position: LspPosition,\n ): Promise<LspCompletionResponse> {\n if (this.content.get(uri) === content) {\n return this.proxy.completion(uri, position);\n }\n const isOpen = this.content.has(uri);\n const version = ++this.versionCounter;\n const result = await this.proxy.completionWithDoc(\n uri,\n version,\n content,\n position,\n isOpen ? \"change\" : \"open\",\n );\n this.content.set(uri, content);\n return result;\n }\n\n /**\n * Request hover at `position` for `uri`, with `content` as the current\n * document state. Bundles the didChange notification with the request in one\n * worker roundtrip; degrades to a plain hover request when the worker\n * already has this exact content.\n */\n async hover(\n uri: string,\n content: string,\n position: LspPosition,\n ): Promise<LspHover | null> {\n if (this.content.get(uri) === content) {\n return this.proxy.hover(uri, position);\n }\n const isOpen = this.content.has(uri);\n const version = ++this.versionCounter;\n const result = await this.proxy.hoverWithDoc(\n uri,\n version,\n content,\n position,\n isOpen ? \"change\" : \"open\",\n );\n this.content.set(uri, content);\n return result;\n }\n\n destroy(): void {\n this.proxy[Comlink.releaseProxy]();\n this.worker.terminate();\n }\n}\n","// Injected at build time by tsup (see tsup.config.ts)\ndeclare const __WORKER_CODE__: string;\ndeclare const __ANALYZER_WORKER_CODE__: string;\n\n/** Create a Worker from an inlined code string, auto-revoking the blob URL on terminate. */\nfunction createBlobWorker(code: string): Worker {\n const blob = new Blob([code], { type: \"application/javascript\" });\n const url = URL.createObjectURL(blob);\n const worker = new Worker(url);\n const origTerminate = worker.terminate.bind(worker);\n worker.terminate = () => {\n origTerminate();\n URL.revokeObjectURL(url);\n };\n return worker;\n}\n\n/** Create a blob Worker from the inlined compiler worker code. */\nexport function createWorker(): Worker {\n return createBlobWorker(__WORKER_CODE__);\n}\n\n/** Create a blob Worker from the inlined analyzer worker code. */\nexport function createAnalyzerWorker(): Worker {\n return createBlobWorker(__ANALYZER_WORKER_CODE__);\n}\n","/**\n * Two addressing schemes live in this codebase. This file is the only place\n * that knows how they relate. Keeping the vocabulary and conversions colocated\n * means a new contributor can read one file and understand the model.\n *\n * 1. Path — \"/main.typ\". Always leading-slash, always forward slashes.\n * Used by the compiler VFS, the `TypstProject` public API,\n * and the `typstFilePath` facet. Addresses a file within\n * the project.\n *\n * 2. AnalyzerUri — \"untitled:project/main.typ\". What tinymist's LSP expects.\n * Derived from a Path plus the analyzer URI root.\n *\n * The types below are type aliases, not branded/opaque types. That's\n * deliberate: callers can pass string literals where a Path is expected\n * without wrapping, and the aliases only serve as self-documenting\n * signatures. Ingress points still need to call `normalizePath` to defend\n * against un-normalized input — the alias does not imply the string has\n * been normalized.\n */\n\n/** `/path/to/file.typ` — leading-slash, forward slashes only. */\nexport type Path = string;\n\n/** `untitled:project/path/to/file.typ` — what tinymist's LSP consumes. */\nexport type AnalyzerUri = string;\n\n/** Ensure a path starts with a leading slash. Idempotent. */\nexport function normalizePath(path: string): Path {\n return path.startsWith(\"/\") ? path : `/${path}`;\n}\n\n/**\n * Normalize an analyzer URI root. Ensures leading slash, strips trailing\n * slashes. Special-cases \"/\" → \"\" so URIs don't end up with a leading\n * `untitled://`.\n */\nexport function normalizeRoot(rootPath: string): string {\n const root = normalizePath(rootPath);\n return root === \"/\" ? \"\" : root.replace(/\\/+$/, \"\");\n}\n\n/**\n * Build the analyzer URI for a given project path. `root` is expected to\n * already be normalized (see `normalizeRoot`).\n *\n * pathToAnalyzerUri(\"/main.typ\", \"/project\") -> \"untitled:project/main.typ\"\n */\nexport function pathToAnalyzerUri(path: Path, root: string): AnalyzerUri {\n const bare = root.replace(/^\\//, \"\");\n return `untitled:${bare}${normalizePath(path)}`;\n}\n","import * as Comlink from \"comlink\";\nimport { createWorker } from \"./rpc.js\";\nimport type { DiagnosticMessage } from \"./types.js\";\nimport type { CompilerWorker } from \"./compiler-worker.js\";\n\ndeclare const __TYPST_TS_WEB_COMPILER_VERSION__: string;\n\nexport interface CompileResult {\n diagnostics: DiagnosticMessage[];\n /** Vector artifact bytes from the compiler, usable with TypstRenderer for SVG rendering. */\n vector?: Uint8Array;\n}\n\nexport interface TypstCompilerOptions {\n /**\n * Explicit Worker instance. When omitted, an inlined blob worker is created automatically.\n * Use this for Vite apps to get proper source maps:\n * `await TypstCompiler.create({ worker: new Worker(new URL('typst-web-service/compiler-worker', import.meta.url)) })`\n */\n worker?: Worker;\n /**\n * URL to the typst-ts-web-compiler WASM binary.\n * Defaults to the matching version on jsDelivr CDN.\n * Override with a local asset URL for offline support or faster load:\n * `new URL('@myriaddreamin/typst-ts-web-compiler/pkg/typst_ts_web_compiler_bg.wasm', import.meta.url).href`\n */\n wasmUrl?: string;\n /** Font URLs to load into the Typst compiler. Defaults to Roboto from jsDelivr. */\n fonts?: string[];\n /**\n * Enable fetching @preview/ packages from packages.typst.org on demand.\n * Default: true.\n */\n packages?: boolean;\n}\n\nconst DEFAULT_FONTS = [\n \"https://cdn.jsdelivr.net/npm/roboto-font@0.1.0/fonts/Roboto/roboto-regular-webfont.ttf\",\n];\n\nconst defaultWasmUrl = () =>\n `https://cdn.jsdelivr.net/npm/@myriaddreamin/typst-ts-web-compiler@${__TYPST_TS_WEB_COMPILER_VERSION__}/pkg/typst_ts_web_compiler_bg.wasm`;\n\n/**\n * Manages a Typst compiler worker. Create one instance and share it across\n * all extensions (linter, autocomplete, preview, etc.).\n *\n * await TypstCompiler.create() // blob worker, defaults\n * await TypstCompiler.create({ wasmUrl: '...' }) // blob worker, custom WASM\n * await TypstCompiler.create({ worker: myWorker }) // explicit Worker (Vite)\n * await TypstCompiler.create({ worker: myWorker, fonts: [...] }) // explicit Worker + options\n */\nexport class TypstCompiler {\n private readonly proxy: Comlink.Remote<CompilerWorker>;\n private readonly worker: Worker;\n private readonly encoder = new TextEncoder();\n /** Last text content pushed per path. Drives own-RPC dedup. Invalidated by binary writes. */\n private readonly content = new Map<string, string>();\n\n private constructor(worker: Worker, proxy: Comlink.Remote<CompilerWorker>) {\n this.worker = worker;\n this.proxy = proxy;\n }\n\n static async create(\n options: TypstCompilerOptions = {},\n ): Promise<TypstCompiler> {\n const worker = options.worker ?? createWorker();\n const proxy = Comlink.wrap<CompilerWorker>(worker);\n\n await proxy.init(\n options.wasmUrl ?? defaultWasmUrl(),\n options.fonts ?? DEFAULT_FONTS,\n options.packages ?? true,\n );\n\n return new TypstCompiler(worker, proxy);\n }\n\n /**\n * Compile whatever is currently in the VFS (populated via\n * setText/setBinary/setJson/setMany). Defaults to compiling \"/main.typ\";\n * override with `entry`.\n */\n compile(entry?: string): Promise<CompileResult> {\n return this.proxy.compile(entry);\n }\n\n /**\n * Compile to PDF. Operates on whatever is currently in the VFS (populated\n * via setText/setBinary/setJson/setMany). Defaults to compiling \"/main.typ\";\n * override with `entry`.\n */\n compilePdf(entry?: string): Promise<Uint8Array> {\n return this.proxy.compilePdf(entry);\n }\n\n /**\n * Add or overwrite a text file in the virtual compiler filesystem. Skips\n * the worker RPC when `source` matches the last value pushed for `path`.\n */\n async setText(path: string, source: string): Promise<void> {\n if (this.content.get(path) === source) return;\n await this.proxy.mapShadow(path, this.encoder.encode(source));\n this.content.set(path, source);\n }\n\n /** Add or overwrite a JSON file in the virtual compiler filesystem. */\n setJson(\n path: string,\n value: unknown,\n replacer?: (this: unknown, key: string, value: unknown) => unknown,\n space?: string | number,\n ): Promise<void> {\n return this.setText(path, JSON.stringify(value, replacer, space));\n }\n\n /**\n * Add or overwrite multiple files in the virtual compiler filesystem in a\n * single worker roundtrip. Strings are UTF-8 encoded; Uint8Arrays are\n * passed through. Text entries unchanged since their last push are skipped;\n * binary entries always push and invalidate the text cache for their path.\n */\n async setMany(files: Record<string, string | Uint8Array>): Promise<void> {\n const encoded: Record<string, Uint8Array> = {};\n const textUpdates: Array<[string, string]> = [];\n const binaryInvalidations: string[] = [];\n for (const [path, content] of Object.entries(files)) {\n if (typeof content === \"string\") {\n if (this.content.get(path) === content) continue;\n textUpdates.push([path, content]);\n encoded[path] = this.encoder.encode(content);\n } else {\n binaryInvalidations.push(path);\n encoded[path] = content;\n }\n }\n if (Object.keys(encoded).length === 0) return;\n await this.proxy.mapShadowMany(encoded);\n for (const [path, content] of textUpdates) this.content.set(path, content);\n for (const path of binaryInvalidations) this.content.delete(path);\n }\n\n /** Add or overwrite a binary file in the virtual compiler filesystem. */\n async setBinary(\n path: string,\n content: ArrayBuffer | ArrayBufferView,\n ): Promise<void> {\n const bytes =\n content instanceof ArrayBuffer\n ? new Uint8Array(content)\n : new Uint8Array(\n content.buffer,\n content.byteOffset,\n content.byteLength,\n );\n await this.proxy.mapShadow(path, bytes);\n this.content.delete(path);\n }\n\n /** Remove a file from the virtual compiler filesystem. */\n async remove(path: string): Promise<void> {\n await this.proxy.unmapShadow(path);\n this.content.delete(path);\n }\n\n /** Clear all virtual files from the compiler filesystem. */\n async clear(): Promise<void> {\n await this.proxy.resetShadow();\n this.content.clear();\n }\n\n destroy(): void {\n this.proxy[Comlink.releaseProxy]();\n this.worker.terminate();\n }\n}\n","export interface FormatConfig {\n /** Number of spaces per indentation level. Default: 2 */\n tab_spaces?: number;\n /** Maximum line width. Default: 80 */\n max_width?: number;\n /** Maximum consecutive blank lines allowed. */\n blank_lines_upper_bound?: number;\n /** Collapse consecutive whitespace in markup to a single space. */\n collapse_markup_spaces?: boolean;\n /** Sort import items alphabetically. */\n reorder_import_items?: boolean;\n /** Wrap text in markup to fit within max_width. Implies collapse_markup_spaces. */\n wrap_text?: boolean;\n}\n\nexport interface FormatRangeResult {\n /** Start index (UTF-16) of the actual formatted range. */\n start: number;\n /** End index (UTF-16) of the actual formatted range. */\n end: number;\n /** The formatted text for the range. */\n text: string;\n}\n\ntype TypstyleModule = typeof import(\"@typstyle/typstyle-wasm-bundler\");\n\nlet typstylePromise: Promise<TypstyleModule> | null = null;\n\nfunction getTypstyle(): Promise<TypstyleModule> {\n if (!typstylePromise) {\n typstylePromise = import(\"@typstyle/typstyle-wasm-bundler\").catch((err) => {\n typstylePromise = null;\n throw err;\n });\n }\n return typstylePromise;\n}\n\n/**\n * Typst code formatter powered by typstyle.\n *\n * Runs on the main thread — typstyle is lightweight and fast.\n * The WASM module is loaded lazily on first format call.\n * Requires a bundler that supports WASM imports (e.g. Vite, webpack).\n *\n * const formatter = TypstFormatter.create({ tab_spaces: 2, max_width: 80 });\n * const formatted = await formatter.format(source);\n */\nexport class TypstFormatter {\n private config: FormatConfig;\n\n private constructor(config: FormatConfig = {}) {\n this.config = config;\n // Eagerly start loading WASM so it's ready by first use.\n // Swallow preload errors — they'll surface on first format() call.\n getTypstyle().catch(() => {});\n }\n\n static create(config: FormatConfig = {}): TypstFormatter {\n return new TypstFormatter(config);\n }\n\n /** Format an entire Typst source string. */\n async format(source: string): Promise<string> {\n const typstyle = await getTypstyle();\n return typstyle.format(source, this.config);\n }\n\n /** Format a range within a Typst source string. Indices are UTF-16 code units. */\n async formatRange(\n source: string,\n start: number,\n end: number,\n ): Promise<FormatRangeResult> {\n const typstyle = await getTypstyle();\n const result = typstyle.format_range(source, start, end, this.config);\n return { start: result.start, end: result.end, text: result.text };\n }\n}\n","interface CompileSchedulerOptions {\n /** Minimum idle time (ms) after the last request before firing. Default: 0. */\n debounceMs?: number;\n /**\n * Maximum time (ms) the debounce may keep deferring during continuous\n * requests. Guarantees a fire at least this often so users see progress\n * while typing. Default: 0 (no cap).\n */\n maxWaitMs?: number;\n}\n\n/**\n * Debounce helper with a max-wait ceiling for coalescing compile requests. A\n * burst of `schedule(cb)` calls within `debounceMs` collapses into one fire;\n * during sustained bursts, `maxWaitMs` caps how long the debounce may keep\n * deferring.\n */\nexport class CompileScheduler {\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private maxWaitTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(private readonly options: CompileSchedulerOptions = {}) {}\n\n schedule(callback: () => void): void {\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n\n const delay = Math.max(0, this.options.debounceMs ?? 0);\n this.debounceTimer = setTimeout(() => {\n this.debounceTimer = null;\n this.clearMaxWait();\n callback();\n }, delay);\n\n const maxWait = this.options.maxWaitMs;\n if (maxWait != null && maxWait > 0 && !this.maxWaitTimer) {\n this.maxWaitTimer = setTimeout(() => {\n this.maxWaitTimer = null;\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n callback();\n }\n }, maxWait);\n }\n }\n\n /** Cancel any pending scheduled fire without calling the callback. */\n cancel(): void {\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n this.clearMaxWait();\n }\n\n private clearMaxWait(): void {\n if (this.maxWaitTimer) {\n clearTimeout(this.maxWaitTimer);\n this.maxWaitTimer = null;\n }\n }\n}\n","import type { TypstAnalyzer } from \"./analyzer.js\";\nimport type {\n LspCompletionResponse,\n LspHover,\n LspPosition,\n} from \"./analyzer-types.js\";\nimport { CompileScheduler } from \"./compile-scheduler.js\";\nimport type { CompileResult, TypstCompiler } from \"./compiler.js\";\nimport {\n normalizePath,\n normalizeRoot,\n type Path,\n pathToAnalyzerUri,\n} from \"./identifiers.js\";\n\nexport interface TypstProjectOptions {\n compiler: TypstCompiler;\n /**\n * Optional analyzer. When provided, text file operations also sync with the\n * analyzer so completions / hover reflect the current state.\n */\n analyzer?: TypstAnalyzer;\n /** Default entry file path. Default: \"/main.typ\". */\n entry?: string;\n /**\n * Prefix used to build the `untitled:` URIs handed to the analyzer.\n * Default: \"/project\". A path of `/main.typ` becomes\n * `untitled:project/main.typ`. Only affects URI construction — the compiler\n * and project VFS use the raw paths unchanged.\n */\n analyzerUriRoot?: string;\n /**\n * Scheduling for auto-compiles after VFS mutations. Mutations debounce by\n * `debounceMs`; `maxWaitMs` caps how long the debounce can keep deferring\n * during sustained edits so the user still sees progress.\n */\n autoCompile?: AutoCompileOptions;\n}\n\nexport interface AutoCompileOptions {\n /**\n * Idle time (ms) after the last VFS mutation before a compile fires.\n * Default: 0 — compile fires on the next macrotask. Set higher (e.g. 150) to\n * coalesce rapid edits.\n */\n debounceMs?: number;\n /**\n * Maximum time (ms) the debounce is allowed to defer a compile during\n * sustained mutation bursts. Default: 0 (no cap — pure debounce).\n */\n maxWaitMs?: number;\n}\n\nconst DEFAULT_ENTRY = \"/main.typ\";\nconst DEFAULT_ROOT = \"/project\";\n\n/**\n * Coordinates a compiler + analyzer pair for multi-file Typst projects.\n *\n * Owns the project's virtual filesystem state. Editors push incremental\n * `setText` updates as the user types; the project mirrors those edits to\n * both the compiler's shadow VFS and the analyzer's open-document set, then\n * compiles or services LSP requests against the current state.\n *\n * const project = new TypstProject({ compiler, analyzer });\n * await project.setMany({ \"/main.typ\": \"...\", \"/utils.typ\": \"...\" });\n * const result = await project.compile();\n */\nexport type CompileListener = (result: CompileResult) => void;\n\nfunction errorAsCompileResult(\n err: unknown,\n paths: readonly string[],\n): CompileResult {\n const message = err instanceof Error ? err.message : String(err);\n return {\n diagnostics: paths.map((path) => ({\n package: \"\",\n path,\n severity: \"Error\",\n range: { startLine: 0, startCol: 0, endLine: 0, endCol: 1 },\n message,\n })),\n };\n}\n\nexport class TypstProject {\n private readonly compiler: TypstCompiler;\n private readonly analyzer?: TypstAnalyzer;\n private readonly analyzerUriRoot: string;\n /**\n * Tracked text files: path → latest content observed. Presence in this map\n * is the source of truth for \"is this a tracked text file?\"; insertion\n * order drives the `files` getter. Per-sink dedup lives in the compiler and\n * analyzer.\n */\n private readonly contentByPath = new Map<Path, string>();\n private readonly compileListeners = new Set<CompileListener>();\n private readonly scheduler: CompileScheduler;\n private compileVersion = 0;\n private _lastResult: CompileResult | undefined;\n private _entry: Path;\n private destroyed = false;\n\n private invokeListener(\n listener: CompileListener,\n result: CompileResult,\n ): void {\n try {\n listener(result);\n } catch (err) {\n console.error(\"[typst] compile listener threw:\", err);\n }\n }\n\n constructor(options: TypstProjectOptions) {\n this.compiler = options.compiler;\n this.analyzer = options.analyzer;\n this.analyzerUriRoot = normalizeRoot(\n options.analyzerUriRoot ?? DEFAULT_ROOT,\n );\n this._entry = normalizePath(options.entry ?? DEFAULT_ENTRY);\n this.scheduler = new CompileScheduler({\n debounceMs: options.autoCompile?.debounceMs,\n maxWaitMs: options.autoCompile?.maxWaitMs,\n });\n }\n\n /**\n * Schedule an auto-compile after VFS mutations. Coalesces rapid calls via\n * the configured debounce/throttle. Errors surface through `onCompile`\n * listeners via a synthetic diagnostic; callers awaiting a specific compile\n * should call `compile()` directly.\n */\n private scheduleCompile(): void {\n if (this.destroyed) return;\n this.scheduler.schedule(() => {\n this.compile().catch((err) => console.error(\"[typst]\", err));\n });\n }\n\n /** Current entry file path. Assign to change the sticky entry used by subsequent `compile()` calls. */\n get entry(): Path {\n return this._entry;\n }\n\n set entry(path: Path) {\n const next = normalizePath(path);\n if (next === this._entry) return;\n this._entry = next;\n this.scheduleCompile();\n }\n\n /** Whether an analyzer is attached. */\n get hasAnalyzer(): boolean {\n return this.analyzer !== undefined;\n }\n\n /**\n * Most recent compile result, or `undefined` before the first compile has\n * settled. Useful for lazy-mounted UI that subscribes after boot and needs\n * an initial value.\n */\n get lastResult(): CompileResult | undefined {\n return this._lastResult;\n }\n\n /**\n * Snapshot of tracked text file paths, in insertion order. Updated by\n * `setText`, `setMany`, `remove`, and `clear`. Returns a fresh array — mutate\n * freely without affecting project state.\n */\n get files(): Path[] {\n return [...this.contentByPath.keys()];\n }\n\n /**\n * Current text content for a tracked file, or `undefined` if the path was\n * never written via `setText`/`setMany` (or was removed). Read-through to the\n * project's sync cache — lets consumers avoid shadowing the VFS themselves.\n */\n getText(path: Path): string | undefined {\n return this.contentByPath.get(normalizePath(path));\n }\n\n /**\n * Add or overwrite a text file. Goes to the compiler's VFS and, when an\n * analyzer is attached, to the analyzer as a document change. No-op when\n * the tracked path already has this exact content — skips both worker RPCs\n * and the auto-scheduled compile.\n */\n async setText(path: Path, content: string): Promise<void> {\n const p = normalizePath(path);\n if (this.contentByPath.get(p) === content) return;\n await Promise.all([\n this.compiler.setText(p, content),\n this.analyzer?.didChange(\n pathToAnalyzerUri(p, this.analyzerUriRoot),\n content,\n ) ?? Promise.resolve(),\n ]);\n this.contentByPath.set(p, content);\n this.scheduleCompile();\n }\n\n /**\n * Add or overwrite a JSON file. The analyzer does not track data files, but\n * if `path` was previously tracked as text the analyzer document is closed\n * and the text entry retired so the three views stay consistent.\n */\n async setJson(path: Path, value: unknown): Promise<void> {\n const p = normalizePath(path);\n const wasText = this.contentByPath.has(p);\n await Promise.all([\n this.compiler.setJson(p, value),\n wasText\n ? this.analyzer?.didClose(pathToAnalyzerUri(p, this.analyzerUriRoot))\n : undefined,\n ]);\n this.contentByPath.delete(p);\n this.scheduleCompile();\n }\n\n /**\n * Add or overwrite a binary file. If `path` was previously tracked as text,\n * the analyzer document is closed and the text entry retired in the same\n * call so the compiler / analyzer / project views stay consistent.\n */\n async setBinary(\n path: Path,\n content: ArrayBuffer | ArrayBufferView,\n ): Promise<void> {\n const p = normalizePath(path);\n const wasText = this.contentByPath.has(p);\n await Promise.all([\n this.compiler.setBinary(p, content),\n wasText\n ? this.analyzer?.didClose(pathToAnalyzerUri(p, this.analyzerUriRoot))\n : undefined,\n ]);\n this.contentByPath.delete(p);\n this.scheduleCompile();\n }\n\n /**\n * Batch set multiple files. Strings route to both compiler and analyzer;\n * Uint8Array entries go to the compiler only. Strings matching the last\n * tracked content for their path are skipped on both sinks. Binary entries\n * always go through (no content cache, so no dedup).\n */\n async setMany(files: Record<Path, string | Uint8Array>): Promise<void> {\n const canonical = new Map<Path, string | Uint8Array>();\n for (const [path, content] of Object.entries(files)) {\n canonical.set(normalizePath(path), content);\n }\n\n const normalized: Record<Path, string | Uint8Array> = {};\n const analyzerDocs: Record<string, string> = {};\n const textUpdates: Array<[Path, string]> = [];\n const analyzerCloses: string[] = [];\n const binaryRetirements: Path[] = [];\n for (const [p, content] of canonical) {\n if (typeof content !== \"string\") {\n normalized[p] = content;\n if (this.contentByPath.has(p)) {\n analyzerCloses.push(pathToAnalyzerUri(p, this.analyzerUriRoot));\n binaryRetirements.push(p);\n }\n continue;\n }\n if (this.contentByPath.get(p) === content) continue;\n textUpdates.push([p, content]);\n normalized[p] = content;\n analyzerDocs[pathToAnalyzerUri(p, this.analyzerUriRoot)] = content;\n }\n if (Object.keys(normalized).length === 0) return;\n await Promise.all([\n this.compiler.setMany(normalized),\n this.analyzer?.didChangeMany(analyzerDocs) ?? Promise.resolve(),\n analyzerCloses.length > 0\n ? (this.analyzer?.didCloseMany(analyzerCloses) ?? Promise.resolve())\n : Promise.resolve(),\n ]);\n for (const [p, content] of textUpdates) this.contentByPath.set(p, content);\n for (const p of binaryRetirements) this.contentByPath.delete(p);\n this.scheduleCompile();\n }\n\n /**\n * Remove a file. Always removed from the compiler's VFS; also closed on the\n * analyzer when it was previously tracked as text.\n */\n async remove(path: Path): Promise<void> {\n const p = normalizePath(path);\n const wasText = this.contentByPath.has(p);\n await Promise.all([\n this.compiler.remove(p),\n wasText\n ? this.analyzer?.didClose(pathToAnalyzerUri(p, this.analyzerUriRoot))\n : undefined,\n ]);\n this.contentByPath.delete(p);\n this.scheduleCompile();\n }\n\n /** Clear all files from both compiler VFS and analyzer document set. */\n async clear(): Promise<void> {\n const uris = Array.from(this.contentByPath.keys(), (p) =>\n pathToAnalyzerUri(p, this.analyzerUriRoot),\n );\n await Promise.all([\n this.compiler.clear(),\n uris.length > 0 ? this.analyzer?.didCloseMany(uris) : undefined,\n ]);\n this.contentByPath.clear();\n this.scheduleCompile();\n }\n\n /**\n * Subscribe to compile results. Fires after every `compile()` whose result is\n * still current (stale results from out-of-order concurrent compiles are\n * dropped). If a compile has already settled, the most recent result is\n * delivered synchronously so late-mounted listeners aren't stuck blank until\n * the next compile. Returns an unsubscribe function.\n */\n onCompile(listener: CompileListener): () => void {\n this.compileListeners.add(listener);\n if (this._lastResult !== undefined) {\n this.invokeListener(listener, this._lastResult);\n }\n return () => {\n this.compileListeners.delete(listener);\n };\n }\n\n /**\n * Compile the current VFS state using the sticky entry. Errors from the\n * underlying compiler are converted into a synthetic error diagnostic so\n * callers and listeners always receive a `CompileResult`. Listeners are\n * notified only for the most recent compile — results from an earlier call\n * that resolves after a later one are suppressed.\n *\n * VFS mutations (`setText`, `remove`, etc.) auto-schedule a debounced\n * compile; call this directly only when you need an awaitable handle on the\n * result (e.g., to flush before rendering to PDF).\n */\n async compile(): Promise<CompileResult> {\n this.scheduler.cancel();\n const version = ++this.compileVersion;\n let result: CompileResult;\n try {\n result = await this.compiler.compile(this._entry);\n } catch (err) {\n // Spread the synthetic error across every tracked text path so the\n // diagnostic is visible no matter which file the user is viewing.\n // Falls back to the entry if nothing is tracked yet.\n const paths =\n this.contentByPath.size > 0\n ? [...this.contentByPath.keys()]\n : [this._entry];\n result = errorAsCompileResult(err, paths);\n }\n if (version === this.compileVersion) {\n this._lastResult = result;\n for (const listener of this.compileListeners) {\n this.invokeListener(listener, result);\n }\n }\n return result;\n }\n\n /** Compile the current VFS state to PDF using the sticky entry. */\n compilePdf(): Promise<Uint8Array> {\n return this.compiler.compilePdf(this._entry);\n }\n\n private requireAnalyzer(operation: string): TypstAnalyzer {\n if (!this.analyzer) {\n throw new Error(`TypstProject: ${operation} requires an analyzer`);\n }\n return this.analyzer;\n }\n\n /**\n * Request completion for `path` at `position`, using `source` as the\n * current document state. Pure analyzer query — neither the compiler VFS\n * nor project tracking (`files`/`getText`) is touched. Throws when no\n * analyzer is attached.\n */\n completion(\n path: Path,\n source: string,\n position: LspPosition,\n ): Promise<LspCompletionResponse> {\n const analyzer = this.requireAnalyzer(\"completion\");\n return analyzer.completion(\n pathToAnalyzerUri(normalizePath(path), this.analyzerUriRoot),\n source,\n position,\n );\n }\n\n /**\n * Request hover for `path` at `position`, using `source` as the current\n * document state. Pure analyzer query — neither the compiler VFS nor\n * project tracking is touched. Throws when no analyzer is attached.\n */\n hover(\n path: Path,\n source: string,\n position: LspPosition,\n ): Promise<LspHover | null> {\n const analyzer = this.requireAnalyzer(\"hover\");\n return analyzer.hover(\n pathToAnalyzerUri(normalizePath(path), this.analyzerUriRoot),\n source,\n position,\n );\n }\n\n /**\n * Tear down the project and the services it owns. Destroys the attached\n * compiler and analyzer, drops all listeners, and clears VFS tracking state.\n * Idempotent — calling twice is a no-op. After destruction, further calls on\n * the project are not supported; construct a new one.\n *\n * If you need to share a compiler or analyzer across projects, destroy them\n * yourself and don't call this method — the project does not provide an\n * ownership toggle.\n */\n destroy(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n this.scheduler.cancel();\n this.compileListeners.clear();\n this.contentByPath.clear();\n this._lastResult = undefined;\n this.compiler.destroy();\n this.analyzer?.destroy();\n }\n}\n","/** Minimal interface for the built TypstRenderer instance. */\nexport interface RendererInstance {\n free(): void;\n create_session(): RendererSession;\n manipulate_data(\n session: RendererSession,\n action: string,\n data: Uint8Array,\n ): void;\n svg_data(session: RendererSession): string;\n}\n\n/** Minimal interface for a TypstRenderer session. */\nexport interface RendererSession {\n free(): void;\n}\n\ntype RendererWasmModule = typeof import(\"@myriaddreamin/typst-ts-renderer\");\n\ndeclare const __TYPST_TS_RENDERER_VERSION__: string;\n\nconst DEFAULT_RENDERER_WASM_URL = `https://cdn.jsdelivr.net/npm/@myriaddreamin/typst-ts-renderer@${__TYPST_TS_RENDERER_VERSION__}/pkg/typst_ts_renderer_bg.wasm`;\n\nlet rendererModulePromise: Promise<RendererWasmModule> | null = null;\n\nfunction getRendererModule(): Promise<RendererWasmModule> {\n if (!rendererModulePromise) {\n rendererModulePromise = import(\"@myriaddreamin/typst-ts-renderer\").catch(\n (err) => {\n rendererModulePromise = null;\n throw err;\n },\n );\n }\n return rendererModulePromise;\n}\n\nexport interface TypstRendererOptions {\n /** URL to the typst-ts-renderer WASM binary. Defaults to jsDelivr CDN. */\n wasmUrl?: string;\n}\n\nexport interface RenderedSvgPage {\n /** Zero-based page index within the document. */\n index: number;\n /** Page width in typographic points. */\n width: number;\n /** Page height in typographic points. */\n height: number;\n /** Standalone SVG string for just this page. */\n svg: string;\n}\n\n/**\n * Converts Typst vector artifacts to SVG strings.\n *\n * The renderer WASM module is loaded lazily on first use.\n *\n * const renderer = TypstRenderer.create();\n * const svg = await renderer.renderSvg(vector);\n */\nexport class TypstRenderer {\n private wasmUrl: string;\n private instance: Promise<RendererInstance> | null = null;\n\n private constructor(options: TypstRendererOptions = {}) {\n this.wasmUrl = options.wasmUrl ?? DEFAULT_RENDERER_WASM_URL;\n // Eagerly start loading the WASM module so it's ready by first use.\n getRendererModule().catch(() => {});\n }\n\n static create(options: TypstRendererOptions = {}): TypstRenderer {\n return new TypstRenderer(options);\n }\n\n private getInstance(): Promise<RendererInstance> {\n if (!this.instance) {\n this.instance = this.#init().catch((err) => {\n this.instance = null;\n throw err;\n });\n }\n return this.instance;\n }\n\n async #init(): Promise<RendererInstance> {\n const mod = await getRendererModule();\n await mod.default({ module_or_path: this.wasmUrl });\n return new mod.TypstRendererBuilder().build();\n }\n\n /** Free the underlying WASM renderer instance. */\n async destroy(): Promise<void> {\n const instance = this.instance;\n this.instance = null;\n if (instance) {\n (await instance).free();\n }\n }\n\n /** Render a Typst vector artifact to an SVG string. */\n async renderSvg(vector: Uint8Array): Promise<string> {\n const renderer = await this.getInstance();\n const session = renderer.create_session();\n try {\n renderer.manipulate_data(session, \"reset\", vector);\n return renderer.svg_data(session);\n } finally {\n session.free();\n }\n }\n\n /**\n * Render a Typst vector artifact into one self-contained SVG string per\n * physical page. The merged SVG is split by `<g class=\"typst-page\">`\n * children; each group's `data-page-width` / `data-page-height` give the\n * page-local viewBox. Shared `<defs>` / `<style>` are duplicated into each\n * page so the output SVGs render independently. Returns an empty array if\n * the document has no page groups.\n */\n async renderSvgPages(vector: Uint8Array): Promise<RenderedSvgPage[]> {\n return splitMergedSvgPages(await this.renderSvg(vector));\n }\n}\n\n// Parsing must use \"text/html\", not \"image/svg+xml\": Typst's merged SVG\n// output has repeatedly failed XML-strict parsing. HTML mode tolerates it\n// and still produces real SVGSVGElement nodes for inline SVG.\nfunction splitMergedSvgPages(svg: string): RenderedSvgPage[] {\n const doc = new DOMParser().parseFromString(svg, \"text/html\");\n const root = doc.querySelector(\"svg\");\n if (!root) return [];\n\n const children = Array.from(root.children);\n const pageGroups = children.filter(\n (el) =>\n el.tagName.toLowerCase() === \"g\" && el.classList.contains(\"typst-page\"),\n );\n if (pageGroups.length === 0) return [];\n\n const sharedHtml = children\n .filter((el) => !el.classList.contains(\"typst-page\"))\n .map((el) => el.outerHTML)\n .join(\"\");\n\n const namespaceAttrs = Array.from(root.attributes)\n .filter((attr) => attr.name === \"xmlns\" || attr.name.startsWith(\"xmlns:\"))\n .map((attr) => `${attr.name}=\"${attr.value}\"`)\n .join(\" \");\n\n return pageGroups.flatMap((group, index) => {\n const width = Number(group.getAttribute(\"data-page-width\")) || 0;\n const height = Number(group.getAttribute(\"data-page-height\")) || 0;\n if (width <= 0 || height <= 0) return [];\n\n const clone = group.cloneNode(true) as Element;\n clone.removeAttribute(\"transform\");\n\n return [\n {\n index,\n width,\n height,\n svg:\n `<svg ${namespaceAttrs} viewBox=\"0 0 ${width} ${height}\" ` +\n `width=\"${width}\" height=\"${height}\">` +\n `${sharedHtml}${clone.outerHTML}</svg>`,\n },\n ];\n });\n}\n"],"mappings":"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","names":["Comlink"]}
1
+ {"version":3,"sources":["../src/analyzer.ts","../src/rpc.ts","../src/identifiers.ts","../src/compiler.ts","../src/formatter.ts","../src/compile-scheduler.ts","../src/project.ts","../src/renderer.ts"],"sourcesContent":["import * as Comlink from \"comlink\";\nimport type {\n LspCompletionResponse,\n LspHover,\n LspPosition,\n} from \"./analyzer-types.js\";\nimport type { AnalyzerWorker } from \"./analyzer-worker.js\";\nimport { createAnalyzerWorker } from \"./rpc.js\";\n\nexport interface TypstAnalyzerOptions {\n /**\n * Explicit Worker instance. When omitted, an inlined blob worker is created automatically.\n * Use this for Vite apps:\n * `await TypstAnalyzer.create({ worker: new Worker(new URL('typst-web-service/analyzer-worker', import.meta.url), { type: 'module' }) })`\n */\n worker?: Worker;\n /**\n * URL to the tinymist WASM binary.\n * Required — there is no default CDN URL for tinymist-web.\n */\n wasmUrl: string;\n}\n\n/**\n * Manages a tinymist language server in a Web Worker. Provides LSP-based\n * completion and hover for Typst documents.\n *\n * const analyzer = await TypstAnalyzer.create({ wasmUrl: '...' });\n */\nexport class TypstAnalyzer {\n private readonly proxy: Comlink.Remote<AnalyzerWorker>;\n private readonly worker: Worker;\n private versionCounter = 0;\n /**\n * Last content pushed to the worker per URI. Presence is the source of\n * truth for \"is this URI opened on the worker?\"; value drives own-RPC dedup.\n */\n private readonly content = new Map<string, string>();\n\n private constructor(worker: Worker, proxy: Comlink.Remote<AnalyzerWorker>) {\n this.worker = worker;\n this.proxy = proxy;\n }\n\n static async create(options: TypstAnalyzerOptions): Promise<TypstAnalyzer> {\n const worker = options.worker ?? createAnalyzerWorker();\n const proxy = Comlink.wrap<AnalyzerWorker>(worker);\n const absoluteWasmUrl = new URL(options.wasmUrl, globalThis.location?.href)\n .href;\n\n const analyzer = new TypstAnalyzer(worker, proxy);\n\n await proxy.init(absoluteWasmUrl);\n\n return analyzer;\n }\n\n async didOpen(uri: string, content: string): Promise<void> {\n await this.proxy.didOpen(uri, content);\n this.content.set(uri, content);\n }\n\n async didClose(uri: string): Promise<void> {\n if (!this.content.has(uri)) return;\n await this.proxy.didClose(uri);\n this.content.delete(uri);\n }\n\n /**\n * Notify the analyzer that a document has changed. Skips the RPC when the\n * content matches what the worker last saw.\n */\n async didChange(uri: string, content: string): Promise<void> {\n if (!this.content.has(uri)) {\n await this.didOpen(uri, content);\n return;\n }\n if (this.content.get(uri) === content) return;\n const version = ++this.versionCounter;\n await this.proxy.didChange(uri, version, content);\n this.content.set(uri, content);\n }\n\n /**\n * Batch document changes. Splits inputs into opens (first-time URIs) and\n * changes (already-open URIs) and sends them in a single worker roundtrip.\n * Skips unchanged documents; returns without an RPC if nothing is pending.\n */\n async didChangeMany(docs: Record<string, string>): Promise<void> {\n const opens: Array<{ uri: string; content: string }> = [];\n const changes: Array<{ uri: string; version: number; content: string }> =\n [];\n const updates: Array<[string, string]> = [];\n for (const [uri, content] of Object.entries(docs)) {\n if (!this.content.has(uri)) {\n opens.push({ uri, content });\n updates.push([uri, content]);\n } else if (this.content.get(uri) !== content) {\n changes.push({ uri, version: ++this.versionCounter, content });\n updates.push([uri, content]);\n }\n }\n if (opens.length === 0 && changes.length === 0) return;\n await this.proxy.didChangeMany(opens, changes);\n for (const [uri, content] of updates) this.content.set(uri, content);\n }\n\n /**\n * Batch document closes. Filters to currently-open URIs and sends the set\n * in a single worker roundtrip.\n */\n async didCloseMany(uris: string[]): Promise<void> {\n const toClose: string[] = [];\n for (const uri of uris) {\n if (this.content.has(uri)) toClose.push(uri);\n }\n if (toClose.length === 0) return;\n await this.proxy.didCloseMany(toClose);\n for (const uri of toClose) this.content.delete(uri);\n }\n\n /**\n * Request completion at `position` for `uri`, with `content` as the current\n * document state. Bundles the didChange notification with the request in one\n * worker roundtrip; degrades to a plain completion request when the worker\n * already has this exact content.\n */\n async completion(\n uri: string,\n content: string,\n position: LspPosition,\n ): Promise<LspCompletionResponse> {\n if (this.content.get(uri) === content) {\n return this.proxy.completion(uri, position);\n }\n const isOpen = this.content.has(uri);\n const version = ++this.versionCounter;\n const result = await this.proxy.completionWithDoc(\n uri,\n version,\n content,\n position,\n isOpen ? \"change\" : \"open\",\n );\n this.content.set(uri, content);\n return result;\n }\n\n /**\n * Request hover at `position` for `uri`, with `content` as the current\n * document state. Bundles the didChange notification with the request in one\n * worker roundtrip; degrades to a plain hover request when the worker\n * already has this exact content.\n */\n async hover(\n uri: string,\n content: string,\n position: LspPosition,\n ): Promise<LspHover | null> {\n if (this.content.get(uri) === content) {\n return this.proxy.hover(uri, position);\n }\n const isOpen = this.content.has(uri);\n const version = ++this.versionCounter;\n const result = await this.proxy.hoverWithDoc(\n uri,\n version,\n content,\n position,\n isOpen ? \"change\" : \"open\",\n );\n this.content.set(uri, content);\n return result;\n }\n\n destroy(): void {\n this.proxy[Comlink.releaseProxy]();\n this.worker.terminate();\n }\n}\n","// Injected at build time by tsup (see tsup.config.ts)\ndeclare const __WORKER_CODE__: string;\ndeclare const __ANALYZER_WORKER_CODE__: string;\n\n/** Create a Worker from an inlined code string, auto-revoking the blob URL on terminate. */\nfunction createBlobWorker(code: string): Worker {\n const blob = new Blob([code], { type: \"application/javascript\" });\n const url = URL.createObjectURL(blob);\n const worker = new Worker(url);\n const origTerminate = worker.terminate.bind(worker);\n worker.terminate = () => {\n origTerminate();\n URL.revokeObjectURL(url);\n };\n return worker;\n}\n\n/** Create a blob Worker from the inlined compiler worker code. */\nexport function createWorker(): Worker {\n return createBlobWorker(__WORKER_CODE__);\n}\n\n/** Create a blob Worker from the inlined analyzer worker code. */\nexport function createAnalyzerWorker(): Worker {\n return createBlobWorker(__ANALYZER_WORKER_CODE__);\n}\n","/**\n * Two addressing schemes live in this codebase. This file is the only place\n * that knows how they relate. Keeping the vocabulary and conversions colocated\n * means a new contributor can read one file and understand the model.\n *\n * 1. Path — \"/main.typ\". Always leading-slash, always forward slashes.\n * Used by the compiler VFS, the `TypstProject` public API,\n * and the `typstFilePath` facet. Addresses a file within\n * the project.\n *\n * 2. AnalyzerUri — \"untitled:project/main.typ\". What tinymist's LSP expects.\n * Derived from a Path plus the analyzer URI root.\n *\n * The types below are type aliases, not branded/opaque types. That's\n * deliberate: callers can pass string literals where a Path is expected\n * without wrapping, and the aliases only serve as self-documenting\n * signatures. Ingress points still need to call `normalizePath` to defend\n * against un-normalized input — the alias does not imply the string has\n * been normalized.\n */\n\n/** `/path/to/file.typ` — leading-slash, forward slashes only. */\nexport type Path = string;\n\n/** `untitled:project/path/to/file.typ` — what tinymist's LSP consumes. */\nexport type AnalyzerUri = string;\n\n/** Ensure a path starts with a leading slash. Idempotent. */\nexport function normalizePath(path: string): Path {\n return path.startsWith(\"/\") ? path : `/${path}`;\n}\n\n/**\n * Normalize an analyzer URI root. Ensures leading slash, strips trailing\n * slashes. Special-cases \"/\" → \"\" so URIs don't end up with a leading\n * `untitled://`.\n */\nexport function normalizeRoot(rootPath: string): string {\n const root = normalizePath(rootPath);\n return root === \"/\" ? \"\" : root.replace(/\\/+$/, \"\");\n}\n\n/**\n * Build the analyzer URI for a given project path. `root` is expected to\n * already be normalized (see `normalizeRoot`).\n *\n * pathToAnalyzerUri(\"/main.typ\", \"/project\") -> \"untitled:project/main.typ\"\n */\nexport function pathToAnalyzerUri(path: Path, root: string): AnalyzerUri {\n const bare = root.replace(/^\\//, \"\");\n return `untitled:${bare}${normalizePath(path)}`;\n}\n","import * as Comlink from \"comlink\";\nimport { createWorker } from \"./rpc.js\";\nimport type { DiagnosticMessage } from \"./types.js\";\nimport type { CompilerWorker } from \"./compiler-worker.js\";\n\ndeclare const __TYPST_TS_WEB_COMPILER_VERSION__: string;\n\nexport interface CompileResult {\n diagnostics: DiagnosticMessage[];\n /** Vector artifact bytes from the compiler, usable with TypstRenderer for SVG rendering. */\n vector?: Uint8Array;\n}\n\nexport interface TypstCompilerOptions {\n /**\n * Explicit Worker instance. When omitted, an inlined blob worker is created automatically.\n * Use this for Vite apps to get proper source maps:\n * `await TypstCompiler.create({ worker: new Worker(new URL('typst-web-service/compiler-worker', import.meta.url)) })`\n */\n worker?: Worker;\n /**\n * URL to the typst-ts-web-compiler WASM binary.\n * Defaults to the matching version on jsDelivr CDN.\n * Override with a local asset URL for offline support or faster load:\n * `new URL('@myriaddreamin/typst-ts-web-compiler/pkg/typst_ts_web_compiler_bg.wasm', import.meta.url).href`\n */\n wasmUrl?: string;\n /** Font URLs to load into the Typst compiler. Defaults to Roboto from jsDelivr. */\n fonts?: string[];\n /**\n * Enable fetching @preview/ packages from packages.typst.org on demand.\n * Default: true.\n */\n packages?: boolean;\n}\n\nconst DEFAULT_FONTS = [\n \"https://cdn.jsdelivr.net/npm/roboto-font@0.1.0/fonts/Roboto/roboto-regular-webfont.ttf\",\n];\n\nconst defaultWasmUrl = () =>\n `https://cdn.jsdelivr.net/npm/@myriaddreamin/typst-ts-web-compiler@${__TYPST_TS_WEB_COMPILER_VERSION__}/pkg/typst_ts_web_compiler_bg.wasm`;\n\n/**\n * Manages a Typst compiler worker. Create one instance and share it across\n * all extensions (linter, autocomplete, preview, etc.).\n *\n * await TypstCompiler.create() // blob worker, defaults\n * await TypstCompiler.create({ wasmUrl: '...' }) // blob worker, custom WASM\n * await TypstCompiler.create({ worker: myWorker }) // explicit Worker (Vite)\n * await TypstCompiler.create({ worker: myWorker, fonts: [...] }) // explicit Worker + options\n */\nexport class TypstCompiler {\n private readonly proxy: Comlink.Remote<CompilerWorker>;\n private readonly worker: Worker;\n private readonly encoder = new TextEncoder();\n /** Last text content pushed per path. Drives own-RPC dedup. Invalidated by binary writes. */\n private readonly content = new Map<string, string>();\n\n private constructor(worker: Worker, proxy: Comlink.Remote<CompilerWorker>) {\n this.worker = worker;\n this.proxy = proxy;\n }\n\n static async create(\n options: TypstCompilerOptions = {},\n ): Promise<TypstCompiler> {\n const worker = options.worker ?? createWorker();\n const proxy = Comlink.wrap<CompilerWorker>(worker);\n\n await proxy.init(\n options.wasmUrl ?? defaultWasmUrl(),\n options.fonts ?? DEFAULT_FONTS,\n options.packages ?? true,\n );\n\n return new TypstCompiler(worker, proxy);\n }\n\n /**\n * Compile whatever is currently in the VFS (populated via\n * setText/setBinary/setJson/setMany). Defaults to compiling \"/main.typ\";\n * override with `entry`.\n */\n compile(entry?: string): Promise<CompileResult> {\n return this.proxy.compile(entry);\n }\n\n /**\n * Compile to PDF. Operates on whatever is currently in the VFS (populated\n * via setText/setBinary/setJson/setMany). Defaults to compiling \"/main.typ\";\n * override with `entry`.\n */\n compilePdf(entry?: string): Promise<Uint8Array> {\n return this.proxy.compilePdf(entry);\n }\n\n /**\n * Add or overwrite a text file in the virtual compiler filesystem. Skips\n * the worker RPC when `source` matches the last value pushed for `path`.\n */\n async setText(path: string, source: string): Promise<void> {\n if (this.content.get(path) === source) return;\n await this.proxy.mapShadow(path, this.encoder.encode(source));\n this.content.set(path, source);\n }\n\n /** Add or overwrite a JSON file in the virtual compiler filesystem. */\n setJson(\n path: string,\n value: unknown,\n replacer?: (this: unknown, key: string, value: unknown) => unknown,\n space?: string | number,\n ): Promise<void> {\n return this.setText(path, JSON.stringify(value, replacer, space));\n }\n\n /**\n * Add or overwrite multiple files in the virtual compiler filesystem in a\n * single worker roundtrip. Strings are UTF-8 encoded; Uint8Arrays are\n * passed through. Text entries unchanged since their last push are skipped;\n * binary entries always push and invalidate the text cache for their path.\n */\n async setMany(files: Record<string, string | Uint8Array>): Promise<void> {\n const encoded: Record<string, Uint8Array> = {};\n const textUpdates: Array<[string, string]> = [];\n const binaryInvalidations: string[] = [];\n for (const [path, content] of Object.entries(files)) {\n if (typeof content === \"string\") {\n if (this.content.get(path) === content) continue;\n textUpdates.push([path, content]);\n encoded[path] = this.encoder.encode(content);\n } else {\n binaryInvalidations.push(path);\n encoded[path] = content;\n }\n }\n if (Object.keys(encoded).length === 0) return;\n await this.proxy.mapShadowMany(encoded);\n for (const [path, content] of textUpdates) this.content.set(path, content);\n for (const path of binaryInvalidations) this.content.delete(path);\n }\n\n /** Add or overwrite a binary file in the virtual compiler filesystem. */\n async setBinary(\n path: string,\n content: ArrayBuffer | ArrayBufferView,\n ): Promise<void> {\n const bytes =\n content instanceof ArrayBuffer\n ? new Uint8Array(content)\n : new Uint8Array(\n content.buffer,\n content.byteOffset,\n content.byteLength,\n );\n await this.proxy.mapShadow(path, bytes);\n this.content.delete(path);\n }\n\n /** Remove a file from the virtual compiler filesystem. */\n async remove(path: string): Promise<void> {\n await this.proxy.unmapShadow(path);\n this.content.delete(path);\n }\n\n /** Clear all virtual files from the compiler filesystem. */\n async clear(): Promise<void> {\n await this.proxy.resetShadow();\n this.content.clear();\n }\n\n destroy(): void {\n this.proxy[Comlink.releaseProxy]();\n this.worker.terminate();\n }\n}\n","export interface FormatConfig {\n /** Number of spaces per indentation level. Default: 2 */\n tab_spaces?: number;\n /** Maximum line width. Default: 80 */\n max_width?: number;\n /** Maximum consecutive blank lines allowed. */\n blank_lines_upper_bound?: number;\n /** Collapse consecutive whitespace in markup to a single space. */\n collapse_markup_spaces?: boolean;\n /** Sort import items alphabetically. */\n reorder_import_items?: boolean;\n /** Wrap text in markup to fit within max_width. Implies collapse_markup_spaces. */\n wrap_text?: boolean;\n}\n\nexport interface FormatRangeResult {\n /** Start index (UTF-16) of the actual formatted range. */\n start: number;\n /** End index (UTF-16) of the actual formatted range. */\n end: number;\n /** The formatted text for the range. */\n text: string;\n}\n\ntype TypstyleModule = typeof import(\"@typstyle/typstyle-wasm-bundler\");\n\nlet typstylePromise: Promise<TypstyleModule> | null = null;\n\nfunction getTypstyle(): Promise<TypstyleModule> {\n if (!typstylePromise) {\n typstylePromise = import(\"@typstyle/typstyle-wasm-bundler\").catch((err) => {\n typstylePromise = null;\n throw err;\n });\n }\n return typstylePromise;\n}\n\n/**\n * Typst code formatter powered by typstyle.\n *\n * Runs on the main thread — typstyle is lightweight and fast.\n * The WASM module is loaded lazily on first format call.\n * Requires a bundler that supports WASM imports (e.g. Vite, webpack).\n *\n * const formatter = TypstFormatter.create({ tab_spaces: 2, max_width: 80 });\n * const formatted = await formatter.format(source);\n */\nexport class TypstFormatter {\n private config: FormatConfig;\n\n private constructor(config: FormatConfig = {}) {\n this.config = config;\n // Eagerly start loading WASM so it's ready by first use.\n // Swallow preload errors — they'll surface on first format() call.\n getTypstyle().catch(() => {});\n }\n\n static create(config: FormatConfig = {}): TypstFormatter {\n return new TypstFormatter(config);\n }\n\n /** Format an entire Typst source string. */\n async format(source: string): Promise<string> {\n const typstyle = await getTypstyle();\n return typstyle.format(source, this.config);\n }\n\n /** Format a range within a Typst source string. Indices are UTF-16 code units. */\n async formatRange(\n source: string,\n start: number,\n end: number,\n ): Promise<FormatRangeResult> {\n const typstyle = await getTypstyle();\n const result = typstyle.format_range(source, start, end, this.config);\n return { start: result.start, end: result.end, text: result.text };\n }\n}\n","interface CompileSchedulerOptions {\n /** Minimum idle time (ms) after the last request before firing. Default: 0. */\n debounceMs?: number;\n /**\n * Maximum time (ms) the debounce may keep deferring during continuous\n * requests. Guarantees a fire at least this often so users see progress\n * while typing. Default: 0 (no cap).\n */\n maxWaitMs?: number;\n}\n\n/**\n * Debounce helper with a max-wait ceiling for coalescing compile requests. A\n * burst of `schedule(cb)` calls within `debounceMs` collapses into one fire;\n * during sustained bursts, `maxWaitMs` caps how long the debounce may keep\n * deferring.\n */\nexport class CompileScheduler {\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private maxWaitTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(private readonly options: CompileSchedulerOptions = {}) {}\n\n schedule(callback: () => void): void {\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n\n const delay = Math.max(0, this.options.debounceMs ?? 0);\n this.debounceTimer = setTimeout(() => {\n this.debounceTimer = null;\n this.clearMaxWait();\n callback();\n }, delay);\n\n const maxWait = this.options.maxWaitMs;\n if (maxWait != null && maxWait > 0 && !this.maxWaitTimer) {\n this.maxWaitTimer = setTimeout(() => {\n this.maxWaitTimer = null;\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n callback();\n }\n }, maxWait);\n }\n }\n\n /** Cancel any pending scheduled fire without calling the callback. */\n cancel(): void {\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n this.clearMaxWait();\n }\n\n private clearMaxWait(): void {\n if (this.maxWaitTimer) {\n clearTimeout(this.maxWaitTimer);\n this.maxWaitTimer = null;\n }\n }\n}\n","import type { TypstAnalyzer } from \"./analyzer.js\";\nimport type {\n LspCompletionResponse,\n LspHover,\n LspPosition,\n} from \"./analyzer-types.js\";\nimport { CompileScheduler } from \"./compile-scheduler.js\";\nimport type { CompileResult, TypstCompiler } from \"./compiler.js\";\nimport {\n normalizePath,\n normalizeRoot,\n type Path,\n pathToAnalyzerUri,\n} from \"./identifiers.js\";\n\nexport interface TypstProjectOptions {\n compiler: TypstCompiler;\n /**\n * Optional analyzer. When provided, text file operations also sync with the\n * analyzer so completions / hover reflect the current state.\n */\n analyzer?: TypstAnalyzer;\n /** Default entry file path. Default: \"/main.typ\". */\n entry?: string;\n /**\n * Prefix used to build the `untitled:` URIs handed to the analyzer.\n * Default: \"/project\". A path of `/main.typ` becomes\n * `untitled:project/main.typ`. Only affects URI construction — the compiler\n * and project VFS use the raw paths unchanged.\n */\n analyzerUriRoot?: string;\n /**\n * Scheduling for auto-compiles after VFS mutations. Mutations debounce by\n * `debounceMs`; `maxWaitMs` caps how long the debounce can keep deferring\n * during sustained edits so the user still sees progress.\n */\n autoCompile?: AutoCompileOptions;\n}\n\nexport interface AutoCompileOptions {\n /**\n * Idle time (ms) after the last VFS mutation before a compile fires.\n * Default: 0 — compile fires on the next macrotask. Set higher (e.g. 150) to\n * coalesce rapid edits.\n */\n debounceMs?: number;\n /**\n * Maximum time (ms) the debounce is allowed to defer a compile during\n * sustained mutation bursts. Default: 0 (no cap — pure debounce).\n */\n maxWaitMs?: number;\n}\n\nconst DEFAULT_ENTRY = \"/main.typ\";\nconst DEFAULT_ROOT = \"/project\";\n\n/**\n * Coordinates a compiler + analyzer pair for multi-file Typst projects.\n *\n * Owns the project's virtual filesystem state. Editors push incremental\n * `setText` updates as the user types; the project mirrors those edits to\n * both the compiler's shadow VFS and the analyzer's open-document set, then\n * compiles or services LSP requests against the current state.\n *\n * const project = new TypstProject({ compiler, analyzer });\n * await project.setMany({ \"/main.typ\": \"...\", \"/utils.typ\": \"...\" });\n * const result = await project.compile();\n */\nexport type CompileListener = (result: CompileResult) => void;\n\nfunction errorAsCompileResult(\n err: unknown,\n paths: readonly string[],\n): CompileResult {\n const message = err instanceof Error ? err.message : String(err);\n return {\n diagnostics: paths.map((path) => ({\n package: \"\",\n path,\n severity: \"Error\",\n range: { startLine: 0, startCol: 0, endLine: 0, endCol: 1 },\n message,\n })),\n };\n}\n\nexport class TypstProject {\n private readonly compiler: TypstCompiler;\n private readonly analyzer?: TypstAnalyzer;\n private readonly analyzerUriRoot: string;\n /**\n * Tracked text files: path → latest content observed. Presence in this map\n * is the source of truth for \"is this a tracked text file?\"; insertion\n * order drives the `files` getter. Per-sink dedup lives in the compiler and\n * analyzer.\n */\n private readonly contentByPath = new Map<Path, string>();\n private readonly compileListeners = new Set<CompileListener>();\n private readonly scheduler: CompileScheduler;\n private compileVersion = 0;\n private _lastResult: CompileResult | undefined;\n private _entry: Path;\n private destroyed = false;\n\n private invokeListener(\n listener: CompileListener,\n result: CompileResult,\n ): void {\n try {\n listener(result);\n } catch (err) {\n console.error(\"[typst] compile listener threw:\", err);\n }\n }\n\n constructor(options: TypstProjectOptions) {\n this.compiler = options.compiler;\n this.analyzer = options.analyzer;\n this.analyzerUriRoot = normalizeRoot(\n options.analyzerUriRoot ?? DEFAULT_ROOT,\n );\n this._entry = normalizePath(options.entry ?? DEFAULT_ENTRY);\n this.scheduler = new CompileScheduler({\n debounceMs: options.autoCompile?.debounceMs,\n maxWaitMs: options.autoCompile?.maxWaitMs,\n });\n }\n\n /**\n * Schedule an auto-compile after VFS mutations. Coalesces rapid calls via\n * the configured debounce/throttle. Errors surface through `onCompile`\n * listeners via a synthetic diagnostic; callers awaiting a specific compile\n * should call `compile()` directly.\n */\n private scheduleCompile(): void {\n if (this.destroyed) return;\n this.scheduler.schedule(() => {\n this.compile().catch((err) => console.error(\"[typst]\", err));\n });\n }\n\n /** Current entry file path. Assign to change the sticky entry used by subsequent `compile()` calls. */\n get entry(): Path {\n return this._entry;\n }\n\n set entry(path: Path) {\n const next = normalizePath(path);\n if (next === this._entry) return;\n this._entry = next;\n this.scheduleCompile();\n }\n\n /** Whether an analyzer is attached. */\n get hasAnalyzer(): boolean {\n return this.analyzer !== undefined;\n }\n\n /**\n * Most recent compile result, or `undefined` before the first compile has\n * settled. Useful for lazy-mounted UI that subscribes after boot and needs\n * an initial value.\n */\n get lastResult(): CompileResult | undefined {\n return this._lastResult;\n }\n\n /**\n * Snapshot of tracked text file paths, in insertion order. Updated by\n * `setText`, `setMany`, `remove`, and `clear`. Returns a fresh array — mutate\n * freely without affecting project state.\n */\n get files(): Path[] {\n return [...this.contentByPath.keys()];\n }\n\n /**\n * Current text content for a tracked file, or `undefined` if the path was\n * never written via `setText`/`setMany` (or was removed). Read-through to the\n * project's sync cache — lets consumers avoid shadowing the VFS themselves.\n */\n getText(path: Path): string | undefined {\n return this.contentByPath.get(normalizePath(path));\n }\n\n /**\n * Add or overwrite a text file. Goes to the compiler's VFS and, when an\n * analyzer is attached, to the analyzer as a document change. No-op when\n * the tracked path already has this exact content — skips both worker RPCs\n * and the auto-scheduled compile.\n */\n async setText(path: Path, content: string): Promise<void> {\n const p = normalizePath(path);\n if (this.contentByPath.get(p) === content) return;\n await Promise.all([\n this.compiler.setText(p, content),\n this.analyzer?.didChange(\n pathToAnalyzerUri(p, this.analyzerUriRoot),\n content,\n ) ?? Promise.resolve(),\n ]);\n this.contentByPath.set(p, content);\n this.scheduleCompile();\n }\n\n /**\n * Add or overwrite a JSON file. The analyzer does not track data files, but\n * if `path` was previously tracked as text the analyzer document is closed\n * and the text entry retired so the three views stay consistent.\n */\n async setJson(path: Path, value: unknown): Promise<void> {\n const p = normalizePath(path);\n const wasText = this.contentByPath.has(p);\n await Promise.all([\n this.compiler.setJson(p, value),\n wasText\n ? this.analyzer?.didClose(pathToAnalyzerUri(p, this.analyzerUriRoot))\n : undefined,\n ]);\n this.contentByPath.delete(p);\n this.scheduleCompile();\n }\n\n /**\n * Add or overwrite a binary file. If `path` was previously tracked as text,\n * the analyzer document is closed and the text entry retired in the same\n * call so the compiler / analyzer / project views stay consistent.\n */\n async setBinary(\n path: Path,\n content: ArrayBuffer | ArrayBufferView,\n ): Promise<void> {\n const p = normalizePath(path);\n const wasText = this.contentByPath.has(p);\n await Promise.all([\n this.compiler.setBinary(p, content),\n wasText\n ? this.analyzer?.didClose(pathToAnalyzerUri(p, this.analyzerUriRoot))\n : undefined,\n ]);\n this.contentByPath.delete(p);\n this.scheduleCompile();\n }\n\n /**\n * Batch set multiple files. Strings route to both compiler and analyzer;\n * Uint8Array entries go to the compiler only. Strings matching the last\n * tracked content for their path are skipped on both sinks. Binary entries\n * always go through (no content cache, so no dedup).\n */\n async setMany(files: Record<Path, string | Uint8Array>): Promise<void> {\n const canonical = new Map<Path, string | Uint8Array>();\n for (const [path, content] of Object.entries(files)) {\n canonical.set(normalizePath(path), content);\n }\n\n const normalized: Record<Path, string | Uint8Array> = {};\n const analyzerDocs: Record<string, string> = {};\n const textUpdates: Array<[Path, string]> = [];\n const analyzerCloses: string[] = [];\n const binaryRetirements: Path[] = [];\n for (const [p, content] of canonical) {\n if (typeof content !== \"string\") {\n normalized[p] = content;\n if (this.contentByPath.has(p)) {\n analyzerCloses.push(pathToAnalyzerUri(p, this.analyzerUriRoot));\n binaryRetirements.push(p);\n }\n continue;\n }\n if (this.contentByPath.get(p) === content) continue;\n textUpdates.push([p, content]);\n normalized[p] = content;\n analyzerDocs[pathToAnalyzerUri(p, this.analyzerUriRoot)] = content;\n }\n if (Object.keys(normalized).length === 0) return;\n await Promise.all([\n this.compiler.setMany(normalized),\n this.analyzer?.didChangeMany(analyzerDocs) ?? Promise.resolve(),\n analyzerCloses.length > 0\n ? (this.analyzer?.didCloseMany(analyzerCloses) ?? Promise.resolve())\n : Promise.resolve(),\n ]);\n for (const [p, content] of textUpdates) this.contentByPath.set(p, content);\n for (const p of binaryRetirements) this.contentByPath.delete(p);\n this.scheduleCompile();\n }\n\n /**\n * Remove a file. Always removed from the compiler's VFS; also closed on the\n * analyzer when it was previously tracked as text.\n */\n async remove(path: Path): Promise<void> {\n const p = normalizePath(path);\n const wasText = this.contentByPath.has(p);\n await Promise.all([\n this.compiler.remove(p),\n wasText\n ? this.analyzer?.didClose(pathToAnalyzerUri(p, this.analyzerUriRoot))\n : undefined,\n ]);\n this.contentByPath.delete(p);\n this.scheduleCompile();\n }\n\n /** Clear all files from both compiler VFS and analyzer document set. */\n async clear(): Promise<void> {\n const uris = Array.from(this.contentByPath.keys(), (p) =>\n pathToAnalyzerUri(p, this.analyzerUriRoot),\n );\n await Promise.all([\n this.compiler.clear(),\n uris.length > 0 ? this.analyzer?.didCloseMany(uris) : undefined,\n ]);\n this.contentByPath.clear();\n this.scheduleCompile();\n }\n\n /**\n * Subscribe to compile results. Fires after every `compile()` whose result is\n * still current (stale results from out-of-order concurrent compiles are\n * dropped). If a compile has already settled, the most recent result is\n * delivered synchronously so late-mounted listeners aren't stuck blank until\n * the next compile. Returns an unsubscribe function.\n */\n onCompile(listener: CompileListener): () => void {\n this.compileListeners.add(listener);\n if (this._lastResult !== undefined) {\n this.invokeListener(listener, this._lastResult);\n }\n return () => {\n this.compileListeners.delete(listener);\n };\n }\n\n /**\n * Compile the current VFS state using the sticky entry. Errors from the\n * underlying compiler are converted into a synthetic error diagnostic so\n * callers and listeners always receive a `CompileResult`. Listeners are\n * notified only for the most recent compile — results from an earlier call\n * that resolves after a later one are suppressed.\n *\n * VFS mutations (`setText`, `remove`, etc.) auto-schedule a debounced\n * compile; call this directly only when you need an awaitable handle on the\n * result (e.g., to flush before rendering to PDF).\n */\n async compile(): Promise<CompileResult> {\n this.scheduler.cancel();\n const version = ++this.compileVersion;\n let result: CompileResult;\n try {\n result = await this.compiler.compile(this._entry);\n } catch (err) {\n // Spread the synthetic error across every tracked text path so the\n // diagnostic is visible no matter which file the user is viewing.\n // Falls back to the entry if nothing is tracked yet.\n const paths =\n this.contentByPath.size > 0\n ? [...this.contentByPath.keys()]\n : [this._entry];\n result = errorAsCompileResult(err, paths);\n }\n if (version === this.compileVersion) {\n this._lastResult = result;\n for (const listener of this.compileListeners) {\n this.invokeListener(listener, result);\n }\n }\n return result;\n }\n\n /** Compile the current VFS state to PDF using the sticky entry. */\n compilePdf(): Promise<Uint8Array> {\n return this.compiler.compilePdf(this._entry);\n }\n\n private requireAnalyzer(operation: string): TypstAnalyzer {\n if (!this.analyzer) {\n throw new Error(`TypstProject: ${operation} requires an analyzer`);\n }\n return this.analyzer;\n }\n\n /**\n * Request completion for `path` at `position`, using `source` as the\n * current document state. Pure analyzer query — neither the compiler VFS\n * nor project tracking (`files`/`getText`) is touched. Throws when no\n * analyzer is attached.\n */\n completion(\n path: Path,\n source: string,\n position: LspPosition,\n ): Promise<LspCompletionResponse> {\n const analyzer = this.requireAnalyzer(\"completion\");\n return analyzer.completion(\n pathToAnalyzerUri(normalizePath(path), this.analyzerUriRoot),\n source,\n position,\n );\n }\n\n /**\n * Request hover for `path` at `position`, using `source` as the current\n * document state. Pure analyzer query — neither the compiler VFS nor\n * project tracking is touched. Throws when no analyzer is attached.\n */\n hover(\n path: Path,\n source: string,\n position: LspPosition,\n ): Promise<LspHover | null> {\n const analyzer = this.requireAnalyzer(\"hover\");\n return analyzer.hover(\n pathToAnalyzerUri(normalizePath(path), this.analyzerUriRoot),\n source,\n position,\n );\n }\n\n /**\n * Tear down the project and the services it owns. Destroys the attached\n * compiler and analyzer, drops all listeners, and clears VFS tracking state.\n * Idempotent — calling twice is a no-op. After destruction, further calls on\n * the project are not supported; construct a new one.\n *\n * If you need to share a compiler or analyzer across projects, destroy them\n * yourself and don't call this method — the project does not provide an\n * ownership toggle.\n */\n destroy(): void {\n if (this.destroyed) return;\n this.destroyed = true;\n this.scheduler.cancel();\n this.compileListeners.clear();\n this.contentByPath.clear();\n this._lastResult = undefined;\n this.compiler.destroy();\n this.analyzer?.destroy();\n }\n}\n","import {\n createTypstRenderer,\n type RenderSession,\n type TypstRenderer as InnerTypstRenderer,\n} from \"@myriaddreamin/typst.ts\";\n\ndeclare const __TYPST_TS_RENDERER_VERSION__: string;\n\nconst DEFAULT_RENDERER_WASM_URL = `https://cdn.jsdelivr.net/npm/@myriaddreamin/typst-ts-renderer@${__TYPST_TS_RENDERER_VERSION__}/pkg/typst_ts_renderer_bg.wasm`;\n\nexport interface TypstRendererOptions {\n /** URL to the typst-ts-renderer WASM binary. Defaults to jsDelivr CDN. */\n wasmUrl?: string;\n}\n\nexport interface RenderedSvgPage {\n /** Zero-based page index within the document. */\n index: number;\n /** Page width in typographic points. */\n width: number;\n /** Page height in typographic points. */\n height: number;\n /** Standalone SVG string for just this page. */\n svg: string;\n}\n\n/**\n * Converts Typst vector artifacts to SVG strings.\n *\n * Thin wrapper around `@myriaddreamin/typst.ts`'s renderer: we own the public\n * API surface and WASM-URL convention; typst.ts owns the WASM session,\n * sub-page rendering, and lifecycle. See `renderer.mts` in that package for\n * the inner mechanics.\n *\n * const renderer = TypstRenderer.create();\n * const svg = await renderer.renderSvg(vector);\n */\nexport class TypstRenderer {\n private readonly wasmUrl: string;\n private inner: Promise<InnerTypstRenderer> | null = null;\n\n private constructor(options: TypstRendererOptions = {}) {\n this.wasmUrl = options.wasmUrl ?? DEFAULT_RENDERER_WASM_URL;\n // Kick off WASM fetch eagerly so it's warm by first render.\n void this.getInner();\n }\n\n static create(options: TypstRendererOptions = {}): TypstRenderer {\n return new TypstRenderer(options);\n }\n\n private getInner(): Promise<InnerTypstRenderer> {\n if (!this.inner) {\n this.inner = this.#initInner().catch((err) => {\n this.inner = null;\n throw err;\n });\n }\n return this.inner;\n }\n\n async #initInner(): Promise<InnerTypstRenderer> {\n const inner = createTypstRenderer();\n await inner.init({ getModule: () => this.wasmUrl });\n return inner;\n }\n\n /**\n * Drop the reference to the inner renderer. The upstream wrapper doesn't\n * expose an explicit free(); the WASM module is reclaimed when the page\n * unloads. Consumers may still call this to release the JS-side handle.\n */\n destroy(): void {\n this.inner = null;\n }\n\n /** Render a Typst vector artifact to a single merged SVG string. */\n async renderSvg(vector: Uint8Array): Promise<string> {\n const inner = await this.getInner();\n return inner.renderSvg({ format: \"vector\", artifactContent: vector });\n }\n\n /**\n * Render a Typst vector artifact into one self-contained SVG string per\n * physical page. The merged SVG is split by `<g class=\"typst-page\">`\n * children; each group's `data-page-width` / `data-page-height` give the\n * page-local viewBox. Shared `<defs>` / `<style>` are duplicated into each\n * page so the output SVGs render independently. Returns an empty array if\n * the document has no page groups.\n */\n async renderSvgPages(vector: Uint8Array): Promise<RenderedSvgPage[]> {\n return splitMergedSvgPages(await this.renderSvg(vector));\n }\n\n /**\n * Mount an incremental canvas renderer into `root`. Each `update(vector)`\n * call repaints the canvases in place. The win for image-heavy docs comes\n * from the persistent WASM session: decoded image bitmaps are cached in\n * the renderer, so subsequent paints skip the decode step. Canvas elements\n * are recreated each update (typst.ts manages the DOM inside `root`), but\n * canvas creation is cheap — the bottleneck the SVG path was solving was\n * `<image>` data-URL decode, not DOM construction.\n *\n * Call `reset()` when switching to an unrelated document (next `update`\n * starts fresh from the session). Call `dispose()` to free the session.\n *\n * Trade-off vs the SVG paths: output is rasterized — text in the rendered\n * canvas isn't selectable, and zoom past `pixelPerPt` needs a re-render to\n * stay crisp. typst.ts builds a `<canvas>` per page inside `root` and adds\n * a text-semantic layer alongside it for screen readers / a11y.\n */\n createIncrementalCanvasRenderer(\n root: HTMLElement,\n options: IncrementalCanvasOptions = {},\n ): IncrementalCanvasRenderer {\n return new IncrementalCanvasImpl(root, () => this.getInner(), options);\n }\n}\n\nexport interface IncrementalCanvasOptions {\n pixelPerPt?: number;\n backgroundColor?: string;\n}\n\nexport interface IncrementalCanvasRenderer {\n /**\n * Apply a new vector and repaint. First call seeds the session.\n *\n * Concurrent calls are serialized: while a repaint is in flight, the most\n * recent `update` becomes the next paint and intermediates are dropped.\n * All overlapping callers share one returned promise that resolves when\n * the chain catches up — your specific vector may have been superseded.\n */\n update(vector: Uint8Array): Promise<void>;\n /** Force the next `update` to re-seed (use when the doc identity changes). */\n reset(): void;\n /** Free the WASM session. */\n dispose(): void;\n}\n\n// Hold a typst.ts render session alive across multiple operations. typst.ts's\n// API is scope-guarded (`runWithSession(fn)` frees the session when `fn`\n// resolves); we occupy it with a never-resolving inner promise and let\n// `close()` resolve it on dispose. The returned `session` is usable until\n// `close()` is called.\nasync function openSession(\n inner: InnerTypstRenderer,\n): Promise<{ session: RenderSession; close: () => void }> {\n let close!: () => void;\n const closed = new Promise<void>((resolve) => {\n close = resolve;\n });\n let session!: RenderSession;\n await new Promise<void>((ready, reject) => {\n void inner\n .runWithSession(async (s) => {\n session = s;\n ready();\n await closed;\n })\n .catch(reject);\n });\n return { session, close };\n}\n\n// typst.ts uses 3 as its internal default (PIXEL_PER_PT). Match on standard\n// displays, bump on retina so canvases stay crisp at typical display sizes.\nfunction defaultPixelPerPt(): number {\n const dpr =\n typeof globalThis !== \"undefined\" &&\n typeof (globalThis as { devicePixelRatio?: number }).devicePixelRatio === \"number\"\n ? (globalThis as { devicePixelRatio: number }).devicePixelRatio\n : 1;\n return Math.max(dpr * 1.5, 3);\n}\n\ninterface OpenedSession {\n session: RenderSession;\n close: () => void;\n inner: InnerTypstRenderer;\n}\n\nclass IncrementalCanvasImpl implements IncrementalCanvasRenderer {\n private opened: Promise<OpenedSession> | null = null;\n private inflight: Promise<void> | null = null;\n private pending: Uint8Array | null = null;\n private firstUpdate = true;\n private disposed = false;\n\n constructor(\n private readonly root: HTMLElement,\n private readonly getInner: () => Promise<InnerTypstRenderer>,\n private readonly options: IncrementalCanvasOptions,\n ) {}\n\n private getOpened(): Promise<OpenedSession> {\n this.opened ??= this.getInner().then(async (inner) => ({\n ...(await openSession(inner)),\n inner,\n }));\n return this.opened;\n }\n\n update(vector: Uint8Array): Promise<void> {\n if (this.disposed) return Promise.resolve();\n this.pending = vector;\n this.inflight ??= this.drain();\n return this.inflight;\n }\n\n private async drain(): Promise<void> {\n try {\n while (this.pending && !this.disposed) {\n const vector = this.pending;\n this.pending = null;\n await this.paint(vector);\n }\n } finally {\n this.inflight = null;\n }\n }\n\n private async paint(vector: Uint8Array): Promise<void> {\n const { session, inner } = await this.getOpened();\n if (this.disposed) return;\n // Commit firstUpdate before awaiting renderToCanvas: a `reset()` called\n // mid-render must flip it back to true for the *next* paint instead of\n // being clobbered when this one finishes.\n const wasFirst = this.firstUpdate;\n this.firstUpdate = false;\n if (wasFirst) session.reset();\n session.manipulateData({ action: \"merge\", data: vector });\n await inner.renderToCanvas({\n container: this.root,\n renderSession: session,\n pixelPerPt: this.options.pixelPerPt ?? defaultPixelPerPt(),\n backgroundColor: this.options.backgroundColor ?? \"#ffffff\",\n });\n }\n\n reset(): void {\n this.firstUpdate = true;\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n // Fire-and-forget: if the session is still opening, we'll close it once\n // it's ready; if it's already open, close immediately.\n void this.opened?.then(({ close }) => close());\n }\n}\n\n// Parsing must use \"text/html\", not \"image/svg+xml\": Typst's merged SVG\n// output has repeatedly failed XML-strict parsing. HTML mode tolerates it\n// and still produces real SVGSVGElement nodes for inline SVG.\nfunction splitMergedSvgPages(svg: string): RenderedSvgPage[] {\n const doc = new DOMParser().parseFromString(svg, \"text/html\");\n const root = doc.querySelector(\"svg\");\n if (!root) return [];\n\n const children = Array.from(root.children);\n const pageGroups = children.filter(\n (el) =>\n el.tagName.toLowerCase() === \"g\" && el.classList.contains(\"typst-page\"),\n );\n if (pageGroups.length === 0) return [];\n\n const sharedHtml = children\n .filter((el) => !el.classList.contains(\"typst-page\"))\n .map((el) => el.outerHTML)\n .join(\"\");\n\n const namespaceAttrs = Array.from(root.attributes)\n .filter((attr) => attr.name === \"xmlns\" || attr.name.startsWith(\"xmlns:\"))\n .map((attr) => `${attr.name}=\"${attr.value}\"`)\n .join(\" \");\n\n return pageGroups.flatMap((group, index) => {\n const width = Number(group.getAttribute(\"data-page-width\")) || 0;\n const height = Number(group.getAttribute(\"data-page-height\")) || 0;\n if (width <= 0 || height <= 0) return [];\n\n const clone = group.cloneNode(true) as Element;\n clone.removeAttribute(\"transform\");\n\n return [\n {\n index,\n width,\n height,\n svg:\n `<svg ${namespaceAttrs} viewBox=\"0 0 ${width} ${height}\" ` +\n `width=\"${width}\" height=\"${height}\">` +\n `${sharedHtml}${clone.outerHTML}</svg>`,\n },\n ];\n });\n}\n"],"mappings":"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","names":["Comlink"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vedivad/typst-web-service",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Editor-agnostic Typst compilation service running in a Web Worker",
5
5
  "license": "MIT",
6
6
  "repository": {