diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
new file mode 100644
index 0000000..88b1e8a
--- /dev/null
+++ b/.github/workflows/pages.yml
@@ -0,0 +1,57 @@
+name: Deploy React Flow map
+
+on:
+ push:
+ branches:
+ - master
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+concurrency:
+ group: github-pages
+ cancel-in-progress: false
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v6
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v6
+ with:
+ node-version: 24
+ cache: npm
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Configure Pages
+ id: pages
+ uses: actions/configure-pages@v5
+
+ - name: Build site
+ run: npm run build
+ env:
+ BASE_PATH: ${{ steps.pages.outputs.base_path }}/
+
+ - name: Upload Pages artifact
+ uses: actions/upload-pages-artifact@v4
+ with:
+ path: dist
+
+ deploy:
+ runs-on: ubuntu-latest
+ needs: build
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3bdd52e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+node_modules/
+dist/
+.DS_Store
diff --git a/README.md b/README.md
index bb0a81a..101cf9c 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# gNMI Map
-[](https://gitlab.com/rdodin/pics/-/wikis/uploads/d275425d2b66601be213c6722dadd4d6/gnmi_0.7.0_map.pdf)
+[](./public/gnmi_0.10.0_map.pdf)
gNMI Map provides a visual representation of the [gNMI](https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md) service.
@@ -9,10 +9,53 @@ gNMI Map makes it easy to understand the composition of the gNMI service as well

+## React Flow map
+
+This repository now includes a React Flow recreation of the latest tagged upstream gNMI protobuf IDL. The app currently tracks `openconfig/gnmi` release `v0.14.1`, whose `gnmi.proto` advertises gNMI service compatibility `0.10.0`. It adds an interactive canvas with search, field-level links, a minimap, source/documentation links, an extension-edge toggle, and a deprecated-field toggle. Deprecated proto fields are hidden by default so the map follows the current spec-facing surface.
+
+```bash
+npm install
+npm run dev
+```
+
+Open the dev-server URL printed by Vite. For a production build:
+
+```bash
+npm run build
+```
+
+## GitHub Pages
+
+The React Flow map deploys to GitHub Pages through `.github/workflows/pages.yml`.
+The workflow runs on pushes to `master` and can also be started manually from the Actions tab.
+
+Enable Pages in the repository settings with **Source: GitHub Actions**. After deployment, the default project Pages URL is:
+
+```text
+https://hellt.github.io/gnmi-map/
+```
+
+The workflow builds the Vite app into `dist/` and uses the Pages base path when generating asset URLs, so the app and PDF link work under `/gnmi-map/`.
+
+To refresh the generated React Flow map from the latest `openconfig/gnmi` tag:
+
+```bash
+npm run build:map
+npm run test:map
+```
+
+Proto links are pinned to the latest gNMI tag. Specification links track `openconfig/reference` `master`, since that repository does not publish tags.
+
+To refresh the generated PDF map from the default non-deprecated view:
+
+```bash
+npm run build:pdf
+```
+
## Usage
-The map can be downloaded from this repository or viewed right in a browser. The maps for the following gNMI service versions have been created so far:
+The map can be downloaded from this repository or viewed right in a browser:
-* **gNMI 0.7.0** - [view](https://gitlab.com/rdodin/pics/-/wikis/uploads/d275425d2b66601be213c6722dadd4d6/gnmi_0.7.0_map.pdf) / [download](https://github.com/hellt/gnmi-map/raw/master/gnmi_0.7.0_map.pdf)
+* **gNMI 0.10.0** - [view](./public/gnmi_0.10.0_map.pdf)
## OS X Preview app issue
-Mac OS X default PDF reader app - Preview - messes with the link fragments (`http://url.com/page#fragment`), therefore the links won't work in this app (see [1](https://discussions.apple.com/thread/251041261), [2](https://discussions.apple.com/thread/250919338)).
\ No newline at end of file
+Mac OS X default PDF reader app - Preview - messes with the link fragments (`http://url.com/page#fragment`), therefore the links won't work in this app (see [1](https://discussions.apple.com/thread/251041261), [2](https://discussions.apple.com/thread/250919338)).
diff --git a/gnmi_0.7.0_map.pdf b/gnmi_0.7.0_map.pdf
deleted file mode 100644
index cce2f5a..0000000
Binary files a/gnmi_0.7.0_map.pdf and /dev/null differ
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..31dcb56
--- /dev/null
+++ b/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ gNMI React Flow Map
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..029e956
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,2866 @@
+{
+ "name": "gnmi-map",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "gnmi-map",
+ "version": "0.1.0",
+ "dependencies": {
+ "@xyflow/react": "^12.8.4",
+ "elkjs": "^0.11.1",
+ "lucide-react": "^0.468.0",
+ "pdfkit": "^0.18.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@types/node": "^25.9.1",
+ "@types/pdfkit": "^0.17.6",
+ "@types/react": "^18.3.29",
+ "@types/react-dom": "^18.3.7",
+ "@vitejs/plugin-react": "^4.3.4",
+ "protobufjs": "^8.4.2",
+ "tsx": "^4.22.3",
+ "typescript": "^6.0.3",
+ "vite": "^6.0.7"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-compilation-targets": "^7.29.7",
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helpers": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
+ "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
+ "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
+ "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
+ "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.7"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
+ "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
+ "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz",
+ "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
+ "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@noble/ciphers": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz",
+ "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@noble/hashes": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
+ "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz",
+ "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz",
+ "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz",
+ "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz",
+ "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz",
+ "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz",
+ "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz",
+ "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz",
+ "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz",
+ "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz",
+ "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz",
+ "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz",
+ "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz",
+ "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz",
+ "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz",
+ "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz",
+ "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz",
+ "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz",
+ "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz",
+ "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz",
+ "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz",
+ "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz",
+ "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz",
+ "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz",
+ "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz",
+ "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.23",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz",
+ "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "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==",
+ "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==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-selection": "*"
+ }
+ },
+ "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==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-color": "*"
+ }
+ },
+ "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==",
+ "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==",
+ "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==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-interpolate": "*",
+ "@types/d3-selection": "*"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "25.9.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz",
+ "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": ">=7.24.0 <7.24.7"
+ }
+ },
+ "node_modules/@types/pdfkit": {
+ "version": "0.17.6",
+ "resolved": "https://registry.npmjs.org/@types/pdfkit/-/pdfkit-0.17.6.tgz",
+ "integrity": "sha512-tIwzxk2uWKp0Cq9JIluQXJid77lYhF52EsIOwhsMF4iWLA6YneoBR1xVKYYdAysHuepUB0OX4tdwMiUDdGKmig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.29",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.29.tgz",
+ "integrity": "sha512-ch0qJdr2JY0r04NXSprbK6TXOgnaJ1Tz23fm5W+z0/CBah6BSBc3n96h7K9GOtwh0HrilNWHIBzE1Ko4Dcw/Wg==",
+ "devOptional": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/@xyflow/react": {
+ "version": "12.10.2",
+ "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.10.2.tgz",
+ "integrity": "sha512-CgIi6HwlcHXwlkTpr0fxLv/0sRVNZ8IdwKLzzeCscaYBwpvfcH1QFOCeaTCuEn1FQEs/B8CjnTSjhs8udgmBgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@xyflow/system": "0.0.76",
+ "classcat": "^5.0.3",
+ "zustand": "^4.4.0"
+ },
+ "peerDependencies": {
+ "react": ">=17",
+ "react-dom": ">=17"
+ }
+ },
+ "node_modules/@xyflow/system": {
+ "version": "0.0.76",
+ "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.76.tgz",
+ "integrity": "sha512-hvwvnRS1B3REwVDlWexsq7YQaPZeG3/mKo1jv38UmnpWmxihp14bW6VtEOuHEwJX2FvzFw8k77LyKSk/wiZVNA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-drag": "^3.0.7",
+ "@types/d3-interpolate": "^3.0.4",
+ "@types/d3-selection": "^3.0.10",
+ "@types/d3-transition": "^3.0.8",
+ "@types/d3-zoom": "^3.0.8",
+ "d3-drag": "^3.0.0",
+ "d3-interpolate": "^3.0.1",
+ "d3-selection": "^3.0.0",
+ "d3-zoom": "^3.0.0"
+ }
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.32",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz",
+ "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/brotli": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz",
+ "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==",
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.1.2"
+ }
+ },
+ "node_modules/browserify-zlib": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
+ "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
+ "license": "MIT",
+ "dependencies": {
+ "pako": "~1.0.5"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
+ "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "baseline-browser-mapping": "^2.10.12",
+ "caniuse-lite": "^1.0.30001782",
+ "electron-to-chromium": "^1.5.328",
+ "node-releases": "^2.0.36",
+ "update-browserslist-db": "^1.2.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001793",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz",
+ "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/classcat": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
+ "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
+ "license": "MIT"
+ },
+ "node_modules/clone": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+ "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "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==",
+ "license": "ISC",
+ "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==",
+ "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==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-dispatch": "1 - 3",
+ "d3-selection": "3"
+ },
+ "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==",
+ "license": "BSD-3-Clause",
+ "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==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "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==",
+ "license": "ISC",
+ "peer": true,
+ "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==",
+ "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==",
+ "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==",
+ "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/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/dfa": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz",
+ "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==",
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.362",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.362.tgz",
+ "integrity": "sha512-PUY2DrLvkjkUuWqq+KPL2iWshrJsZOcIojzRQ7eXFacc9dWga7MGMJAa15VbiejSZB1PAXaRLAiKgruHP8LB1w==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/elkjs": {
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.11.1.tgz",
+ "integrity": "sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==",
+ "license": "EPL-2.0"
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.12",
+ "@esbuild/android-arm": "0.25.12",
+ "@esbuild/android-arm64": "0.25.12",
+ "@esbuild/android-x64": "0.25.12",
+ "@esbuild/darwin-arm64": "0.25.12",
+ "@esbuild/darwin-x64": "0.25.12",
+ "@esbuild/freebsd-arm64": "0.25.12",
+ "@esbuild/freebsd-x64": "0.25.12",
+ "@esbuild/linux-arm": "0.25.12",
+ "@esbuild/linux-arm64": "0.25.12",
+ "@esbuild/linux-ia32": "0.25.12",
+ "@esbuild/linux-loong64": "0.25.12",
+ "@esbuild/linux-mips64el": "0.25.12",
+ "@esbuild/linux-ppc64": "0.25.12",
+ "@esbuild/linux-riscv64": "0.25.12",
+ "@esbuild/linux-s390x": "0.25.12",
+ "@esbuild/linux-x64": "0.25.12",
+ "@esbuild/netbsd-arm64": "0.25.12",
+ "@esbuild/netbsd-x64": "0.25.12",
+ "@esbuild/openbsd-arm64": "0.25.12",
+ "@esbuild/openbsd-x64": "0.25.12",
+ "@esbuild/openharmony-arm64": "0.25.12",
+ "@esbuild/sunos-x64": "0.25.12",
+ "@esbuild/win32-arm64": "0.25.12",
+ "@esbuild/win32-ia32": "0.25.12",
+ "@esbuild/win32-x64": "0.25.12"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT"
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fontkit": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz",
+ "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==",
+ "license": "MIT",
+ "dependencies": {
+ "@swc/helpers": "^0.5.12",
+ "brotli": "^1.3.2",
+ "clone": "^2.1.2",
+ "dfa": "^1.2.0",
+ "fast-deep-equal": "^3.1.3",
+ "restructure": "^3.0.0",
+ "tiny-inflate": "^1.0.3",
+ "unicode-properties": "^1.4.0",
+ "unicode-trie": "^2.0.0"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/js-md5": {
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.8.3.tgz",
+ "integrity": "sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/linebreak": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz",
+ "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "0.0.8",
+ "unicode-trie": "^2.0.0"
+ }
+ },
+ "node_modules/linebreak/node_modules/base64-js": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
+ "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/long": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "0.468.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.468.0.tgz",
+ "integrity": "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.12",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
+ "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.46",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz",
+ "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+ "license": "(MIT AND Zlib)"
+ },
+ "node_modules/pdfkit": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.18.0.tgz",
+ "integrity": "sha512-NvUwSDZ0eYEzqAiWwVQkRkjYUkZ48kcsHuCO31ykqPPIVkwoSDjDGiwIgHHNtsiwls3z3P/zy4q00hl2chg2Ug==",
+ "license": "MIT",
+ "dependencies": {
+ "@noble/ciphers": "^1.0.0",
+ "@noble/hashes": "^1.6.0",
+ "fontkit": "^2.0.4",
+ "js-md5": "^0.8.3",
+ "linebreak": "^1.1.0",
+ "png-js": "^1.0.0"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/png-js": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.1.0.tgz",
+ "integrity": "sha512-PM/uYGzGdNSzqeOgly68+6wKQDL1SY0a/N+OEa/+br6LnHWOAJB0Npiamnodfq3jd2LS/i2fMeOKSAILjA+m5Q==",
+ "dependencies": {
+ "browserify-zlib": "^0.2.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.15",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
+ "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.12",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/protobufjs": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.4.2.tgz",
+ "integrity": "sha512-64rfNzkWOZAIazXzpBFPWq6F9up6gMvTzjE2oWIzApx2N/dqVUEE7+bCn2+40780dFVtKOUab8QfxJ6KJDWbqA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "long": "^5.3.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/restructure": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz",
+ "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==",
+ "license": "MIT"
+ },
+ "node_modules/rollup": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz",
+ "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.60.4",
+ "@rollup/rollup-android-arm64": "4.60.4",
+ "@rollup/rollup-darwin-arm64": "4.60.4",
+ "@rollup/rollup-darwin-x64": "4.60.4",
+ "@rollup/rollup-freebsd-arm64": "4.60.4",
+ "@rollup/rollup-freebsd-x64": "4.60.4",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.60.4",
+ "@rollup/rollup-linux-arm-musleabihf": "4.60.4",
+ "@rollup/rollup-linux-arm64-gnu": "4.60.4",
+ "@rollup/rollup-linux-arm64-musl": "4.60.4",
+ "@rollup/rollup-linux-loong64-gnu": "4.60.4",
+ "@rollup/rollup-linux-loong64-musl": "4.60.4",
+ "@rollup/rollup-linux-ppc64-gnu": "4.60.4",
+ "@rollup/rollup-linux-ppc64-musl": "4.60.4",
+ "@rollup/rollup-linux-riscv64-gnu": "4.60.4",
+ "@rollup/rollup-linux-riscv64-musl": "4.60.4",
+ "@rollup/rollup-linux-s390x-gnu": "4.60.4",
+ "@rollup/rollup-linux-x64-gnu": "4.60.4",
+ "@rollup/rollup-linux-x64-musl": "4.60.4",
+ "@rollup/rollup-openbsd-x64": "4.60.4",
+ "@rollup/rollup-openharmony-arm64": "4.60.4",
+ "@rollup/rollup-win32-arm64-msvc": "4.60.4",
+ "@rollup/rollup-win32-ia32-msvc": "4.60.4",
+ "@rollup/rollup-win32-x64-gnu": "4.60.4",
+ "@rollup/rollup-win32-x64-msvc": "4.60.4",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/tiny-inflate": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
+ "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.16",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
+ "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/tsx": {
+ "version": "4.22.3",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.3.tgz",
+ "integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.28.0"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz",
+ "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/android-arm": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz",
+ "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/android-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz",
+ "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/android-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz",
+ "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz",
+ "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/darwin-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz",
+ "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz",
+ "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz",
+ "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-arm": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz",
+ "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz",
+ "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-ia32": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz",
+ "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-loong64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz",
+ "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz",
+ "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz",
+ "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz",
+ "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-s390x": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz",
+ "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz",
+ "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz",
+ "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz",
+ "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz",
+ "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz",
+ "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz",
+ "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/sunos-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz",
+ "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/win32-arm64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz",
+ "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/win32-ia32": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz",
+ "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/win32-x64": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz",
+ "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/esbuild": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz",
+ "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.28.0",
+ "@esbuild/android-arm": "0.28.0",
+ "@esbuild/android-arm64": "0.28.0",
+ "@esbuild/android-x64": "0.28.0",
+ "@esbuild/darwin-arm64": "0.28.0",
+ "@esbuild/darwin-x64": "0.28.0",
+ "@esbuild/freebsd-arm64": "0.28.0",
+ "@esbuild/freebsd-x64": "0.28.0",
+ "@esbuild/linux-arm": "0.28.0",
+ "@esbuild/linux-arm64": "0.28.0",
+ "@esbuild/linux-ia32": "0.28.0",
+ "@esbuild/linux-loong64": "0.28.0",
+ "@esbuild/linux-mips64el": "0.28.0",
+ "@esbuild/linux-ppc64": "0.28.0",
+ "@esbuild/linux-riscv64": "0.28.0",
+ "@esbuild/linux-s390x": "0.28.0",
+ "@esbuild/linux-x64": "0.28.0",
+ "@esbuild/netbsd-arm64": "0.28.0",
+ "@esbuild/netbsd-x64": "0.28.0",
+ "@esbuild/openbsd-arm64": "0.28.0",
+ "@esbuild/openbsd-x64": "0.28.0",
+ "@esbuild/openharmony-arm64": "0.28.0",
+ "@esbuild/sunos-x64": "0.28.0",
+ "@esbuild/win32-arm64": "0.28.0",
+ "@esbuild/win32-ia32": "0.28.0",
+ "@esbuild/win32-x64": "0.28.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",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.24.6",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
+ "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unicode-properties": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz",
+ "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==",
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.0",
+ "unicode-trie": "^2.0.0"
+ }
+ },
+ "node_modules/unicode-trie": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
+ "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "pako": "^0.2.5",
+ "tiny-inflate": "^1.0.0"
+ }
+ },
+ "node_modules/unicode-trie/node_modules/pako": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
+ "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==",
+ "license": "MIT"
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "6.4.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz",
+ "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2",
+ "postcss": "^8.5.3",
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.13"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/zustand": {
+ "version": "4.5.7",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
+ "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
+ "license": "MIT",
+ "dependencies": {
+ "use-sync-external-store": "^1.2.2"
+ },
+ "engines": {
+ "node": ">=12.7.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8",
+ "immer": ">=9.0.6",
+ "react": ">=16.8"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..83fe5e1
--- /dev/null
+++ b/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "gnmi-map",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite --host 0.0.0.0",
+ "build:map": "tsx scripts/generate-map-data.ts",
+ "build": "npm run typecheck && vite build",
+ "build:pdf": "tsx scripts/generate-pdf.ts",
+ "test:map": "tsx scripts/validate-map.ts",
+ "typecheck": "tsc --noEmit",
+ "preview": "vite preview --host 0.0.0.0"
+ },
+ "dependencies": {
+ "@xyflow/react": "^12.8.4",
+ "elkjs": "^0.11.1",
+ "lucide-react": "^0.468.0",
+ "pdfkit": "^0.18.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@types/node": "^25.9.1",
+ "@types/pdfkit": "^0.17.6",
+ "@types/react": "^18.3.29",
+ "@types/react-dom": "^18.3.7",
+ "@vitejs/plugin-react": "^4.3.4",
+ "protobufjs": "^8.4.2",
+ "tsx": "^4.22.3",
+ "typescript": "^6.0.3",
+ "vite": "^6.0.7"
+ }
+}
diff --git a/public/gnmi_0.10.0_map.pdf b/public/gnmi_0.10.0_map.pdf
new file mode 100644
index 0000000..7c12c20
Binary files /dev/null and b/public/gnmi_0.10.0_map.pdf differ
diff --git a/scripts/generate-map-data.ts b/scripts/generate-map-data.ts
new file mode 100644
index 0000000..40c3343
--- /dev/null
+++ b/scripts/generate-map-data.ts
@@ -0,0 +1,679 @@
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import protobuf from 'protobufjs';
+import {
+ mapBounds,
+ mapNodes as layoutNodes,
+ type MapBounds,
+ type MapBadge,
+ type MapEdge,
+ type MapEdgeKind,
+ type MapField,
+ type MapNode,
+ type MapSource,
+} from '../src/gnmiMap';
+
+const GNMI_TAGS_API = 'https://api.github.com/repos/openconfig/gnmi/tags?per_page=30';
+const GNMI_GITHUB_BASE = 'https://github.com/openconfig/gnmi/blob';
+const GNMI_RAW_BASE = 'https://raw.githubusercontent.com/openconfig/gnmi';
+const SPECBASE =
+ 'https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md';
+const OUTPUT_PATH = path.resolve('src/gnmiMap.ts');
+
+type Definition = protobuf.Type | protobuf.Enum;
+type Definitions = Map;
+type ByShortName = Map;
+type DefinitionLineStackEntry = {
+ name: string;
+ depth: number;
+};
+type ProtoNamespace = protobuf.ReflectionObject & {
+ nested?: Record;
+};
+type ProtoField = protobuf.Field & {
+ keyType?: string;
+};
+type SourceKind = 'gnmi' | 'gnmi_ext';
+type LinesBySource = Record>;
+type SymbolMaps = {
+ nodeIdToSymbol: Map;
+ symbolToNodeId: Map;
+};
+type GitHubTag = {
+ name: string;
+};
+type GeneratedSourceInput = {
+ nodes: MapNode[];
+ edges: MapEdge[];
+ bounds: MapBounds;
+ source: MapSource;
+};
+
+const SCALAR_TYPES = new Set([
+ 'bool',
+ 'bytes',
+ 'double',
+ 'fixed32',
+ 'fixed64',
+ 'float',
+ 'int32',
+ 'int64',
+ 'sfixed32',
+ 'sfixed64',
+ 'sint32',
+ 'sint64',
+ 'string',
+ 'uint32',
+ 'uint64',
+]);
+
+const EXTERNAL_REFS = new Map([
+ ['google.protobuf.Any', 'any'],
+ ['google.protobuf.Duration', 'duration'],
+]);
+
+async function fetchJson(url: string): Promise {
+ const response = await fetch(url, {
+ headers: { Accept: 'application/vnd.github+json' },
+ });
+ if (!response.ok) {
+ throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
+ }
+ return response.json() as Promise;
+}
+
+async function fetchText(url: string): Promise {
+ const response = await fetch(url);
+ if (!response.ok) {
+ throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
+ }
+ return response.text();
+}
+
+function stripLineComment(line: string): string {
+ return line.replace(/\/\/.*$/, '');
+}
+
+function definitionLines(protoText: string): Map {
+ const lines = new Map();
+ const stack: DefinitionLineStackEntry[] = [];
+ let packageName = '';
+ let depth = 0;
+
+ protoText.split('\n').forEach((line, index) => {
+ const code = stripLineComment(line);
+ const packageMatch = code.match(/^\s*package\s+([A-Za-z0-9_.]+)\s*;/);
+ if (packageMatch) {
+ packageName = packageMatch[1];
+ }
+
+ const definitionMatch = code.match(/^\s*(message|enum|service)\s+([A-Za-z_][A-Za-z0-9_]*)\b/);
+ if (definitionMatch) {
+ const [, , name] = definitionMatch;
+ const parents = stack.map((entry) => entry.name);
+ const fullName = [packageName, ...parents, name].filter(Boolean).join('.');
+ lines.set(fullName, index + 1);
+ }
+
+ const openCount = (code.match(/{/g) ?? []).length;
+ const closeCount = (code.match(/}/g) ?? []).length;
+
+ if (definitionMatch && openCount > 0) {
+ stack.push({ name: definitionMatch[2], depth: depth + openCount });
+ }
+
+ depth += openCount - closeCount;
+ while (stack.length && depth < stack[stack.length - 1].depth) {
+ stack.pop();
+ }
+ });
+
+ return lines;
+}
+
+function methodLines(protoText: string): Map {
+ const lines = new Map();
+ protoText.split('\n').forEach((line, index) => {
+ const match = stripLineComment(line).match(/^\s*rpc\s+([A-Za-z_][A-Za-z0-9_]*)\b/);
+ if (match) {
+ lines.set(match[1], index + 1);
+ }
+ });
+ return lines;
+}
+
+function kebab(value: string): string {
+ return value
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
+ .replace(/_/g, '-')
+ .replace(/\./g, '-')
+ .toLowerCase();
+}
+
+function titleFromNode(node: MapNode): string {
+ return node.data.label.replace(/^enum\s+/, '');
+}
+
+function collectDefinitions(root: protobuf.Root): Definitions {
+ const definitions: Definitions = new Map();
+
+ function visit(namespace: ProtoNamespace): void {
+ if (!namespace.nested) {
+ return;
+ }
+
+ Object.values(namespace.nested).forEach((item) => {
+ if (item instanceof protobuf.Type || item instanceof protobuf.Enum) {
+ definitions.set(item.fullName.replace(/^\./, ''), item);
+ }
+ visit(item as ProtoNamespace);
+ });
+ }
+
+ visit(root);
+ return definitions;
+}
+
+function groupByShortName(definitions: Definitions): ByShortName {
+ const byShort: ByShortName = new Map();
+ definitions.forEach((definition, fullName) => {
+ const shortName = fullName.split('.').at(-1) ?? fullName;
+ byShort.set(shortName, [...(byShort.get(shortName) ?? []), fullName]);
+ });
+ return byShort;
+}
+
+function specUrlFromLayout(node: MapNode): string | undefined {
+ if (!node.data.specUrl) {
+ return undefined;
+ }
+ const hash = node.data.specUrl.split('#')[1];
+ return hash ? `${SPECBASE}#${hash}` : SPECBASE;
+}
+
+function resolveSymbol(
+ node: MapNode,
+ definitions: Definitions,
+ byShortName: ByShortName,
+): string | null {
+ if (node.data.sourceSymbol) {
+ return node.data.sourceSymbol;
+ }
+
+ const title = titleFromNode(node);
+ if (definitions.has(title)) {
+ return title;
+ }
+
+ if (title.startsWith('gnmi_ext.')) {
+ return title;
+ }
+
+ const matches = byShortName.get(title) ?? [];
+ if (matches.length === 1) {
+ return matches[0];
+ }
+
+ const gnmiMatch = matches.find((name) => name.startsWith('gnmi.'));
+ if (gnmiMatch) {
+ return gnmiMatch;
+ }
+
+ const extMatch = matches.find((name) => name.startsWith('gnmi_ext.'));
+ if (extMatch) {
+ return extMatch;
+ }
+
+ return null;
+}
+
+function displayType(field: ProtoField): string {
+ const base = field.map ? `map<${field.keyType ?? 'string'},${field.type}>` : field.type;
+ return field.repeated ? `repeated ${base}` : base;
+}
+
+function resolveFieldRef(
+ field: ProtoField,
+ currentSymbol: string,
+ symbolToNodeId: Map,
+ byShortName: ByShortName,
+): string | null {
+ if (field.map || SCALAR_TYPES.has(field.type)) {
+ return null;
+ }
+
+ if (EXTERNAL_REFS.has(field.type)) {
+ return EXTERNAL_REFS.get(field.type) ?? null;
+ }
+
+ const currentParts = currentSymbol.split('.');
+ const packageName = currentParts[0];
+ const candidates: string[] = [];
+
+ if (field.type.includes('.')) {
+ candidates.push(field.type);
+ } else {
+ candidates.push(`${currentSymbol}.${field.type}`);
+ candidates.push(`${packageName}.${field.type}`);
+ candidates.push(...(byShortName.get(field.type) ?? []));
+ }
+
+ const target = candidates.find((candidate) => symbolToNodeId.has(candidate));
+ return target ? (symbolToNodeId.get(target) ?? null) : null;
+}
+
+function reservedFields(type: protobuf.Type): MapField[] {
+ const numbers: string[] = [];
+ const names: string[] = [];
+
+ for (const item of type.reserved ?? []) {
+ if (Array.isArray(item)) {
+ const [start, end] = item;
+ numbers.push(start === end ? `${start}` : `${start}-${end}`);
+ } else {
+ names.push(item);
+ }
+ }
+
+ const count = Math.max(numbers.length, names.length);
+ return Array.from({ length: count }, (_, index) => {
+ const name = names[index];
+ const number = numbers[index];
+ const label = [name, number].filter(Boolean).join(' / ');
+ return {
+ id: `reserved-${kebab(name ?? number ?? `${index + 1}`)}`,
+ type: 'reserved',
+ name: label,
+ ref: null,
+ badge: 'reserved',
+ };
+ });
+}
+
+function fieldData(
+ field: ProtoField,
+ currentSymbol: string,
+ symbolToNodeId: Map,
+ byShortName: ByShortName,
+): MapField {
+ const deprecated = Boolean(field.options?.deprecated);
+ const data: MapField = {
+ id: kebab(field.name),
+ type: displayType(field),
+ name: field.name,
+ ref: resolveFieldRef(field, currentSymbol, symbolToNodeId, byShortName),
+ };
+
+ if (field.partOf) {
+ data.group = `oneof ${field.partOf.name}`;
+ }
+ if (deprecated) {
+ data.badge = 'deprecated';
+ data.deprecated = true;
+ } else if (field.name === 'extension' && field.type === 'gnmi_ext.Extension') {
+ data.badge = 'optional';
+ }
+
+ return data;
+}
+
+function enumFields(enumDefinition: protobuf.Enum): MapField[] {
+ return Object.entries(enumDefinition.values).map(([name, value]) => ({
+ id: kebab(name.replace(/^EID_/, '')),
+ type: `${value}`,
+ name,
+ ref: null,
+ }));
+}
+
+function protoUrl(source: SourceKind, line: number | undefined, gnmiTag: string): string | undefined {
+ if (!line) {
+ return undefined;
+ }
+
+ const file =
+ source === 'gnmi_ext' ? 'proto/gnmi_ext/gnmi_ext.proto' : 'proto/gnmi/gnmi.proto';
+ return `${GNMI_GITHUB_BASE}/${gnmiTag}/${file}#L${line}`;
+}
+
+function buildSymbolMaps(
+ nodes: MapNode[],
+ definitions: Definitions,
+ byShortName: ByShortName,
+): SymbolMaps {
+ const symbolToNodeId = new Map();
+ const nodeIdToSymbol = new Map();
+
+ nodes.forEach((node) => {
+ const symbol = resolveSymbol(node, definitions, byShortName);
+ if (!symbol || !definitions.has(symbol)) {
+ return;
+ }
+ symbolToNodeId.set(symbol, node.id);
+ nodeIdToSymbol.set(node.id, symbol);
+ });
+
+ return { nodeIdToSymbol, symbolToNodeId };
+}
+
+function serviceNode(
+ node: MapNode,
+ service: protobuf.Service,
+ serviceLine: number | undefined,
+ serviceVersion: string,
+ gnmiTag: string,
+): MapNode {
+ const methods = service.methodsArray;
+ return {
+ ...node,
+ style: { ...node.style },
+ data: {
+ id: node.id,
+ kind: node.data.kind,
+ label: `service gNMI ${serviceVersion}`,
+ protoUrl: protoUrl('gnmi', serviceLine, gnmiTag),
+ specUrl: specUrlFromLayout(node),
+ fields: methods.map((method) => ({
+ id: kebab(method.name),
+ type: 'rpc',
+ name: method.name,
+ ref: `rpc-${kebab(method.name)}`,
+ ...(method.requestStream || method.responseStream ? { badge: 'stream' as const } : {}),
+ })),
+ },
+ };
+}
+
+function rpcNode(
+ node: MapNode,
+ service: protobuf.Service,
+ rpcLines: Map,
+ gnmiTag: string,
+): MapNode {
+ const methodName = node.id.replace(
+ /^rpc-/,
+ '',
+ ).replace(/(^|-)([a-z])/g, (_match: string, _separator: string, letter: string) =>
+ letter.toUpperCase(),
+ );
+ const method = service.methods[methodName];
+ if (!method) {
+ return node;
+ }
+
+ return {
+ ...node,
+ style: { ...node.style },
+ data: {
+ id: node.id,
+ kind: node.data.kind,
+ label: `rpc ${method.name}`,
+ protoUrl: protoUrl('gnmi', rpcLines.get(method.name), gnmiTag),
+ specUrl: specUrlFromLayout(node),
+ fields: [
+ {
+ id: 'takes',
+ type: method.requestStream ? 'takes stream' : 'takes',
+ name: method.requestType,
+ ref: kebab(method.requestType),
+ },
+ {
+ id: 'returns',
+ type: method.responseStream ? 'returns stream' : 'returns',
+ name: method.responseType,
+ ref: kebab(method.responseType),
+ },
+ ],
+ },
+ };
+}
+
+function schemaNode(
+ node: MapNode,
+ definition: Definition,
+ symbol: string,
+ symbolToNodeId: Map,
+ byShortName: ByShortName,
+ linesBySource: LinesBySource,
+ gnmiTag: string,
+): MapNode {
+ const source: SourceKind = symbol.startsWith('gnmi_ext.') ? 'gnmi_ext' : 'gnmi';
+ const fields =
+ definition instanceof protobuf.Type
+ ? [
+ ...definition.fieldsArray.map((field) =>
+ fieldData(field, symbol, symbolToNodeId, byShortName),
+ ),
+ ...reservedFields(definition),
+ ]
+ : enumFields(definition);
+ const deprecated = Boolean(definition.options?.deprecated);
+ const badges = deprecated
+ ? [...new Set([...(node.data.badges ?? []), 'deprecated'])]
+ : node.data.badges?.filter((badge) => badge !== 'deprecated');
+
+ return {
+ ...node,
+ style: { ...node.style },
+ data: {
+ id: node.id,
+ kind: node.data.kind,
+ label: node.data.label,
+ sourceSymbol: symbol,
+ deprecated,
+ protoUrl: protoUrl(source, linesBySource[source].get(symbol), gnmiTag),
+ specUrl: specUrlFromLayout(node),
+ ...(badges?.length ? { badges } : {}),
+ fields,
+ },
+ };
+}
+
+function edgeKind(sourceNode: MapNode, field: MapField, targetNode: MapNode): MapEdgeKind {
+ if (field.type === 'rpc') {
+ return 'rpc';
+ }
+ if (field.type.includes('gnmi_ext.Extension')) {
+ return 'extension';
+ }
+ if (sourceNode.data.sourceSymbol?.startsWith('gnmi_ext.')) {
+ return 'extension-detail';
+ }
+ if (targetNode.data.sourceSymbol?.startsWith('gnmi_ext.')) {
+ return 'extension-detail';
+ }
+ return 'field';
+}
+
+function buildEdges(nodes: MapNode[]): MapEdge[] {
+ const nodesById = new Map(nodes.map((node) => [node.id, node]));
+ const edges: MapEdge[] = [];
+
+ for (const node of nodes) {
+ for (const field of node.data.fields ?? []) {
+ if (!field.ref || !nodesById.has(field.ref)) {
+ continue;
+ }
+ const targetNode = nodesById.get(field.ref);
+ if (!targetNode) {
+ continue;
+ }
+ const kind = edgeKind(node, field, targetNode);
+ edges.push({
+ id: `${node.id}:${field.id}->${field.ref}`,
+ source: node.id,
+ sourceHandle: field.id,
+ target: field.ref,
+ kind,
+ deprecated: Boolean(field.deprecated || targetNode.data.deprecated),
+ });
+ }
+ }
+
+ return edges;
+}
+
+const GENERATED_TYPE_DEFINITIONS = `import type { Edge, Node } from '@xyflow/react';
+
+export type MapNodeKind = 'service' | 'rpc' | 'message' | 'enum' | 'external' | 'legend';
+export type MapEdgeKind = 'rpc' | 'field' | 'extension' | 'extension-detail';
+export type MapBadge = 'stream' | 'optional' | 'deprecated' | 'reserved';
+
+export type MapSource = {
+ gnmiTag: string;
+ gnmiServiceVersion: string;
+ gnmiBase: string;
+ extBase: string;
+ specBase: string;
+};
+
+export type MapBounds = {
+ width: number;
+ height: number;
+};
+
+export type MapField = {
+ id: string;
+ type: string;
+ name: string;
+ ref?: string | null;
+ group?: string;
+ badge?: MapBadge;
+ deprecated?: boolean;
+};
+
+export type MapNodeData = Record & {
+ id: string;
+ kind: MapNodeKind;
+ label: string;
+ sourceSymbol?: string;
+ deprecated?: boolean;
+ protoUrl?: string;
+ specUrl?: string;
+ fields?: MapField[];
+ badges?: MapBadge[];
+ active?: boolean;
+ query?: string;
+ showExtensions?: boolean;
+};
+
+export type MapNode = Node;
+
+export type MapEdge = Edge, 'smoothstep'> & {
+ sourceHandle: string;
+ kind: MapEdgeKind;
+ deprecated: boolean;
+};
+
+export type VisibleMapOptions = {
+ showDeprecated?: boolean;
+ showExtensions?: boolean;
+};
+
+export type VisibleMap = {
+ nodes: MapNode[];
+ edges: MapEdge[];
+};`;
+
+function generatedSource({ nodes, edges, bounds, source }: GeneratedSourceInput): string {
+ return `// Generated by scripts/generate-map-data.ts. Do not edit by hand.\n\n${GENERATED_TYPE_DEFINITIONS}\n\nexport const mapSource: MapSource = ${JSON.stringify(
+ source,
+ null,
+ 2,
+ )};\n\nexport const mapNodes: MapNode[] = ${JSON.stringify(nodes, null, 2)};\n\nexport const mapEdges: MapEdge[] = ${JSON.stringify(
+ edges,
+ null,
+ 2,
+ )};\n\nexport const mapBounds: MapBounds = ${JSON.stringify(bounds, null, 2)};\n\nexport function getVisibleMap({\n showDeprecated = false,\n showExtensions = true,\n}: VisibleMapOptions = {}): VisibleMap {\n const visibleNodes = mapNodes\n .filter((node) => node.data.kind !== 'legend')\n .filter((node) => showDeprecated || !node.data.deprecated)\n .map((node) => ({\n ...node,\n data: {\n ...node.data,\n fields: (node.data.fields ?? []).filter((field) => showDeprecated || !field.deprecated),\n },\n }));\n const visibleNodeIds = new Set(visibleNodes.map((node) => node.id));\n const visibleHandles = new Set(\n visibleNodes.flatMap((node) =>\n (node.data.fields ?? []).map((field) => \`\${node.id}:\${field.id}\`),\n ),\n );\n const visibleEdges = mapEdges.filter((edge) => {\n if (!showExtensions && edge.kind === 'extension') {\n return false;\n }\n if (!showDeprecated && edge.deprecated) {\n return false;\n }\n return (\n visibleNodeIds.has(edge.source) &&\n visibleNodeIds.has(edge.target) &&\n visibleHandles.has(\`\${edge.source}:\${edge.sourceHandle}\`)\n );\n });\n\n return { nodes: visibleNodes, edges: visibleEdges };\n}\n`;
+}
+
+async function main() {
+ const tags = await fetchJson(GNMI_TAGS_API);
+ const latestTag = tags.find((tag) => /^v\d+\.\d+\.\d+$/.test(tag.name));
+ if (!latestTag) {
+ throw new Error('Could not resolve latest openconfig/gnmi tag');
+ }
+
+ const gnmiTag = latestTag.name;
+ const gnmiRawUrl = `${GNMI_RAW_BASE}/${gnmiTag}/proto/gnmi/gnmi.proto`;
+ const extRawUrl = `${GNMI_RAW_BASE}/${gnmiTag}/proto/gnmi_ext/gnmi_ext.proto`;
+ const [gnmiProto, extProto] = await Promise.all([fetchText(gnmiRawUrl), fetchText(extRawUrl)]);
+
+ const root = new protobuf.Root();
+ protobuf.parse(extProto, root, { keepCase: true });
+ protobuf.parse(gnmiProto, root, { keepCase: true });
+
+ const definitions = collectDefinitions(root);
+ const byShortName = groupByShortName(definitions);
+ const { nodeIdToSymbol, symbolToNodeId } = buildSymbolMaps(
+ layoutNodes,
+ definitions,
+ byShortName,
+ );
+ const service = root.lookupService('gnmi.gNMI');
+ const gnmiNamespace = root.lookup('gnmi');
+ if (!gnmiNamespace) {
+ throw new Error('Could not resolve gNMI namespace');
+ }
+ const serviceVersion = gnmiNamespace.options?.['(gnmi_service)'];
+ if (typeof serviceVersion !== 'string') {
+ throw new Error('Could not resolve gNMI service version');
+ }
+ const linesBySource: LinesBySource = {
+ gnmi: definitionLines(gnmiProto),
+ gnmi_ext: definitionLines(extProto),
+ };
+ const rpcLines = methodLines(gnmiProto);
+
+ const nodes: MapNode[] = layoutNodes.map((node) => {
+ if (node.id === 'service-gnmi') {
+ return serviceNode(node, service, linesBySource.gnmi.get('gnmi.gNMI'), serviceVersion, gnmiTag);
+ }
+ if (node.data.kind === 'rpc') {
+ return rpcNode(node, service, rpcLines, gnmiTag);
+ }
+
+ const symbol = nodeIdToSymbol.get(node.id);
+ if (symbol) {
+ const definition = definitions.get(symbol);
+ if (!definition) {
+ return node;
+ }
+ return schemaNode(
+ node,
+ definition,
+ symbol,
+ symbolToNodeId,
+ byShortName,
+ linesBySource,
+ gnmiTag,
+ );
+ }
+
+ return {
+ ...node,
+ style: { ...node.style },
+ data: {
+ ...node.data,
+ id: node.id,
+ specUrl: specUrlFromLayout(node),
+ },
+ };
+ });
+ const edges = buildEdges(nodes);
+ const source: MapSource = {
+ gnmiTag,
+ gnmiServiceVersion: serviceVersion,
+ gnmiBase: `${GNMI_GITHUB_BASE}/${gnmiTag}/proto/gnmi/gnmi.proto`,
+ extBase: `${GNMI_GITHUB_BASE}/${gnmiTag}/proto/gnmi_ext/gnmi_ext.proto`,
+ specBase: SPECBASE,
+ };
+
+ await fs.writeFile(OUTPUT_PATH, generatedSource({ nodes, edges, bounds: mapBounds, source }));
+ console.log(`Wrote ${OUTPUT_PATH} from openconfig/gnmi ${gnmiTag}`);
+}
+
+main().catch((error) => {
+ console.error(error);
+ process.exit(1);
+});
diff --git a/scripts/generate-pdf.ts b/scripts/generate-pdf.ts
new file mode 100644
index 0000000..c850cc9
--- /dev/null
+++ b/scripts/generate-pdf.ts
@@ -0,0 +1,598 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import PDFDocument from 'pdfkit';
+import {
+ getVisibleMap,
+ mapSource,
+ type MapEdgeKind,
+ type MapField,
+ type MapNode,
+ type MapNodeKind,
+} from '../src/gnmiMap';
+import {
+ computeReadableNodeLayout,
+ estimatedMapNodeHeight,
+ mapFieldRowHeight,
+ mapNodeWidth,
+ nodeBadgeHeight,
+ nodeBodyPadding,
+ nodeHeaderHeight,
+ routeReadableLayout,
+ type RoutedLayoutEdge,
+ type TargetHandleLayout,
+} from '../src/mapLayout';
+
+const outputPath = path.resolve('public/gnmi_0.10.0_map.pdf');
+const pageMargin = 56;
+const titleBandHeight = 84;
+const footerBandHeight = 44;
+const gridGap = 34;
+const edgeBendRadius = 18;
+const handleOffset = 5;
+const handleRadius = 4;
+
+const colors = {
+ background: '#f4f6f8',
+ panel: '#ffffff',
+ panelSoft: '#f8fafc',
+ border: '#b8c5d3',
+ text: '#172033',
+ muted: '#677486',
+ blue: '#0b4b8f',
+ blueSoft: '#d9eaf9',
+ rpc: '#1c62a0',
+ teal: '#16736b',
+ amber: '#a15c03',
+ amberSoft: '#fbebd3',
+ gray: '#4d5a6b',
+ focus: '#2474c9',
+ reserved: '#e7ebf0',
+ deprecated: '#fee2df',
+ deprecatedText: '#9b1c15',
+ handle: '#2d6f97',
+ grid: '#c4ced9',
+};
+
+type Point = {
+ x: number;
+ y: number;
+};
+
+type PdfContext = {
+ offset: Point;
+ connectedHandles: Set;
+};
+
+type EdgeStyle = {
+ color: string;
+ width: number;
+ dash?: [number, number];
+};
+
+const headerColors: Record = {
+ service: colors.blue,
+ rpc: colors.rpc,
+ enum: colors.amber,
+ external: colors.gray,
+ legend: colors.gray,
+ message: colors.teal,
+};
+
+const edgeStyles: Record = {
+ rpc: { color: '#0b4b8f', width: 2.2 },
+ field: { color: '#5b708a', width: 1.6 },
+ extension: { color: '#8a6a1f', width: 1.4, dash: [7, 6] },
+ 'extension-detail': { color: '#b47a18', width: 1.5 },
+};
+
+function nodeHeight(node: MapNode): number {
+ return estimatedMapNodeHeight(node);
+}
+
+function toPdfPoint(point: Point, context: PdfContext): Point {
+ return {
+ x: point.x + context.offset.x,
+ y: point.y + context.offset.y,
+ };
+}
+
+function trimTextToWidth(doc: PDFKit.PDFDocument, value: string, maxWidth: number): string {
+ if (doc.widthOfString(value) <= maxWidth) {
+ return value;
+ }
+
+ const suffix = '...';
+ let trimmed = value;
+ while (trimmed.length > 0 && doc.widthOfString(`${trimmed}${suffix}`) > maxWidth) {
+ trimmed = trimmed.slice(0, -1);
+ }
+
+ return trimmed ? `${trimmed}${suffix}` : suffix;
+}
+
+function fieldHandleKey(node: MapNode, field: MapField): string {
+ return `${node.id}:${field.id}`;
+}
+
+function handleFill(node: MapNode): string {
+ return node.data.kind === 'enum' ? colors.amber : colors.handle;
+}
+
+function drawArrow(
+ doc: PDFKit.PDFDocument,
+ x: number,
+ y: number,
+ angle: number,
+ color: string,
+): void {
+ const size = 8;
+ doc
+ .save()
+ .fillColor(color)
+ .moveTo(x, y)
+ .lineTo(
+ x - size * Math.cos(angle - Math.PI / 6),
+ y - size * Math.sin(angle - Math.PI / 6),
+ )
+ .lineTo(
+ x - size * Math.cos(angle + Math.PI / 6),
+ y - size * Math.sin(angle + Math.PI / 6),
+ )
+ .closePath()
+ .fill()
+ .restore();
+}
+
+function distance(first: Point, second: Point): number {
+ return Math.hypot(second.x - first.x, second.y - first.y);
+}
+
+function drawRoundedPath(
+ doc: PDFKit.PDFDocument,
+ points: Point[],
+ context: PdfContext,
+): void {
+ const pdfPoints = points.map((point) => toPdfPoint(point, context));
+ const [start] = pdfPoints;
+ doc.moveTo(start.x, start.y);
+
+ for (let index = 1; index < pdfPoints.length; index += 1) {
+ const previous = pdfPoints[index - 1];
+ const current = pdfPoints[index];
+ const next = pdfPoints[index + 1];
+
+ if (!next) {
+ doc.lineTo(current.x, current.y);
+ continue;
+ }
+
+ const incomingDistance = distance(previous, current);
+ const outgoingDistance = distance(current, next);
+ if (incomingDistance === 0 || outgoingDistance === 0) {
+ doc.lineTo(current.x, current.y);
+ continue;
+ }
+
+ const radius = Math.min(edgeBendRadius, incomingDistance / 2, outgoingDistance / 2);
+ const incomingUnit = {
+ x: (current.x - previous.x) / incomingDistance,
+ y: (current.y - previous.y) / incomingDistance,
+ };
+ const outgoingUnit = {
+ x: (next.x - current.x) / outgoingDistance,
+ y: (next.y - current.y) / outgoingDistance,
+ };
+ const beforeCorner = {
+ x: current.x - incomingUnit.x * radius,
+ y: current.y - incomingUnit.y * radius,
+ };
+ const afterCorner = {
+ x: current.x + outgoingUnit.x * radius,
+ y: current.y + outgoingUnit.y * radius,
+ };
+
+ doc
+ .lineTo(beforeCorner.x, beforeCorner.y)
+ .quadraticCurveTo(current.x, current.y, afterCorner.x, afterCorner.y);
+ }
+}
+
+function drawEdge(
+ doc: PDFKit.PDFDocument,
+ routedEdge: RoutedLayoutEdge,
+ context: PdfContext,
+): void {
+ if (routedEdge.routePoints.length < 2) {
+ return;
+ }
+
+ const edge = routedEdge.edge;
+ const style = edgeStyles[edge.kind] ?? edgeStyles.field;
+ const points = routedEdge.routePoints;
+ const end = toPdfPoint(points[points.length - 1], context);
+ const beforeEnd = toPdfPoint(points[points.length - 2], context);
+
+ doc
+ .save()
+ .lineCap('round')
+ .lineJoin('round')
+ .lineWidth(style.width + 3)
+ .strokeColor('#ffffff')
+ .opacity(0.72);
+ drawRoundedPath(doc, points, context);
+ doc.stroke().restore();
+
+ doc
+ .save()
+ .lineCap('round')
+ .lineJoin('round')
+ .lineWidth(style.width)
+ .strokeColor(style.color);
+ if (style.dash) {
+ doc.dash(style.dash[0], { space: style.dash[1] });
+ }
+ drawRoundedPath(doc, points, context);
+ doc.stroke().undash().restore();
+
+ drawArrow(doc, end.x, end.y, Math.atan2(end.y - beforeEnd.y, end.x - beforeEnd.x), style.color);
+}
+
+function drawBadge(
+ doc: PDFKit.PDFDocument,
+ text: string,
+ x: number,
+ y: number,
+ color: string,
+ fill: string,
+): number {
+ doc.font('Helvetica-Bold').fontSize(6.8);
+ const width = Math.max(46, doc.widthOfString(text.toUpperCase()) + 12);
+ doc
+ .save()
+ .fillColor(fill)
+ .roundedRect(x, y, width, 18, 9)
+ .fill()
+ .fillColor(color)
+ .text(text.toUpperCase(), x + 6, y + 6, { lineBreak: false })
+ .restore();
+
+ return width;
+}
+
+function drawHandle(doc: PDFKit.PDFDocument, x: number, y: number, fill: string): void {
+ doc
+ .save()
+ .circle(x, y, handleRadius)
+ .fillColor(fill)
+ .fill()
+ .circle(x, y, handleRadius)
+ .lineWidth(2)
+ .strokeColor('#ffffff')
+ .stroke()
+ .restore();
+}
+
+function drawHeaderLinks(doc: PDFKit.PDFDocument, node: MapNode, x: number, y: number): void {
+ let linkX = x + mapNodeWidth(node) - 54;
+
+ if (node.data.protoUrl) {
+ doc
+ .save()
+ .fillOpacity(0.14)
+ .fillColor('#ffffff')
+ .roundedRect(linkX, y + 7, 24, 24, 6)
+ .fill()
+ .fillOpacity(1)
+ .font('Helvetica-Bold')
+ .fontSize(8)
+ .fillColor('#ffffff')
+ .text('P', linkX + 9, y + 15, { lineBreak: false })
+ .restore();
+ doc.link(linkX, y + 7, 24, 24, node.data.protoUrl);
+ linkX += 29;
+ }
+
+ if (node.data.specUrl) {
+ doc
+ .save()
+ .fillOpacity(0.14)
+ .fillColor('#ffffff')
+ .roundedRect(linkX, y + 7, 24, 24, 6)
+ .fill()
+ .fillOpacity(1)
+ .font('Helvetica-Bold')
+ .fontSize(8)
+ .fillColor('#ffffff')
+ .text('D', linkX + 9, y + 15, { lineBreak: false })
+ .restore();
+ doc.link(linkX, y + 7, 24, 24, node.data.specUrl);
+ }
+}
+
+function drawFieldRow(
+ doc: PDFKit.PDFDocument,
+ node: MapNode,
+ field: MapField,
+ rowIndex: number,
+ x: number,
+ y: number,
+ width: number,
+ context: PdfContext,
+): void {
+ const rowHeight = mapFieldRowHeight(field);
+ const rowRadius = 6;
+ const nameX = x + Math.floor(width * 0.46);
+ const rowRightPadding = context.connectedHandles.has(fieldHandleKey(node, field)) ? 30 : 18;
+ const typeMaxWidth = nameX - x - 26;
+ const nameMaxWidth = width - (nameX - x) - rowRightPadding;
+
+ if (rowIndex % 2 === 0) {
+ doc
+ .save()
+ .fillColor(colors.panelSoft)
+ .roundedRect(x + 8, y, width - 16, rowHeight - 2, rowRadius)
+ .fill()
+ .restore();
+ }
+
+ doc
+ .font('Helvetica-Bold')
+ .fontSize(8.5)
+ .fillColor('#526071')
+ .text(trimTextToWidth(doc, field.type, typeMaxWidth), x + 14, y + 8, {
+ width: typeMaxWidth,
+ lineBreak: false,
+ });
+
+ doc
+ .font('Courier-Bold')
+ .fontSize(8.5)
+ .fillColor(colors.text)
+ .text(trimTextToWidth(doc, field.name, nameMaxWidth), nameX, y + 8, {
+ width: nameMaxWidth,
+ lineBreak: false,
+ });
+
+ if (field.badge === 'deprecated') {
+ doc
+ .save()
+ .moveTo(x + 14, y + 15)
+ .lineTo(nameX + Math.min(nameMaxWidth, doc.widthOfString(field.name)), y + 15)
+ .lineWidth(0.6)
+ .strokeColor(colors.deprecatedText)
+ .opacity(0.55)
+ .stroke()
+ .restore();
+ }
+
+ if (field.group || field.badge) {
+ const detailY = y + 28;
+ let badgeX = x + 14;
+ if (field.group) {
+ badgeX += drawBadge(doc, field.group, badgeX, detailY, '#164675', colors.blueSoft) + 6;
+ }
+
+ if (field.badge) {
+ const fill = field.badge === 'reserved' ? colors.reserved : colors.deprecated;
+ const textColor = field.badge === 'reserved' ? colors.gray : colors.deprecatedText;
+ drawBadge(doc, field.badge, badgeX, detailY, textColor, fill);
+ }
+ }
+
+ if (context.connectedHandles.has(fieldHandleKey(node, field))) {
+ drawHandle(doc, x + width + handleOffset, y + rowHeight / 2, handleFill(node));
+ }
+}
+
+function drawNode(doc: PDFKit.PDFDocument, node: MapNode, context: PdfContext): void {
+ const origin = toPdfPoint(node.position, context);
+ const x = origin.x;
+ const y = origin.y;
+ const width = mapNodeWidth(node);
+ const height = nodeHeight(node);
+ const fields = node.data.fields ?? [];
+ const headerColor = headerColors[node.data.kind] ?? colors.teal;
+
+ doc
+ .save()
+ .fillOpacity(0.09)
+ .fillColor('#1f2937')
+ .roundedRect(x, y + 8, width, height, 8)
+ .fill()
+ .restore();
+
+ doc
+ .save()
+ .fillColor(colors.panel)
+ .roundedRect(x, y, width, height, 8)
+ .fill()
+ .lineWidth(1)
+ .strokeColor(colors.border)
+ .roundedRect(x, y, width, height, 8)
+ .stroke()
+ .restore();
+
+ doc
+ .save()
+ .fillColor(headerColor)
+ .roundedRect(x, y, width, nodeHeaderHeight, 8)
+ .fill()
+ .rect(x, y + nodeHeaderHeight - 8, width, 8)
+ .fill()
+ .restore();
+
+ const kindLabel = node.data.kind.toUpperCase();
+ doc.font('Helvetica-Bold').fontSize(6.8);
+ const kindPillWidth = Math.max(34, doc.widthOfString(kindLabel) + 12);
+ doc
+ .save()
+ .fillOpacity(0.16)
+ .fillColor('#ffffff')
+ .roundedRect(x + 10, y + 10, kindPillWidth, 16, 8)
+ .fill()
+ .fillOpacity(1)
+ .fillColor('#ffffff')
+ .text(kindLabel, x + 16, y + 15, { lineBreak: false })
+ .restore();
+
+ const titleX = x + 10 + kindPillWidth + 8;
+ const titleWidth = width - (titleX - x) - 70;
+ doc
+ .font('Helvetica-Bold')
+ .fontSize(13)
+ .fillColor('#ffffff')
+ .text(trimTextToWidth(doc, node.data.label, titleWidth), titleX, y + 11, {
+ width: titleWidth,
+ lineBreak: false,
+ });
+
+ drawHeaderLinks(doc, node, x, y);
+
+ let rowY = y + nodeHeaderHeight + nodeBodyPadding;
+
+ if (node.data.badges?.length) {
+ let badgeX = x + 10;
+ for (const badge of node.data.badges) {
+ badgeX += drawBadge(doc, badge, badgeX, rowY + 2, colors.deprecatedText, colors.deprecated) + 6;
+ }
+ rowY += nodeBadgeHeight;
+ }
+
+ if (!fields.length) {
+ doc
+ .font('Helvetica-Oblique')
+ .fontSize(10)
+ .fillColor(colors.muted)
+ .text('empty message', x + 12, rowY + 8, { lineBreak: false });
+ } else {
+ fields.forEach((field, index) => {
+ drawFieldRow(doc, node, field, index, x, rowY, width, context);
+ rowY += mapFieldRowHeight(field);
+ });
+ }
+
+ const targetHandles = targetHandlesFromData(node);
+ if (targetHandles.length) {
+ for (const handle of targetHandles) {
+ drawHandle(doc, x - handleOffset, y + handle.y, handleFill(node));
+ }
+ } else {
+ drawHandle(doc, x - handleOffset, y + height / 2, handleFill(node));
+ }
+}
+
+function targetHandlesFromData(node: MapNode): TargetHandleLayout[] {
+ return Array.isArray(node.data.targetHandles)
+ ? (node.data.targetHandles as TargetHandleLayout[])
+ : [];
+}
+
+function drawCanvasBackground(doc: PDFKit.PDFDocument, pageWidth: number, pageHeight: number): void {
+ doc.rect(0, 0, pageWidth, pageHeight).fill(colors.background);
+
+ doc
+ .save()
+ .fillOpacity(0.04)
+ .fillColor(colors.blue)
+ .rect(0, 0, pageWidth * 0.36, pageHeight)
+ .fill()
+ .fillColor(colors.teal)
+ .rect(0, pageHeight * 0.58, pageWidth, pageHeight * 0.42)
+ .fill()
+ .restore();
+
+ doc.save().fillColor(colors.grid).fillOpacity(0.62);
+ for (let x = gridGap; x < pageWidth; x += gridGap) {
+ for (let y = gridGap; y < pageHeight; y += gridGap) {
+ doc.circle(x, y, 0.55).fill();
+ }
+ }
+ doc.restore();
+}
+
+function drawTitle(doc: PDFKit.PDFDocument, pageWidth: number): void {
+ doc
+ .font('Helvetica-Bold')
+ .fontSize(26)
+ .fillColor(colors.text)
+ .text(`gNMI service ${mapSource.gnmiServiceVersion} map`, pageMargin, 18, {
+ width: pageWidth - pageMargin * 2,
+ lineBreak: false,
+ });
+ doc
+ .font('Helvetica')
+ .fontSize(12)
+ .fillColor(colors.muted)
+ .text(`Generated from openconfig/gnmi ${mapSource.gnmiTag} protobuf IDL`, pageMargin, 48, {
+ width: pageWidth - pageMargin * 2,
+ lineBreak: false,
+ });
+}
+
+async function main() {
+ const { nodes, edges: pdfEdges } = getVisibleMap({
+ showDeprecated: false,
+ showExtensions: true,
+ });
+ const layoutNodes = await computeReadableNodeLayout(nodes, pdfEdges);
+ const layout = routeReadableLayout(layoutNodes, pdfEdges);
+ const pdfNodes = layout.nodes;
+ const bounds = layout.bounds;
+ const pageWidth = bounds.width + pageMargin * 2;
+ const pageHeight = bounds.height + titleBandHeight + footerBandHeight;
+ const context: PdfContext = {
+ offset: {
+ x: pageMargin - bounds.x,
+ y: titleBandHeight - bounds.y,
+ },
+ connectedHandles: new Set(pdfEdges.map((edge) => `${edge.source}:${edge.sourceHandle}`)),
+ };
+ const doc = new PDFDocument({
+ autoFirstPage: false,
+ compress: true,
+ info: {
+ Title: `gNMI service ${mapSource.gnmiServiceVersion} React Flow Map`,
+ Author: 'gnmi-map',
+ Subject: `Generated from openconfig/gnmi ${mapSource.gnmiTag} protobuf IDL`,
+ Keywords: 'gNMI, OpenConfig, React Flow, protobuf',
+ },
+ });
+
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
+
+ const stream = fs.createWriteStream(outputPath);
+ doc.pipe(stream);
+ doc.addPage({ size: [pageWidth, pageHeight], margin: 0 });
+
+ drawCanvasBackground(doc, pageWidth, pageHeight);
+ drawTitle(doc, pageWidth);
+
+ for (const edge of layout.edges) {
+ drawEdge(doc, edge, context);
+ }
+
+ for (const node of pdfNodes) {
+ drawNode(doc, node, context);
+ }
+
+ doc
+ .font('Helvetica')
+ .fontSize(10)
+ .fillColor(colors.muted)
+ .text('P = proto definition, D = specification documentation', pageMargin, pageHeight - 30, {
+ lineBreak: false,
+ });
+
+ doc.end();
+ await new Promise((resolve, reject) => {
+ stream.on('finish', resolve);
+ stream.on('error', reject);
+ });
+
+ console.log(`Wrote ${outputPath}`);
+}
+
+main().catch((error) => {
+ console.error(error);
+ process.exit(1);
+});
diff --git a/scripts/validate-map.ts b/scripts/validate-map.ts
new file mode 100644
index 0000000..2e18fad
--- /dev/null
+++ b/scripts/validate-map.ts
@@ -0,0 +1,147 @@
+import {
+ getVisibleMap,
+ mapEdges,
+ mapNodes,
+ mapSource,
+ type MapEdge,
+ type MapNode,
+} from '../src/gnmiMap';
+import {
+ computeReadableNodeLayout,
+ estimatedMapNodeHeight,
+ mapNodeWidth,
+ routeIntersectsNode,
+ routeReadableLayout,
+} from '../src/mapLayout';
+
+function assert(condition: unknown, message: string): asserts condition {
+ if (!condition) {
+ throw new Error(message);
+ }
+}
+
+function validateEdges(nodes: MapNode[], edges: MapEdge[], label: string): void {
+ const nodeIds = new Set(nodes.map((node) => node.id));
+ const handles = new Set(
+ nodes.flatMap((node) => (node.data.fields ?? []).map((field) => `${node.id}:${field.id}`)),
+ );
+
+ for (const edge of edges) {
+ assert(nodeIds.has(edge.source), `${label}: edge ${edge.id} has missing source ${edge.source}`);
+ assert(nodeIds.has(edge.target), `${label}: edge ${edge.id} has missing target ${edge.target}`);
+ assert(
+ handles.has(`${edge.source}:${edge.sourceHandle}`),
+ `${label}: edge ${edge.id} has missing source handle ${edge.sourceHandle}`,
+ );
+ }
+}
+
+function validateLinks(): void {
+ for (const node of mapNodes) {
+ if (node.data.protoUrl?.startsWith('https://github.com/openconfig/gnmi/blob/')) {
+ const validProtoBase =
+ node.data.protoUrl.startsWith(mapSource.gnmiBase) ||
+ node.data.protoUrl.startsWith(mapSource.extBase);
+ assert(validProtoBase, `${node.id}: protoUrl does not use configured proto bases`);
+ }
+
+ if (node.data.specUrl) {
+ assert(
+ node.data.specUrl.startsWith(mapSource.specBase),
+ `${node.id}: specUrl does not use configured spec base`,
+ );
+ }
+ }
+}
+
+function validateDeprecatedVisibility(): void {
+ const rawSubscribeResponse = mapNodes.find((node) => node.id === 'subscribe-response');
+ assert(rawSubscribeResponse, 'raw map is missing SubscribeResponse');
+ const rawErrorField = rawSubscribeResponse.data.fields?.find((field) => field.name === 'error');
+ assert(rawErrorField?.deprecated, 'raw SubscribeResponse.error must be preserved as deprecated');
+
+ const defaultMap = getVisibleMap();
+ const defaultSubscribeResponse = defaultMap.nodes.find((node) => node.id === 'subscribe-response');
+ assert(defaultSubscribeResponse, 'default map is missing SubscribeResponse');
+ assert(
+ !(defaultSubscribeResponse.data.fields?.some((field) => field.name === 'error') ?? false),
+ 'default SubscribeResponse must hide deprecated error field',
+ );
+ assert(
+ defaultMap.nodes.every((node) => !node.data.deprecated),
+ 'default map must hide deprecated nodes',
+ );
+ assert(
+ defaultMap.nodes.every((node) => (node.data.fields ?? []).every((field) => !field.deprecated)),
+ 'default map must hide deprecated fields',
+ );
+
+ const deprecatedMap = getVisibleMap({ showDeprecated: true });
+ const deprecatedSubscribeResponse = deprecatedMap.nodes.find(
+ (node) => node.id === 'subscribe-response',
+ );
+ assert(
+ deprecatedSubscribeResponse?.data.fields?.some((field) => field.name === 'error'),
+ 'deprecated map must include SubscribeResponse.error',
+ );
+}
+
+async function validateReadableLayout(nodes: MapNode[], edges: MapEdge[], label: string): Promise {
+ const layoutNodes = await computeReadableNodeLayout(nodes, edges);
+ const layout = routeReadableLayout(layoutNodes, edges);
+
+ for (let firstIndex = 0; firstIndex < layout.nodes.length; firstIndex += 1) {
+ const first = layout.nodes[firstIndex];
+ for (let secondIndex = firstIndex + 1; secondIndex < layout.nodes.length; secondIndex += 1) {
+ const second = layout.nodes[secondIndex];
+ assert(!nodesOverlap(first, second), `${label}: nodes ${first.id} and ${second.id} overlap`);
+ }
+ }
+
+ for (const routedEdge of layout.edges) {
+ for (const node of layout.nodes) {
+ if (node.id === routedEdge.edge.source || node.id === routedEdge.edge.target) {
+ continue;
+ }
+
+ assert(
+ !routeIntersectsNode(routedEdge.routePoints, node),
+ `${label}: edge ${routedEdge.edge.id} intersects node ${node.id}`,
+ );
+ }
+ }
+}
+
+function nodesOverlap(first: MapNode, second: MapNode): boolean {
+ return (
+ first.position.x < second.position.x + mapNodeWidth(second) &&
+ first.position.x + mapNodeWidth(first) > second.position.x &&
+ first.position.y < second.position.y + estimatedMapNodeHeight(second) &&
+ first.position.y + estimatedMapNodeHeight(first) > second.position.y
+ );
+}
+
+async function main(): Promise {
+ const appDefaultMap = getVisibleMap({ showExtensions: false });
+ const extensionMap = getVisibleMap({ showExtensions: true });
+ const deprecatedMap = getVisibleMap({ showDeprecated: true, showExtensions: false });
+ const fullMap = getVisibleMap({ showDeprecated: true, showExtensions: true });
+
+ validateEdges(mapNodes, mapEdges, 'raw map');
+ validateEdges(appDefaultMap.nodes, appDefaultMap.edges, 'default map');
+ validateEdges(deprecatedMap.nodes, deprecatedMap.edges, 'deprecated map');
+ validateLinks();
+ validateDeprecatedVisibility();
+
+ await validateReadableLayout(appDefaultMap.nodes, appDefaultMap.edges, 'default layout');
+ await validateReadableLayout(extensionMap.nodes, extensionMap.edges, 'extension layout');
+ await validateReadableLayout(deprecatedMap.nodes, deprecatedMap.edges, 'deprecated layout');
+ await validateReadableLayout(fullMap.nodes, fullMap.edges, 'full layout');
+
+ console.log('Map data is valid');
+}
+
+main().catch((error) => {
+ console.error(error);
+ process.exit(1);
+});
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000..05f3428
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,1015 @@
+import { type CSSProperties, useCallback, useEffect, useMemo, useState } from 'react';
+import {
+ BaseEdge,
+ type NodeChange,
+ Background,
+ Controls,
+ type Edge,
+ type EdgeProps,
+ type EdgeTypes,
+ Handle,
+ MarkerType,
+ type NodeProps,
+ type NodeTypes,
+ Position,
+ ReactFlow,
+ ReactFlowProvider,
+ useReactFlow,
+} from '@xyflow/react';
+import '@xyflow/react/dist/style.css';
+import {
+ BookOpen,
+ ExternalLink,
+ EyeOff,
+ FileCode2,
+ FileDown,
+ Focus,
+ GitBranch,
+ RotateCcw,
+ Search,
+} from 'lucide-react';
+import {
+ getVisibleMap,
+ type MapEdge,
+ type MapEdgeKind,
+ type MapField,
+ type MapNode,
+ mapSource,
+} from './gnmiMap';
+import {
+ applyManualPositions,
+ computeReadableNodeLayout,
+ improveNodeLayout,
+ routeReadableLayout,
+ type RoutePoint,
+ type TargetHandleLayout,
+} from './mapLayout';
+
+const edgeStyleByKind: Record = {
+ rpc: { stroke: '#0b4b8f', strokeWidth: 2.2 },
+ field: { stroke: '#5b708a', strokeWidth: 1.6 },
+ extension: {
+ stroke: '#8a6a1f',
+ strokeWidth: 1.4,
+ strokeDasharray: '7 6',
+ },
+ 'extension-detail': { stroke: '#b47a18', strokeWidth: 1.5 },
+};
+
+const nodeTypes: NodeTypes = {
+ schema: SchemaNode,
+};
+
+const edgeTypes: EdgeTypes = {
+ routed: RoutedEdge,
+};
+
+const pdfMapUrl = `${import.meta.env.BASE_URL}gnmi_0.10.0_map.pdf`;
+
+type NodePosition = {
+ x: number;
+ y: number;
+};
+
+type RoutedEdgeData = Record & {
+ routePoints: RoutePoint[];
+ routeBridges: RouteBridge[];
+};
+
+type RoutedMapEdge = Edge & {
+ sourceHandle: string;
+ targetHandle: string;
+ kind: MapEdgeKind;
+ deprecated: boolean;
+};
+
+type FieldConnectionIds = Record;
+type FieldClickHandler = (edgeId: string) => void;
+
+type RouteBridge = RoutePoint & {
+ orientation: 'horizontal' | 'vertical';
+};
+
+type RouteSegment = {
+ edgeId: string;
+ index: number;
+ start: RoutePoint;
+ end: RoutePoint;
+ orientation: 'horizontal' | 'vertical';
+ fixed: number;
+ from: number;
+ to: number;
+};
+
+function searchableText(node: MapNode): string {
+ const fieldText = node.data.fields
+ ?.map((field) => `${field.type} ${field.name} ${field.group ?? ''} ${field.badge ?? ''}`)
+ .join(' ');
+
+ return `${node.data.kind} ${node.data.label} ${fieldText ?? ''}`.toLowerCase();
+}
+
+function fieldMatches(field: MapField, query: string): boolean {
+ if (!query) {
+ return false;
+ }
+
+ return `${field.type} ${field.name} ${field.group ?? ''} ${field.badge ?? ''}`
+ .toLowerCase()
+ .includes(query);
+}
+
+function AppShell() {
+ const { fitView } = useReactFlow();
+ const [queryValue, setQueryValue] = useState('');
+ const [showExtensions, setShowExtensions] = useState(false);
+ const [showDeprecated, setShowDeprecated] = useState(false);
+ const [selectedId, setSelectedId] = useState(null);
+ const [selectedEdgeId, setSelectedEdgeId] = useState(null);
+ const [manualPositions, setManualPositions] = useState>({});
+ const [routingPositions, setRoutingPositions] = useState>({});
+
+ const query = queryValue.trim().toLowerCase();
+ const visibleMap = useMemo(
+ () => getVisibleMap({ showDeprecated, showExtensions }),
+ [showDeprecated, showExtensions],
+ );
+ const fallbackLayoutNodes = useMemo(() => improveNodeLayout(visibleMap.nodes), [visibleMap.nodes]);
+ const [elkLayoutNodes, setElkLayoutNodes] = useState(null);
+
+ useEffect(() => {
+ let cancelled = false;
+ setElkLayoutNodes(null);
+
+ computeReadableNodeLayout(visibleMap.nodes, visibleMap.edges)
+ .then((layoutNodes) => {
+ if (!cancelled) {
+ setElkLayoutNodes(layoutNodes);
+ }
+ })
+ .catch((error) => {
+ console.error('Failed to compute readable map layout', error);
+ });
+
+ return () => {
+ cancelled = true;
+ };
+ }, [visibleMap.edges, visibleMap.nodes]);
+
+ const layoutNodes = elkLayoutNodes ?? fallbackLayoutNodes;
+ useEffect(() => {
+ if (!elkLayoutNodes) {
+ return;
+ }
+
+ let secondFrame: number | null = null;
+ const firstFrame = window.requestAnimationFrame(() => {
+ secondFrame = window.requestAnimationFrame(() => fitView({ padding: 0.1, duration: 350 }));
+ });
+
+ return () => {
+ window.cancelAnimationFrame(firstFrame);
+ if (secondFrame !== null) {
+ window.cancelAnimationFrame(secondFrame);
+ }
+ };
+ }, [elkLayoutNodes, fitView]);
+
+ const routedLayoutNodes = useMemo(
+ () => applyManualPositions(layoutNodes, routingPositions),
+ [layoutNodes, routingPositions],
+ );
+ const readableLayout = useMemo(
+ () => routeReadableLayout(routedLayoutNodes, visibleMap.edges),
+ [routedLayoutNodes, visibleMap.edges],
+ );
+ const displayedLayoutNodes = useMemo(
+ () => applyManualPositions(readableLayout.nodes, manualPositions),
+ [manualPositions, readableLayout.nodes],
+ );
+
+ const nodeMatches = useMemo(() => {
+ if (!query) {
+ return new Set();
+ }
+
+ return new Set(
+ visibleMap.nodes
+ .filter((currentNode) => searchableText(currentNode).includes(query))
+ .map((currentNode) => currentNode.id),
+ );
+ }, [query, visibleMap.nodes]);
+
+ const selectedEdge = useMemo(
+ () => visibleMap.edges.find((edge) => edge.id === selectedEdgeId) ?? null,
+ [selectedEdgeId, visibleMap.edges],
+ );
+ const selectedEdgeEndpointIds = useMemo(
+ () =>
+ selectedEdge ? new Set([selectedEdge.source, selectedEdge.target]) : new Set(),
+ [selectedEdge],
+ );
+ const edgeIdBySourceHandle = useMemo(
+ () =>
+ new Map(
+ visibleMap.edges.map((edge) => [`${edge.source}:${edge.sourceHandle}`, edge.id] as const),
+ ),
+ [visibleMap.edges],
+ );
+ const selectFieldConnection = useCallback((edgeId: string) => {
+ setSelectedId(null);
+ setSelectedEdgeId(edgeId);
+ }, []);
+
+ const nodes = useMemo(
+ () =>
+ displayedLayoutNodes.map((currentNode) => {
+ const active = !query || nodeMatches.has(currentNode.id);
+ const edgeEndpoint = selectedEdgeEndpointIds.has(currentNode.id);
+ const fieldConnectionIds = Object.fromEntries(
+ (currentNode.data.fields ?? [])
+ .map((field) => [
+ field.id,
+ edgeIdBySourceHandle.get(`${currentNode.id}:${field.id}`),
+ ])
+ .filter((entry): entry is [string, string] => Boolean(entry[1])),
+ );
+
+ return {
+ ...currentNode,
+ selected: selectedId === currentNode.id,
+ data: {
+ ...currentNode.data,
+ active: active || edgeEndpoint,
+ edgeEndpoint,
+ activeEdgeSourceHandle:
+ selectedEdge?.source === currentNode.id ? selectedEdge.sourceHandle : null,
+ fieldConnectionIds,
+ onFieldConnectionClick: selectFieldConnection,
+ query,
+ showExtensions,
+ },
+ };
+ }),
+ [
+ nodeMatches,
+ displayedLayoutNodes,
+ edgeIdBySourceHandle,
+ query,
+ selectFieldConnection,
+ selectedEdge,
+ selectedEdgeEndpointIds,
+ selectedId,
+ showExtensions,
+ ],
+ );
+
+ const routeBridgesByEdge = useMemo(
+ () => routeBridges(readableLayout.edges),
+ [readableLayout.edges],
+ );
+
+ const edges = useMemo(
+ () =>
+ readableLayout.edges.map(({ edge, routePoints, targetHandle }) => {
+ const connectedToMatch =
+ !query || nodeMatches.has(edge.source) || nodeMatches.has(edge.target);
+ const style = edgeStyleByKind[edge.kind] ?? edgeStyleByKind.field;
+ const selected = selectedEdge?.id === edge.id;
+ const selectionDimmed = Boolean(selectedEdge) && !selected;
+ const opacity = connectedToMatch ? (selectionDimmed ? 0.24 : 1) : 0.14;
+ const stroke = selected ? '#d21f3c' : style.stroke;
+ const strokeWidth =
+ typeof style.strokeWidth === 'number'
+ ? style.strokeWidth + (selected ? 1.8 : 0)
+ : style.strokeWidth;
+
+ return {
+ ...edge,
+ type: 'routed',
+ targetHandle,
+ selected,
+ zIndex: selected ? 12 : 1,
+ interactionWidth: 28,
+ className: [
+ 'flow-edge',
+ `flow-edge-${edge.kind}`,
+ selected ? 'is-selected' : '',
+ selectionDimmed ? 'is-dimmed' : '',
+ ]
+ .filter(Boolean)
+ .join(' '),
+ markerEnd: { type: MarkerType.ArrowClosed, color: stroke },
+ animated: selected || (query ? connectedToMatch : edge.kind === 'rpc'),
+ data: { routePoints, routeBridges: routeBridgesByEdge.get(edge.id) ?? [] },
+ style: {
+ ...style,
+ opacity,
+ stroke,
+ strokeWidth,
+ },
+ } satisfies RoutedMapEdge;
+ }),
+ [nodeMatches, query, readableLayout.edges, routeBridgesByEdge, selectedEdge],
+ );
+
+ const selectedNode = useMemo(
+ () => nodes.find((currentNode) => currentNode.id === selectedId),
+ [nodes, selectedId],
+ );
+
+ const fit = useCallback(() => {
+ fitView({ padding: 0.12, duration: 450 });
+ }, [fitView]);
+
+ const onNodesChange = useCallback((changes: NodeChange[]) => {
+ setManualPositions((currentPositions) => {
+ let nextPositions = currentPositions;
+
+ for (const change of changes) {
+ if (change.type !== 'position' || !change.position) {
+ continue;
+ }
+
+ if (nextPositions === currentPositions) {
+ nextPositions = { ...currentPositions };
+ }
+
+ nextPositions[change.id] = change.position;
+ }
+
+ return nextPositions;
+ });
+
+ setRoutingPositions((currentPositions) => {
+ let nextPositions = currentPositions;
+
+ for (const change of changes) {
+ if (change.type !== 'position' || !change.position || change.dragging !== false) {
+ continue;
+ }
+
+ if (nextPositions === currentPositions) {
+ nextPositions = { ...currentPositions };
+ }
+
+ nextPositions[change.id] = change.position;
+ }
+
+ return nextPositions;
+ });
+
+ setSelectedId((currentSelectedId) => {
+ let selectedNodeWasCleared = false;
+
+ for (const change of changes) {
+ if (change.type !== 'select') {
+ continue;
+ }
+
+ if (change.selected) {
+ return change.id;
+ }
+
+ if (change.id === currentSelectedId) {
+ selectedNodeWasCleared = true;
+ }
+ }
+
+ return selectedNodeWasCleared ? null : currentSelectedId;
+ });
+ }, []);
+
+ const resetLayout = useCallback(() => {
+ setManualPositions({});
+ setRoutingPositions({});
+ window.requestAnimationFrame(() => fitView({ padding: 0.12, duration: 450 }));
+ }, [fitView]);
+
+ const hasManualPositions = Object.keys(manualPositions).length > 0;
+
+ return (
+
+
+
+
+
+ nodes={nodes}
+ edges={edges}
+ nodeTypes={nodeTypes}
+ edgeTypes={edgeTypes}
+ onNodesChange={onNodesChange}
+ onNodeDragStop={(_, node) => {
+ setRoutingPositions((currentPositions) => ({
+ ...currentPositions,
+ [node.id]: node.position,
+ }));
+ }}
+ nodesDraggable
+ minZoom={0.18}
+ maxZoom={1.7}
+ defaultViewport={{ x: 70, y: 40, zoom: 0.42 }}
+ fitView
+ fitViewOptions={{ padding: 0.08 }}
+ onNodeClick={(_, node) => {
+ setSelectedId(node.id);
+ setSelectedEdgeId(null);
+ }}
+ onEdgeClick={(event, edge) => {
+ event.stopPropagation();
+ setSelectedId(null);
+ setSelectedEdgeId(edge.id);
+ }}
+ onPaneClick={() => {
+ setSelectedId(null);
+ setSelectedEdgeId(null);
+ }}
+ proOptions={{ hideAttribution: true }}
+ >
+
+
+
+
+
+
+
+ );
+}
+
+function SchemaNode({ data, selected }: NodeProps) {
+ const fields = data.fields ?? [];
+ const dimmed = data.active === false;
+ const targetHandles = targetHandlesFromData(data);
+ const fieldConnectionIds = fieldConnectionIdsFromData(data);
+ const onFieldConnectionClick = fieldClickHandlerFromData(data);
+ const activeEdgeSourceHandle =
+ typeof data.activeEdgeSourceHandle === 'string' ? data.activeEdgeSourceHandle : null;
+ const className = [
+ 'schema-node',
+ `kind-${data.kind}`,
+ selected ? 'is-selected' : '',
+ data.edgeEndpoint ? 'is-edge-endpoint' : '',
+ dimmed ? 'is-dimmed' : '',
+ ]
+ .filter(Boolean)
+ .join(' ');
+
+ return (
+
+ {targetHandles.length ? (
+ targetHandles.map((handle) => (
+
+ ))
+ ) : (
+
+ )}
+
+
+
+ {data.badges?.length ? (
+
+ {data.badges.map((badge) => (
+ {badge}
+ ))}
+
+ ) : null}
+
+
+ {fields.length ? (
+ fields.map((field) => (
+
+ ))
+ ) : (
+
empty message
+ )}
+
+
+ );
+}
+
+type FieldRowProps = {
+ field: MapField;
+ highlighted: boolean;
+ edgeHighlighted: boolean;
+ connectionEdgeId?: string;
+ onConnectionClick?: FieldClickHandler;
+ showExtensions?: boolean;
+};
+
+function FieldRow({
+ field,
+ highlighted,
+ edgeHighlighted,
+ connectionEdgeId,
+ onConnectionClick,
+ showExtensions,
+}: FieldRowProps) {
+ const isExtension = field.ref === 'extension';
+ const visibleExtensionHandle = !isExtension || showExtensions;
+ const clickable = Boolean(connectionEdgeId && onConnectionClick);
+ const selectConnection = () => {
+ if (connectionEdgeId && onConnectionClick) {
+ onConnectionClick(connectionEdgeId);
+ }
+ };
+
+ return (
+ ${field.ref}` : undefined}
+ aria-label={clickable ? `Highlight ${field.name} connection to ${field.ref}` : undefined}
+ onClick={(event) => {
+ if (!clickable) {
+ return;
+ }
+ event.stopPropagation();
+ selectConnection();
+ }}
+ onKeyDown={(event) => {
+ if (!clickable || (event.key !== 'Enter' && event.key !== ' ')) {
+ return;
+ }
+ event.preventDefault();
+ event.stopPropagation();
+ selectConnection();
+ }}
+ >
+ {field.type}
+ {field.name}
+ {field.group ? {field.group} : null}
+ {field.badge ? {field.badge} : null}
+ {field.ref && visibleExtensionHandle ? (
+ ${field.ref}`}
+ />
+ ) : null}
+
+ );
+}
+
+function RoutedEdge({
+ id,
+ sourceX,
+ sourceY,
+ targetX,
+ targetY,
+ data,
+ markerEnd,
+ style,
+ interactionWidth,
+}: EdgeProps) {
+ const routePoints =
+ data?.routePoints?.length && data.routePoints.length > 1
+ ? data.routePoints
+ : [
+ { x: sourceX, y: sourceY },
+ { x: targetX, y: targetY },
+ ];
+ const routeBridges = data?.routeBridges ?? [];
+ const stroke = typeof style?.stroke === 'string' ? style.stroke : '#5b708a';
+ const strokeWidth = typeof style?.strokeWidth === 'number' ? style.strokeWidth : 1.6;
+ const path = roundedRoutePath(routePoints);
+
+ return (
+ <>
+
+
+ {routeBridges.map((bridge, index) => {
+ const path = bridgePath(bridge);
+
+ return (
+
+
+
+
+ );
+ })}
+ >
+ );
+}
+
+function roundedRoutePath(points: RoutePoint[], radius = 18): string {
+ if (!points.length) {
+ return '';
+ }
+
+ const [start] = points;
+ const commands = [`M ${start.x} ${start.y}`];
+
+ for (let index = 1; index < points.length; index += 1) {
+ const previous = points[index - 1];
+ const current = points[index];
+ const next = points[index + 1];
+
+ if (!next) {
+ commands.push(`L ${current.x} ${current.y}`);
+ continue;
+ }
+
+ const incomingDistance = pointDistance(previous, current);
+ const outgoingDistance = pointDistance(current, next);
+ if (incomingDistance === 0 || outgoingDistance === 0) {
+ commands.push(`L ${current.x} ${current.y}`);
+ continue;
+ }
+
+ const cornerRadius = Math.min(radius, incomingDistance / 2, outgoingDistance / 2);
+ const incomingUnit = {
+ x: (current.x - previous.x) / incomingDistance,
+ y: (current.y - previous.y) / incomingDistance,
+ };
+ const outgoingUnit = {
+ x: (next.x - current.x) / outgoingDistance,
+ y: (next.y - current.y) / outgoingDistance,
+ };
+ const beforeCorner = {
+ x: current.x - incomingUnit.x * cornerRadius,
+ y: current.y - incomingUnit.y * cornerRadius,
+ };
+ const afterCorner = {
+ x: current.x + outgoingUnit.x * cornerRadius,
+ y: current.y + outgoingUnit.y * cornerRadius,
+ };
+
+ commands.push(
+ `L ${roundPathNumber(beforeCorner.x)} ${roundPathNumber(beforeCorner.y)}`,
+ `Q ${current.x} ${current.y} ${roundPathNumber(afterCorner.x)} ${roundPathNumber(afterCorner.y)}`,
+ );
+ }
+
+ return commands.join(' ');
+}
+
+function bridgePath(bridge: RouteBridge): string {
+ const radius = 9;
+ const height = 4;
+
+ if (bridge.orientation === 'horizontal') {
+ return [
+ `M ${bridge.x - radius} ${bridge.y}`,
+ `Q ${bridge.x} ${bridge.y - height} ${bridge.x + radius} ${bridge.y}`,
+ ].join(' ');
+ }
+
+ return [
+ `M ${bridge.x} ${bridge.y - radius}`,
+ `Q ${bridge.x + height} ${bridge.y} ${bridge.x} ${bridge.y + radius}`,
+ ].join(' ');
+}
+
+function routeBridges(
+ routedEdges: Array<{ edge: MapEdge; routePoints: RoutePoint[] }>,
+): Map {
+ const bridgesByEdge = new Map();
+ const segments = routedEdges.flatMap(({ edge, routePoints }) =>
+ routeSegments(edge.id, routePoints),
+ );
+
+ for (let firstIndex = 0; firstIndex < segments.length; firstIndex += 1) {
+ const first = segments[firstIndex];
+
+ for (let secondIndex = firstIndex + 1; secondIndex < segments.length; secondIndex += 1) {
+ const second = segments[secondIndex];
+ if (first.edgeId === second.edgeId) {
+ continue;
+ }
+
+ if (first.orientation !== second.orientation) {
+ addCrossingBridge(bridgesByEdge, first, second);
+ }
+ }
+ }
+
+ for (const [edgeId, bridges] of bridgesByEdge) {
+ bridgesByEdge.set(edgeId, dedupeBridges(bridges));
+ }
+
+ return bridgesByEdge;
+}
+
+function routeSegments(edgeId: string, points: RoutePoint[]): RouteSegment[] {
+ const segments: RouteSegment[] = [];
+
+ for (let index = 0; index < points.length - 1; index += 1) {
+ const start = points[index];
+ const end = points[index + 1];
+ if (start.x === end.x && start.y === end.y) {
+ continue;
+ }
+
+ if (start.y === end.y) {
+ segments.push({
+ edgeId,
+ index,
+ start,
+ end,
+ orientation: 'horizontal',
+ fixed: start.y,
+ from: Math.min(start.x, end.x),
+ to: Math.max(start.x, end.x),
+ });
+ continue;
+ }
+
+ if (start.x === end.x) {
+ segments.push({
+ edgeId,
+ index,
+ start,
+ end,
+ orientation: 'vertical',
+ fixed: start.x,
+ from: Math.min(start.y, end.y),
+ to: Math.max(start.y, end.y),
+ });
+ }
+ }
+
+ return segments;
+}
+
+function addCrossingBridge(
+ bridgesByEdge: Map,
+ first: RouteSegment,
+ second: RouteSegment,
+): void {
+ const horizontal = first.orientation === 'horizontal' ? first : second;
+ const vertical = first.orientation === 'vertical' ? first : second;
+ const x = vertical.fixed;
+ const y = horizontal.fixed;
+ const crossingMargin = 34;
+
+ if (
+ x <= horizontal.from + crossingMargin ||
+ x >= horizontal.to - crossingMargin ||
+ y <= vertical.from + crossingMargin ||
+ y >= vertical.to - crossingMargin
+ ) {
+ return;
+ }
+
+ if (segmentsShareEndpoint(first, second)) {
+ return;
+ }
+
+ const bridgeSegment = first.edgeId > second.edgeId ? first : second;
+ appendBridge(bridgesByEdge, bridgeSegment.edgeId, {
+ x,
+ y,
+ orientation: bridgeSegment.orientation,
+ });
+}
+
+function appendBridge(
+ bridgesByEdge: Map,
+ edgeId: string,
+ bridge: RouteBridge,
+): void {
+ bridgesByEdge.set(edgeId, [...(bridgesByEdge.get(edgeId) ?? []), bridge]);
+}
+
+function dedupeBridges(bridges: RouteBridge[]): RouteBridge[] {
+ const seen = new Set();
+
+ return bridges
+ .sort((first, second) => first.x - second.x || first.y - second.y)
+ .filter((bridge) => {
+ const key = `${bridge.orientation}:${Math.round(bridge.x / 8)}:${Math.round(bridge.y / 8)}`;
+ if (seen.has(key)) {
+ return false;
+ }
+ seen.add(key);
+ return true;
+ });
+}
+
+function segmentsShareEndpoint(first: RouteSegment, second: RouteSegment): boolean {
+ return (
+ pointsEqual(first.start, second.start) ||
+ pointsEqual(first.start, second.end) ||
+ pointsEqual(first.end, second.start) ||
+ pointsEqual(first.end, second.end)
+ );
+}
+
+function pointsEqual(first: RoutePoint, second: RoutePoint): boolean {
+ return Math.abs(first.x - second.x) < 0.5 && Math.abs(first.y - second.y) < 0.5;
+}
+
+function pointDistance(first: RoutePoint, second: RoutePoint): number {
+ return Math.hypot(second.x - first.x, second.y - first.y);
+}
+
+function roundPathNumber(value: number): number {
+ return Math.round(value * 100) / 100;
+}
+
+function targetHandlesFromData(data: MapNode['data']): TargetHandleLayout[] {
+ return Array.isArray(data.targetHandles)
+ ? (data.targetHandles as TargetHandleLayout[])
+ : [];
+}
+
+function fieldConnectionIdsFromData(data: MapNode['data']): FieldConnectionIds {
+ return data.fieldConnectionIds &&
+ typeof data.fieldConnectionIds === 'object' &&
+ !Array.isArray(data.fieldConnectionIds)
+ ? (data.fieldConnectionIds as FieldConnectionIds)
+ : {};
+}
+
+function fieldClickHandlerFromData(data: MapNode['data']): FieldClickHandler | undefined {
+ return typeof data.onFieldConnectionClick === 'function'
+ ? (data.onFieldConnectionClick as FieldClickHandler)
+ : undefined;
+}
+
+type InspectorProps = {
+ node?: MapNode;
+ totalNodes: number;
+ totalEdges: number;
+};
+
+function Inspector({ node, totalNodes, totalEdges }: InspectorProps) {
+ if (!node) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+}
+
+export default function App() {
+ return (
+
+
+
+ );
+}
diff --git a/src/gnmiMap.ts b/src/gnmiMap.ts
new file mode 100644
index 0000000..137296d
--- /dev/null
+++ b/src/gnmiMap.ts
@@ -0,0 +1,2959 @@
+// Generated by scripts/generate-map-data.ts. Do not edit by hand.
+
+import type { Edge, Node } from '@xyflow/react';
+
+export type MapNodeKind = 'service' | 'rpc' | 'message' | 'enum' | 'external' | 'legend';
+export type MapEdgeKind = 'rpc' | 'field' | 'extension' | 'extension-detail';
+export type MapBadge = 'stream' | 'optional' | 'deprecated' | 'reserved';
+
+export type MapSource = {
+ gnmiTag: string;
+ gnmiServiceVersion: string;
+ gnmiBase: string;
+ extBase: string;
+ specBase: string;
+};
+
+export type MapBounds = {
+ width: number;
+ height: number;
+};
+
+export type MapField = {
+ id: string;
+ type: string;
+ name: string;
+ ref?: string | null;
+ group?: string;
+ badge?: MapBadge;
+ deprecated?: boolean;
+};
+
+export type MapNodeData = Record & {
+ id: string;
+ kind: MapNodeKind;
+ label: string;
+ sourceSymbol?: string;
+ deprecated?: boolean;
+ protoUrl?: string;
+ specUrl?: string;
+ fields?: MapField[];
+ badges?: MapBadge[];
+ active?: boolean;
+ query?: string;
+ showExtensions?: boolean;
+};
+
+export type MapNode = Node;
+
+export type MapEdge = Edge, 'smoothstep'> & {
+ sourceHandle: string;
+ kind: MapEdgeKind;
+ deprecated: boolean;
+};
+
+export type VisibleMapOptions = {
+ showDeprecated?: boolean;
+ showExtensions?: boolean;
+};
+
+export type VisibleMap = {
+ nodes: MapNode[];
+ edges: MapEdge[];
+};
+
+export const mapSource: MapSource = {
+ "gnmiTag": "v0.14.1",
+ "gnmiServiceVersion": "0.10.0",
+ "gnmiBase": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto",
+ "extBase": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto",
+ "specBase": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md"
+};
+
+export const mapNodes: MapNode[] = [
+ {
+ "id": "service-gnmi",
+ "type": "schema",
+ "position": {
+ "x": 1240,
+ "y": 40
+ },
+ "style": {
+ "width": 360
+ },
+ "data": {
+ "id": "service-gnmi",
+ "kind": "service",
+ "label": "service gNMI 0.10.0",
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L49",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#grpc-network-management-interface-gnmi",
+ "fields": [
+ {
+ "id": "capabilities",
+ "type": "rpc",
+ "name": "Capabilities",
+ "ref": "rpc-capabilities"
+ },
+ {
+ "id": "get",
+ "type": "rpc",
+ "name": "Get",
+ "ref": "rpc-get"
+ },
+ {
+ "id": "set",
+ "type": "rpc",
+ "name": "Set",
+ "ref": "rpc-set"
+ },
+ {
+ "id": "subscribe",
+ "type": "rpc",
+ "name": "Subscribe",
+ "ref": "rpc-subscribe",
+ "badge": "stream"
+ }
+ ]
+ }
+ },
+ {
+ "id": "rpc-set",
+ "type": "schema",
+ "position": {
+ "x": 80,
+ "y": 230
+ },
+ "style": {
+ "width": 250
+ },
+ "data": {
+ "id": "rpc-set",
+ "kind": "rpc",
+ "label": "rpc Set",
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L67",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#34-modifying-state",
+ "fields": [
+ {
+ "id": "takes",
+ "type": "takes",
+ "name": "SetRequest",
+ "ref": "set-request"
+ },
+ {
+ "id": "returns",
+ "type": "returns",
+ "name": "SetResponse",
+ "ref": "set-response"
+ }
+ ]
+ }
+ },
+ {
+ "id": "rpc-subscribe",
+ "type": "schema",
+ "position": {
+ "x": 780,
+ "y": 230
+ },
+ "style": {
+ "width": 290
+ },
+ "data": {
+ "id": "rpc-subscribe",
+ "kind": "rpc",
+ "label": "rpc Subscribe",
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L73",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#35-subscribing-to-telemetry-updates",
+ "fields": [
+ {
+ "id": "takes",
+ "type": "takes stream",
+ "name": "SubscribeRequest",
+ "ref": "subscribe-request"
+ },
+ {
+ "id": "returns",
+ "type": "returns stream",
+ "name": "SubscribeResponse",
+ "ref": "subscribe-response"
+ }
+ ]
+ }
+ },
+ {
+ "id": "rpc-get",
+ "type": "schema",
+ "position": {
+ "x": 1520,
+ "y": 230
+ },
+ "style": {
+ "width": 250
+ },
+ "data": {
+ "id": "rpc-get",
+ "kind": "rpc",
+ "label": "rpc Get",
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L62",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#33-retrieving-snapshots-of-state-information",
+ "fields": [
+ {
+ "id": "takes",
+ "type": "takes",
+ "name": "GetRequest",
+ "ref": "get-request"
+ },
+ {
+ "id": "returns",
+ "type": "returns",
+ "name": "GetResponse",
+ "ref": "get-response"
+ }
+ ]
+ }
+ },
+ {
+ "id": "rpc-capabilities",
+ "type": "schema",
+ "position": {
+ "x": 2200,
+ "y": 230
+ },
+ "style": {
+ "width": 300
+ },
+ "data": {
+ "id": "rpc-capabilities",
+ "kind": "rpc",
+ "label": "rpc Capabilities",
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L56",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#32-capability-discovery",
+ "fields": [
+ {
+ "id": "takes",
+ "type": "takes",
+ "name": "CapabilityRequest",
+ "ref": "capability-request"
+ },
+ {
+ "id": "returns",
+ "type": "returns",
+ "name": "CapabilityResponse",
+ "ref": "capability-response"
+ }
+ ]
+ }
+ },
+ {
+ "id": "set-request",
+ "type": "schema",
+ "position": {
+ "x": 20,
+ "y": 450
+ },
+ "style": {
+ "width": 340
+ },
+ "data": {
+ "id": "set-request",
+ "kind": "message",
+ "label": "SetRequest",
+ "sourceSymbol": "gnmi.SetRequest",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L342",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#341-the-setrequest-message",
+ "fields": [
+ {
+ "id": "prefix",
+ "type": "Path",
+ "name": "prefix",
+ "ref": "path"
+ },
+ {
+ "id": "delete",
+ "type": "repeated Path",
+ "name": "delete",
+ "ref": "path"
+ },
+ {
+ "id": "replace",
+ "type": "repeated Update",
+ "name": "replace",
+ "ref": "update"
+ },
+ {
+ "id": "update",
+ "type": "repeated Update",
+ "name": "update",
+ "ref": "update"
+ },
+ {
+ "id": "union-replace",
+ "type": "repeated Update",
+ "name": "union_replace",
+ "ref": "update"
+ },
+ {
+ "id": "extension",
+ "type": "repeated gnmi_ext.Extension",
+ "name": "extension",
+ "ref": "extension",
+ "badge": "optional"
+ }
+ ]
+ }
+ },
+ {
+ "id": "set-response",
+ "type": "schema",
+ "position": {
+ "x": 390,
+ "y": 450
+ },
+ "style": {
+ "width": 360
+ },
+ "data": {
+ "id": "set-response",
+ "kind": "message",
+ "label": "SetResponse",
+ "sourceSymbol": "gnmi.SetResponse",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L364",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#342-the-setresponse-message",
+ "fields": [
+ {
+ "id": "prefix",
+ "type": "Path",
+ "name": "prefix",
+ "ref": "path"
+ },
+ {
+ "id": "response",
+ "type": "repeated UpdateResult",
+ "name": "response",
+ "ref": "update-result"
+ },
+ {
+ "id": "message",
+ "type": "Error",
+ "name": "message",
+ "ref": "error",
+ "badge": "deprecated",
+ "deprecated": true
+ },
+ {
+ "id": "timestamp",
+ "type": "int64",
+ "name": "timestamp",
+ "ref": null
+ },
+ {
+ "id": "extension",
+ "type": "repeated gnmi_ext.Extension",
+ "name": "extension",
+ "ref": "extension",
+ "badge": "optional"
+ }
+ ]
+ }
+ },
+ {
+ "id": "subscribe-request",
+ "type": "schema",
+ "position": {
+ "x": 780,
+ "y": 450
+ },
+ "style": {
+ "width": 360
+ },
+ "data": {
+ "id": "subscribe-request",
+ "kind": "message",
+ "label": "SubscribeRequest",
+ "sourceSymbol": "gnmi.SubscribeRequest",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L220",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#3511-the-subscriberequest-message",
+ "fields": [
+ {
+ "id": "subscribe",
+ "type": "SubscriptionList",
+ "name": "subscribe",
+ "ref": "subscription-list",
+ "group": "oneof request"
+ },
+ {
+ "id": "poll",
+ "type": "Poll",
+ "name": "poll",
+ "ref": "poll",
+ "group": "oneof request"
+ },
+ {
+ "id": "extension",
+ "type": "repeated gnmi_ext.Extension",
+ "name": "extension",
+ "ref": "extension",
+ "badge": "optional"
+ },
+ {
+ "id": "reserved-aliases",
+ "type": "reserved",
+ "name": "aliases / 4",
+ "ref": null,
+ "badge": "reserved"
+ }
+ ]
+ }
+ },
+ {
+ "id": "subscribe-response",
+ "type": "schema",
+ "position": {
+ "x": 1180,
+ "y": 450
+ },
+ "style": {
+ "width": 360
+ },
+ "data": {
+ "id": "subscribe-response",
+ "kind": "message",
+ "label": "SubscribeResponse",
+ "sourceSymbol": "gnmi.SubscribeResponse",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L245",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#3514-the-subscriberesponse-message",
+ "fields": [
+ {
+ "id": "update",
+ "type": "Notification",
+ "name": "update",
+ "ref": "notification",
+ "group": "oneof response"
+ },
+ {
+ "id": "sync-response",
+ "type": "bool",
+ "name": "sync_response",
+ "ref": null,
+ "group": "oneof response"
+ },
+ {
+ "id": "error",
+ "type": "Error",
+ "name": "error",
+ "ref": "error",
+ "group": "oneof response",
+ "badge": "deprecated",
+ "deprecated": true
+ },
+ {
+ "id": "extension",
+ "type": "repeated gnmi_ext.Extension",
+ "name": "extension",
+ "ref": "extension",
+ "badge": "optional"
+ }
+ ]
+ }
+ },
+ {
+ "id": "get-request",
+ "type": "schema",
+ "position": {
+ "x": 1570,
+ "y": 450
+ },
+ "style": {
+ "width": 350
+ },
+ "data": {
+ "id": "get-request",
+ "kind": "message",
+ "label": "GetRequest",
+ "sourceSymbol": "gnmi.GetRequest",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L405",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#331-the-getrequest-message",
+ "fields": [
+ {
+ "id": "prefix",
+ "type": "Path",
+ "name": "prefix",
+ "ref": "path"
+ },
+ {
+ "id": "path",
+ "type": "repeated Path",
+ "name": "path",
+ "ref": "path"
+ },
+ {
+ "id": "type",
+ "type": "DataType",
+ "name": "type",
+ "ref": "data-type"
+ },
+ {
+ "id": "encoding",
+ "type": "Encoding",
+ "name": "encoding",
+ "ref": "encoding"
+ },
+ {
+ "id": "use-models",
+ "type": "repeated ModelData",
+ "name": "use_models",
+ "ref": "model-data"
+ },
+ {
+ "id": "extension",
+ "type": "repeated gnmi_ext.Extension",
+ "name": "extension",
+ "ref": "extension",
+ "badge": "optional"
+ }
+ ]
+ }
+ },
+ {
+ "id": "get-response",
+ "type": "schema",
+ "position": {
+ "x": 1960,
+ "y": 450
+ },
+ "style": {
+ "width": 350
+ },
+ "data": {
+ "id": "get-response",
+ "kind": "message",
+ "label": "GetResponse",
+ "sourceSymbol": "gnmi.GetResponse",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L430",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#332-the-getresponse-message",
+ "fields": [
+ {
+ "id": "notification",
+ "type": "repeated Notification",
+ "name": "notification",
+ "ref": "notification"
+ },
+ {
+ "id": "error",
+ "type": "Error",
+ "name": "error",
+ "ref": "error",
+ "badge": "deprecated",
+ "deprecated": true
+ },
+ {
+ "id": "extension",
+ "type": "repeated gnmi_ext.Extension",
+ "name": "extension",
+ "ref": "extension",
+ "badge": "optional"
+ }
+ ]
+ }
+ },
+ {
+ "id": "capability-request",
+ "type": "schema",
+ "position": {
+ "x": 2350,
+ "y": 450
+ },
+ "style": {
+ "width": 340
+ },
+ "data": {
+ "id": "capability-request",
+ "kind": "message",
+ "label": "CapabilityRequest",
+ "sourceSymbol": "gnmi.CapabilityRequest",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L441",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#321-the-capabilityrequest-message",
+ "fields": [
+ {
+ "id": "extension",
+ "type": "repeated gnmi_ext.Extension",
+ "name": "extension",
+ "ref": "extension",
+ "badge": "optional"
+ }
+ ]
+ }
+ },
+ {
+ "id": "capability-response",
+ "type": "schema",
+ "position": {
+ "x": 2730,
+ "y": 450
+ },
+ "style": {
+ "width": 370
+ },
+ "data": {
+ "id": "capability-response",
+ "kind": "message",
+ "label": "CapabilityResponse",
+ "sourceSymbol": "gnmi.CapabilityResponse",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L450",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#322-the-capabilityresponse-message",
+ "fields": [
+ {
+ "id": "supported-models",
+ "type": "repeated ModelData",
+ "name": "supported_models",
+ "ref": "model-data"
+ },
+ {
+ "id": "supported-encodings",
+ "type": "repeated Encoding",
+ "name": "supported_encodings",
+ "ref": "encoding"
+ },
+ {
+ "id": "g-nmi-version",
+ "type": "string",
+ "name": "gNMI_version",
+ "ref": null
+ },
+ {
+ "id": "extension",
+ "type": "repeated gnmi_ext.Extension",
+ "name": "extension",
+ "ref": "extension",
+ "badge": "optional"
+ }
+ ]
+ }
+ },
+ {
+ "id": "error",
+ "type": "schema",
+ "position": {
+ "x": 120,
+ "y": 850
+ },
+ "style": {
+ "width": 300
+ },
+ "data": {
+ "id": "error",
+ "kind": "message",
+ "label": "Error",
+ "sourceSymbol": "gnmi.Error",
+ "deprecated": true,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L187",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#23-structured-data-types",
+ "badges": [
+ "deprecated"
+ ],
+ "fields": [
+ {
+ "id": "code",
+ "type": "uint32",
+ "name": "code",
+ "ref": null
+ },
+ {
+ "id": "message",
+ "type": "string",
+ "name": "message",
+ "ref": null
+ },
+ {
+ "id": "data",
+ "type": "google.protobuf.Any",
+ "name": "data",
+ "ref": "any"
+ }
+ ]
+ }
+ },
+ {
+ "id": "update-result",
+ "type": "schema",
+ "position": {
+ "x": 450,
+ "y": 820
+ },
+ "style": {
+ "width": 330
+ },
+ "data": {
+ "id": "update-result",
+ "kind": "message",
+ "label": "UpdateResult",
+ "sourceSymbol": "gnmi.UpdateResult",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L380",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#342-the-setresponse-message",
+ "fields": [
+ {
+ "id": "timestamp",
+ "type": "int64",
+ "name": "timestamp",
+ "ref": null,
+ "badge": "deprecated",
+ "deprecated": true
+ },
+ {
+ "id": "path",
+ "type": "Path",
+ "name": "path",
+ "ref": "path"
+ },
+ {
+ "id": "message",
+ "type": "Error",
+ "name": "message",
+ "ref": "error",
+ "badge": "deprecated",
+ "deprecated": true
+ },
+ {
+ "id": "op",
+ "type": "Operation",
+ "name": "op",
+ "ref": "operation"
+ }
+ ]
+ }
+ },
+ {
+ "id": "operation",
+ "type": "schema",
+ "position": {
+ "x": 430,
+ "y": 1120
+ },
+ "style": {
+ "width": 260
+ },
+ "data": {
+ "id": "operation",
+ "kind": "enum",
+ "label": "enum Operation",
+ "sourceSymbol": "gnmi.UpdateResult.Operation",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L382",
+ "fields": [
+ {
+ "id": "invalid",
+ "type": "0",
+ "name": "INVALID",
+ "ref": null
+ },
+ {
+ "id": "delete",
+ "type": "1",
+ "name": "DELETE",
+ "ref": null
+ },
+ {
+ "id": "replace",
+ "type": "2",
+ "name": "REPLACE",
+ "ref": null
+ },
+ {
+ "id": "update",
+ "type": "3",
+ "name": "UPDATE",
+ "ref": null
+ },
+ {
+ "id": "union-replace",
+ "type": "4",
+ "name": "UNION_REPLACE",
+ "ref": null
+ }
+ ]
+ }
+ },
+ {
+ "id": "poll",
+ "type": "schema",
+ "position": {
+ "x": 800,
+ "y": 780
+ },
+ "style": {
+ "width": 180
+ },
+ "data": {
+ "id": "poll",
+ "kind": "message",
+ "label": "Poll",
+ "sourceSymbol": "gnmi.Poll",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L237",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#35153-poll-subscriptions",
+ "fields": []
+ }
+ },
+ {
+ "id": "subscription-list",
+ "type": "schema",
+ "position": {
+ "x": 1050,
+ "y": 750
+ },
+ "style": {
+ "width": 390
+ },
+ "data": {
+ "id": "subscription-list",
+ "kind": "message",
+ "label": "SubscriptionList",
+ "sourceSymbol": "gnmi.SubscriptionList",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L264",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#3512-the-subscriptionlist-message",
+ "fields": [
+ {
+ "id": "prefix",
+ "type": "Path",
+ "name": "prefix",
+ "ref": "path"
+ },
+ {
+ "id": "subscription",
+ "type": "repeated Subscription",
+ "name": "subscription",
+ "ref": "subscription"
+ },
+ {
+ "id": "qos",
+ "type": "QOSMarking",
+ "name": "qos",
+ "ref": "qos-marking"
+ },
+ {
+ "id": "mode",
+ "type": "Mode",
+ "name": "mode",
+ "ref": "mode"
+ },
+ {
+ "id": "allow-aggregation",
+ "type": "bool",
+ "name": "allow_aggregation",
+ "ref": null
+ },
+ {
+ "id": "use-models",
+ "type": "repeated ModelData",
+ "name": "use_models",
+ "ref": "model-data"
+ },
+ {
+ "id": "encoding",
+ "type": "Encoding",
+ "name": "encoding",
+ "ref": "encoding"
+ },
+ {
+ "id": "updates-only",
+ "type": "bool",
+ "name": "updates_only",
+ "ref": null
+ },
+ {
+ "id": "reserved-use-aliases",
+ "type": "reserved",
+ "name": "use_aliases / 3",
+ "ref": null,
+ "badge": "reserved"
+ }
+ ]
+ }
+ },
+ {
+ "id": "qos-marking",
+ "type": "schema",
+ "position": {
+ "x": 990,
+ "y": 1210
+ },
+ "style": {
+ "width": 270
+ },
+ "data": {
+ "id": "qos-marking",
+ "kind": "message",
+ "label": "QOSMarking",
+ "sourceSymbol": "gnmi.QOSMarking",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L331",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#3512-the-subscriptionlist-message",
+ "fields": [
+ {
+ "id": "marking",
+ "type": "uint32",
+ "name": "marking",
+ "ref": null
+ }
+ ]
+ }
+ },
+ {
+ "id": "mode",
+ "type": "schema",
+ "position": {
+ "x": 990,
+ "y": 1390
+ },
+ "style": {
+ "width": 240
+ },
+ "data": {
+ "id": "mode",
+ "kind": "enum",
+ "label": "enum Mode",
+ "sourceSymbol": "gnmi.SubscriptionList.Mode",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L269",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#3512-the-subscriptionlist-message",
+ "fields": [
+ {
+ "id": "stream",
+ "type": "0",
+ "name": "STREAM",
+ "ref": null
+ },
+ {
+ "id": "once",
+ "type": "1",
+ "name": "ONCE",
+ "ref": null
+ },
+ {
+ "id": "poll",
+ "type": "2",
+ "name": "POLL",
+ "ref": null
+ }
+ ]
+ }
+ },
+ {
+ "id": "subscription",
+ "type": "schema",
+ "position": {
+ "x": 1460,
+ "y": 920
+ },
+ "style": {
+ "width": 360
+ },
+ "data": {
+ "id": "subscription",
+ "kind": "message",
+ "label": "Subscription",
+ "sourceSymbol": "gnmi.Subscription",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L300",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#3513-the-subscription-message",
+ "fields": [
+ {
+ "id": "path",
+ "type": "Path",
+ "name": "path",
+ "ref": "path"
+ },
+ {
+ "id": "mode",
+ "type": "SubscriptionMode",
+ "name": "mode",
+ "ref": "subscription-mode"
+ },
+ {
+ "id": "sample-interval",
+ "type": "uint64",
+ "name": "sample_interval",
+ "ref": null
+ },
+ {
+ "id": "suppress-redundant",
+ "type": "bool",
+ "name": "suppress_redundant",
+ "ref": null
+ },
+ {
+ "id": "heartbeat-interval",
+ "type": "uint64",
+ "name": "heartbeat_interval",
+ "ref": null
+ }
+ ]
+ }
+ },
+ {
+ "id": "subscription-mode",
+ "type": "schema",
+ "position": {
+ "x": 1460,
+ "y": 1250
+ },
+ "style": {
+ "width": 310
+ },
+ "data": {
+ "id": "subscription-mode",
+ "kind": "enum",
+ "label": "enum SubscriptionMode",
+ "sourceSymbol": "gnmi.SubscriptionMode",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L322",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#35152-stream-subscriptions",
+ "fields": [
+ {
+ "id": "target-defined",
+ "type": "0",
+ "name": "TARGET_DEFINED",
+ "ref": null
+ },
+ {
+ "id": "on-change",
+ "type": "1",
+ "name": "ON_CHANGE",
+ "ref": null
+ },
+ {
+ "id": "sample",
+ "type": "2",
+ "name": "SAMPLE",
+ "ref": null
+ }
+ ]
+ }
+ },
+ {
+ "id": "data-type",
+ "type": "schema",
+ "position": {
+ "x": 1660,
+ "y": 720
+ },
+ "style": {
+ "width": 280
+ },
+ "data": {
+ "id": "data-type",
+ "kind": "enum",
+ "label": "enum DataType",
+ "sourceSymbol": "gnmi.GetRequest.DataType",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L409",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#331-the-getrequest-message",
+ "fields": [
+ {
+ "id": "all",
+ "type": "0",
+ "name": "ALL",
+ "ref": null
+ },
+ {
+ "id": "config",
+ "type": "1",
+ "name": "CONFIG",
+ "ref": null
+ },
+ {
+ "id": "state",
+ "type": "2",
+ "name": "STATE",
+ "ref": null
+ },
+ {
+ "id": "operational",
+ "type": "3",
+ "name": "OPERATIONAL",
+ "ref": null
+ }
+ ]
+ }
+ },
+ {
+ "id": "model-data",
+ "type": "schema",
+ "position": {
+ "x": 2410,
+ "y": 790
+ },
+ "style": {
+ "width": 300
+ },
+ "data": {
+ "id": "model-data",
+ "kind": "message",
+ "label": "ModelData",
+ "sourceSymbol": "gnmi.ModelData",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L464",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#261-the-modeldata-message",
+ "fields": [
+ {
+ "id": "name",
+ "type": "string",
+ "name": "name",
+ "ref": null
+ },
+ {
+ "id": "organization",
+ "type": "string",
+ "name": "organization",
+ "ref": null
+ },
+ {
+ "id": "version",
+ "type": "string",
+ "name": "version",
+ "ref": null
+ }
+ ]
+ }
+ },
+ {
+ "id": "encoding",
+ "type": "schema",
+ "position": {
+ "x": 2380,
+ "y": 1120
+ },
+ "style": {
+ "width": 260
+ },
+ "data": {
+ "id": "encoding",
+ "kind": "enum",
+ "label": "enum Encoding",
+ "sourceSymbol": "gnmi.Encoding",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L175",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#23-structured-data-types",
+ "fields": [
+ {
+ "id": "json",
+ "type": "0",
+ "name": "JSON",
+ "ref": null
+ },
+ {
+ "id": "bytes",
+ "type": "1",
+ "name": "BYTES",
+ "ref": null
+ },
+ {
+ "id": "proto",
+ "type": "2",
+ "name": "PROTO",
+ "ref": null
+ },
+ {
+ "id": "ascii",
+ "type": "3",
+ "name": "ASCII",
+ "ref": null
+ },
+ {
+ "id": "json-ietf",
+ "type": "4",
+ "name": "JSON_IETF",
+ "ref": null
+ }
+ ]
+ }
+ },
+ {
+ "id": "notification",
+ "type": "schema",
+ "position": {
+ "x": 1740,
+ "y": 1160
+ },
+ "style": {
+ "width": 350
+ },
+ "data": {
+ "id": "notification",
+ "kind": "message",
+ "label": "Notification",
+ "sourceSymbol": "gnmi.Notification",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L84",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#21-reusable-notification-message-format",
+ "fields": [
+ {
+ "id": "timestamp",
+ "type": "int64",
+ "name": "timestamp",
+ "ref": null
+ },
+ {
+ "id": "prefix",
+ "type": "Path",
+ "name": "prefix",
+ "ref": "path"
+ },
+ {
+ "id": "update",
+ "type": "repeated Update",
+ "name": "update",
+ "ref": "update"
+ },
+ {
+ "id": "delete",
+ "type": "repeated Path",
+ "name": "delete",
+ "ref": "path"
+ },
+ {
+ "id": "atomic",
+ "type": "bool",
+ "name": "atomic",
+ "ref": null
+ },
+ {
+ "id": "reserved-alias",
+ "type": "reserved",
+ "name": "alias / 3",
+ "ref": null,
+ "badge": "reserved"
+ }
+ ]
+ }
+ },
+ {
+ "id": "update",
+ "type": "schema",
+ "position": {
+ "x": 1320,
+ "y": 1460
+ },
+ "style": {
+ "width": 330
+ },
+ "data": {
+ "id": "update",
+ "kind": "message",
+ "label": "Update",
+ "sourceSymbol": "gnmi.Update",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L100",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#21-reusable-notification-message-format",
+ "fields": [
+ {
+ "id": "path",
+ "type": "Path",
+ "name": "path",
+ "ref": "path"
+ },
+ {
+ "id": "value",
+ "type": "Value",
+ "name": "value",
+ "ref": "value",
+ "badge": "deprecated",
+ "deprecated": true
+ },
+ {
+ "id": "val",
+ "type": "TypedValue",
+ "name": "val",
+ "ref": "typed-value"
+ },
+ {
+ "id": "duplicates",
+ "type": "uint32",
+ "name": "duplicates",
+ "ref": null
+ }
+ ]
+ }
+ },
+ {
+ "id": "path",
+ "type": "schema",
+ "position": {
+ "x": 1780,
+ "y": 1530
+ },
+ "style": {
+ "width": 360
+ },
+ "data": {
+ "id": "path",
+ "kind": "message",
+ "label": "Path",
+ "sourceSymbol": "gnmi.Path",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L142",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#222-paths",
+ "fields": [
+ {
+ "id": "element",
+ "type": "repeated string",
+ "name": "element",
+ "ref": null,
+ "badge": "deprecated",
+ "deprecated": true
+ },
+ {
+ "id": "origin",
+ "type": "string",
+ "name": "origin",
+ "ref": null
+ },
+ {
+ "id": "elem",
+ "type": "repeated PathElem",
+ "name": "elem",
+ "ref": "path-elem"
+ },
+ {
+ "id": "target",
+ "type": "string",
+ "name": "target",
+ "ref": null
+ }
+ ]
+ }
+ },
+ {
+ "id": "path-elem",
+ "type": "schema",
+ "position": {
+ "x": 2210,
+ "y": 1600
+ },
+ "style": {
+ "width": 330
+ },
+ "data": {
+ "id": "path-elem",
+ "kind": "message",
+ "label": "PathElem",
+ "sourceSymbol": "gnmi.PathElem",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L155",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#222-paths",
+ "fields": [
+ {
+ "id": "name",
+ "type": "string",
+ "name": "name",
+ "ref": null
+ },
+ {
+ "id": "key",
+ "type": "map",
+ "name": "key",
+ "ref": null
+ }
+ ]
+ }
+ },
+ {
+ "id": "value",
+ "type": "schema",
+ "position": {
+ "x": 700,
+ "y": 1570
+ },
+ "style": {
+ "width": 300
+ },
+ "data": {
+ "id": "value",
+ "kind": "message",
+ "label": "Value",
+ "sourceSymbol": "gnmi.Value",
+ "deprecated": true,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L163",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#223-node-values",
+ "badges": [
+ "deprecated"
+ ],
+ "fields": [
+ {
+ "id": "value",
+ "type": "bytes",
+ "name": "value",
+ "ref": null
+ },
+ {
+ "id": "type",
+ "type": "Encoding",
+ "name": "type",
+ "ref": "encoding"
+ }
+ ]
+ }
+ },
+ {
+ "id": "typed-value",
+ "type": "schema",
+ "position": {
+ "x": 980,
+ "y": 1780
+ },
+ "style": {
+ "width": 390
+ },
+ "data": {
+ "id": "typed-value",
+ "kind": "message",
+ "label": "TypedValue",
+ "sourceSymbol": "gnmi.TypedValue",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L109",
+ "fields": [
+ {
+ "id": "string-val",
+ "type": "string",
+ "name": "string_val",
+ "ref": null,
+ "group": "oneof value"
+ },
+ {
+ "id": "int-val",
+ "type": "int64",
+ "name": "int_val",
+ "ref": null,
+ "group": "oneof value"
+ },
+ {
+ "id": "uint-val",
+ "type": "uint64",
+ "name": "uint_val",
+ "ref": null,
+ "group": "oneof value"
+ },
+ {
+ "id": "bool-val",
+ "type": "bool",
+ "name": "bool_val",
+ "ref": null,
+ "group": "oneof value"
+ },
+ {
+ "id": "bytes-val",
+ "type": "bytes",
+ "name": "bytes_val",
+ "ref": null,
+ "group": "oneof value"
+ },
+ {
+ "id": "float-val",
+ "type": "float",
+ "name": "float_val",
+ "ref": null,
+ "group": "oneof value",
+ "badge": "deprecated",
+ "deprecated": true
+ },
+ {
+ "id": "double-val",
+ "type": "double",
+ "name": "double_val",
+ "ref": null,
+ "group": "oneof value"
+ },
+ {
+ "id": "decimal-val",
+ "type": "Decimal64",
+ "name": "decimal_val",
+ "ref": "decimal64",
+ "group": "oneof value",
+ "badge": "deprecated",
+ "deprecated": true
+ },
+ {
+ "id": "leaflist-val",
+ "type": "ScalarArray",
+ "name": "leaflist_val",
+ "ref": "scalar-array",
+ "group": "oneof value"
+ },
+ {
+ "id": "any-val",
+ "type": "google.protobuf.Any",
+ "name": "any_val",
+ "ref": "any",
+ "group": "oneof value"
+ },
+ {
+ "id": "json-val",
+ "type": "bytes",
+ "name": "json_val",
+ "ref": null,
+ "group": "oneof value"
+ },
+ {
+ "id": "json-ietf-val",
+ "type": "bytes",
+ "name": "json_ietf_val",
+ "ref": null,
+ "group": "oneof value"
+ },
+ {
+ "id": "ascii-val",
+ "type": "string",
+ "name": "ascii_val",
+ "ref": null,
+ "group": "oneof value"
+ },
+ {
+ "id": "proto-bytes",
+ "type": "bytes",
+ "name": "proto_bytes",
+ "ref": null,
+ "group": "oneof value"
+ }
+ ]
+ }
+ },
+ {
+ "id": "decimal64",
+ "type": "schema",
+ "position": {
+ "x": 1430,
+ "y": 1780
+ },
+ "style": {
+ "width": 280
+ },
+ "data": {
+ "id": "decimal64",
+ "kind": "message",
+ "label": "Decimal64",
+ "sourceSymbol": "gnmi.Decimal64",
+ "deprecated": true,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L200",
+ "badges": [
+ "deprecated"
+ ],
+ "fields": [
+ {
+ "id": "digits",
+ "type": "int64",
+ "name": "digits",
+ "ref": null
+ },
+ {
+ "id": "precision",
+ "type": "uint32",
+ "name": "precision",
+ "ref": null
+ }
+ ]
+ }
+ },
+ {
+ "id": "scalar-array",
+ "type": "schema",
+ "position": {
+ "x": 1430,
+ "y": 1980
+ },
+ "style": {
+ "width": 330
+ },
+ "data": {
+ "id": "scalar-array",
+ "kind": "message",
+ "label": "ScalarArray",
+ "sourceSymbol": "gnmi.ScalarArray",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L208",
+ "fields": [
+ {
+ "id": "element",
+ "type": "repeated TypedValue",
+ "name": "element",
+ "ref": "typed-value"
+ }
+ ]
+ }
+ },
+ {
+ "id": "any",
+ "type": "schema",
+ "position": {
+ "x": 550,
+ "y": 1870
+ },
+ "style": {
+ "width": 330
+ },
+ "data": {
+ "id": "any",
+ "kind": "external",
+ "label": "google.protobuf.Any",
+ "protoUrl": "https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto",
+ "fields": [
+ {
+ "id": "type-url",
+ "type": "string",
+ "name": "type_url"
+ },
+ {
+ "id": "value",
+ "type": "bytes",
+ "name": "value"
+ }
+ ]
+ }
+ },
+ {
+ "id": "extension",
+ "type": "schema",
+ "position": {
+ "x": 2020,
+ "y": 820
+ },
+ "style": {
+ "width": 360
+ },
+ "data": {
+ "id": "extension",
+ "kind": "message",
+ "label": "gnmi_ext.Extension",
+ "sourceSymbol": "gnmi_ext.Extension",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L29",
+ "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#27-extensions-to-gnmi",
+ "fields": [
+ {
+ "id": "registered-ext",
+ "type": "RegisteredExtension",
+ "name": "registered_ext",
+ "ref": "registered-extension",
+ "group": "oneof ext"
+ },
+ {
+ "id": "master-arbitration",
+ "type": "MasterArbitration",
+ "name": "master_arbitration",
+ "ref": "master-arbitration",
+ "group": "oneof ext"
+ },
+ {
+ "id": "history",
+ "type": "History",
+ "name": "history",
+ "ref": "history",
+ "group": "oneof ext"
+ },
+ {
+ "id": "commit",
+ "type": "Commit",
+ "name": "commit",
+ "ref": "commit",
+ "group": "oneof ext"
+ },
+ {
+ "id": "depth",
+ "type": "Depth",
+ "name": "depth",
+ "ref": "depth",
+ "group": "oneof ext"
+ },
+ {
+ "id": "config-subscription",
+ "type": "ConfigSubscription",
+ "name": "config_subscription",
+ "ref": "config-subscription",
+ "group": "oneof ext"
+ }
+ ]
+ }
+ },
+ {
+ "id": "registered-extension",
+ "type": "schema",
+ "position": {
+ "x": 2510,
+ "y": 760
+ },
+ "style": {
+ "width": 390
+ },
+ "data": {
+ "id": "registered-extension",
+ "kind": "message",
+ "label": "gnmi_ext.RegisteredExtension",
+ "sourceSymbol": "gnmi_ext.RegisteredExtension",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L43",
+ "fields": [
+ {
+ "id": "id",
+ "type": "ExtensionID",
+ "name": "id",
+ "ref": "extension-id"
+ },
+ {
+ "id": "msg",
+ "type": "bytes",
+ "name": "msg",
+ "ref": null
+ }
+ ]
+ }
+ },
+ {
+ "id": "extension-id",
+ "type": "schema",
+ "position": {
+ "x": 2940,
+ "y": 700
+ },
+ "style": {
+ "width": 310
+ },
+ "data": {
+ "id": "extension-id",
+ "kind": "enum",
+ "label": "enum gnmi_ext.ExtensionID",
+ "sourceSymbol": "gnmi_ext.ExtensionID",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L50",
+ "fields": [
+ {
+ "id": "unset",
+ "type": "0",
+ "name": "EID_UNSET",
+ "ref": null
+ },
+ {
+ "id": "experimental",
+ "type": "999",
+ "name": "EID_EXPERIMENTAL",
+ "ref": null
+ }
+ ]
+ }
+ },
+ {
+ "id": "master-arbitration",
+ "type": "schema",
+ "position": {
+ "x": 2510,
+ "y": 1010
+ },
+ "style": {
+ "width": 390
+ },
+ "data": {
+ "id": "master-arbitration",
+ "kind": "message",
+ "label": "gnmi_ext.MasterArbitration",
+ "sourceSymbol": "gnmi_ext.MasterArbitration",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L65",
+ "fields": [
+ {
+ "id": "role",
+ "type": "Role",
+ "name": "role",
+ "ref": "role"
+ },
+ {
+ "id": "election-id",
+ "type": "Uint128",
+ "name": "election_id",
+ "ref": "uint128"
+ }
+ ]
+ }
+ },
+ {
+ "id": "uint128",
+ "type": "schema",
+ "position": {
+ "x": 2940,
+ "y": 1040
+ },
+ "style": {
+ "width": 260
+ },
+ "data": {
+ "id": "uint128",
+ "kind": "message",
+ "label": "gnmi_ext.Uint128",
+ "sourceSymbol": "gnmi_ext.Uint128",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L71",
+ "fields": [
+ {
+ "id": "high",
+ "type": "uint64",
+ "name": "high",
+ "ref": null
+ },
+ {
+ "id": "low",
+ "type": "uint64",
+ "name": "low",
+ "ref": null
+ }
+ ]
+ }
+ },
+ {
+ "id": "role",
+ "type": "schema",
+ "position": {
+ "x": 2940,
+ "y": 1210
+ },
+ "style": {
+ "width": 260
+ },
+ "data": {
+ "id": "role",
+ "kind": "message",
+ "label": "gnmi_ext.Role",
+ "sourceSymbol": "gnmi_ext.Role",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L77",
+ "fields": [
+ {
+ "id": "id",
+ "type": "string",
+ "name": "id",
+ "ref": null
+ }
+ ]
+ }
+ },
+ {
+ "id": "history",
+ "type": "schema",
+ "position": {
+ "x": 2510,
+ "y": 1260
+ },
+ "style": {
+ "width": 330
+ },
+ "data": {
+ "id": "history",
+ "kind": "message",
+ "label": "gnmi_ext.History",
+ "sourceSymbol": "gnmi_ext.History",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L86",
+ "fields": [
+ {
+ "id": "snapshot-time",
+ "type": "int64",
+ "name": "snapshot_time",
+ "ref": null,
+ "group": "oneof request"
+ },
+ {
+ "id": "range",
+ "type": "TimeRange",
+ "name": "range",
+ "ref": "time-range",
+ "group": "oneof request"
+ }
+ ]
+ }
+ },
+ {
+ "id": "time-range",
+ "type": "schema",
+ "position": {
+ "x": 2940,
+ "y": 1390
+ },
+ "style": {
+ "width": 270
+ },
+ "data": {
+ "id": "time-range",
+ "kind": "message",
+ "label": "gnmi_ext.TimeRange",
+ "sourceSymbol": "gnmi_ext.TimeRange",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L93",
+ "fields": [
+ {
+ "id": "start",
+ "type": "int64",
+ "name": "start",
+ "ref": null
+ },
+ {
+ "id": "end",
+ "type": "int64",
+ "name": "end",
+ "ref": null
+ }
+ ]
+ }
+ },
+ {
+ "id": "commit",
+ "type": "schema",
+ "position": {
+ "x": 2510,
+ "y": 1500
+ },
+ "style": {
+ "width": 390
+ },
+ "data": {
+ "id": "commit",
+ "kind": "message",
+ "label": "gnmi_ext.Commit",
+ "sourceSymbol": "gnmi_ext.Commit",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L104",
+ "fields": [
+ {
+ "id": "id",
+ "type": "string",
+ "name": "id",
+ "ref": null
+ },
+ {
+ "id": "commit",
+ "type": "CommitRequest",
+ "name": "commit",
+ "ref": "commit-request",
+ "group": "oneof action"
+ },
+ {
+ "id": "confirm",
+ "type": "CommitConfirm",
+ "name": "confirm",
+ "ref": "commit-confirm",
+ "group": "oneof action"
+ },
+ {
+ "id": "cancel",
+ "type": "CommitCancel",
+ "name": "cancel",
+ "ref": "commit-cancel",
+ "group": "oneof action"
+ },
+ {
+ "id": "set-rollback-duration",
+ "type": "CommitSetRollbackDuration",
+ "name": "set_rollback_duration",
+ "ref": "commit-set-rollback-duration",
+ "group": "oneof action"
+ }
+ ]
+ }
+ },
+ {
+ "id": "commit-request",
+ "type": "schema",
+ "position": {
+ "x": 2960,
+ "y": 1580
+ },
+ "style": {
+ "width": 360
+ },
+ "data": {
+ "id": "commit-request",
+ "kind": "message",
+ "label": "gnmi_ext.CommitRequest",
+ "sourceSymbol": "gnmi_ext.CommitRequest",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L129",
+ "fields": [
+ {
+ "id": "rollback-duration",
+ "type": "google.protobuf.Duration",
+ "name": "rollback_duration",
+ "ref": "duration"
+ }
+ ]
+ }
+ },
+ {
+ "id": "commit-confirm",
+ "type": "schema",
+ "position": {
+ "x": 2960,
+ "y": 1760
+ },
+ "style": {
+ "width": 300
+ },
+ "data": {
+ "id": "commit-confirm",
+ "kind": "message",
+ "label": "gnmi_ext.CommitConfirm",
+ "sourceSymbol": "gnmi_ext.CommitConfirm",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L136",
+ "fields": []
+ }
+ },
+ {
+ "id": "commit-cancel",
+ "type": "schema",
+ "position": {
+ "x": 2960,
+ "y": 1900
+ },
+ "style": {
+ "width": 300
+ },
+ "data": {
+ "id": "commit-cancel",
+ "kind": "message",
+ "label": "gnmi_ext.CommitCancel",
+ "sourceSymbol": "gnmi_ext.CommitCancel",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L140",
+ "fields": []
+ }
+ },
+ {
+ "id": "commit-set-rollback-duration",
+ "type": "schema",
+ "position": {
+ "x": 2960,
+ "y": 2040
+ },
+ "style": {
+ "width": 400
+ },
+ "data": {
+ "id": "commit-set-rollback-duration",
+ "kind": "message",
+ "label": "gnmi_ext.CommitSetRollbackDuration",
+ "sourceSymbol": "gnmi_ext.CommitSetRollbackDuration",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L144",
+ "fields": [
+ {
+ "id": "rollback-duration",
+ "type": "google.protobuf.Duration",
+ "name": "rollback_duration",
+ "ref": "duration"
+ }
+ ]
+ }
+ },
+ {
+ "id": "duration",
+ "type": "schema",
+ "position": {
+ "x": 3400,
+ "y": 1740
+ },
+ "style": {
+ "width": 300
+ },
+ "data": {
+ "id": "duration",
+ "kind": "external",
+ "label": "google.protobuf.Duration",
+ "protoUrl": "https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/duration.proto",
+ "fields": [
+ {
+ "id": "seconds",
+ "type": "int64",
+ "name": "seconds"
+ },
+ {
+ "id": "nanos",
+ "type": "int32",
+ "name": "nanos"
+ }
+ ]
+ }
+ },
+ {
+ "id": "depth",
+ "type": "schema",
+ "position": {
+ "x": 3350,
+ "y": 850
+ },
+ "style": {
+ "width": 260
+ },
+ "data": {
+ "id": "depth",
+ "kind": "message",
+ "label": "gnmi_ext.Depth",
+ "sourceSymbol": "gnmi_ext.Depth",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L155",
+ "fields": [
+ {
+ "id": "level",
+ "type": "uint32",
+ "name": "level",
+ "ref": null
+ }
+ ]
+ }
+ },
+ {
+ "id": "config-subscription",
+ "type": "schema",
+ "position": {
+ "x": 3350,
+ "y": 1030
+ },
+ "style": {
+ "width": 400
+ },
+ "data": {
+ "id": "config-subscription",
+ "kind": "message",
+ "label": "gnmi_ext.ConfigSubscription",
+ "sourceSymbol": "gnmi_ext.ConfigSubscription",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L166",
+ "fields": [
+ {
+ "id": "start",
+ "type": "ConfigSubscriptionStart",
+ "name": "start",
+ "ref": "config-subscription-start",
+ "group": "oneof action"
+ },
+ {
+ "id": "sync-done",
+ "type": "ConfigSubscriptionSyncDone",
+ "name": "sync_done",
+ "ref": "config-subscription-sync-done",
+ "group": "oneof action"
+ }
+ ]
+ }
+ },
+ {
+ "id": "config-subscription-start",
+ "type": "schema",
+ "position": {
+ "x": 3810,
+ "y": 980
+ },
+ "style": {
+ "width": 360
+ },
+ "data": {
+ "id": "config-subscription-start",
+ "kind": "message",
+ "label": "gnmi_ext.ConfigSubscriptionStart",
+ "sourceSymbol": "gnmi_ext.ConfigSubscriptionStart",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L178",
+ "fields": []
+ }
+ },
+ {
+ "id": "config-subscription-sync-done",
+ "type": "schema",
+ "position": {
+ "x": 3810,
+ "y": 1130
+ },
+ "style": {
+ "width": 400
+ },
+ "data": {
+ "id": "config-subscription-sync-done",
+ "kind": "message",
+ "label": "gnmi_ext.ConfigSubscriptionSyncDone",
+ "sourceSymbol": "gnmi_ext.ConfigSubscriptionSyncDone",
+ "deprecated": false,
+ "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L182",
+ "fields": [
+ {
+ "id": "commit-confirm-id",
+ "type": "string",
+ "name": "commit_confirm_id",
+ "ref": null
+ },
+ {
+ "id": "server-commit-id",
+ "type": "string",
+ "name": "server_commit_id",
+ "ref": null
+ },
+ {
+ "id": "done",
+ "type": "bool",
+ "name": "done",
+ "ref": null
+ }
+ ]
+ }
+ },
+ {
+ "id": "legend",
+ "type": "schema",
+ "position": {
+ "x": 2720,
+ "y": 1470
+ },
+ "style": {
+ "width": 410
+ },
+ "data": {
+ "id": "legend",
+ "kind": "legend",
+ "label": "Legend",
+ "fields": [
+ {
+ "id": "proto",
+ "type": "file icon",
+ "name": "proto definition link"
+ },
+ {
+ "id": "docs",
+ "type": "book icon",
+ "name": "documentation link"
+ },
+ {
+ "id": "extension-note",
+ "type": "toggle",
+ "name": "extension relationship edges"
+ },
+ {
+ "id": "pdf",
+ "type": "reference",
+ "name": "gnmi_0.7.0_map.pdf"
+ }
+ ]
+ }
+ }
+];
+
+export const mapEdges: MapEdge[] = [
+ {
+ "id": "service-gnmi:capabilities->rpc-capabilities",
+ "source": "service-gnmi",
+ "sourceHandle": "capabilities",
+ "target": "rpc-capabilities",
+ "kind": "rpc",
+ "deprecated": false
+ },
+ {
+ "id": "service-gnmi:get->rpc-get",
+ "source": "service-gnmi",
+ "sourceHandle": "get",
+ "target": "rpc-get",
+ "kind": "rpc",
+ "deprecated": false
+ },
+ {
+ "id": "service-gnmi:set->rpc-set",
+ "source": "service-gnmi",
+ "sourceHandle": "set",
+ "target": "rpc-set",
+ "kind": "rpc",
+ "deprecated": false
+ },
+ {
+ "id": "service-gnmi:subscribe->rpc-subscribe",
+ "source": "service-gnmi",
+ "sourceHandle": "subscribe",
+ "target": "rpc-subscribe",
+ "kind": "rpc",
+ "deprecated": false
+ },
+ {
+ "id": "rpc-set:takes->set-request",
+ "source": "rpc-set",
+ "sourceHandle": "takes",
+ "target": "set-request",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "rpc-set:returns->set-response",
+ "source": "rpc-set",
+ "sourceHandle": "returns",
+ "target": "set-response",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "rpc-subscribe:takes->subscribe-request",
+ "source": "rpc-subscribe",
+ "sourceHandle": "takes",
+ "target": "subscribe-request",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "rpc-subscribe:returns->subscribe-response",
+ "source": "rpc-subscribe",
+ "sourceHandle": "returns",
+ "target": "subscribe-response",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "rpc-get:takes->get-request",
+ "source": "rpc-get",
+ "sourceHandle": "takes",
+ "target": "get-request",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "rpc-get:returns->get-response",
+ "source": "rpc-get",
+ "sourceHandle": "returns",
+ "target": "get-response",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "rpc-capabilities:takes->capability-request",
+ "source": "rpc-capabilities",
+ "sourceHandle": "takes",
+ "target": "capability-request",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "rpc-capabilities:returns->capability-response",
+ "source": "rpc-capabilities",
+ "sourceHandle": "returns",
+ "target": "capability-response",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "set-request:prefix->path",
+ "source": "set-request",
+ "sourceHandle": "prefix",
+ "target": "path",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "set-request:delete->path",
+ "source": "set-request",
+ "sourceHandle": "delete",
+ "target": "path",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "set-request:replace->update",
+ "source": "set-request",
+ "sourceHandle": "replace",
+ "target": "update",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "set-request:update->update",
+ "source": "set-request",
+ "sourceHandle": "update",
+ "target": "update",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "set-request:union-replace->update",
+ "source": "set-request",
+ "sourceHandle": "union-replace",
+ "target": "update",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "set-request:extension->extension",
+ "source": "set-request",
+ "sourceHandle": "extension",
+ "target": "extension",
+ "kind": "extension",
+ "deprecated": false
+ },
+ {
+ "id": "set-response:prefix->path",
+ "source": "set-response",
+ "sourceHandle": "prefix",
+ "target": "path",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "set-response:response->update-result",
+ "source": "set-response",
+ "sourceHandle": "response",
+ "target": "update-result",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "set-response:message->error",
+ "source": "set-response",
+ "sourceHandle": "message",
+ "target": "error",
+ "kind": "field",
+ "deprecated": true
+ },
+ {
+ "id": "set-response:extension->extension",
+ "source": "set-response",
+ "sourceHandle": "extension",
+ "target": "extension",
+ "kind": "extension",
+ "deprecated": false
+ },
+ {
+ "id": "subscribe-request:subscribe->subscription-list",
+ "source": "subscribe-request",
+ "sourceHandle": "subscribe",
+ "target": "subscription-list",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "subscribe-request:poll->poll",
+ "source": "subscribe-request",
+ "sourceHandle": "poll",
+ "target": "poll",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "subscribe-request:extension->extension",
+ "source": "subscribe-request",
+ "sourceHandle": "extension",
+ "target": "extension",
+ "kind": "extension",
+ "deprecated": false
+ },
+ {
+ "id": "subscribe-response:update->notification",
+ "source": "subscribe-response",
+ "sourceHandle": "update",
+ "target": "notification",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "subscribe-response:error->error",
+ "source": "subscribe-response",
+ "sourceHandle": "error",
+ "target": "error",
+ "kind": "field",
+ "deprecated": true
+ },
+ {
+ "id": "subscribe-response:extension->extension",
+ "source": "subscribe-response",
+ "sourceHandle": "extension",
+ "target": "extension",
+ "kind": "extension",
+ "deprecated": false
+ },
+ {
+ "id": "get-request:prefix->path",
+ "source": "get-request",
+ "sourceHandle": "prefix",
+ "target": "path",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "get-request:path->path",
+ "source": "get-request",
+ "sourceHandle": "path",
+ "target": "path",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "get-request:type->data-type",
+ "source": "get-request",
+ "sourceHandle": "type",
+ "target": "data-type",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "get-request:encoding->encoding",
+ "source": "get-request",
+ "sourceHandle": "encoding",
+ "target": "encoding",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "get-request:use-models->model-data",
+ "source": "get-request",
+ "sourceHandle": "use-models",
+ "target": "model-data",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "get-request:extension->extension",
+ "source": "get-request",
+ "sourceHandle": "extension",
+ "target": "extension",
+ "kind": "extension",
+ "deprecated": false
+ },
+ {
+ "id": "get-response:notification->notification",
+ "source": "get-response",
+ "sourceHandle": "notification",
+ "target": "notification",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "get-response:error->error",
+ "source": "get-response",
+ "sourceHandle": "error",
+ "target": "error",
+ "kind": "field",
+ "deprecated": true
+ },
+ {
+ "id": "get-response:extension->extension",
+ "source": "get-response",
+ "sourceHandle": "extension",
+ "target": "extension",
+ "kind": "extension",
+ "deprecated": false
+ },
+ {
+ "id": "capability-request:extension->extension",
+ "source": "capability-request",
+ "sourceHandle": "extension",
+ "target": "extension",
+ "kind": "extension",
+ "deprecated": false
+ },
+ {
+ "id": "capability-response:supported-models->model-data",
+ "source": "capability-response",
+ "sourceHandle": "supported-models",
+ "target": "model-data",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "capability-response:supported-encodings->encoding",
+ "source": "capability-response",
+ "sourceHandle": "supported-encodings",
+ "target": "encoding",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "capability-response:extension->extension",
+ "source": "capability-response",
+ "sourceHandle": "extension",
+ "target": "extension",
+ "kind": "extension",
+ "deprecated": false
+ },
+ {
+ "id": "error:data->any",
+ "source": "error",
+ "sourceHandle": "data",
+ "target": "any",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "update-result:path->path",
+ "source": "update-result",
+ "sourceHandle": "path",
+ "target": "path",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "update-result:message->error",
+ "source": "update-result",
+ "sourceHandle": "message",
+ "target": "error",
+ "kind": "field",
+ "deprecated": true
+ },
+ {
+ "id": "update-result:op->operation",
+ "source": "update-result",
+ "sourceHandle": "op",
+ "target": "operation",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "subscription-list:prefix->path",
+ "source": "subscription-list",
+ "sourceHandle": "prefix",
+ "target": "path",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "subscription-list:subscription->subscription",
+ "source": "subscription-list",
+ "sourceHandle": "subscription",
+ "target": "subscription",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "subscription-list:qos->qos-marking",
+ "source": "subscription-list",
+ "sourceHandle": "qos",
+ "target": "qos-marking",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "subscription-list:mode->mode",
+ "source": "subscription-list",
+ "sourceHandle": "mode",
+ "target": "mode",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "subscription-list:use-models->model-data",
+ "source": "subscription-list",
+ "sourceHandle": "use-models",
+ "target": "model-data",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "subscription-list:encoding->encoding",
+ "source": "subscription-list",
+ "sourceHandle": "encoding",
+ "target": "encoding",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "subscription:path->path",
+ "source": "subscription",
+ "sourceHandle": "path",
+ "target": "path",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "subscription:mode->subscription-mode",
+ "source": "subscription",
+ "sourceHandle": "mode",
+ "target": "subscription-mode",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "notification:prefix->path",
+ "source": "notification",
+ "sourceHandle": "prefix",
+ "target": "path",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "notification:update->update",
+ "source": "notification",
+ "sourceHandle": "update",
+ "target": "update",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "notification:delete->path",
+ "source": "notification",
+ "sourceHandle": "delete",
+ "target": "path",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "update:path->path",
+ "source": "update",
+ "sourceHandle": "path",
+ "target": "path",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "update:value->value",
+ "source": "update",
+ "sourceHandle": "value",
+ "target": "value",
+ "kind": "field",
+ "deprecated": true
+ },
+ {
+ "id": "update:val->typed-value",
+ "source": "update",
+ "sourceHandle": "val",
+ "target": "typed-value",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "path:elem->path-elem",
+ "source": "path",
+ "sourceHandle": "elem",
+ "target": "path-elem",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "value:type->encoding",
+ "source": "value",
+ "sourceHandle": "type",
+ "target": "encoding",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "typed-value:decimal-val->decimal64",
+ "source": "typed-value",
+ "sourceHandle": "decimal-val",
+ "target": "decimal64",
+ "kind": "field",
+ "deprecated": true
+ },
+ {
+ "id": "typed-value:leaflist-val->scalar-array",
+ "source": "typed-value",
+ "sourceHandle": "leaflist-val",
+ "target": "scalar-array",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "typed-value:any-val->any",
+ "source": "typed-value",
+ "sourceHandle": "any-val",
+ "target": "any",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "scalar-array:element->typed-value",
+ "source": "scalar-array",
+ "sourceHandle": "element",
+ "target": "typed-value",
+ "kind": "field",
+ "deprecated": false
+ },
+ {
+ "id": "extension:registered-ext->registered-extension",
+ "source": "extension",
+ "sourceHandle": "registered-ext",
+ "target": "registered-extension",
+ "kind": "extension-detail",
+ "deprecated": false
+ },
+ {
+ "id": "extension:master-arbitration->master-arbitration",
+ "source": "extension",
+ "sourceHandle": "master-arbitration",
+ "target": "master-arbitration",
+ "kind": "extension-detail",
+ "deprecated": false
+ },
+ {
+ "id": "extension:history->history",
+ "source": "extension",
+ "sourceHandle": "history",
+ "target": "history",
+ "kind": "extension-detail",
+ "deprecated": false
+ },
+ {
+ "id": "extension:commit->commit",
+ "source": "extension",
+ "sourceHandle": "commit",
+ "target": "commit",
+ "kind": "extension-detail",
+ "deprecated": false
+ },
+ {
+ "id": "extension:depth->depth",
+ "source": "extension",
+ "sourceHandle": "depth",
+ "target": "depth",
+ "kind": "extension-detail",
+ "deprecated": false
+ },
+ {
+ "id": "extension:config-subscription->config-subscription",
+ "source": "extension",
+ "sourceHandle": "config-subscription",
+ "target": "config-subscription",
+ "kind": "extension-detail",
+ "deprecated": false
+ },
+ {
+ "id": "registered-extension:id->extension-id",
+ "source": "registered-extension",
+ "sourceHandle": "id",
+ "target": "extension-id",
+ "kind": "extension-detail",
+ "deprecated": false
+ },
+ {
+ "id": "master-arbitration:role->role",
+ "source": "master-arbitration",
+ "sourceHandle": "role",
+ "target": "role",
+ "kind": "extension-detail",
+ "deprecated": false
+ },
+ {
+ "id": "master-arbitration:election-id->uint128",
+ "source": "master-arbitration",
+ "sourceHandle": "election-id",
+ "target": "uint128",
+ "kind": "extension-detail",
+ "deprecated": false
+ },
+ {
+ "id": "history:range->time-range",
+ "source": "history",
+ "sourceHandle": "range",
+ "target": "time-range",
+ "kind": "extension-detail",
+ "deprecated": false
+ },
+ {
+ "id": "commit:commit->commit-request",
+ "source": "commit",
+ "sourceHandle": "commit",
+ "target": "commit-request",
+ "kind": "extension-detail",
+ "deprecated": false
+ },
+ {
+ "id": "commit:confirm->commit-confirm",
+ "source": "commit",
+ "sourceHandle": "confirm",
+ "target": "commit-confirm",
+ "kind": "extension-detail",
+ "deprecated": false
+ },
+ {
+ "id": "commit:cancel->commit-cancel",
+ "source": "commit",
+ "sourceHandle": "cancel",
+ "target": "commit-cancel",
+ "kind": "extension-detail",
+ "deprecated": false
+ },
+ {
+ "id": "commit:set-rollback-duration->commit-set-rollback-duration",
+ "source": "commit",
+ "sourceHandle": "set-rollback-duration",
+ "target": "commit-set-rollback-duration",
+ "kind": "extension-detail",
+ "deprecated": false
+ },
+ {
+ "id": "commit-request:rollback-duration->duration",
+ "source": "commit-request",
+ "sourceHandle": "rollback-duration",
+ "target": "duration",
+ "kind": "extension-detail",
+ "deprecated": false
+ },
+ {
+ "id": "commit-set-rollback-duration:rollback-duration->duration",
+ "source": "commit-set-rollback-duration",
+ "sourceHandle": "rollback-duration",
+ "target": "duration",
+ "kind": "extension-detail",
+ "deprecated": false
+ },
+ {
+ "id": "config-subscription:start->config-subscription-start",
+ "source": "config-subscription",
+ "sourceHandle": "start",
+ "target": "config-subscription-start",
+ "kind": "extension-detail",
+ "deprecated": false
+ },
+ {
+ "id": "config-subscription:sync-done->config-subscription-sync-done",
+ "source": "config-subscription",
+ "sourceHandle": "sync-done",
+ "target": "config-subscription-sync-done",
+ "kind": "extension-detail",
+ "deprecated": false
+ }
+];
+
+export const mapBounds: MapBounds = {
+ "width": 4260,
+ "height": 2250
+};
+
+export function getVisibleMap({
+ showDeprecated = false,
+ showExtensions = true,
+}: VisibleMapOptions = {}): VisibleMap {
+ const visibleNodes = mapNodes
+ .filter((node) => node.data.kind !== 'legend')
+ .filter((node) => showDeprecated || !node.data.deprecated)
+ .map((node) => ({
+ ...node,
+ data: {
+ ...node.data,
+ fields: (node.data.fields ?? []).filter((field) => showDeprecated || !field.deprecated),
+ },
+ }));
+ const visibleNodeIds = new Set(visibleNodes.map((node) => node.id));
+ const visibleHandles = new Set(
+ visibleNodes.flatMap((node) =>
+ (node.data.fields ?? []).map((field) => `${node.id}:${field.id}`),
+ ),
+ );
+ const visibleEdges = mapEdges.filter((edge) => {
+ if (!showExtensions && edge.kind === 'extension') {
+ return false;
+ }
+ if (!showDeprecated && edge.deprecated) {
+ return false;
+ }
+ return (
+ visibleNodeIds.has(edge.source) &&
+ visibleNodeIds.has(edge.target) &&
+ visibleHandles.has(`${edge.source}:${edge.sourceHandle}`)
+ );
+ });
+
+ return { nodes: visibleNodes, edges: visibleEdges };
+}
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 0000000..f7b11eb
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import { createRoot } from 'react-dom/client';
+import App from './App';
+import './styles.css';
+
+const rootElement = document.getElementById('root');
+
+if (!rootElement) {
+ throw new Error('Root element not found');
+}
+
+createRoot(rootElement).render(
+
+
+ ,
+);
diff --git a/src/mapLayout.ts b/src/mapLayout.ts
new file mode 100644
index 0000000..12ef108
--- /dev/null
+++ b/src/mapLayout.ts
@@ -0,0 +1,797 @@
+import ELK, { type ElkExtendedEdge, type ElkNode } from 'elkjs/lib/elk.bundled.js';
+import type { MapEdge, MapField, MapNode, VisibleMap } from './gnmiMap';
+
+export const nodeHeaderHeight = 36;
+export const nodeBodyPadding = 8;
+export const nodeBadgeHeight = 24;
+export const baseFieldRowHeight = 32;
+export const detailFieldRowHeight = 52;
+export const layoutGapX = 8;
+export const layoutGapY = 18;
+
+const maxLayoutPasses = 50;
+const elk = new ELK();
+const nodeSpacing = 86;
+const layerSpacing = 150;
+const routePadding = 18;
+const routeEndpointOffset = 34;
+const routeOuterMargin = 160;
+const routeBendPenalty = 42;
+
+export type RoutePoint = {
+ x: number;
+ y: number;
+};
+
+export type LayoutBox = {
+ id: string;
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+};
+
+export type TargetHandleLayout = {
+ id: string;
+ edgeId: string;
+ y: number;
+};
+
+export type RoutedLayoutEdge = {
+ edge: MapEdge;
+ targetHandle: string;
+ routePoints: RoutePoint[];
+};
+
+export type ReadableLayout = {
+ nodes: MapNode[];
+ edges: RoutedLayoutEdge[];
+ bounds: LayoutBounds;
+};
+
+export type ManualNodePositions = Record;
+
+export type LayoutBounds = {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+};
+
+export function mapNodeWidth(node: MapNode): number {
+ const width = node.style?.width;
+
+ if (typeof width === 'number') {
+ return width;
+ }
+
+ if (typeof width === 'string') {
+ const parsedWidth = Number.parseFloat(width);
+ return Number.isFinite(parsedWidth) ? parsedWidth : 320;
+ }
+
+ return 320;
+}
+
+export function mapFieldRowHeight(field?: MapField): number {
+ if (!field) {
+ return baseFieldRowHeight;
+ }
+
+ return field.group || field.badge ? detailFieldRowHeight : baseFieldRowHeight;
+}
+
+export function sourceHandleY(node: MapNode, sourceHandle: string): number {
+ const fields = node.data.fields ?? [];
+ let y =
+ nodeHeaderHeight +
+ (node.data.badges?.length ? nodeBadgeHeight : 0) +
+ nodeBodyPadding;
+
+ for (const field of fields) {
+ const fieldHeight = mapFieldRowHeight(field);
+ if (field.id === sourceHandle) {
+ return y + fieldHeight / 2;
+ }
+ y += fieldHeight;
+ }
+
+ return estimatedMapNodeHeight(node) / 2;
+}
+
+export function estimatedMapNodeHeight(node: MapNode): number {
+ const fields = node.data.fields ?? [];
+ const badgeHeight = node.data.badges?.length ? nodeBadgeHeight : 0;
+ const fieldsHeight = fields.length
+ ? fields.reduce((height, field) => height + mapFieldRowHeight(field), 0)
+ : baseFieldRowHeight;
+
+ return nodeHeaderHeight + badgeHeight + nodeBodyPadding * 2 + fieldsHeight;
+}
+
+function boxesOverlap(first: LayoutBox, second: LayoutBox): boolean {
+ return (
+ first.x < second.x + second.width + layoutGapX &&
+ first.x + first.width + layoutGapX > second.x &&
+ first.y < second.y + second.height + layoutGapY &&
+ first.y + first.height + layoutGapY > second.y
+ );
+}
+
+export function improveNodeLayout(nodes: MapNode[]): MapNode[] {
+ const boxes = new Map(
+ nodes.map((node) => [
+ node.id,
+ {
+ id: node.id,
+ x: node.position.x,
+ y: node.position.y,
+ width: mapNodeWidth(node),
+ height: estimatedMapNodeHeight(node),
+ },
+ ]),
+ );
+
+ for (let pass = 0; pass < maxLayoutPasses; pass += 1) {
+ let moved = false;
+ const sortedBoxes = [...boxes.values()].sort((first, second) => {
+ if (first.y !== second.y) {
+ return first.y - second.y;
+ }
+
+ return first.x - second.x;
+ });
+
+ for (let index = 0; index < sortedBoxes.length; index += 1) {
+ const anchor = sortedBoxes[index];
+
+ for (let nextIndex = index + 1; nextIndex < sortedBoxes.length; nextIndex += 1) {
+ const candidate = sortedBoxes[nextIndex];
+
+ if (candidate.y >= anchor.y + anchor.height + layoutGapY) {
+ break;
+ }
+
+ if (!boxesOverlap(anchor, candidate)) {
+ continue;
+ }
+
+ const nextY = anchor.y + anchor.height + layoutGapY;
+ if (candidate.y < nextY) {
+ candidate.y = nextY;
+ moved = true;
+ }
+ }
+ }
+
+ if (!moved) {
+ break;
+ }
+ }
+
+ return nodes.map((node) => {
+ const box = boxes.get(node.id);
+ if (!box || (box.x === node.position.x && box.y === node.position.y)) {
+ return node;
+ }
+
+ return {
+ ...node,
+ position: {
+ x: box.x,
+ y: box.y,
+ },
+ };
+ });
+}
+
+export async function computeReadableNodeLayout(nodes: MapNode[], edges: MapEdge[]): Promise {
+ const graph: ElkNode = {
+ id: 'gnmi-map',
+ layoutOptions: {
+ 'elk.algorithm': 'layered',
+ 'elk.direction': 'RIGHT',
+ 'elk.edgeRouting': 'ORTHOGONAL',
+ 'elk.layered.nodePlacement.strategy': 'BRANDES_KOEPF',
+ 'elk.layered.crossingMinimization.strategy': 'LAYER_SWEEP',
+ 'elk.layered.layering.strategy': 'NETWORK_SIMPLEX',
+ 'elk.layered.spacing.nodeNodeBetweenLayers': `${layerSpacing}`,
+ 'elk.spacing.nodeNode': `${nodeSpacing}`,
+ 'elk.spacing.edgeEdge': '26',
+ 'elk.spacing.edgeNode': '42',
+ 'elk.padding': '[top=40,left=40,bottom=40,right=40]',
+ },
+ children: nodes.map((node) => ({
+ id: node.id,
+ width: mapNodeWidth(node),
+ height: estimatedMapNodeHeight(node),
+ })),
+ edges: edges.map((edge) => ({
+ id: edge.id,
+ sources: [edge.source],
+ targets: [edge.target],
+ })),
+ };
+
+ const result = await elk.layout(graph);
+ const layoutById = new Map((result.children ?? []).map((node) => [node.id, node]));
+
+ return improveNodeLayout(
+ nodes.map((node) => {
+ const layoutNode = layoutById.get(node.id);
+ if (layoutNode?.x === undefined || layoutNode.y === undefined) {
+ return node;
+ }
+
+ return {
+ ...node,
+ position: {
+ x: Math.round(layoutNode.x),
+ y: Math.round(layoutNode.y),
+ },
+ };
+ }),
+ );
+}
+
+export async function computeReadableLayout(
+ visibleMap: VisibleMap,
+ manualPositions: ManualNodePositions = {},
+): Promise {
+ const layoutNodes = await computeReadableNodeLayout(visibleMap.nodes, visibleMap.edges);
+ return routeReadableLayout(applyManualPositions(layoutNodes, manualPositions), visibleMap.edges);
+}
+
+export function applyManualPositions(
+ nodes: MapNode[],
+ manualPositions: ManualNodePositions,
+): MapNode[] {
+ return nodes.map((node) => {
+ const manualPosition = manualPositions[node.id];
+ if (!manualPosition) {
+ return node;
+ }
+
+ return {
+ ...node,
+ position: manualPosition,
+ };
+ });
+}
+
+export function routeReadableLayout(nodes: MapNode[], edges: MapEdge[]): ReadableLayout {
+ const bounds = mapNodesBounds(nodes);
+ const boxes = new Map(
+ nodes.map((node) => [
+ node.id,
+ {
+ id: node.id,
+ x: node.position.x,
+ y: node.position.y,
+ width: mapNodeWidth(node),
+ height: estimatedMapNodeHeight(node),
+ },
+ ]),
+ );
+ const nodesById = new Map(nodes.map((node) => [node.id, node]));
+ const targetHandlesByNode = targetHandles(nodes, edges, boxes);
+ const targetHandleByEdge = new Map(
+ [...targetHandlesByNode.values()]
+ .flat()
+ .map((handle) => [handle.edgeId, handle] as const),
+ );
+ const nodesWithTargetHandles = nodes.map((node) => ({
+ ...node,
+ data: {
+ ...node.data,
+ targetHandles: targetHandlesByNode.get(node.id) ?? [],
+ },
+ }));
+
+ return {
+ nodes: nodesWithTargetHandles,
+ edges: edges.map((edge) => {
+ const targetHandle = targetHandleByEdge.get(edge.id);
+ return {
+ edge,
+ targetHandle: targetHandle?.id ?? targetHandleId(edge),
+ routePoints: routeEdge(edge, nodesById, boxes, bounds, targetHandle),
+ };
+ }),
+ bounds,
+ };
+}
+
+export function routeIntersectsNode(
+ routePoints: RoutePoint[],
+ node: MapNode,
+ padding = 0,
+): boolean {
+ const box = expandedBox(
+ {
+ id: node.id,
+ x: node.position.x,
+ y: node.position.y,
+ width: mapNodeWidth(node),
+ height: estimatedMapNodeHeight(node),
+ },
+ padding,
+ );
+
+ return routeSegments(routePoints).some(([start, end]) => segmentIntersectsBox(start, end, box));
+}
+
+export function mapNodesBounds(nodes: MapNode[]): LayoutBounds {
+ if (!nodes.length) {
+ return { x: 0, y: 0, width: 0, height: 0 };
+ }
+
+ const minX = Math.min(...nodes.map((node) => node.position.x));
+ const minY = Math.min(...nodes.map((node) => node.position.y));
+ const maxX = Math.max(...nodes.map((node) => node.position.x + mapNodeWidth(node)));
+ const maxY = Math.max(...nodes.map((node) => node.position.y + estimatedMapNodeHeight(node)));
+
+ return {
+ x: minX,
+ y: minY,
+ width: maxX - minX,
+ height: maxY - minY,
+ };
+}
+
+function targetHandles(
+ nodes: MapNode[],
+ edges: MapEdge[],
+ boxes: Map,
+): Map {
+ const nodesById = new Map(nodes.map((node) => [node.id, node]));
+ const incoming = new Map();
+
+ for (const edge of edges) {
+ incoming.set(edge.target, [...(incoming.get(edge.target) ?? []), edge]);
+ }
+
+ const handles = new Map();
+
+ for (const [nodeId, nodeEdges] of incoming) {
+ const node = nodesById.get(nodeId);
+ const box = boxes.get(nodeId);
+ if (!node || !box) {
+ continue;
+ }
+
+ const sortedEdges = [...nodeEdges].sort((first, second) => {
+ const firstSource = boxes.get(first.source);
+ const secondSource = boxes.get(second.source);
+ if (!firstSource || !secondSource) {
+ return first.id.localeCompare(second.id);
+ }
+
+ const yDiff = boxCenterY(firstSource) - boxCenterY(secondSource);
+ return yDiff || firstSource.x - secondSource.x || first.id.localeCompare(second.id);
+ });
+ const nodeHeight = estimatedMapNodeHeight(node);
+ const top = Math.min(nodeHeaderHeight + 18, nodeHeight / 2);
+ const bottom = Math.max(top + 1, nodeHeight - 18);
+
+ handles.set(
+ nodeId,
+ sortedEdges.map((edge, index) => {
+ const ratio = sortedEdges.length === 1 ? 0.5 : (index + 1) / (sortedEdges.length + 1);
+ return {
+ id: targetHandleId(edge),
+ edgeId: edge.id,
+ y: Math.round(top + (bottom - top) * ratio),
+ };
+ }),
+ );
+ }
+
+ return handles;
+}
+
+function routeEdge(
+ edge: MapEdge,
+ nodesById: Map,
+ boxes: Map,
+ bounds: LayoutBounds,
+ targetHandle?: TargetHandleLayout,
+): RoutePoint[] {
+ const source = nodesById.get(edge.source);
+ const target = nodesById.get(edge.target);
+ const sourceBox = boxes.get(edge.source);
+ const targetBox = boxes.get(edge.target);
+
+ if (!source || !target || !sourceBox || !targetBox) {
+ return [];
+ }
+
+ const start: RoutePoint = {
+ x: sourceBox.x + sourceBox.width,
+ y: sourceBox.y + sourceHandleY(source, edge.sourceHandle),
+ };
+ const end: RoutePoint = {
+ x: targetBox.x,
+ y: targetBox.y + (targetHandle?.y ?? targetBox.height / 2),
+ };
+ const routeStart = { x: start.x + routeEndpointOffset, y: start.y };
+ const routeEnd = { x: end.x - routeEndpointOffset, y: end.y };
+ const obstacles = [...boxes.values()].map((box) => expandedBox(box, routePadding));
+ const routed = findOrthogonalRoute(routeStart, routeEnd, obstacles, bounds);
+
+ return compactRoute([
+ start,
+ routeStart,
+ ...(routed ?? fallbackRoute(routeStart, routeEnd, bounds)).slice(1, -1),
+ routeEnd,
+ end,
+ ]);
+}
+
+function findOrthogonalRoute(
+ start: RoutePoint,
+ end: RoutePoint,
+ obstacles: LayoutBox[],
+ bounds: LayoutBounds,
+): RoutePoint[] | null {
+ const xCoordinates = uniqueSortedNumbers([
+ start.x,
+ end.x,
+ bounds.x - routeOuterMargin,
+ bounds.x + bounds.width + routeOuterMargin,
+ ...obstacles.flatMap((box) => [box.x - routePadding, box.x + box.width + routePadding]),
+ ]);
+ const yCoordinates = uniqueSortedNumbers([
+ start.y,
+ end.y,
+ bounds.y - routeOuterMargin,
+ bounds.y + bounds.height + routeOuterMargin,
+ ...obstacles.flatMap((box) => [box.y - routePadding, box.y + box.height + routePadding]),
+ ]);
+ const points: RoutePoint[] = [];
+ const pointIndex = new Map();
+
+ for (const y of yCoordinates) {
+ for (const x of xCoordinates) {
+ const point = { x, y };
+ if (obstacles.some((box) => pointInsideBox(point, box))) {
+ continue;
+ }
+
+ pointIndex.set(pointKey(point), points.length);
+ points.push(point);
+ }
+ }
+
+ const startIndex = pointIndex.get(pointKey(start));
+ const endIndex = pointIndex.get(pointKey(end));
+ if (startIndex === undefined || endIndex === undefined) {
+ return null;
+ }
+
+ const adjacency = buildRouteAdjacency(points, obstacles);
+ const path = shortestRoute(points, adjacency, startIndex, endIndex);
+ return path ? compactRoute(path.map((index) => points[index])) : null;
+}
+
+function buildRouteAdjacency(points: RoutePoint[], obstacles: LayoutBox[]): number[][] {
+ const adjacency = Array.from({ length: points.length }, () => [] as number[]);
+ const rows = new Map();
+ const columns = new Map();
+
+ points.forEach((point, index) => {
+ rows.set(point.y, [...(rows.get(point.y) ?? []), index]);
+ columns.set(point.x, [...(columns.get(point.x) ?? []), index]);
+ });
+
+ for (const row of rows.values()) {
+ row.sort((first, second) => points[first].x - points[second].x);
+ connectVisibleNeighbors(row, adjacency, points, obstacles);
+ }
+
+ for (const column of columns.values()) {
+ column.sort((first, second) => points[first].y - points[second].y);
+ connectVisibleNeighbors(column, adjacency, points, obstacles);
+ }
+
+ return adjacency;
+}
+
+function connectVisibleNeighbors(
+ sortedIndexes: number[],
+ adjacency: number[][],
+ points: RoutePoint[],
+ obstacles: LayoutBox[],
+): void {
+ for (let index = 0; index < sortedIndexes.length - 1; index += 1) {
+ const first = sortedIndexes[index];
+ const second = sortedIndexes[index + 1];
+ if (obstacles.some((box) => segmentIntersectsBox(points[first], points[second], box))) {
+ continue;
+ }
+
+ adjacency[first].push(second);
+ adjacency[second].push(first);
+ }
+}
+
+function shortestRoute(
+ points: RoutePoint[],
+ adjacency: number[][],
+ startIndex: number,
+ endIndex: number,
+): number[] | null {
+ const directions = 3;
+ const directionStart = 0;
+ const directionHorizontal = 1;
+ const directionVertical = 2;
+ const totalStates = points.length * directions;
+ const distances = Array.from({ length: totalStates }, () => Number.POSITIVE_INFINITY);
+ const previous = Array<{ state: number; pointIndex: number } | null>(totalStates).fill(null);
+ const heap = new RouteHeap();
+ const startState = routeState(startIndex, directionStart);
+ distances[startState] = 0;
+ heap.push({ state: startState, distance: 0, priority: 0 });
+
+ while (heap.size) {
+ const current = heap.pop();
+ if (!current || current.distance !== distances[current.state]) {
+ continue;
+ }
+
+ const currentPointIndex = Math.floor(current.state / directions);
+ const currentDirection = current.state % directions;
+ if (currentPointIndex === endIndex) {
+ return reconstructRoute(previous, current.state);
+ }
+
+ for (const nextPointIndex of adjacency[currentPointIndex]) {
+ const nextDirection =
+ points[currentPointIndex].x === points[nextPointIndex].x
+ ? directionVertical
+ : directionHorizontal;
+ const bendCost =
+ currentDirection !== directionStart && currentDirection !== nextDirection
+ ? routeBendPenalty
+ : 0;
+ const nextState = routeState(nextPointIndex, nextDirection);
+ const nextDistance =
+ distances[current.state] +
+ manhattanDistance(points[currentPointIndex], points[nextPointIndex]) +
+ bendCost;
+
+ if (nextDistance >= distances[nextState]) {
+ continue;
+ }
+
+ distances[nextState] = nextDistance;
+ previous[nextState] = { state: current.state, pointIndex: currentPointIndex };
+ heap.push({
+ state: nextState,
+ distance: nextDistance,
+ priority: nextDistance + manhattanDistance(points[nextPointIndex], points[endIndex]),
+ });
+ }
+ }
+
+ return null;
+}
+
+function reconstructRoute(
+ previous: Array<{ state: number; pointIndex: number } | null>,
+ endState: number,
+): number[] {
+ const directions = 3;
+ const path = [Math.floor(endState / directions)];
+ let currentState = endState;
+
+ while (previous[currentState]) {
+ const currentPrevious = previous[currentState];
+ if (!currentPrevious) {
+ break;
+ }
+
+ path.push(currentPrevious.pointIndex);
+ currentState = currentPrevious.state;
+ }
+
+ return path.reverse();
+}
+
+function routeState(pointIndex: number, direction: number): number {
+ return pointIndex * 3 + direction;
+}
+
+function fallbackRoute(start: RoutePoint, end: RoutePoint, bounds: LayoutBounds): RoutePoint[] {
+ const y =
+ Math.abs(start.y - (bounds.y - routeOuterMargin)) <
+ Math.abs(start.y - (bounds.y + bounds.height + routeOuterMargin))
+ ? bounds.y + bounds.height + routeOuterMargin
+ : bounds.y - routeOuterMargin;
+
+ return compactRoute([start, { x: start.x, y }, { x: end.x, y }, end]);
+}
+
+function compactRoute(points: RoutePoint[]): RoutePoint[] {
+ const deduped = points.filter((point, index) => {
+ const previous = points[index - 1];
+ return !previous || previous.x !== point.x || previous.y !== point.y;
+ });
+ const compacted: RoutePoint[] = [];
+
+ for (const point of deduped) {
+ const previous = compacted[compacted.length - 1];
+ const beforePrevious = compacted[compacted.length - 2];
+ if (
+ previous &&
+ beforePrevious &&
+ ((beforePrevious.x === previous.x && previous.x === point.x) ||
+ (beforePrevious.y === previous.y && previous.y === point.y))
+ ) {
+ compacted[compacted.length - 1] = point;
+ continue;
+ }
+
+ compacted.push(point);
+ }
+
+ return compacted;
+}
+
+function routeSegments(points: RoutePoint[]): Array<[RoutePoint, RoutePoint]> {
+ const segments: Array<[RoutePoint, RoutePoint]> = [];
+ for (let index = 0; index < points.length - 1; index += 1) {
+ segments.push([points[index], points[index + 1]]);
+ }
+ return segments;
+}
+
+function segmentIntersectsBox(start: RoutePoint, end: RoutePoint, box: LayoutBox): boolean {
+ if (start.y === end.y) {
+ const y = start.y;
+ if (y <= box.y || y >= box.y + box.height) {
+ return false;
+ }
+
+ return rangesOverlap(start.x, end.x, box.x, box.x + box.width);
+ }
+
+ if (start.x === end.x) {
+ const x = start.x;
+ if (x <= box.x || x >= box.x + box.width) {
+ return false;
+ }
+
+ return rangesOverlap(start.y, end.y, box.y, box.y + box.height);
+ }
+
+ return false;
+}
+
+function rangesOverlap(firstStart: number, firstEnd: number, secondStart: number, secondEnd: number): boolean {
+ const firstMin = Math.min(firstStart, firstEnd);
+ const firstMax = Math.max(firstStart, firstEnd);
+ const secondMin = Math.min(secondStart, secondEnd);
+ const secondMax = Math.max(secondStart, secondEnd);
+ return firstMin < secondMax && firstMax > secondMin;
+}
+
+function expandedBox(box: LayoutBox, padding: number): LayoutBox {
+ return {
+ id: box.id,
+ x: box.x - padding,
+ y: box.y - padding,
+ width: box.width + padding * 2,
+ height: box.height + padding * 2,
+ };
+}
+
+function pointInsideBox(point: RoutePoint, box: LayoutBox): boolean {
+ return (
+ point.x > box.x &&
+ point.x < box.x + box.width &&
+ point.y > box.y &&
+ point.y < box.y + box.height
+ );
+}
+
+function pointKey(point: RoutePoint): string {
+ return `${point.x}:${point.y}`;
+}
+
+function uniqueSortedNumbers(values: number[]): number[] {
+ return [...new Set(values.map((value) => Math.round(value)))].sort((first, second) => first - second);
+}
+
+function manhattanDistance(first: RoutePoint, second: RoutePoint): number {
+ return Math.abs(first.x - second.x) + Math.abs(first.y - second.y);
+}
+
+function boxCenterY(box: LayoutBox): number {
+ return box.y + box.height / 2;
+}
+
+function targetHandleId(edge: MapEdge): string {
+ return `target-${edge.id.replace(/[^A-Za-z0-9_-]/g, '-')}`;
+}
+
+type HeapItem = {
+ state: number;
+ distance: number;
+ priority: number;
+};
+
+class RouteHeap {
+ private readonly items: HeapItem[] = [];
+
+ get size(): number {
+ return this.items.length;
+ }
+
+ push(item: HeapItem): void {
+ this.items.push(item);
+ this.bubbleUp(this.items.length - 1);
+ }
+
+ pop(): HeapItem | undefined {
+ if (!this.items.length) {
+ return undefined;
+ }
+
+ const top = this.items[0];
+ const last = this.items.pop();
+ if (last && this.items.length) {
+ this.items[0] = last;
+ this.bubbleDown(0);
+ }
+
+ return top;
+ }
+
+ private bubbleUp(index: number): void {
+ let currentIndex = index;
+ while (currentIndex > 0) {
+ const parentIndex = Math.floor((currentIndex - 1) / 2);
+ if (this.items[parentIndex].priority <= this.items[currentIndex].priority) {
+ return;
+ }
+
+ [this.items[parentIndex], this.items[currentIndex]] = [
+ this.items[currentIndex],
+ this.items[parentIndex],
+ ];
+ currentIndex = parentIndex;
+ }
+ }
+
+ private bubbleDown(index: number): void {
+ let currentIndex = index;
+ while (true) {
+ const leftIndex = currentIndex * 2 + 1;
+ const rightIndex = currentIndex * 2 + 2;
+ let smallestIndex = currentIndex;
+
+ if (
+ leftIndex < this.items.length &&
+ this.items[leftIndex].priority < this.items[smallestIndex].priority
+ ) {
+ smallestIndex = leftIndex;
+ }
+ if (
+ rightIndex < this.items.length &&
+ this.items[rightIndex].priority < this.items[smallestIndex].priority
+ ) {
+ smallestIndex = rightIndex;
+ }
+ if (smallestIndex === currentIndex) {
+ return;
+ }
+
+ [this.items[currentIndex], this.items[smallestIndex]] = [
+ this.items[smallestIndex],
+ this.items[currentIndex],
+ ];
+ currentIndex = smallestIndex;
+ }
+ }
+}
diff --git a/src/styles.css b/src/styles.css
new file mode 100644
index 0000000..91d5322
--- /dev/null
+++ b/src/styles.css
@@ -0,0 +1,609 @@
+:root {
+ --page-bg: #f4f6f8;
+ --panel: #ffffff;
+ --panel-soft: #f8fafc;
+ --ink: #172033;
+ --muted: #677486;
+ --line: #cfd8e3;
+ --blue: #0b4b8f;
+ --blue-soft: #d9eaf9;
+ --teal: #16736b;
+ --teal-soft: #dcefeb;
+ --amber: #a15c03;
+ --amber-soft: #fbebd3;
+ --gray: #4d5a6b;
+ --focus: #2474c9;
+ font-family:
+ Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html,
+body,
+#root {
+ height: 100%;
+ margin: 0;
+}
+
+body {
+ background: var(--page-bg);
+ color: var(--ink);
+}
+
+button,
+input {
+ font: inherit;
+}
+
+a {
+ color: inherit;
+}
+
+.app-shell {
+ display: grid;
+ grid-template-rows: auto 1fr;
+ height: 100%;
+ min-width: 0;
+}
+
+.topbar {
+ z-index: 5;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 20px;
+ min-height: 76px;
+ padding: 12px 18px;
+ border-bottom: 1px solid var(--line);
+ background: rgba(255, 255, 255, 0.94);
+ backdrop-filter: blur(10px);
+}
+
+.brand {
+ min-width: 178px;
+}
+
+.brand-kicker,
+.inspector-kicker {
+ display: block;
+ color: var(--muted);
+ font-size: 11px;
+ font-weight: 700;
+ line-height: 1.2;
+ text-transform: uppercase;
+}
+
+.brand h1,
+.inspector h2 {
+ margin: 2px 0 0;
+ font-size: 21px;
+ line-height: 1.2;
+}
+
+.toolbar {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 8px;
+ min-width: 0;
+}
+
+.search-box {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ width: min(360px, 44vw);
+ min-width: 210px;
+ height: 38px;
+ padding: 0 11px;
+ border: 1px solid var(--line);
+ border-radius: 7px;
+ background: var(--panel);
+ color: var(--muted);
+}
+
+.search-box input {
+ min-width: 0;
+ width: 100%;
+ border: 0;
+ outline: 0;
+ background: transparent;
+ color: var(--ink);
+}
+
+.tool-button {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 7px;
+ height: 38px;
+ padding: 0 12px;
+ border: 1px solid var(--line);
+ border-radius: 7px;
+ background: var(--panel);
+ color: var(--ink);
+ cursor: pointer;
+ font-size: 14px;
+ font-weight: 700;
+ text-decoration: none;
+}
+
+.tool-button[data-tooltip]::after {
+ position: absolute;
+ top: calc(100% + 8px);
+ right: 0;
+ z-index: 20;
+ width: max-content;
+ max-width: 270px;
+ padding: 8px 10px;
+ border: 1px solid #27364a;
+ border-radius: 6px;
+ background: #172033;
+ color: #ffffff;
+ content: attr(data-tooltip);
+ font-size: 12px;
+ font-weight: 700;
+ line-height: 1.35;
+ opacity: 0;
+ pointer-events: none;
+ text-align: left;
+ transform: translateY(-2px);
+ transition:
+ opacity 140ms ease,
+ transform 140ms ease;
+ white-space: normal;
+}
+
+.tool-button[data-tooltip]:hover::after,
+.tool-button[data-tooltip]:focus-visible::after {
+ opacity: 1;
+ transform: translateY(0);
+}
+
+.tool-button:hover,
+.tool-button:focus-visible,
+.tool-button.is-active {
+ border-color: var(--focus);
+ color: var(--focus);
+ outline: none;
+}
+
+.tool-button:disabled {
+ border-color: var(--line);
+ color: var(--muted);
+ cursor: not-allowed;
+ opacity: 0.56;
+}
+
+.map-stage {
+ position: relative;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 330px;
+ min-height: 0;
+}
+
+.react-flow {
+ width: 100%;
+ height: 100%;
+ background:
+ linear-gradient(90deg, rgba(11, 75, 143, 0.04), transparent 36%),
+ linear-gradient(0deg, rgba(22, 115, 107, 0.04), transparent 42%),
+ var(--page-bg);
+}
+
+.react-flow__node {
+ font-family: inherit;
+}
+
+.schema-node {
+ overflow: hidden;
+ border: 1px solid #b8c5d3;
+ border-radius: 8px;
+ background: var(--panel);
+ box-shadow: 0 8px 22px rgba(31, 41, 55, 0.09);
+ color: var(--ink);
+ transition:
+ box-shadow 160ms ease,
+ opacity 160ms ease,
+ transform 160ms ease;
+}
+
+.schema-node.is-selected {
+ border-color: var(--focus);
+ box-shadow:
+ 0 0 0 3px rgba(36, 116, 201, 0.16),
+ 0 10px 28px rgba(31, 41, 55, 0.16);
+}
+
+.schema-node.is-edge-endpoint {
+ border-color: #d21f3c;
+ box-shadow:
+ 0 0 0 3px rgba(210, 31, 60, 0.16),
+ 0 10px 28px rgba(31, 41, 55, 0.14);
+}
+
+.schema-node.is-dimmed {
+ opacity: 0.22;
+}
+
+.node-header {
+ display: grid;
+ grid-template-columns: auto minmax(0, 1fr) auto;
+ align-items: center;
+ gap: 8px;
+ min-height: 36px;
+ padding: 8px 10px;
+ background: var(--teal);
+ color: #fff;
+}
+
+.kind-service .node-header {
+ background: var(--blue);
+}
+
+.kind-rpc .node-header {
+ background: #1c62a0;
+}
+
+.kind-enum .node-header {
+ background: var(--amber);
+}
+
+.kind-external .node-header,
+.kind-legend .node-header {
+ background: var(--gray);
+}
+
+.node-kind {
+ padding: 2px 6px;
+ border-radius: 999px;
+ background: rgba(255, 255, 255, 0.16);
+ color: rgba(255, 255, 255, 0.9);
+ font-size: 10px;
+ font-weight: 800;
+ line-height: 1.2;
+ text-transform: uppercase;
+}
+
+.node-header strong {
+ overflow: hidden;
+ font-size: 14px;
+ line-height: 1.2;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.node-links {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+}
+
+.node-links a {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ border-radius: 6px;
+ background: rgba(255, 255, 255, 0.14);
+}
+
+.node-links a:hover,
+.node-links a:focus-visible {
+ background: rgba(255, 255, 255, 0.25);
+ outline: none;
+}
+
+.node-badges {
+ display: flex;
+ gap: 6px;
+ padding: 7px 10px 0;
+}
+
+.node-badges span,
+.field-badge,
+.field-group {
+ display: inline-flex;
+ align-items: center;
+ min-height: 18px;
+ padding: 2px 6px;
+ border-radius: 999px;
+ font-size: 10px;
+ font-weight: 800;
+ line-height: 1;
+ text-transform: uppercase;
+}
+
+.node-badges span,
+.badge-deprecated {
+ background: #fee2df;
+ color: #9b1c15;
+}
+
+.badge-optional {
+ background: var(--amber-soft);
+ color: #744200;
+}
+
+.badge-reserved {
+ background: #e7ebf0;
+ color: #4d5a6b;
+}
+
+.field-group {
+ background: var(--blue-soft);
+ color: #164675;
+}
+
+.node-body {
+ padding: 8px;
+}
+
+.field-row,
+.empty-field {
+ position: relative;
+ display: grid;
+ grid-template-columns: minmax(86px, 0.9fr) minmax(84px, 1fr);
+ align-items: center;
+ column-gap: 8px;
+ row-gap: 5px;
+ min-height: 30px;
+ padding: 6px 9px;
+ border-radius: 6px;
+ font-size: 12px;
+ line-height: 1.25;
+}
+
+.field-row:nth-child(odd) {
+ background: var(--panel-soft);
+}
+
+.field-row.has-ref {
+ padding-right: 18px;
+}
+
+.field-row.is-highlighted {
+ background: #fff5c7;
+ box-shadow: inset 0 0 0 1px #ecd676;
+}
+
+.field-row.is-clickable {
+ cursor: pointer;
+}
+
+.field-row.is-clickable:hover,
+.field-row.is-clickable:focus-visible {
+ background: #edf6ff;
+ box-shadow: inset 0 0 0 1px rgba(36, 116, 201, 0.34);
+ outline: none;
+}
+
+.field-row.is-edge-highlighted {
+ background: #ffe4e8;
+ box-shadow: inset 0 0 0 1px rgba(210, 31, 60, 0.44);
+}
+
+.field-row.is-deprecated .field-type,
+.field-row.is-deprecated .field-name {
+ text-decoration: line-through;
+ text-decoration-color: rgba(155, 28, 21, 0.55);
+}
+
+.field-type {
+ overflow-wrap: anywhere;
+ color: #526071;
+ font-weight: 700;
+}
+
+.field-name {
+ overflow-wrap: anywhere;
+ font-family:
+ "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
+ font-weight: 700;
+}
+
+.field-group,
+.field-badge {
+ grid-column: span 1;
+ justify-self: start;
+}
+
+.empty-field {
+ display: flex;
+ color: var(--muted);
+ font-style: italic;
+}
+
+.react-flow__handle {
+ width: 8px;
+ height: 8px;
+ border: 2px solid #fff;
+ background: #2d6f97;
+}
+
+.node-target {
+ left: -5px;
+ opacity: 0;
+}
+
+.field-handle {
+ right: -12px;
+ background: #2d6f97;
+}
+
+.kind-enum .field-handle,
+.kind-enum .react-flow__handle {
+ background: var(--amber);
+}
+
+.flow-edge-rpc .react-flow__edge-path {
+ filter: drop-shadow(0 1px 1px rgba(11, 75, 143, 0.16));
+}
+
+.flow-edge .react-flow__edge-path {
+ transition:
+ opacity 160ms ease,
+ stroke 160ms ease,
+ stroke-width 160ms ease;
+}
+
+.flow-edge-halo {
+ fill: none;
+ opacity: 0.72;
+ pointer-events: none;
+ stroke: #ffffff;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+}
+
+.flow-edge-bridge,
+.flow-edge-bridge-gap {
+ fill: none;
+ pointer-events: none;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+}
+
+.flow-edge-bridge-gap {
+ stroke: var(--page-bg);
+ stroke-width: 8px;
+}
+
+.flow-edge-bridge {
+ stroke-dasharray: none;
+}
+
+.flow-edge.is-selected .react-flow__edge-path {
+ filter: drop-shadow(0 2px 3px rgba(210, 31, 60, 0.28));
+}
+
+.flow-edge.is-dimmed .react-flow__edge-path {
+ pointer-events: stroke;
+}
+
+.react-flow__controls {
+ overflow: hidden;
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ box-shadow: 0 8px 20px rgba(31, 41, 55, 0.12);
+}
+
+.react-flow__controls-button {
+ border-bottom-color: var(--line);
+}
+
+.inspector {
+ position: relative;
+ z-index: 4;
+ width: auto;
+ max-height: none;
+ overflow: auto;
+ padding: 16px;
+ border: 0;
+ border-left: 1px solid var(--line);
+ border-radius: 0;
+ background: rgba(255, 255, 255, 0.96);
+ box-shadow: -8px 0 20px rgba(31, 41, 55, 0.07);
+}
+
+.inspector p {
+ margin: 10px 0 0;
+ color: var(--muted);
+ font-size: 14px;
+ line-height: 1.45;
+}
+
+.inspector-actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-top: 12px;
+}
+
+.inspector-actions a {
+ display: inline-flex;
+ align-items: center;
+ gap: 7px;
+ min-height: 32px;
+ padding: 6px 9px;
+ border: 1px solid var(--line);
+ border-radius: 7px;
+ color: var(--blue);
+ font-size: 13px;
+ font-weight: 800;
+ text-decoration: none;
+}
+
+.inspector-actions a:hover,
+.inspector-actions a:focus-visible {
+ border-color: var(--blue);
+ outline: none;
+}
+
+.inspector-fields {
+ display: grid;
+ gap: 6px;
+ margin-top: 14px;
+}
+
+.inspector-field {
+ display: grid;
+ grid-template-columns: minmax(72px, 0.8fr) minmax(96px, 1fr);
+ gap: 8px;
+ padding: 7px 8px;
+ border-radius: 6px;
+ background: var(--panel-soft);
+ font-size: 12px;
+}
+
+.inspector-field span {
+ overflow-wrap: anywhere;
+ color: var(--muted);
+ font-weight: 700;
+}
+
+.inspector-field strong {
+ overflow-wrap: anywhere;
+ font-family:
+ "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
+}
+
+@media (max-width: 760px) {
+ .topbar {
+ align-items: stretch;
+ flex-direction: column;
+ gap: 10px;
+ }
+
+ .toolbar {
+ justify-content: flex-start;
+ }
+
+ .search-box {
+ width: 100%;
+ }
+
+ .tool-button {
+ flex: 1 1 120px;
+ }
+
+ .map-stage {
+ grid-template-columns: 1fr;
+ grid-template-rows: minmax(420px, 1fr) auto;
+ }
+
+ .inspector {
+ position: relative;
+ width: auto;
+ max-height: 34vh;
+ border-top: 1px solid var(--line);
+ border-left: 0;
+ box-shadow: 0 -8px 20px rgba(31, 41, 55, 0.08);
+ }
+}
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 0000000..2ab5f4e
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1,4 @@
+///
+
+declare module '*.css';
+declare module '@xyflow/react/dist/style.css';
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..de6c207
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["DOM", "DOM.Iterable", "ES2022"],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "types": ["node", "react", "react-dom"]
+ },
+ "include": ["src", "scripts", "vite.config.ts"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..71bf3c2
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,19 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+function normalizeBasePath(basePath: string | undefined): string {
+ if (!basePath) {
+ return '/';
+ }
+
+ const withLeadingSlash = /^[a-z]+:\/\//i.test(basePath) || basePath.startsWith('/')
+ ? basePath
+ : `/${basePath}`;
+
+ return withLeadingSlash.endsWith('/') ? withLeadingSlash : `${withLeadingSlash}/`;
+}
+
+export default defineConfig({
+ base: normalizeBasePath(process.env.BASE_PATH),
+ plugins: [react()],
+});