diff --git a/.gitignore b/.gitignore index 64440fb..104d01c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules docs/.vitepress/dist docs/.vitepress/cache +docs/api docs/public/demo/playground docs/public/demo/simple-app examples/cpp/vtk diff --git a/README.md b/README.md index 9751b62..614d2ad 100644 --- a/README.md +++ b/README.md @@ -1,69 +1,52 @@ -# vtk-wasm +# @kitware/vtk-wasm -## Project Structure and Build Scripts +JavaScript bindings for loading and driving [VTK](https://www.vtk.org) compiled to WebAssembly — build 3D visualizations in the browser with no C++ required. -This repository provides the infrastructure to bundle the pure JavaScript library for loading VTK.wasm. +**Full documentation, guides, and API reference:** https://kitware.github.io/vtk-wasm/ -The full usage documentation of vtk-wasm can be found at: https://kitware.github.io/vtk-wasm/ +## Usage -### File Structure +Install the package: -- `dist/` — Bundled output files. -- `examples/` — Example applications demonstrating vtk-wasm usage. -- `docs/` — Documentation and guide pages. -- `src/` — Source code for the library. -- `README.md` — Project documentation. -- `package.json` — Project metadata and build scripts. - -### Build Scripts - -The following scripts are available in `package.json`: +```bash +npm install @kitware/vtk-wasm +``` -- **`npm run docs:dev`** - Starts a local development server for the guide pages with live reloading -- **`npm run docs:build`** - Builds the guide pages for VTK.wasm -- **`npm run build`** — Builds the ESM and UMD bundles for both RemoteSession and StandaloneSession. -- **`npm run build:esm`** — Builds only the ESM bundles. -- **`npm run build:examples`** — Builds the example applications. -- **`npm run build:vtk`** — Builds only the UMD bundle for StandaloneSession. -- **`npm run build:viewer`** - Builds the vtkWASMViewer JavaScript library. -- **`npm run clean`** — Cleans the `dist/` directory. -- **`npm run lint`** — Runs code linting on the source files. +Load the runtime, create a session, and use its `vtk` namespace: -### Bundles +```js +import { loadVtkWasm } from "@kitware/vtk-wasm"; -- **ESM Bundles:** For both RemoteSession and StandaloneSession. -- **UMD Bundle:** For StandaloneSession, exposed as the `VTK` namespace for use in browser environments. +const runtime = await loadVtkWasm({ url: VTK_WASM_BUNDLE_URL }); +const session = runtime.createStandaloneSession(); +const vtk = session.vtk; -Refer to the `package.json` for the full list of scripts and configuration details. +const cone = vtk.vtkConeSource(); +// ... build and render your scene +``` -### ESM imports +No build step? Load the UMD bundle from a CDN and use the global `vtkWASM`: -```js -import { RemoteSession } from "@kitware/vtk-wasm/remote" -import { createVtkObjectProxy, createNamespace } from "@kitware/vtk-wasm/vtk" -import { ExportViewer, createViewer } from "@kitware/vtk-wasm/viewer" +```html + ``` -### RemoteSession progress +See the [Loading VTK.wasm guide](https://kitware.github.io/vtk-wasm/guide/js/loading) to get started and the [API reference](https://kitware.github.io/vtk-wasm/api/) for every export. -```js -import { RemoteSession } from "@kitware/vtk-wasm/remote" +## Contributing -const session = new RemoteSession() -const removeProgress = session.addProgressCallback(({ active, state, hash }) => { - // state: { current, total }, hash: { current, total } - // active is true while states/blobs are being fetched -}) +Requires [Node.js](https://nodejs.org/) (LTS). -// Later, to stop listening: -removeProgress() +```bash +npm install # install dependencies +npm run build # build the ESM + UMD bundles into dist/ +npm run docs:dev # run the documentation site locally +npm run lint # lint the source ``` -### UMD imports +### Repository layout -```js -import "@kitware/vtk-wasm/viewer.css"; -import "@kitware/vtk-wasm/viewer-umd.js"; - -import "@kitware/vtk-wasm/vtk-umd.js"; -``` +- `src/` — library source. +- `examples/` — example applications. +- `docs/` — documentation site (VitePress); the API reference is generated from source JSDoc. +- `dist/` — build output. diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs index fbec974..40569c3 100644 --- a/docs/.vitepress/config.mjs +++ b/docs/.vitepress/config.mjs @@ -1,7 +1,28 @@ +import { existsSync, readFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; import { defineConfig } from "vitepress"; +import { withMermaid } from "vitepress-plugin-mermaid"; + +// API reference sidebar generated by TypeDoc (npm run docs:api). Falls back to +// an empty list so the docs still build before the API has been generated. +const __dirname = dirname(fileURLToPath(import.meta.url)); +const apiSidebarFile = resolve(__dirname, "../api/typedoc-sidebar.json"); + +// TypeDoc marks every group `collapsed: true`; expand them by default. +const expandSidebar = (items) => + items.map((item) => + item.items + ? { ...item, collapsed: false, items: expandSidebar(item.items) } + : item, + ); + +const apiSidebar = existsSync(apiSidebarFile) + ? expandSidebar(JSON.parse(readFileSync(apiSidebarFile, "utf-8"))) + : []; // https://vitepress.dev/reference/site-config -export default defineConfig({ +export default withMermaid(defineConfig({ base: "/vtk-wasm", title: "VTK.wasm", description: "Guides and documentation around VTK.wasm", @@ -50,6 +71,7 @@ export default defineConfig({ nav: [ { text: "Home", link: "/" }, { text: "News", link: "/news" }, + { text: "API", link: "/api/" }, { text: "Guides", link: "/guide/" }, { text: "Roadmap", items: [ @@ -91,10 +113,11 @@ export default defineConfig({ { text: "For JavaScript developers", items: [ - { text: "Getting started", link: "/guide/js/" }, + { text: "Loading VTK.wasm", link: "/guide/js/loading" }, + { text: "Standalone Session", link: "/guide/js/standalone-session" }, { text: "Primer on VTK.wasm", link: "/guide/js/primer" }, - { text: "HTML Script Tag", link: "/guide/js/plain" }, - { text: "Bundler Integration", link: "/guide/js/bundler" }, + { text: "Remote Session", link: "/guide/js/remote-session" }, + { text: "Adding VTK.wasm to a Project", link: "/guide/js/integration" }, ], }, { @@ -113,6 +136,10 @@ export default defineConfig({ ], }, ], + "/api/": [ + { text: "API Reference", link: "/api/" }, + ...apiSidebar, + ], "/roadmap/": [ { text: "Overview", link: "/roadmap/" }, { text: "Module Availability", link: "/roadmap/modules" }, @@ -124,4 +151,4 @@ export default defineConfig({ { icon: 'github', link: 'https://github.com/Kitware/vtk-wasm' } ] }, -}); +})); diff --git a/docs/guide/js/bundler.md b/docs/guide/js/bundler.md deleted file mode 100644 index 0409b81..0000000 --- a/docs/guide/js/bundler.md +++ /dev/null @@ -1,28 +0,0 @@ -# Bundler Integration - -Modern web development rely on package manager to bring project dependencies. This section covers how published releases can be used within a JavaScript project. - -## Project setup - -In the simple example we are going to use [Vite](https://vite.dev/) with Vanilla JavaScript. The full code is available for reference [here](https://github.com/Kitware/vtk-wasm/tree/main/examples/js/simple-app). Please note that you should use a concrete version, or "latest" for the `@kitware/vtk-wasm` package. Here, the example uses a relative path to the `vtk-wasm` project root, so the in-repo documentation stays relevant. - -::: code-group -<<< ../../../examples/js/simple-app/package.json -<<< ../../../examples/js/simple-app/index.html -<<< ../../../examples/js/simple-app/src/main.js [src/main.js] -<<< ../../../examples/js/simple-app/src/example.js [src/example.js] -<<< ../../../examples/js/simple-app/src/style.css [src/style.css] -```bash [Install/Build] -npm install -npm run build -``` -::: - -Here, the VTK.wasm bundle is downloaded in the browser directly from the Gitlab package registry. See the `src/main.js` file for the relevant code. - - -## Result - - - -[Full Screen Viewer](../../demo/simple-app/index.html){target="_blank"} diff --git a/docs/guide/js/index.md b/docs/guide/js/index.md deleted file mode 100644 index 99f4a1b..0000000 --- a/docs/guide/js/index.md +++ /dev/null @@ -1,31 +0,0 @@ -# VTK.wasm from JavaScript - -This guide focuses on using the VTK WASM bundle from plain JavaScript, requiring no prior C++ knowledge. VTK.wasm allows you to use [VTK](https://www.vtk.org) without needing to learn C++. This can be particularly beneficial for web developers who are already familiar with JavaScript and want to integrate advanced visualization capabilities into their web applications. - -## Overview -Most C++ classes from the [VTK C++ Documentation](https://vtk.org/doc/nightly/html/) are available through a single JavaScript object, which we refer to as the **vtk** namespace. - -Once you have access to the **vtk** namespace (see [Bundler Integration](./bundler.md)), you can interact with VTK classes using standard JavaScript. - - - -
const vtk = await window.vtkReady;
-
-
- -The following sections will guide you through the essential aspects of using VTK.wasm with JavaScript: - -- [Primer on VTK.wasm](./primer.md) -- [HTML Script Tag](./plain.md) -- [Bundler Integration](./bundler.md) diff --git a/docs/guide/js/integration.md b/docs/guide/js/integration.md new file mode 100644 index 0000000..93778ad --- /dev/null +++ b/docs/guide/js/integration.md @@ -0,0 +1,79 @@ +# Adding VTK.wasm to a Project + +There are two ways to get VTK.wasm into a web project: + +- **HTML Script Tag** — load a prebuilt bundle from a CDN, no build step required. Best for quick prototypes, demos, and embedding into existing pages. +- **Bundler** — install the `@kitware/vtk-wasm` package and `import` it. Best for application development with a tool like Vite. + +Either way you end up calling [`loadVtkWasm`](./loading.md); only how it reaches the page differs. + +## HTML Script Tag + +Use VTK.wasm directly in an HTML file using a ` diff --git a/docs/public/demo/plain-javascript.html b/docs/public/demo/plain-javascript.html index 501c97d..cd7a40c 100644 --- a/docs/public/demo/plain-javascript.html +++ b/docs/public/demo/plain-javascript.html @@ -6,9 +6,10 @@ diff --git a/examples/js/simple-app/src/main.js b/examples/js/simple-app/src/main.js index fc55e1d..4001b17 100644 --- a/examples/js/simple-app/src/main.js +++ b/examples/js/simple-app/src/main.js @@ -1,8 +1,9 @@ import "./style.css"; -import { VtkWASMLoader } from "@kitware/vtk-wasm/vtk"; +import { loadVtkWasm } from "@kitware/vtk-wasm"; import { buildWASMScene } from "./example"; -const loader = new VtkWASMLoader(); -await loader.load("https://gitlab.kitware.com/api/v4/projects/13/packages/generic/vtk-wasm32-emscripten/9.5.20251215/vtk-9.5.20251215-wasm32-emscripten.tar.gz"); -const vtk = loader.createNamespace(); -buildWASMScene(vtk, "#app > canvas", "This scene passes the VTK.wasm bundle from GitLab registry to createNamespace()"); +const runtime = await loadVtkWasm({ + url: "https://gitlab.kitware.com/api/v4/projects/13/packages/generic/vtk-wasm32-emscripten/9.5.20251215/vtk-9.5.20251215-wasm32-emscripten.tar.gz", +}); +const session = runtime.createStandaloneSession(); +buildWASMScene(session.vtk, "#app > canvas", "This scene passes the VTK.wasm bundle from GitLab registry to loadVtkWasm()"); diff --git a/package-lock.json b/package-lock.json index d184a6d..c380771 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,11 +20,16 @@ "conventional-changelog-conventionalcommits": "9.1.0", "eslint": "^9.22.0", "globals": "^16.0.0", + "mermaid": "^11.15.0", "prettier": "3.5.3", "rimraf": "^6.0.1", "semantic-release": "25.0.2", + "typedoc": "^0.28.19", + "typedoc-plugin-markdown": "^4.12.0", + "typedoc-vitepress-theme": "^1.1.3", "vite": "^6.2.4", - "vitepress": "^1.6.3" + "vitepress": "^1.6.3", + "vitepress-plugin-mermaid": "^2.0.17" } }, "node_modules/@actions/core": { @@ -334,6 +339,20 @@ "node": ">= 14.0.0" } }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -399,6 +418,20 @@ "node": ">=6.9.0" } }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", + "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@chevrotain/types": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", + "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@codemirror/autocomplete": { "version": "6.20.1", "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.1.tgz", @@ -1221,6 +1254,62 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@gerrit0/mini-shiki": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.23.0.tgz", + "integrity": "sha512-bEMORlG0cqdjVyCEuU0cDQbORWX+kYCeo0kV1lbxF5bt4r7SID2l9bqsxJEM0zndaxpOUT7riCyIVEuqq/Ynxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-oniguruma": "^3.23.0", + "@shikijs/langs": "^3.23.0", + "@shikijs/themes": "^3.23.0", + "@shikijs/types": "^3.23.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@gerrit0/mini-shiki/node_modules/@shikijs/engine-oniguruma": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz", + "integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@gerrit0/mini-shiki/node_modules/@shikijs/langs": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz", + "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0" + } + }, + "node_modules/@gerrit0/mini-shiki/node_modules/@shikijs/themes": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz", + "integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.23.0" + } + }, + "node_modules/@gerrit0/mini-shiki/node_modules/@shikijs/types": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz", + "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1304,27 +1393,16 @@ "dev": true, "license": "MIT" }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "node_modules/@iconify/utils": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.3.tgz", + "integrity": "sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw==", "dev": true, "license": "MIT", "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" + "@antfu/install-pkg": "^1.1.0", + "@iconify/types": "^2.0.0", + "import-meta-resolve": "^4.2.0" } }, "node_modules/@isaacs/cliui": { @@ -1415,6 +1493,41 @@ "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", "license": "MIT" }, + "node_modules/@mermaid-js/mermaid-mindmap": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/mermaid-mindmap/-/mermaid-mindmap-9.3.0.tgz", + "integrity": "sha512-IhtYSVBBRYviH1Ehu8gk69pMDF8DSRqXBRDMWrEfHoaMruHeaP2DXA3PBnuwsMaCdPQhlUUcy/7DBLAEIXvCAw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@braintree/sanitize-url": "^6.0.0", + "cytoscape": "^3.23.0", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.1.0", + "d3": "^7.0.0", + "khroma": "^2.0.0", + "non-layered-tidy-tree-layout": "^2.0.2" + } + }, + "node_modules/@mermaid-js/mermaid-mindmap/node_modules/@braintree/sanitize-url": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz", + "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@mermaid-js/parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.1.1.tgz", + "integrity": "sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@chevrotain/types": "~11.1.1" + } + }, "node_modules/@octokit/auth-token": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", @@ -2267,6 +2380,290 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", @@ -2274,6 +2671,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", @@ -2333,6 +2737,14 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -2354,6 +2766,17 @@ "dev": true, "license": "ISC" }, + "node_modules/@upsetjs/venn.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", + "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "d3-selection": "^3.0.0", + "d3-transition": "^3.0.1" + } + }, "node_modules/@vitejs/plugin-vue": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", @@ -3259,6 +3682,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/compare-func": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", @@ -3394,6 +3827,16 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "license": "MIT" }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "dev": true, + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, "node_modules/cosmiconfig": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", @@ -3471,10 +3914,558 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cytoscape": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.34.0.tgz", + "integrity": "sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "dev": true, + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dev": true, + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "dev": true, + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dev": true, + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", + "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", "dev": true, "license": "MIT" }, @@ -3513,6 +4504,16 @@ "dev": true, "license": "MIT" }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -3550,6 +4551,16 @@ "node": ">=8" } }, + "node_modules/dompurify": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.10.tgz", + "integrity": "sha512-0xzNv0e7oYC6yyuOGZIABPM4qtg3QxLFniDNPP4ZP90wR8Yq3zgwpRbrNiT4N3IKqDbbYFEJLV+JWEs19aZ//w==", + "dev": true, + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", @@ -3763,6 +4774,17 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-toolkit": { + "version": "1.47.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.47.1.tgz", + "integrity": "sha512-5RAqEwf4P4E17p+W75KLOWw/nOvKZzSQpxM32IpI2KZLaVonjTrZ0Ai5ghMaVI9eKC2p8eoQgcBdkEDgzFk6+Q==", + "dev": true, + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/esbuild": { "version": "0.25.8", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", @@ -4399,6 +5421,13 @@ "dev": true, "license": "ISC" }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "dev": true, + "license": "MIT" + }, "node_modules/handlebars": { "version": "4.7.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", @@ -4561,6 +5590,19 @@ "node": ">=18.18.0" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4609,9 +5651,9 @@ } }, "node_modules/import-meta-resolve": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", - "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", "dev": true, "license": "MIT", "funding": { @@ -4668,6 +5710,16 @@ "dev": true, "license": "ISC" }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/into-stream": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz", @@ -4946,6 +5998,33 @@ "setimmediate": "^1.0.5" } }, + "node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "dev": true, + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4956,6 +6035,19 @@ "json-buffer": "3.0.1" } }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==", + "dev": true + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "dev": true, + "license": "MIT" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4986,6 +6078,26 @@ "dev": true, "license": "MIT" }, + "node_modules/linkify-it": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.1.tgz", + "integrity": "sha512-wVoTjP4Q6R0NW5hiZkVJaFZPWgtXfoGF+6LucL3/FtiNjmcHhYjEr5f1Kqjirc1nBW07J/ZuRFumqr2oqccEWg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -5091,6 +6203,13 @@ "node": "20 || >=22" } }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true, + "license": "MIT" + }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -5108,6 +6227,34 @@ "dev": true, "license": "MIT" }, + "node_modules/markdown-it": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.2.0.tgz", + "integrity": "sha512-1TGiQiJVRQ3NPmZH6sx5Cfnmg6GQm9jvC1ch4TK511NjSJvjzKLzn5pPfZRNZkRPZP0HqCioSndqH8v2nRaWVQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.1", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, "node_modules/marked": { "version": "15.0.12", "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", @@ -5178,6 +6325,13 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, "node_modules/meow": { "version": "13.2.0", "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", @@ -5198,6 +6352,49 @@ "dev": true, "license": "MIT" }, + "node_modules/mermaid": { + "version": "11.15.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.15.0.tgz", + "integrity": "sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.1.1", + "@iconify/utils": "^3.0.2", + "@mermaid-js/parser": "^1.1.1", + "@types/d3": "^7.4.3", + "@upsetjs/venn.js": "^2.0.0", + "cytoscape": "^3.33.1", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.14", + "dayjs": "^1.11.19", + "dompurify": "^3.3.1", + "es-toolkit": "^1.45.1", + "katex": "^0.16.25", + "khroma": "^2.1.0", + "marked": "^16.3.0", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" + } + }, + "node_modules/mermaid/node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/micromark-util-character": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", @@ -5349,21 +6546,44 @@ } }, "node_modules/minimatch": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimatch/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -5473,6 +6693,14 @@ "node": ">=18" } }, + "node_modules/non-layered-tidy-tree-layout": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz", + "integrity": "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/normalize-package-data": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-8.0.0.tgz", @@ -7691,6 +8919,13 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "dev": true, + "license": "MIT" + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -7766,6 +9001,13 @@ "dev": true, "license": "MIT" }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "dev": true, + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7927,6 +9169,24 @@ "node": ">=4" } }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "dev": true, + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -8043,6 +9303,16 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -8253,6 +9523,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "dev": true, + "license": "Unlicense" + }, "node_modules/rollup": { "version": "4.40.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.1.tgz", @@ -8293,12 +9570,39 @@ "fsevents": "~2.3.2" } }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, "node_modules/search-insights": { "version": "2.17.3", "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", @@ -8857,6 +10161,13 @@ "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", "license": "MIT" }, + "node_modules/stylis": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", + "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", + "dev": true, + "license": "MIT" + }, "node_modules/super-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.0.0.tgz", @@ -9042,6 +10353,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", @@ -9096,6 +10417,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/ts-dedent": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.3.0.tgz", + "integrity": "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, "node_modules/tunnel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", @@ -9135,6 +10466,75 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typedoc": { + "version": "0.28.19", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.19.tgz", + "integrity": "sha512-wKh+lhdmMFivMlc6vRRcMGXeGEHGU2g8a2CkPTJjJlwRf1iXbimWIPcFolCqe4E0d/FRtGszpIrsp3WLpDB8Pw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@gerrit0/mini-shiki": "^3.23.0", + "lunr": "^2.3.9", + "markdown-it": "^14.1.1", + "minimatch": "^10.2.5", + "yaml": "^2.8.3" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 18", + "pnpm": ">= 10" + }, + "peerDependencies": { + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x || 6.0.x" + } + }, + "node_modules/typedoc-plugin-markdown": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.12.0.tgz", + "integrity": "sha512-eJDEMAfxCmede22c/Jw7d0FA13ggAQv+KkwQYKYCdqI02cin6Rc9QRwbG/7XvvHWinuFejySnZVUWDtvGk3Vbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "typedoc": "0.28.x" + } + }, + "node_modules/typedoc-vitepress-theme": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/typedoc-vitepress-theme/-/typedoc-vitepress-theme-1.1.3.tgz", + "integrity": "sha512-EK9iV7e3+R8lFNigdc0rIPWMxqfmDku0uGac3qYUu9tS4Qf1rhWZnyZJ4zu4G3iXrP5mqNPkv2wpODzRlA7jLw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typedoc-plugin-markdown": ">=4.11.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", @@ -9314,6 +10714,20 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/uuid": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", + "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -9472,6 +10886,20 @@ } } }, + "node_modules/vitepress-plugin-mermaid": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/vitepress-plugin-mermaid/-/vitepress-plugin-mermaid-2.0.17.tgz", + "integrity": "sha512-IUzYpwf61GC6k0XzfmAmNrLvMi9TRrVRMsUyCA8KNXhg/mQ1VqWnO0/tBVPiX5UoKF1mDUwqn5QV4qAJl6JnUg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "@mermaid-js/mermaid-mindmap": "^9.3.0" + }, + "peerDependencies": { + "mermaid": "10 || 11", + "vitepress": "^1.0.0 || ^1.0.0-alpha" + } + }, "node_modules/vitepress/node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -10141,6 +11569,22 @@ "node": ">=10" } }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yargs": { "version": "18.0.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", diff --git a/package.json b/package.json index d794556..5efe793 100644 --- a/package.json +++ b/package.json @@ -18,9 +18,10 @@ "build": "npm run build:esm && npm run build:vtk && npm run build:viewer", "build:example:simple-app": "npm install --no-save --prefix examples/js/simple-app && npm run build --prefix examples/js/simple-app", "build:examples": "npm run build:example:simple-app", - "docs:build": "npm run build && npm run build:examples && vitepress build docs", - "docs:dev": "vitepress dev docs", - "docs:clean": "rimraf docs/.vitepress/dist", + "docs:api": "typedoc", + "docs:build": "npm run build && npm run build:examples && npm run docs:api && vitepress build docs", + "docs:dev": "npm run docs:api && vitepress dev docs", + "docs:clean": "rimraf docs/.vitepress/dist docs/api", "docs:preview": "vitepress preview docs", "format": "prettier . --write", "lint": "eslint --fix src", @@ -39,19 +40,23 @@ "conventional-changelog-conventionalcommits": "9.1.0", "eslint": "^9.22.0", "globals": "^16.0.0", + "mermaid": "^11.15.0", "prettier": "3.5.3", "rimraf": "^6.0.1", "semantic-release": "25.0.2", + "typedoc": "^0.28.19", + "typedoc-plugin-markdown": "^4.12.0", + "typedoc-vitepress-theme": "^1.1.3", "vite": "^6.2.4", - "vitepress": "^1.6.3" + "vitepress": "^1.6.3", + "vitepress-plugin-mermaid": "^2.0.17" }, "files": [ "dist/**/*" ], "exports": { + ".": "./dist/esm/index.mjs", "./style.css": "./dist/esm/vtk-wasm.css", - "./remote": "./dist/esm/remote.mjs", - "./vtk": "./dist/esm/vtk.mjs", "./viewer": "./dist/esm/viewer.mjs", "./viewer-umd.js": "./dist/umd/viewer.umd.js", "./vtk-umd.js": "./dist/umd/vtk.umd.js", diff --git a/src/core/configManager.js b/src/core/configManager.js index 14f7087..a441dba 100644 --- a/src/core/configManager.js +++ b/src/core/configManager.js @@ -32,15 +32,6 @@ export function normalizeConfig(config) { return config; } -/** - * Check if two configs are the same. Only rendering and exec are compared. - * @param {object} config1 - * @param {object} config2 - * @returns {boolean} - */ -export function isSameConfig(config1, config2) { - return config1.rendering === config2.rendering && config1.exec === config2.exec; -} /** * Create Emscripten config from provided config diff --git a/src/core/constants.js b/src/core/constants.js index 085b33e..6e5807e 100644 --- a/src/core/constants.js +++ b/src/core/constants.js @@ -9,6 +9,7 @@ export const EXECUTION_MODES = Object.freeze({ }); export const DEFAULT_WASM_BASE_NAME = 'vtk'; +export const DEFAULT_WASM_URL_IS_GZIP = true; export const DEFAULT_CONFIG = Object.freeze({ rendering: RENDERING_BACKENDS.WEBGL, diff --git a/src/core/gzipBundle.js b/src/core/gzipBundle.js index ef331c7..0ee7cd9 100644 --- a/src/core/gzipBundle.js +++ b/src/core/gzipBundle.js @@ -2,15 +2,6 @@ import untar from "js-untar"; import { stripLeadingDotSlash } from "./stringOps.js"; import { MODULE_JS_FILE_EXTENSION, WASM_FILE_EXTENSION } from "./constants.js"; -/** - * Check if provided URL points to a gzip bundle - * @param {string} url - * @returns {boolean} - */ -export function isGzipBundle(url) { - return typeof url === "string" && url.endsWith(".gz"); -} - /** * Fetch gzip bundle from provided URL * @param {string} url diff --git a/src/core/proxy.js b/src/core/proxy.js index afd2b34..e0ab328 100644 --- a/src/core/proxy.js +++ b/src/core/proxy.js @@ -2,10 +2,6 @@ import { toCxxName, toCxxKeys, toJsName, toJsKeys } from "./javaScriptCxxTransla export function createPropGetter(wasm, wrapMethods, vtkId) { - if (!wasm.get) { - return {}; - } - const fullState = wasm.get(vtkId); const getPropHandler = {}; Object.keys(fullState).forEach((propName) => { @@ -17,9 +13,6 @@ export function createPropGetter(wasm, wrapMethods, vtkId) { } function createPropSetter(wasm, wrapMethods, vtkId) { - if (!wasm.get) { - return {}; - } const fullState = wasm.get(vtkId); const setPropHandler = {}; Object.keys(fullState).forEach((propName) => { @@ -44,6 +37,12 @@ export function createVtkObjectProxy( // Create methods const observerTags = []; function deleteObject() { + if (typeof wasm.destroy !== "function") { + console.warn( + "Cannot delete object: this session does not support destroying objects.", + ); + return false; + } const result = wasm.destroy(vtkId); if (result) { const removedProxy = idToRef.delete(vtkId); @@ -107,11 +106,6 @@ export function createVtkObjectProxy( return resolver; } if (prop === "state") { - if (!wasm.get) { - // To support old remote API - wasm.updateStateFromObject(vtkId); - return toJsKeys(wasm.getState(vtkId)); - } return toJsKeys(wasm.get(vtkId)); } if (prop === "delete") { @@ -199,6 +193,12 @@ export function createInstantiatorProxy(wasm, vtkProxyCache, idToRef) { } function create(name, args) { + if (typeof wasm.create !== "function") { + console.warn( + `Cannot create '${name}': this session does not support creating objects.`, + ); + return undefined; + } const vtkId = wasm.create(name); if (args) { wasm.set(vtkId, decorateKwargs(toCxxKeys(args))); diff --git a/src/core/scriptLoader.js b/src/core/scriptLoader.js index 113d073..97e9a09 100644 --- a/src/core/scriptLoader.js +++ b/src/core/scriptLoader.js @@ -73,26 +73,18 @@ export function loadWebAssemblyModuleFromScript(url) { /** * Get the URL of the WebAssembly module JavaScript file. This is the glue * code that loads the WebAssembly binary. It tries to fetch the file to - * ensure it exists. If the fetch fails, an error is thrown. - * - * Set legacy to true for legacy-style wasm modules `vtkWasmSceneManager.mjs`, not `vtkWebAssembly[Async].mjs`. - * This will resolve with null if the fetch fails for any reason. - * + * ensure it exists, and resolves with null if the fetch fails for any reason. + * * This is a fallback when not using GZIP bundles. - * + * * @param {string} wasmBaseURL URL where the wasm module JavaScript file is located - * @param {string} wasmBaseName Base name of the wasm module JavaScript file + * @param {string} wasmBaseName Base name of the wasm module JavaScript file * @param {object} config Configuration object * @returns {Promise} URL of the wasm module JavaScript file */ -export async function createScriptURL(wasmBaseURL, wasmBaseName, config, legacy = false) { - let execModeSuffix = ""; - if (!legacy) { - execModeSuffix = config?.exec === "async" ? "Async" : ""; - } - const filename = legacy - ? `vtkWasmSceneManager${MODULE_JS_FILE_EXTENSION}` - : `${wasmBaseName}WebAssembly${execModeSuffix}${MODULE_JS_FILE_EXTENSION}`; +export async function createScriptURL(wasmBaseURL, wasmBaseName, config) { + const execModeSuffix = config?.exec === "async" ? "Async" : ""; + const filename = `${wasmBaseName}WebAssembly${execModeSuffix}${MODULE_JS_FILE_EXTENSION}`; const url = `${wasmBaseURL}/${filename}`; const { promise, resolve, reject } = createFuture(); fetch(url) diff --git a/src/core/sessionFactory.js b/src/core/sessionFactory.js deleted file mode 100644 index b1e5384..0000000 --- a/src/core/sessionFactory.js +++ /dev/null @@ -1,44 +0,0 @@ -import { createEmscriptenConfig, isSameConfig } from "./configManager.js"; - -/** - * Create a vtkRemoteSession, reusing existing wasmInstance if possible - * @param {object} config - * @param {object} currentConfig - * @param {*} wasmInstance - * @returns {vtkRemoteSession} - */ -export async function createRemoteSession(config, currentConfig, wasmInstance) { - if (wasmInstance) { - // New API - if (wasmInstance?.isAsync && wasmInstance.isAsync()) { - if (!config || isSameConfig(currentConfig, config)) { - // Reuse the same runtime - console.debug("Reusing existing async WASM runtime for remote session"); - return new wasmInstance.vtkRemoteSession(); - } else { - console.debug("Creating new async WASM runtime for remote session"); - const newWASMRuntime = await window.createVTKWASM( - createEmscriptenConfig(config || currentConfig), - ); - return new newWASMRuntime.vtkRemoteSession(); - } - } else { - if (!config || isSameConfig(currentConfig, config)) { - // Reuse the same runtime - console.debug("Reusing existing sync WASM runtime for remote session"); - return new wasmInstance.vtkRemoteSession(); - } else { - console.debug("Creating new sync WASM runtime for remote session"); - const newWASMRuntime = await window.createVTKWASM( - createEmscriptenConfig(config || currentConfig), - ); - return new newWASMRuntime.vtkRemoteSession(); - } - } - } - - // Old API - const remoteSession = await window.createVTKWasmSceneManager(); - remoteSession.initialize(); - return remoteSession; -} diff --git a/src/core/stateDecorators.js b/src/core/stateDecorators.js deleted file mode 100644 index e552780..0000000 --- a/src/core/stateDecorators.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Convert state to object - * @param {string|object} state - * @returns {object} - */ -export function convertToObj(state) { - if (state?.Id) { - return state; - } - return JSON.parse(state); -} - -/** - * Convert state to string - * @param {string|object} state - * @returns {string} - */ -export function convertToStr(state) { - if (state?.Id) { - return JSON.stringify(state); - } - return state; -} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..e79a112 --- /dev/null +++ b/src/index.js @@ -0,0 +1,41 @@ +/** + * Public entry point of `@kitware/vtk-wasm`: load the runtime and create + * standalone or remote sessions. + * + * @module @kitware/vtk-wasm + */ +import { createFuture } from "./core/future"; +import { loadVtkWasm, VtkWasmRuntime } from "./runtime"; +import { StandaloneSession } from "./standaloneSession"; +import { RemoteSession } from "./remoteSession"; + +export { loadVtkWasm, VtkWasmRuntime, StandaloneSession, RemoteSession }; + +/** + * If the script is tagged with id="vtk-wasm", a global "vtk" namespace + * will be created automatically. Since loading is asynchronous, a global + * "vtkReady" promise (resolving to the namespace) is provided for code + * synchronization, and the owning session is exposed as "vtkSession". + * + * Possible data attributes: + * - data-url="url to load VTK.wasm from" only needed if VTK.wasm is not already loaded. + * - data-config="{ rendering: 'webgl|webgpu', exec: 'sync|async' }" json config for + * WASM module configuration. + */ +if (typeof window !== "undefined") { + const script = document.querySelector("#vtk-wasm"); + if (script) { + const { promise, resolve, reject } = createFuture(); + const url = script.dataset.url || "."; + const config = JSON.parse(script.dataset.config || "{}"); + window.vtkReady = promise; + loadVtkWasm({ url, ...config }) + .then((runtime) => { + const session = runtime.createStandaloneSession(); + window.vtkSession = session; + window.vtk = session.vtk; + resolve(session.vtk); + }) + .catch(reject); + } +} diff --git a/src/remote.js b/src/remoteSession.js similarity index 55% rename from src/remote.js rename to src/remoteSession.js index 4e070b7..a072733 100644 --- a/src/remote.js +++ b/src/remoteSession.js @@ -1,24 +1,25 @@ import "./style.css"; -import { VtkWASMLoader } from "./wasmLoader"; -import { createVtkObjectProxy } from "./core/proxy"; +import { createInstantiatorProxy } from "./core/proxy"; import { addCanvasEventListeners, removeCanvasEventListeners } from "./core/canvasEventListeners"; -// url => loader instance -const WASM_LOADERS = {}; - /** - * RemoteSession type definition + * A server-driven VTK session. Wraps a C++ `vtkRemoteSession` and synchronizes + * object state fetched over the network into the local WebAssembly scene. * - * @typedef {Object} RemoteSession - * @property {Boolean} loaded - * @property {Set} cameraIds - * @property {wasmSceneManager} sceneManager + * Obtain one from {@link VtkWasmRuntime#createRemoteSession}. */ export class RemoteSession { - constructor() { - this.sceneManager = null; - this.loaded = false; + #native = null; + #disposed = false; + #vtkProxyCache = null; + #idToRef = null; + + /** + * @param {object} native - the C++ vtkRemoteSession instance. + */ + constructor(native) { + this.#native = native; // this.updateInProgress = 0; this.currentMTime = 1; @@ -32,82 +33,23 @@ export class RemoteSession { this.stateCache = {}; this.progressCallbacks = new Set(); this.progressState = null; - // FIXME - remove when VTK>=9.5 - this.renderWindowIds = new Set(); - this.renderWindowIdToInteractorId = new Map(); this.renderWindowSizes = {}; - // vtkObject Proxy handling - this.vtkProxyCache = new WeakMap(); - this.idToRef = new Map(); - this.internalWrapMethods = {}; - this.internalWrapMethods.isVtkObject = (obj) => this.vtkProxyCache.has(obj); - this.internalWrapMethods.decorateKwargs = (kwargs) => { - const wrapped = {}; - Object.entries(kwargs).forEach(([k, v]) => { - if (this.vtkProxyCache.has(v)) { - wrapped[k] = v.obj; - } else { - wrapped[k] = v; - } - }); - return wrapped; - }; - this.internalWrapMethods.decorateArgs = (args) => { - return args.map((v) => (this.vtkProxyCache.has(v) ? v.obj : v)); - }; - this.internalWrapMethods.decorateResult = (result) => { - if (result == null) { - return result; - } - if (result?.Id) { - return createVtkObjectProxy( - this.sceneManager, - this.vtkProxyCache, - this.idToRef, - this.internalWrapMethods, - result.Id, - ); - } - return result; - }; + // renderWindowId -> user-provided canvas DOM id + this.canvasIds = new Map(); - // Canvas management - this.offlineCanvasContainer = document.createElement("div"); - this.offlineCanvasContainer.setAttribute("class", "unused-canvas"); - document.body.appendChild(this.offlineCanvasContainer); - } + // vtkObject proxy handling (create + getVtkObject + result wrapping) + this.#vtkProxyCache = new WeakMap(); + this.#idToRef = new Map(); + this.vtk = createInstantiatorProxy(native, this.#vtkProxyCache, this.#idToRef); - /** - * Load VTK WASM library using the base url provided - * - * @param {str} wasmBaseURL - */ - async load(wasmBaseURL, config, wasmBaseName) { - if (!WASM_LOADERS[wasmBaseURL]) { - WASM_LOADERS[wasmBaseURL] = new VtkWASMLoader(); - } + // Do not let server-side window sizes override the client canvas size. + native.skipProperty("vtkRenderWindow", "Size"); + } - await WASM_LOADERS[wasmBaseURL].load(wasmBaseURL, config, wasmBaseName); - this.sceneManager = - await WASM_LOADERS[wasmBaseURL].createRemoteSession(config); - this.stateDecorator = WASM_LOADERS[wasmBaseURL].createStateDecorator(); - this.loaded = true; - - // Ignore state properties - only in 9.5 - if (this.sceneManager.skipProperty) { - // Will be enough when wasm check for superclass - this.sceneManager.skipProperty("vtkRenderWindow", "Size"); - // FIXME: but for now we need specific class (window/linux/mac/wasm) - [ - "vtkWin32OpenGLRenderWindow", - "vtkXOpenGLRenderWindow", - "vtkCocoaRenderWindow", - "vtkWebAssemblyOpenGLRenderWindow", - ].forEach((className) => - this.sceneManager.skipProperty(className, "Size"), - ); - } + /** The underlying C++ session. Escape hatch; prefer {@link RemoteSession#vtk}. */ + get native() { + return this.#native; } /** @@ -172,7 +114,7 @@ export class RemoteSession { * @param {int} cacheSize */ freeMemory(cacheSize = 0) { - const memArrays = this.sceneManager.getTotalBlobMemoryUsage(); + const memArrays = this.#native.getTotalBlobMemoryUsage(); const threshold = Number(cacheSize); if (memArrays > threshold) { @@ -192,11 +134,11 @@ export class RemoteSession { }); // Remove blobs starting by the old ones - while (this.sceneManager.getTotalBlobMemoryUsage() > threshold) { + while (this.#native.getTotalBlobMemoryUsage() > threshold) { const hashesToDelete = tsMap[mtimeToFree]; if (hashesToDelete) { for (let i = 0; i < hashesToDelete.length; i++) { - this.sceneManager.unRegisterBlob(hashesToDelete[i]); + this.#native.unRegisterBlob(hashesToDelete[i]); delete this.hashesMTime[hashesToDelete[i]]; } } @@ -206,7 +148,7 @@ export class RemoteSession { } /** - * Fetch and register state inside sceneManager + * Fetch and register state inside the session * * @param {int} vtkId * @returns fetched state as string @@ -219,54 +161,21 @@ export class RemoteSession { } /** - * Push state to internal structure + * Record the MTime of an incoming state. * * @param {str} state + * @returns {str} the unchanged state */ patchState(state) { if (state.length > 0) { - const stateObj = JSON.parse(state); - const { Id, MTime } = stateObj; + const { Id, MTime } = JSON.parse(state); this.stateMTimes[Id] = MTime; - - if ( - !this.sceneManager.skipProperty || - !this.sceneManager.bindRenderWindow - ) { - // Not needed in >=9.5 - if (this.renderWindowIds.has(Id) && stateObj?.Interactor?.Id) { - this.renderWindowIdToInteractorId.set(stateObj.Interactor.Id, Id); - - // Connect canvas selector - stateObj.CanvasSelector = this.getCanvasSelector(Id); - - // Don't use server side size (ignore prop skip it) - delete stateObj["Size"]; - if (this.renderWindowSizes[Id]) { - stateObj.Size = this.renderWindowSizes[Id]; - } - - // Need to patch classname to allow OSMesa server to work - stateObj.ClassName = "vtkCocoaRenderWindow"; - - return JSON.stringify(stateObj); - } - - // Interactor - to remove once API available on C++ side - if (this.renderWindowIdToInteractorId.has(Id)) { - // Connect canvas selector - stateObj.CanvasSelector = this.getCanvasSelector( - this.renderWindowIdToInteractorId.get(Id), - ); - return JSON.stringify(stateObj); - } - } return state; } } /** - * Fetch and register blob inside sceneManager + * Fetch and register blob inside the session * * @param {str} hash * @returns typed array matching blob content @@ -281,7 +190,7 @@ export class RemoteSession { } else { // regular network call array = await this.networkFetchHash(hash); - this.sceneManager.registerBlob(hash, array); + this.#native.registerBlob(hash, array); this.hashesMTime[hash] = this.currentMTime; } this.incrementProgress("hash"); @@ -297,12 +206,12 @@ export class RemoteSession { this.pendingArrays[hash] = new Promise((resolve) => { if (arrayOrBlob.arrayBuffer) { arrayOrBlob.arrayBuffer().then((buffer) => { - this.sceneManager.registerBlob(hash, new Uint8Array(buffer)); + this.#native.registerBlob(hash, new Uint8Array(buffer)); this.hashesMTime[hash] = this.currentMTime; resolve(); }); } else { - this.sceneManager.registerBlob(hash, arrayOrBlob); + this.#native.registerBlob(hash, arrayOrBlob); this.hashesMTime[hash] = this.currentMTime; resolve(); } @@ -316,9 +225,6 @@ export class RemoteSession { * @param {int} vtkId */ async update(vtkId, bindCanvas = false) { - // Not needed once 9.5 is out... - this.renderWindowIds.add(vtkId); - this.updateInProgress++; if (this.updateInProgress !== 1) { // console.error("Skip concurrent update"); @@ -367,7 +273,7 @@ export class RemoteSession { // Remove state that should be ignored serverStatus.ignore_ids.forEach((vtkId) => - this.sceneManager.unRegisterState(vtkId), + this.#native.unRegisterState(vtkId), ); // Ensure completion of all network calls @@ -380,27 +286,23 @@ export class RemoteSession { while (statesToRegister.length) { const state = statesToRegister.pop(); if (state) { - this.sceneManager.registerState(this.stateDecorator(state)); + this.#native.registerState(JSON.parse(state)); } } // Bump local mtime and process states to reflect server state try { - this.sceneManager.updateObjectsFromStates(); + this.#native.updateObjectsFromStates(); if (vtkId in this.renderWindowSizes) { const [w, h] = this.renderWindowSizes[vtkId]; - this.sceneManager.setSize(vtkId, w, h); + this.#native.setSize(vtkId, w, h); } - // Prevent state patching with new API - if (bindCanvas && this.sceneManager.bindRenderWindow) { - this.sceneManager.bindRenderWindow( - vtkId, - this.getCanvasSelector(vtkId), - ); + if (bindCanvas) { + this.#native.bindRenderWindow(vtkId, this.getCanvasSelector(vtkId)); } - await this.sceneManager.render(vtkId); + await this.#native.render(vtkId); // TODO outside: // - freeMemory: to keep memory in check } catch (e) { @@ -434,13 +336,7 @@ export class RemoteSession { if (useCache && this.stateCache[wasmId]) { return this.stateCache[wasmId]; } - // New API - if (this.sceneManager.get) { - return this.sceneManager.get(wasmId); - } - // Old API - this.sceneManager.updateStateFromObject(wasmId); - return this.sceneManager.getState(wasmId); + return this.#native.get(wasmId); } /** @@ -474,50 +370,60 @@ export class RemoteSession { } /** - * Return canvas selector based on renderWindowId + * Return the CSS selector for the canvas registered to a render window. * * @param {int} renderWindowId - * @returns the selector string to find the given canvas + * @returns {string} the selector string to find the given canvas */ getCanvasSelector(renderWindowId) { - return `.vtk-wasm-${renderWindowId}`; + const canvasId = this.canvasIds.get(Number(renderWindowId)); + if (!canvasId) { + throw new Error( + `No canvas registered for render window ${renderWindowId}. ` + + `Call bindCanvas(renderWindowId, canvasId) first.`, + ); + } + return `#${canvasId}`; } /** - * Create canvas if missing and add it to the provided targetElement container. + * Associate a render window with a user-provided canvas (by its DOM id) and + * install the interaction listeners on it. + * + * RemoteSession never creates, moves, or removes canvas elements: the caller + * owns the canvas lifecycle and must have added it to the DOM beforehand. * * @param {int} renderWindowId - * @param {HTMLElement} targetElement - * @returns the canvas selector string + * @param {string} canvasId - the `id` attribute of the user's ``. + * @returns {string} the canvas selector string */ - bindCanvasToDOM(renderWindowId, targetElement) { + bindCanvas(renderWindowId, canvasId) { + this.canvasIds.set(Number(renderWindowId), canvasId); const canvasSelector = this.getCanvasSelector(renderWindowId); - let canvas = this.offlineCanvasContainer.querySelector(canvasSelector); - - if (!canvas) { - // Create it - canvas = document.createElement("canvas"); - canvas.setAttribute("class", canvasSelector.substring(1)); - canvas.setAttribute("tabindex", "0"); + const canvas = document.querySelector(canvasSelector); + if (canvas) { addCanvasEventListeners(canvas); } - - targetElement.appendChild(canvas); return canvasSelector; } /** - * Remove canvas from its current container but keep it for possible followup usage. + * Remove the interaction listeners installed by {@link RemoteSession#bindCanvas} + * and forget the render window -> canvas mapping. The canvas element itself is + * left untouched. * * @param {int} renderWindowId */ - unbindCanvasToDOM(renderWindowId) { - const canvasSelector = this.getCanvasSelector(renderWindowId); - const canvas = document.querySelector(canvasSelector); + unbindCanvas(renderWindowId) { + const canvasId = this.canvasIds.get(Number(renderWindowId)); + if (!canvasId) { + return; + } + const canvas = document.getElementById(canvasId); if (canvas) { removeCanvasEventListeners(canvas); - this.offlineCanvasContainer.appendChild(canvas); } + this.canvasIds.delete(Number(renderWindowId)); } /** @@ -529,45 +435,63 @@ export class RemoteSession { */ async setSize(renderWindowId, width, height) { this.renderWindowSizes[renderWindowId] = [width, height]; - const canvasSelector = this.getCanvasSelector(renderWindowId); - const canvas = document.querySelector(canvasSelector); + if (!this.canvasIds.has(Number(renderWindowId))) { + return; + } + const canvas = document.querySelector(this.getCanvasSelector(renderWindowId)); if (canvas) { canvas.width = width; canvas.height = height; - this.sceneManager.setSize(renderWindowId, width, height); - await this.sceneManager.render(renderWindowId); + this.#native.setSize(renderWindowId, width, height); + await this.#native.render(renderWindowId); } } - /** - * @typedef {object} vtkObject - * @property {number} id - WASM id - * @property {object} obj - Return id wrapped as an object {Id: wasmId}. - * @property {object} state - Return full object state - * @method delete - Remove object from WASM stack - * @method set - Update a batch of properties at once using keyword arguments - * @method observe(eventName, fn) -> tag - Attach listener on a specific event - * @method unObserve(tag) - Detach listener - * @method unObserveAll() - Detach all listeners - * @property VTK Property Name - Read VTK property from its state - * @property VTK Property Name as Setter - Set VTK property - * @method VTK Method Name - Async call to vtk internal - */ + /** * Get a helper proxy for controlling the vtkObject available on the WASM side. - * @param {number} vtkId - wasm id for given vtkObject - * @returns {vtkObject} + * + * The returned proxy exposes: + * - `id` — the WASM id, and `obj` — the id wrapped as `{ Id: wasmId }`. + * - `state` — the full object state. + * - `delete()` — remove the object from the WASM stack. + * - `set(kwargs)` — update a batch of properties at once. + * - `observe(eventName, fn) -> tag` / `unObserve(tag)` / `unObserveAll()` — manage listeners. + * - each VTK property as a getter/setter, and each VTK method as an async call. + * + * @param {number} vtkId - wasm id for the given vtkObject + * @returns {object} the vtkObject proxy */ getVtkObject(vtkId) { - return createVtkObjectProxy( - this.sceneManager, - this.vtkProxyCache, - this.idToRef, - this.internalWrapMethods, - vtkId, - ); + return this.vtk.getVtkObject(vtkId); } -} -// For backward compatibility -export const VtkWASMHandler = RemoteSession; + /** + * Free the C++ session and detach the interaction listeners installed on the + * user-provided canvases. The canvas elements themselves are left untouched. + * The session is unusable afterwards. + */ + dispose() { + if (this.#disposed) { + return; + } + this.#disposed = true; + this.progressCallbacks.clear(); + this.#idToRef.clear(); + this.canvasIds.forEach((canvasId) => { + const canvas = document.getElementById(canvasId); + if (canvas) { + removeCanvasEventListeners(canvas); + } + }); + this.canvasIds.clear(); + if (typeof this.#native?.delete === "function") { + this.#native.delete(); + } + this.#native = null; + } + + [Symbol.dispose]() { + this.dispose(); + } +} diff --git a/src/runtime.js b/src/runtime.js new file mode 100644 index 0000000..e7fbc16 --- /dev/null +++ b/src/runtime.js @@ -0,0 +1,187 @@ +import { extractFilesFromGzipBundle, fetchGzipBundle } from "./core/gzipBundle"; +import { createEmscriptenConfig, normalizeConfig, validateConfig } from "./core/configManager"; +import { DEFAULT_CONFIG, DEFAULT_WASM_BASE_NAME, DEFAULT_WASM_URL_IS_GZIP, MIME_TYPES } from "./core/constants"; +import { createScriptURL, loadWebAssemblyModuleFromExistingScript, loadWebAssemblyModuleFromScript } from "./core/scriptLoader"; +import { createBlobURL, disposeBlobURL } from "./core/blobURL"; +import { StandaloneSession } from "./standaloneSession"; +import { RemoteSession } from "./remoteSession"; + +// cacheKey => Promise for in-flight loads +const RUNTIME_PROMISES = new Map(); +// cacheKey => VtkWasmRuntime for resolved runtimes +const RUNTIME_CACHE = new Map(); + +/** + * Build a stable cache key. Only the inputs that change the loaded binary are + * considered, so callbacks like `print` do not fragment the cache. + */ +function cacheKey(url, wasmBaseName, config) { + return `${url}::${wasmBaseName}::${config.rendering}::${config.exec}`; +} + +/** + * Ensure `window.createVTKWASM` is available, loading the glue script if needed. + * Returns the wasm binary descriptor when it had to be extracted from a gzip + * bundle (so it can be handed to Emscripten), otherwise null. + */ +async function prepareModuleFactory(url, urlIsGzip, wasmBaseName, config) { + if (!window.createVTKWASM) { + await loadWebAssemblyModuleFromExistingScript(wasmBaseName); + } + if (window.createVTKWASM) { + return null; + } + + if (urlIsGzip) { + const gzipArrayBuffer = await fetchGzipBundle(url); + const { js, wasm } = await extractFilesFromGzipBundle(gzipArrayBuffer, config, wasmBaseName); + const javaScriptBlobURL = createBlobURL(js.buffer, MIME_TYPES.JAVASCRIPT); + try { + await loadWebAssemblyModuleFromScript(javaScriptBlobURL); + } finally { + disposeBlobURL(javaScriptBlobURL); + } + return wasm; + } + + const scriptURL = await createScriptURL(url, wasmBaseName, config); + if (scriptURL !== null) { + await loadWebAssemblyModuleFromScript(scriptURL); + } + return null; +} + +async function instantiate(url, urlIsGzip, wasmBaseName, config, key) { + try { + const wasmFile = await prepareModuleFactory(url, urlIsGzip, wasmBaseName, config); + if (!window.createVTKWASM) { + throw new Error( + [ + "Could not load WebAssembly module: window.createVTKWASM is not available after loading scripts.", + "Possible causes include:", + ` - Incorrect or unreachable 'url' ("${url}")`, + ` - Wrong 'wasmBaseName' ("${wasmBaseName}") or missing/misnamed .mjs/.wasm files in the bundle`, + " - Network or script loading failures (e.g., 404/500 responses)", + " - Content Security Policy (CSP) blocking script execution", + "", + "Next steps:", + " - Verify that the expected .mjs and .wasm files are present and accessible under 'url'", + " - Check the browser's Network/Console tabs for failed script requests or CSP errors.", + ].join("\n"), + ); + } + const module = await window.createVTKWASM(createEmscriptenConfig(config, wasmFile)); + const runtime = new VtkWasmRuntime(module, key); + RUNTIME_CACHE.set(key, runtime); + return runtime; + } finally { + RUNTIME_PROMISES.delete(key); + } +} + +/** + * Load the VTK.wasm runtime and return a {@link VtkWasmRuntime} ready to create + * sessions. Runtimes are cached per (url, wasmBaseName, rendering, exec), so + * repeated calls with the same options share a single WebAssembly module. + * + * If you want to pipe std::cout/std::cerr to the console, pass `print`/`printErr`. + * + * @param {object} [options] + * @param {string} [options.url] - Directory or .tar.gz bundle to load VTK.wasm from. + * Ignored when the glue script is already present on the page. + * @param {boolean} [options.urlIsGzip] - (default is `true`) specifies whether the + * resource at `options.url` is a Gzip archive. + * @param {string} [options.wasmBaseName] - Base name of the wasm bundle (default "vtk"). + * @param {"webgl"|"webgpu"} [options.rendering] - Rendering backend (default "webgl"). + * @param {"sync"|"async"} [options.exec] - Method execution mode (default "sync"). + * @param {Function} [options.print] - std::cout sink. + * @param {Function} [options.printErr] - std::cerr sink. + * @returns {Promise} + */ +export async function loadVtkWasm(options = {}) { + const { + url = "loaded-module", + urlIsGzip = DEFAULT_WASM_URL_IS_GZIP, + wasmBaseName = DEFAULT_WASM_BASE_NAME, + ...rest + } = options; + const config = normalizeConfig({ ...DEFAULT_CONFIG, ...rest }); + validateConfig(config); + + const key = cacheKey(url, wasmBaseName, config); + if (RUNTIME_CACHE.has(key)) { + return RUNTIME_CACHE.get(key); + } + if (!RUNTIME_PROMISES.has(key)) { + RUNTIME_PROMISES.set(key, instantiate(url, urlIsGzip, wasmBaseName, config, key)); + } + return RUNTIME_PROMISES.get(key); +} + +/** + * A loaded VTK.wasm WebAssembly module. Acts as the single factory for sessions. + */ +export class VtkWasmRuntime { + #module = null; + #key = null; + #disposed = false; + + constructor(module, key) { + this.#module = module; + this.#key = key; + } + + /** The underlying Emscripten module. Escape hatch; prefer the session API. */ + get module() { + return this.#module; + } + + /** @returns {boolean} whether this runtime executes methods asynchronously (JSPI). */ + isAsync() { + return typeof this.#module?.isAsync === "function" && this.#module.isAsync(); + } + + /** + * Create an in-browser standalone session. + * @returns {StandaloneSession} + */ + createStandaloneSession() { + this.#assertLive(); + return new StandaloneSession(new this.#module.vtkStandaloneSession()); + } + + /** + * Create a server-driven remote session. + * @returns {RemoteSession} + */ + createRemoteSession() { + this.#assertLive(); + return new RemoteSession(new this.#module.vtkRemoteSession()); + } + + /** + * Drop this runtime from the shared cache and release the module reference. + * Note: Emscripten cannot reclaim a runtime's heap before page reload; + * disposing individual sessions is what actually frees C++ memory. + */ + dispose() { + if (this.#disposed) { + return; + } + this.#disposed = true; + if (RUNTIME_CACHE.get(this.#key) === this) { + RUNTIME_CACHE.delete(this.#key); + } + this.#module = null; + } + + [Symbol.dispose]() { + this.dispose(); + } + + #assertLive() { + if (this.#disposed) { + throw new Error("VtkWasmRuntime has been disposed."); + } + } +} diff --git a/src/standalone.js b/src/standalone.js deleted file mode 100644 index ade2fe3..0000000 --- a/src/standalone.js +++ /dev/null @@ -1,45 +0,0 @@ -import { createFuture } from "./core/future"; -import { VtkWASMLoader } from "./wasmLoader"; -export { VtkWASMLoader }; - -/** - * Create a VTK namespace for handling vtk object creation. - * - * @param {String} url - Optional directory to where VTK.wasm is getting served from. - * If vtkWebAssemblyInterface.mjs is already loaded as a script, - * this will be ignored. - * @param {Object} config - * - * @returns the vtk namespace for creating VTK objects. - */ -export async function createNamespace(url, config = {}, wasmBaseName = "vtk") { - const loader = new VtkWASMLoader(); - await loader.load(url || "loaded-module", config, wasmBaseName); - return loader.createNamespace(); -} - -/** - * If the script is tagged with id="vtk-wasm", a global "vtk" namespace - * will be created automatically. Since the namespace creation is asynchronous, - * a global "vtkReady" promise will be provided to enable code synchronization. - * - * Possible data attributes: - * - data-url="url to load VTK.wasm from" only needed if VTK.wasm is not already loaded. - * - data-config="{ rendering: 'webgl|webgpu', exec: 'sync|async' }" json config for - * WASM module configuration. - */ -if (typeof window !== "undefined") { - const script = document.querySelector("#vtk-wasm"); - if (script) { - const { promise, resolve, reject } = createFuture(); - const url = script.dataset.url || "."; - const config = JSON.parse(script.dataset.config || "{}"); - window.vtkReady = promise; - createNamespace(url, config) - .then((vtk) => { - window.vtk = vtk; - resolve(vtk); - }) - .catch(reject); - } -} diff --git a/src/standaloneSession.js b/src/standaloneSession.js new file mode 100644 index 0000000..190e7d3 --- /dev/null +++ b/src/standaloneSession.js @@ -0,0 +1,51 @@ +import { createInstantiatorProxy } from "./core/proxy"; + +/** + * An in-browser VTK session. Wraps a C++ `vtkStandaloneSession` and exposes the + * `vtk` namespace proxy used to instantiate and drive VTK objects. + * + * Obtain one from {@link VtkWasmRuntime#createStandaloneSession}. + */ +export class StandaloneSession { + #native = null; + #disposed = false; + #vtkProxyCache = new WeakMap(); + #idToRef = new Map(); + + /** + * @param {object} native - the C++ vtkStandaloneSession instance. + */ + constructor(native) { + this.#native = native; + /** + * The `vtk` namespace: call `vtk.vtkActor({ ... })` to create objects. + * @type {object} + */ + this.vtk = createInstantiatorProxy(native, this.#vtkProxyCache, this.#idToRef); + } + + /** The underlying C++ session. Escape hatch; prefer {@link StandaloneSession#vtk}. */ + get native() { + return this.#native; + } + + /** + * Free the C++ session and all objects it owns, and drop proxy caches. + * The session is unusable afterwards. + */ + dispose() { + if (this.#disposed) { + return; + } + this.#disposed = true; + this.#idToRef.clear(); + if (typeof this.#native?.delete === "function") { + this.#native.delete(); + } + this.#native = null; + } + + [Symbol.dispose]() { + this.dispose(); + } +} diff --git a/src/viewer.js b/src/viewer.js index 6a2e2b1..99d3efb 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -1,5 +1,11 @@ +/** + * `@kitware/vtk-wasm/viewer`: load a serialized VTK scene (`.vtk-wasm` export) + * into a remote session and display it. + * + * @module @kitware/vtk-wasm/viewer + */ import JSZip from "jszip"; -import { RemoteSession } from "./remote"; +import { loadVtkWasm } from "./runtime"; import { createFuture } from "./core/future"; export class ExportViewer { @@ -31,7 +37,7 @@ export class ExportViewer { zipContent.folder("states").forEach(async (relativePath, file) => { progress++; const state = JSON.parse(await file.async("string")); - this.remoting.sceneManager.registerState(state); + this.remoting.native.registerState(state); progress--; if (progress === 0) { resolve(); @@ -40,7 +46,7 @@ export class ExportViewer { zipContent.folder("blobs").forEach(async (relativePath, file) => { progress++; const blob = await file.async("uint8array"); - this.remoting.sceneManager.registerBlob(relativePath, blob); + this.remoting.native.registerBlob(relativePath, blob); progress--; if (progress === 0) { resolve(); @@ -49,19 +55,21 @@ export class ExportViewer { progress--; await promise; - this.remoting.sceneManager.updateObjectsFromStates(); + this.remoting.native.updateObjectsFromStates(); - // Bind canvas - const selector = this.remoting.bindCanvasToDOM(rwId, this.container); - this.container - .querySelector(selector) - .setAttribute( - "style", - "position: absolute; left: 0; top: 0; width: 100%; height: 100%;", - ); + // Create the canvas, place it in the container, then let the session bind to it. + const canvas = document.createElement("canvas"); + canvas.id = `vtk-wasm-${rwId}`; + canvas.setAttribute("tabindex", "0"); + canvas.setAttribute( + "style", + "position: absolute; left: 0; top: 0; width: 100%; height: 100%;", + ); + this.container.appendChild(canvas); - this.remoting.sceneManager.bindRenderWindow(rwId, selector); - this.remoting.sceneManager.startEventLoop(rwId); + const selector = this.remoting.bindCanvas(rwId, canvas.id); + this.remoting.native.bindRenderWindow(rwId, selector); + this.remoting.native.startEventLoop(rwId); } } @@ -69,10 +77,10 @@ export async function createViewer( containerSelector, dataURL, wasmURL, - wasmConfig, + wasmConfig = {}, ) { - const remoting = new RemoteSession(); - await remoting.load(wasmURL || "loaded-module", wasmConfig); + const runtime = await loadVtkWasm({ url: wasmURL || "loaded-module", ...wasmConfig }); + const remoting = runtime.createRemoteSession(); const viewer = new ExportViewer(containerSelector, remoting); await viewer.load(dataURL); return viewer; diff --git a/src/wasmLoader.js b/src/wasmLoader.js deleted file mode 100644 index 37696fc..0000000 --- a/src/wasmLoader.js +++ /dev/null @@ -1,207 +0,0 @@ -import { extractFilesFromGzipBundle, fetchGzipBundle, isGzipBundle } from "./core/gzipBundle"; -import { createEmscriptenConfig, normalizeConfig, validateConfig } from "./core/configManager"; -import { DEFAULT_CONFIG, MIME_TYPES } from "./core/constants"; -import { createScriptURL, loadWebAssemblyModuleFromExistingScript, loadWebAssemblyModuleFromScript } from "./core/scriptLoader"; -import { createRemoteSession } from "./core/sessionFactory"; -import { createFuture } from "./core/future"; -import { createBlobURL, disposeBlobURL } from "./core/blobURL"; -import { convertToObj, convertToStr } from "./core/stateDecorators"; -import { createInstantiatorProxy } from "./core/proxy"; - -/** - * VtkWASMLoader type definition - * - * @typedef {Object} VtkWASMLoader - * @property {Boolean} loaded - */ -export class VtkWASMLoader { - #config = null; - #loaded = false; - #pendingLoad = null; - #wasmInstance = null; - - constructor() { - this.#config = {}; - this.#loaded = false; - this.#pendingLoad = null; - this.#wasmInstance = null; - } - - /** - * Load VTK WASM library using the base url provided - * - * If you want to pipe std::cout and std::cerr to the console, - * you can provide a config like so: - * - * config = { - * print: console.info, - * printErr: console.error, - * rendering: "webgl", // or "webgpu" - * exec: "sync", // or "async" - * } - * - * @param {string} wasmBaseURL - * @param {object} config - for WASM runtime creation. - * @param {string} wasmBaseName - (default is "vtk") base name of the wasm bundle to load. e.g., "vtk" or "addon" will - * look for vtkWebAssembly.mjs or addonWebAssembly.mjs in the wasmBaseURL. - */ - async load( - wasmBaseURL, - config = DEFAULT_CONFIG, - wasmBaseName = "vtk", - ) { - if (this.#loaded) { - return; - } - validateConfig(config); - this.#config = normalizeConfig(config); - - if (!this.#pendingLoad) { - const { promise, resolve, reject } = createFuture(); - this.#pendingLoad = promise; - - // wait for wasm script to load if any (first priority) - if (!window.createVTKWASM) { - await loadWebAssemblyModuleFromExistingScript(wasmBaseName); - } - - let wasmFile = null; - if (!window.createVTKWASM) { - if (isGzipBundle(wasmBaseURL)) { - let gzipArrayBuffer; - let javaScriptBlobURL = null; - try { - gzipArrayBuffer = await fetchGzipBundle(wasmBaseURL); - } catch (e) { - reject(e); - this.#pendingLoad = null; - return; - } - try { - const result = await extractFilesFromGzipBundle( - gzipArrayBuffer, - this.#config, - wasmBaseName, - ); - wasmFile = result.wasm; - javaScriptBlobURL = createBlobURL(result.js.buffer, MIME_TYPES.JAVASCRIPT); - await loadWebAssemblyModuleFromScript(javaScriptBlobURL); - } catch (e) { - reject(e); - this.#pendingLoad = null; - return; - } - finally { - if (javaScriptBlobURL !== null) { - disposeBlobURL(javaScriptBlobURL); - } - } - } else { - try { - const scriptURL = await createScriptURL(wasmBaseURL, wasmBaseName, this.#config); - if (scriptURL !== null) { - await loadWebAssemblyModuleFromScript(scriptURL); - } - // if window.createVTKWASM is still not defined, try legacy loader - if (!window.createVTKWASM) { - const legacyScriptURL = await createScriptURL(wasmBaseURL, null, null, true); - await loadWebAssemblyModuleFromScript(legacyScriptURL); - } - } catch (e) { - reject(e); - this.#pendingLoad = null; - return; - } - } - } - - // Load WASM - if (window.createVTKWASM) { - try { - this.#wasmInstance = await window.createVTKWASM(createEmscriptenConfig(this.#config, wasmFile)); - } catch (e) { - reject(e); - this.#pendingLoad = null; - return; - } - this.#loaded = true; - resolve(); - this.#pendingLoad = null; - } else if (window.createVTKWasmSceneManager) { - // Legacy API - this.#wasmInstance = null; - this.#loaded = true; - resolve(); - this.#pendingLoad = null; - } else { - const errorMessage = [ - "Could not load WebAssembly module: window.createVTKWASM is not available after loading scripts.", - "Possible causes include:", - ` - Incorrect or unreachable 'wasmBaseURL' ("${wasmBaseURL}")`, - ` - Wrong 'wasmBaseName' ("${wasmBaseName}") or missing/misnamed .mjs/.wasm files in the bundle`, - " - Network or script loading failures (e.g., 404/500 responses)", - " - Content Security Policy (CSP) blocking script execution", - "", - "Next steps:", - " - Verify that the expected .mjs and .wasm files are present and accessible under 'wasmBaseURL'", - " - Check the browser's Network/Console tabs for failed script requests or CSP errors.", - ].join("\n"); - reject(new Error(errorMessage)); - this.#pendingLoad = null; - } - } else { - await this.#pendingLoad; - } - } - - /** - * Create a vtk namespace for handling vtk object creation. - * This must be called after load() is complete. The returned namespace will be a proxy that creates VTK objects in the standalone session. - * @returns {Object} The vtk namespace for creating VTK objects. - */ - createNamespace() { - const vtkProxyCache = new WeakMap(); - const idToRef = new Map(); - const wasm = this.createStandaloneSession(); - return createInstantiatorProxy(wasm, vtkProxyCache, idToRef); - } - - /** - * Create a new remote session and return it regardless of WASM version. - * - * @returns {Promise} - */ - async createRemoteSession(config) { - if (!this.#loaded) { - throw new Error("WASM module is not loaded yet. Call load() first."); - } - const remoteSession = await createRemoteSession(config, this.#config, this.#wasmInstance); - return remoteSession; - } - - /** - * Create a new standalone session. Only works with new WASM bundle. - * - * @returns {vtkStandaloneSession} - */ - createStandaloneSession() { - if (!this.#loaded) { - throw new Error("WASM module is not loaded yet. Call load() first."); - } - if (this.#wasmInstance === null) { - throw new Error("The currently loaded VTK.wasm version does not support standalone mode"); - } - return new this.#wasmInstance.vtkStandaloneSession(); - } - - /** Helper for handling API change */ - createStateDecorator() { - if (!this.#loaded) { - throw new Error("WASM module is not loaded yet. Call load() first."); - } - if (this.#wasmInstance !== null) { - return convertToObj; - } - return convertToStr; - } -} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..5ee229d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": false, + "noEmit": true, + "module": "esnext", + "target": "esnext", + "moduleResolution": "bundler", + "skipLibCheck": true, + "strict": false + }, + "include": ["src"] +} diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 0000000..5d32565 --- /dev/null +++ b/typedoc.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "plugin": ["typedoc-plugin-markdown", "typedoc-vitepress-theme"], + "entryPoints": ["src/index.js", "src/viewer.js"], + "tsconfig": "tsconfig.json", + "out": "docs/api", + "readme": "none", + "githubPages": false, + "hideGenerator": true, + "entryFileName": "index", + "useCodeBlocks": true, + "expandObjects": true, + "parametersFormat": "table", + "docsRoot": "docs" +} diff --git a/vite.config.js b/vite.config.js index 339a303..e6c93f4 100644 --- a/vite.config.js +++ b/vite.config.js @@ -3,8 +3,7 @@ export default { build: { lib: { entry: { - remote: "src/remote.js", - vtk: "src/standalone.js", + index: "src/index.js", viewer: "src/viewer.js", }, formats: ["es"], diff --git a/vite.config.standalone.js b/vite.config.standalone.js index fc48f1a..7eaaada 100644 --- a/vite.config.standalone.js +++ b/vite.config.standalone.js @@ -2,7 +2,7 @@ export default { base: "./", build: { lib: { - entry: "src/standalone.js", + entry: "src/index.js", formats: ["umd"], name: "vtkWASM", fileName: "vtk",