diff --git a/Makefile b/Makefile index dc2e39f235..0a27720c26 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .POSIX: .PHONY: all -all: fmt gen lint build test +all: fmt gen js lint build test .PHONY: fmt fmt: @@ -21,3 +21,6 @@ test: fmt .PHONY: race race: fmt prefix "$@" ./ci/test.sh --race ./... +.PHONY: js +js: + cd d2js/js && prefix "$@" ./make.sh diff --git a/d2js/README.md b/d2js/README.md deleted file mode 100644 index 6728d72af6..0000000000 --- a/d2js/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# D2 as a Javascript library - -D2 is runnable as a Javascript library, on both the client and server side. This means you -can run D2 entirely on the browser. - -This is achieved by a JS wrapper around a WASM file. - -## Install - -### NPM - -```sh -npm install @terrastruct/d2 -``` - -### Yarn - -```sh -yarn add @terrastruct/d2 -``` - -## Build - -```sh -GOOS=js GOARCH=wasm go build -ldflags='-s -w' -trimpath -o main.wasm ./d2js -``` - -## API - -todo diff --git a/d2js/d2wasm/functions.go b/d2js/d2wasm/functions.go index d82965a69e..3a2a70330c 100644 --- a/d2js/d2wasm/functions.go +++ b/d2js/d2wasm/functions.go @@ -151,7 +151,7 @@ func Compile(args []js.Value) (interface{}, error) { renderOpts := &d2svg.RenderOpts{} var fontFamily *d2fonts.FontFamily - if input.Opts != nil && input.Opts.Sketch != nil { + if input.Opts != nil && input.Opts.Sketch != nil && *input.Opts.Sketch { fontFamily = go2.Pointer(d2fonts.HandDrawn) renderOpts.Sketch = input.Opts.Sketch } diff --git a/d2js/js/.gitignore b/d2js/js/.gitignore new file mode 100644 index 0000000000..8c8dfb799c --- /dev/null +++ b/d2js/js/.gitignore @@ -0,0 +1,27 @@ +node_modules +.npm +bun.lockb + +wasm/d2.wasm +dist/ + +.vscode/ +.idea/ +*.swp +*.swo +.DS_Store +Thumbs.db + +logs +*.log +npm-debug.log* + +coverage/ + +.env +.env.local +.env.*.local + +*.tmp +*.temp +.cache/ diff --git a/d2js/js/CHANGELOG.md b/d2js/js/CHANGELOG.md new file mode 100644 index 0000000000..542c516295 --- /dev/null +++ b/d2js/js/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +All notable changes to only the d2.js package will be documented in this file. **Does not +include changes to the main d2 project.** + +## [0.1.0] - 2025-01-12 + +First public release diff --git a/d2js/js/Makefile b/d2js/js/Makefile new file mode 100644 index 0000000000..a8b662792f --- /dev/null +++ b/d2js/js/Makefile @@ -0,0 +1,20 @@ +.POSIX: +.PHONY: all +all: fmt build test + +.PHONY: fmt +fmt: node_modules + prefix "$@" ../../ci/sub/bin/fmt.sh + prefix "$@" rm -f yarn.lock + +.PHONY: build +build: node_modules + prefix "$@" ./ci/build.sh + +.PHONY: test +test: build + prefix "$@" bun test:all + +.PHONY: node_modules +node_modules: + prefix "$@" bun install $${CI:+--frozen-lockfile} diff --git a/d2js/js/README.md b/d2js/js/README.md new file mode 100644 index 0000000000..6ce7597c00 --- /dev/null +++ b/d2js/js/README.md @@ -0,0 +1,112 @@ +# D2.js + +[![npm version](https://badge.fury.io/js/%40terrastruct%2Fd2.svg)](https://www.npmjs.com/package/@terrastruct/d2) +[![License: MPL-2.0](https://img.shields.io/badge/License-MPL_2.0-brightgreen.svg)](https://mozilla.org/MPL/2.0/) + +D2.js is a JavaScript wrapper around D2, the modern diagram scripting language. It enables running D2 directly in browsers and Node environments through WebAssembly. + +## Features + +- 🌐 **Universal** - Works in both browser and Node environments +- 🚀 **Modern** - Built with ESM modules, with CJS fallback +- 🔄 **Isomorphic** - Same API everywhere +- ⚡ **Fast** - Powered by WebAssembly for near-native performance +- 📦 **Lightweight** - Minimal wrapper around the core D2 engine + +## Installation + +```bash +# npm +npm install @terrastruct/d2 + +# yarn +yarn add @terrastruct/d2 + +# pnpm +pnpm add @terrastruct/d2 + +# bun +bun add @terrastruct/d2 +``` + +## Usage + +### Browser + +```javascript +import { D2 } from '@terrastruct/d2'; + +const d2 = new D2(); + +const result = await d2.compile('x -> y'); +const svg = await d2.render(result.diagram); + +const result = await d2.compile('x -> y', { + layout: 'dagre', + sketch: true +}); +``` + +### Node + +```javascript +import { D2 } from '@terrastruct/d2'; + +const d2 = new D2(); + +async function createDiagram() { + const result = await d2.compile('x -> y'); + const svg = await d2.render(result.diagram); + console.log(svg); +} + +createDiagram(); +``` + +## API Reference + +### `new D2()` +Creates a new D2 instance. + +### `compile(input: string, options?: CompileOptions): Promise` +Compiles D2 markup into an intermediate representation. + +Options: +- `layout`: Layout engine to use ('dagre' | 'elk') [default: 'dagre'] +- `sketch`: Enable sketch mode [default: false] + +### `render(diagram: Diagram, options?: RenderOptions): Promise` +Renders a compiled diagram to SVG. + +## Development + +D2.js uses Bun, so install this first. + +### Building from source + +```bash +git clone https://github.com/terrastruct/d2.git +cd d2/d2js/js +./make.sh +``` + +If you change the main D2 source code, you should regenerate the WASM file: +```bash +./make.sh build +``` + +### Running the Development Server + +```bash +bun run dev +``` + +Visit `http://localhost:3000` to see the example page. + +## Contributing + +Contributions are welcome! + +## License + +This project is licensed under the Mozilla Public License Version 2.0. diff --git a/d2js/js/build.js b/d2js/js/build.js new file mode 100644 index 0000000000..403c02d7c8 --- /dev/null +++ b/d2js/js/build.js @@ -0,0 +1,35 @@ +import { build } from "bun"; +import { copyFile, mkdir } from "node:fs/promises"; +import { join } from "node:path"; + +await mkdir("./dist/esm", { recursive: true }); +await mkdir("./dist/cjs", { recursive: true }); + +const commonConfig = { + target: "node", + splitting: false, + sourcemap: "external", + minify: true, + naming: { + entry: "[dir]/[name].js", + chunk: "[name]-[hash].js", + asset: "[name]-[hash][ext]", + }, +}; + +async function buildAndCopy(format) { + const outdir = `./dist/${format}`; + + await build({ + ...commonConfig, + entrypoints: ["./src/index.js", "./src/worker.js", "./src/platform.js"], + outdir, + format, + }); + + await copyFile("./wasm/d2.wasm", join(outdir, "d2.wasm")); + await copyFile("./wasm/wasm_exec.js", join(outdir, "wasm_exec.js")); +} + +await buildAndCopy("esm"); +await buildAndCopy("cjs"); diff --git a/d2js/js/bun.lockb b/d2js/js/bun.lockb new file mode 100755 index 0000000000..c2ef20a7e6 Binary files /dev/null and b/d2js/js/bun.lockb differ diff --git a/d2js/js/ci/build.sh b/d2js/js/ci/build.sh new file mode 100755 index 0000000000..9fc0a7392c --- /dev/null +++ b/d2js/js/ci/build.sh @@ -0,0 +1,18 @@ +#!/bin/sh +set -eu +. "$(dirname "$0")/../../../ci/sub/lib.sh" +cd -- "$(dirname "$0")/.." + +cd ../.. +sh_c "GOOS=js GOARCH=wasm go build -ldflags='-s -w' -trimpath -o main.wasm ./d2js" +sh_c "mv main.wasm ./d2js/js/wasm/d2.wasm" + +if [ ! -f ./d2js/js/wasm/d2.wasm ]; then + echoerr "Error: d2.wasm is missing" + exit 1 +else + stat --printf="Size: %s bytes\n" ./d2js/js/wasm/d2.wasm || ls -lh ./d2js/js/wasm/d2.wasm +fi + +cd d2js/js +sh_c bun run build diff --git a/d2js/js/dev-server.js b/d2js/js/dev-server.js new file mode 100644 index 0000000000..c5ed267cad --- /dev/null +++ b/d2js/js/dev-server.js @@ -0,0 +1,57 @@ +const MIME_TYPES = { + ".html": "text/html", + ".js": "text/javascript", + ".mjs": "text/javascript", + ".css": "text/css", + ".wasm": "application/wasm", + ".svg": "image/svg+xml", +}; + +const server = Bun.serve({ + port: 3000, + async fetch(request) { + const url = new URL(request.url); + let path = url.pathname; + + // Serve index page by default + if (path === "/") { + path = "/examples/basic.html"; + } + + // Handle attempts to access files in src + if (path.startsWith("/src/")) { + const wasmFile = path.includes("wasm_exec.js") || path.includes("d2.wasm"); + if (wasmFile) { + path = path.replace("/src/", "/wasm/"); + } + } + + try { + const filePath = path.slice(1); + const file = Bun.file(filePath); + const exists = await file.exists(); + + if (!exists) { + return new Response(`File not found: ${path}`, { status: 404 }); + } + + // Get file extension and corresponding MIME type + const ext = "." + filePath.split(".").pop(); + const mimeType = MIME_TYPES[ext] || "application/octet-stream"; + + return new Response(file, { + headers: { + "Content-Type": mimeType, + "Access-Control-Allow-Origin": "*", + "Cross-Origin-Opener-Policy": "same-origin", + "Cross-Origin-Embedder-Policy": "require-corp", + }, + }); + } catch (err) { + console.error(`Error serving ${path}:`, err); + return new Response(`Server error: ${err.message}`, { status: 500 }); + } + }, +}); + +console.log(`Server running at http://localhost:3000`); diff --git a/d2js/js/examples/basic.html b/d2js/js/examples/basic.html new file mode 100644 index 0000000000..3248f1b9c5 --- /dev/null +++ b/d2js/js/examples/basic.html @@ -0,0 +1,49 @@ + + + + + + +
+ + +
+
+ + + diff --git a/d2js/js/make.sh b/d2js/js/make.sh new file mode 100755 index 0000000000..318dff2e03 --- /dev/null +++ b/d2js/js/make.sh @@ -0,0 +1,23 @@ +#!/bin/sh +set -eu +if [ ! -e "$(dirname "$0")/../../ci/sub/.git" ]; then + set -x + git submodule update --init + set +x +fi +. "$(dirname "$0")/../../ci/sub/lib.sh" +PATH="$(cd -- "$(dirname "$0")" && pwd)/../../ci/sub/bin:$PATH" +cd -- "$(dirname "$0")" + +if ! command -v bun >/dev/null 2>&1; then + if [ -n "${CI-}" ]; then + echo "Bun is not installed. Installing Bun..." + curl -fsSL https://bun.sh/install | bash + export PATH="$HOME/.bun/bin:$PATH" + else + echoerr "You need bun to build d2.js: curl -fsSL https://bun.sh/install | bash" + exit 1 + fi +fi + +_make "$@" diff --git a/d2js/js/package.json b/d2js/js/package.json new file mode 100644 index 0000000000..2bf48ecf61 --- /dev/null +++ b/d2js/js/package.json @@ -0,0 +1,53 @@ +{ + "name": "@terrastruct/d2", + "author": "Terrastruct, Inc.", + "description": "D2.js is a wrapper around the WASM build of D2, the modern text-to-diagram language.", + "version": "0.1.0", + "repository": { + "type": "git", + "url": "https://github.com/terrastruct/d2.git", + "directory": "d2js/js" + }, + "bugs": { + "url": "https://github.com/terrastruct/d2/issues" + }, + "homepage": "https://github.com/terrastruct/d2/tree/master/d2js/js#readme", + "publishConfig": { + "access": "public" + }, + "type": "module", + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "exports": { + ".": { + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "bun build.js", + "test": "bun test test/unit", + "test:integration": "bun run build && bun test test/integration", + "test:all": "bun run test && bun run test:integration", + "dev": "bun --watch dev-server.js", + "prepublishOnly": "./make.sh" + }, + "keywords": [ + "d2", + "d2lang", + "diagram", + "wasm", + "text-to-diagram", + "go" + ], + "engines": { + "bun": ">=1.0.0" + }, + "license": "MPL-2.0", + "devDependencies": { + "bun": "latest" + } +} diff --git a/d2js/js/src/index.js b/d2js/js/src/index.js new file mode 100644 index 0000000000..0f8f38e1d5 --- /dev/null +++ b/d2js/js/src/index.js @@ -0,0 +1,107 @@ +import { createWorker, loadFile } from "./platform.js"; + +const DEFAULT_OPTIONS = { + layout: "dagre", + sketch: false, +}; + +export class D2 { + constructor() { + this.ready = this.init(); + } + + setupMessageHandler() { + const isNode = typeof window === "undefined"; + return new Promise((resolve, reject) => { + if (isNode) { + this.worker.on("message", (data) => { + if (data.type === "ready") resolve(); + if (data.type === "error") reject(new Error(data.error)); + if (data.type === "result" && this.currentResolve) { + this.currentResolve(data.data); + } + if (data.type === "error" && this.currentReject) { + this.currentReject(new Error(data.error)); + } + }); + } else { + this.worker.onmessage = (e) => { + if (e.data.type === "ready") resolve(); + if (e.data.type === "error") reject(new Error(e.data.error)); + if (e.data.type === "result" && this.currentResolve) { + this.currentResolve(e.data.data); + } + if (e.data.type === "error" && this.currentReject) { + this.currentReject(new Error(e.data.error)); + } + }; + } + }); + } + + async init() { + this.worker = await createWorker(); + + const wasmExecContent = await loadFile("./wasm_exec.js"); + const wasmBinary = await loadFile("./d2.wasm"); + + const isNode = typeof window === "undefined"; + const messageHandler = this.setupMessageHandler(); + + if (isNode) { + this.worker.on("error", (error) => { + console.error("Worker encountered an error:", error.message || error); + }); + } else { + this.worker.onerror = (error) => { + console.error("Worker encountered an error:", error.message || error); + }; + } + + this.worker.postMessage({ + type: "init", + data: { + wasm: wasmBinary, + wasmExecContent: isNode ? wasmExecContent.toString() : null, + wasmExecUrl: isNode + ? null + : URL.createObjectURL( + new Blob([wasmExecContent], { type: "application/javascript" }) + ), + }, + }); + + return messageHandler; + } + + async sendMessage(type, data) { + await this.ready; + return new Promise((resolve, reject) => { + this.currentResolve = resolve; + this.currentReject = reject; + this.worker.postMessage({ type, data }); + }); + } + + async compile(input, options = {}) { + const opts = { ...DEFAULT_OPTIONS, ...options }; + const request = + typeof input === "string" + ? { fs: { index: input }, options: opts } + : { ...input, options: { ...opts, ...input.options } }; + return this.sendMessage("compile", request); + } + + async render(diagram, options = {}) { + const opts = { ...DEFAULT_OPTIONS, ...options }; + return this.sendMessage("render", { diagram, options: opts }); + } + + async encode(script) { + return this.sendMessage("encode", script); + } + + async decode(encoded) { + return this.sendMessage("decode", encoded); + } +} diff --git a/d2js/js/src/platform.js b/d2js/js/src/platform.js new file mode 100644 index 0000000000..7a767037a8 --- /dev/null +++ b/d2js/js/src/platform.js @@ -0,0 +1,37 @@ +export async function loadFile(path) { + if (typeof window === "undefined") { + const fs = await import("node:fs/promises"); + const { fileURLToPath } = await import("node:url"); + const { join, dirname } = await import("node:path"); + const __dirname = dirname(fileURLToPath(import.meta.url)); + + try { + return await fs.readFile(join(__dirname, path)); + } catch (err) { + if (err.code === "ENOENT") { + return await fs.readFile(join(__dirname, "../wasm", path.replace("./", ""))); + } + throw err; + } + } + try { + const response = await fetch(new URL(path, import.meta.url)); + return await response.arrayBuffer(); + } catch { + const response = await fetch( + new URL(`../wasm/${path.replace("./", "")}`, import.meta.url) + ); + return await response.arrayBuffer(); + } +} + +export async function createWorker() { + if (typeof window === "undefined") { + const { Worker } = await import("node:worker_threads"); + const { fileURLToPath } = await import("node:url"); + const { join, dirname } = await import("node:path"); + const __dirname = dirname(fileURLToPath(import.meta.url)); + return new Worker(join(__dirname, "worker.js")); + } + return new window.Worker(new URL("./worker.js", import.meta.url)); +} diff --git a/d2js/js/src/worker.js b/d2js/js/src/worker.js new file mode 100644 index 0000000000..08bf6f633a --- /dev/null +++ b/d2js/js/src/worker.js @@ -0,0 +1,94 @@ +const isNode = typeof process !== "undefined" && process.release?.name === "node"; +let currentPort; +let wasm; +let d2; + +async function initWasm(wasmBinary) { + const go = new Go(); + const result = await WebAssembly.instantiate(wasmBinary, go.importObject); + go.run(result.instance); + return isNode ? global.d2 : self.d2; +} + +function setupMessageHandler(port) { + currentPort = port; + if (isNode) { + port.on("message", handleMessage); + } else { + port.onmessage = (e) => handleMessage(e.data); + } +} + +async function handleMessage(e) { + const { type, data } = e; + + switch (type) { + case "init": + try { + if (isNode) { + eval(data.wasmExecContent); + } else { + importScripts(data.wasmExecUrl); + } + d2 = await initWasm(data.wasm); + currentPort.postMessage({ type: "ready" }); + } catch (err) { + currentPort.postMessage({ + type: "error", + error: err.message, + }); + } + break; + + case "compile": + try { + const result = await d2.compile(JSON.stringify(data)); + const response = JSON.parse(result); + if (response.error) { + throw new Error(response.error.message); + } + currentPort.postMessage({ + type: "result", + data: response.data, + }); + } catch (err) { + currentPort.postMessage({ + type: "error", + error: err.message, + }); + } + break; + + case "render": + try { + const result = await d2.render(JSON.stringify(data)); + const response = JSON.parse(result); + if (response.error) { + throw new Error(response.error.message); + } + currentPort.postMessage({ + type: "result", + data: atob(response.data), + }); + } catch (err) { + currentPort.postMessage({ + type: "error", + error: err.message, + }); + } + break; + } +} + +async function init() { + if (isNode) { + const { parentPort } = await import("node:worker_threads"); + setupMessageHandler(parentPort); + } else { + setupMessageHandler(self); + } +} + +init().catch((err) => { + console.error("Initialization error:", err); +}); diff --git a/d2js/js/test/integration/cjs.test.js b/d2js/js/test/integration/cjs.test.js new file mode 100644 index 0000000000..527f43d73a --- /dev/null +++ b/d2js/js/test/integration/cjs.test.js @@ -0,0 +1,11 @@ +import { expect, test, describe } from "bun:test"; + +describe("D2 CJS Integration", () => { + test("can require and use CJS build", async () => { + const { D2 } = require("../../dist/cjs/index.js"); + const d2 = new D2(); + const result = await d2.compile("x -> y"); + expect(result.diagram).toBeDefined(); + await d2.worker.terminate(); + }, 20000); +}); diff --git a/d2js/js/test/integration/esm.test.js b/d2js/js/test/integration/esm.test.js new file mode 100644 index 0000000000..830a62a036 --- /dev/null +++ b/d2js/js/test/integration/esm.test.js @@ -0,0 +1,11 @@ +import { expect, test, describe } from "bun:test"; +import { D2 } from "../../dist/esm/index.js"; + +describe("D2 ESM Integration", () => { + test("can import and use ESM build", async () => { + const d2 = new D2(); + const result = await d2.compile("x -> y"); + expect(result.diagram).toBeDefined(); + await d2.worker.terminate(); + }, 20000); +}); diff --git a/d2js/js/test/unit/basic.test.js b/d2js/js/test/unit/basic.test.js new file mode 100644 index 0000000000..4b169ec1d2 --- /dev/null +++ b/d2js/js/test/unit/basic.test.js @@ -0,0 +1,32 @@ +import { expect, test, describe } from "bun:test"; +import { D2 } from "../../src/index.js"; + +describe("D2 Unit Tests", () => { + test("basic compilation works", async () => { + const d2 = new D2(); + const result = await d2.compile("x -> y"); + expect(result.diagram).toBeDefined(); + await d2.worker.terminate(); + }, 20000); + + test("render works", async () => { + const d2 = new D2(); + const result = await d2.compile("x -> y"); + const svg = await d2.render(result.diagram); + expect(svg).toContain(""); + await d2.worker.terminate(); + }, 20000); + + test("handles syntax errors correctly", async () => { + const d2 = new D2(); + try { + await d2.compile("invalid -> -> syntax"); + throw new Error("Should have thrown syntax error"); + } catch (err) { + expect(err).toBeDefined(); + expect(err.message).not.toContain("Should have thrown syntax error"); + } + await d2.worker.terminate(); + }, 20000); +}); diff --git a/d2js/js/wasm/wasm_exec.js b/d2js/js/wasm/wasm_exec.js new file mode 100644 index 0000000000..22adf20b01 --- /dev/null +++ b/d2js/js/wasm/wasm_exec.js @@ -0,0 +1,477 @@ +"use strict"; +(() => { + const o = () => { + const h = new Error("not implemented"); + return (h.code = "ENOSYS"), h; + }; + if (!globalThis.fs) { + let h = ""; + globalThis.fs = { + constants: { + O_WRONLY: -1, + O_RDWR: -1, + O_CREAT: -1, + O_TRUNC: -1, + O_APPEND: -1, + O_EXCL: -1, + }, + writeSync(n, s) { + h += y.decode(s); + const i = h.lastIndexOf(` +`); + return ( + i != -1 && (console.log(h.substring(0, i)), (h = h.substring(i + 1))), s.length + ); + }, + write(n, s, i, r, f, u) { + if (i !== 0 || r !== s.length || f !== null) { + u(o()); + return; + } + const d = this.writeSync(n, s); + u(null, d); + }, + chmod(n, s, i) { + i(o()); + }, + chown(n, s, i, r) { + r(o()); + }, + close(n, s) { + s(o()); + }, + fchmod(n, s, i) { + i(o()); + }, + fchown(n, s, i, r) { + r(o()); + }, + fstat(n, s) { + s(o()); + }, + fsync(n, s) { + s(null); + }, + ftruncate(n, s, i) { + i(o()); + }, + lchown(n, s, i, r) { + r(o()); + }, + link(n, s, i) { + i(o()); + }, + lstat(n, s) { + s(o()); + }, + mkdir(n, s, i) { + i(o()); + }, + open(n, s, i, r) { + r(o()); + }, + read(n, s, i, r, f, u) { + u(o()); + }, + readdir(n, s) { + s(o()); + }, + readlink(n, s) { + s(o()); + }, + rename(n, s, i) { + i(o()); + }, + rmdir(n, s) { + s(o()); + }, + stat(n, s) { + s(o()); + }, + symlink(n, s, i) { + i(o()); + }, + truncate(n, s, i) { + i(o()); + }, + unlink(n, s) { + s(o()); + }, + utimes(n, s, i, r) { + r(o()); + }, + }; + } + if ( + (globalThis.process || + (globalThis.process = { + getuid() { + return -1; + }, + getgid() { + return -1; + }, + geteuid() { + return -1; + }, + getegid() { + return -1; + }, + getgroups() { + throw o(); + }, + pid: -1, + ppid: -1, + umask() { + throw o(); + }, + cwd() { + throw o(); + }, + chdir() { + throw o(); + }, + }), + !globalThis.crypto) + ) + throw new Error( + "globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)" + ); + if (!globalThis.performance) + throw new Error( + "globalThis.performance is not available, polyfill required (performance.now only)" + ); + if (!globalThis.TextEncoder) + throw new Error("globalThis.TextEncoder is not available, polyfill required"); + if (!globalThis.TextDecoder) + throw new Error("globalThis.TextDecoder is not available, polyfill required"); + const g = new TextEncoder("utf-8"), + y = new TextDecoder("utf-8"); + globalThis.Go = class { + constructor() { + (this.argv = ["js"]), + (this.env = {}), + (this.exit = (t) => { + t !== 0 && console.warn("exit code:", t); + }), + (this._exitPromise = new Promise((t) => { + this._resolveExitPromise = t; + })), + (this._pendingEvent = null), + (this._scheduledTimeouts = new Map()), + (this._nextCallbackTimeoutID = 1); + const h = (t, e) => { + this.mem.setUint32(t + 0, e, !0), + this.mem.setUint32(t + 4, Math.floor(e / 4294967296), !0); + }, + n = (t, e) => { + this.mem.setUint32(t + 0, e, !0); + }, + s = (t) => { + const e = this.mem.getUint32(t + 0, !0), + l = this.mem.getInt32(t + 4, !0); + return e + l * 4294967296; + }, + i = (t) => { + const e = this.mem.getFloat64(t, !0); + if (e === 0) return; + if (!isNaN(e)) return e; + const l = this.mem.getUint32(t, !0); + return this._values[l]; + }, + r = (t, e) => { + if (typeof e == "number" && e !== 0) { + if (isNaN(e)) { + this.mem.setUint32(t + 4, 2146959360, !0), this.mem.setUint32(t, 0, !0); + return; + } + this.mem.setFloat64(t, e, !0); + return; + } + if (e === void 0) { + this.mem.setFloat64(t, 0, !0); + return; + } + let a = this._ids.get(e); + a === void 0 && + ((a = this._idPool.pop()), + a === void 0 && (a = this._values.length), + (this._values[a] = e), + (this._goRefCounts[a] = 0), + this._ids.set(e, a)), + this._goRefCounts[a]++; + let c = 0; + switch (typeof e) { + case "object": + e !== null && (c = 1); + break; + case "string": + c = 2; + break; + case "symbol": + c = 3; + break; + case "function": + c = 4; + break; + } + this.mem.setUint32(t + 4, 2146959360 | c, !0), this.mem.setUint32(t, a, !0); + }, + f = (t) => { + const e = s(t + 0), + l = s(t + 8); + return new Uint8Array(this._inst.exports.mem.buffer, e, l); + }, + u = (t) => { + const e = s(t + 0), + l = s(t + 8), + a = new Array(l); + for (let c = 0; c < l; c++) a[c] = i(e + c * 8); + return a; + }, + d = (t) => { + const e = s(t + 0), + l = s(t + 8); + return y.decode(new DataView(this._inst.exports.mem.buffer, e, l)); + }, + m = Date.now() - performance.now(); + this.importObject = { + _gotest: { add: (t, e) => t + e }, + gojs: { + "runtime.wasmExit": (t) => { + t >>>= 0; + const e = this.mem.getInt32(t + 8, !0); + (this.exited = !0), + delete this._inst, + delete this._values, + delete this._goRefCounts, + delete this._ids, + delete this._idPool, + this.exit(e); + }, + "runtime.wasmWrite": (t) => { + t >>>= 0; + const e = s(t + 8), + l = s(t + 16), + a = this.mem.getInt32(t + 24, !0); + fs.writeSync(e, new Uint8Array(this._inst.exports.mem.buffer, l, a)); + }, + "runtime.resetMemoryDataView": (t) => { + (t >>>= 0), (this.mem = new DataView(this._inst.exports.mem.buffer)); + }, + "runtime.nanotime1": (t) => { + (t >>>= 0), h(t + 8, (m + performance.now()) * 1e6); + }, + "runtime.walltime": (t) => { + t >>>= 0; + const e = new Date().getTime(); + h(t + 8, e / 1e3), this.mem.setInt32(t + 16, (e % 1e3) * 1e6, !0); + }, + "runtime.scheduleTimeoutEvent": (t) => { + t >>>= 0; + const e = this._nextCallbackTimeoutID; + this._nextCallbackTimeoutID++, + this._scheduledTimeouts.set( + e, + setTimeout(() => { + for (this._resume(); this._scheduledTimeouts.has(e); ) + console.warn("scheduleTimeoutEvent: missed timeout event"), + this._resume(); + }, s(t + 8)) + ), + this.mem.setInt32(t + 16, e, !0); + }, + "runtime.clearTimeoutEvent": (t) => { + t >>>= 0; + const e = this.mem.getInt32(t + 8, !0); + clearTimeout(this._scheduledTimeouts.get(e)), + this._scheduledTimeouts.delete(e); + }, + "runtime.getRandomData": (t) => { + (t >>>= 0), crypto.getRandomValues(f(t + 8)); + }, + "syscall/js.finalizeRef": (t) => { + t >>>= 0; + const e = this.mem.getUint32(t + 8, !0); + if ((this._goRefCounts[e]--, this._goRefCounts[e] === 0)) { + const l = this._values[e]; + (this._values[e] = null), this._ids.delete(l), this._idPool.push(e); + } + }, + "syscall/js.stringVal": (t) => { + (t >>>= 0), r(t + 24, d(t + 8)); + }, + "syscall/js.valueGet": (t) => { + t >>>= 0; + const e = Reflect.get(i(t + 8), d(t + 16)); + (t = this._inst.exports.getsp() >>> 0), r(t + 32, e); + }, + "syscall/js.valueSet": (t) => { + (t >>>= 0), Reflect.set(i(t + 8), d(t + 16), i(t + 32)); + }, + "syscall/js.valueDelete": (t) => { + (t >>>= 0), Reflect.deleteProperty(i(t + 8), d(t + 16)); + }, + "syscall/js.valueIndex": (t) => { + (t >>>= 0), r(t + 24, Reflect.get(i(t + 8), s(t + 16))); + }, + "syscall/js.valueSetIndex": (t) => { + (t >>>= 0), Reflect.set(i(t + 8), s(t + 16), i(t + 24)); + }, + "syscall/js.valueCall": (t) => { + t >>>= 0; + try { + const e = i(t + 8), + l = Reflect.get(e, d(t + 16)), + a = u(t + 32), + c = Reflect.apply(l, e, a); + (t = this._inst.exports.getsp() >>> 0), + r(t + 56, c), + this.mem.setUint8(t + 64, 1); + } catch (e) { + (t = this._inst.exports.getsp() >>> 0), + r(t + 56, e), + this.mem.setUint8(t + 64, 0); + } + }, + "syscall/js.valueInvoke": (t) => { + t >>>= 0; + try { + const e = i(t + 8), + l = u(t + 16), + a = Reflect.apply(e, void 0, l); + (t = this._inst.exports.getsp() >>> 0), + r(t + 40, a), + this.mem.setUint8(t + 48, 1); + } catch (e) { + (t = this._inst.exports.getsp() >>> 0), + r(t + 40, e), + this.mem.setUint8(t + 48, 0); + } + }, + "syscall/js.valueNew": (t) => { + t >>>= 0; + try { + const e = i(t + 8), + l = u(t + 16), + a = Reflect.construct(e, l); + (t = this._inst.exports.getsp() >>> 0), + r(t + 40, a), + this.mem.setUint8(t + 48, 1); + } catch (e) { + (t = this._inst.exports.getsp() >>> 0), + r(t + 40, e), + this.mem.setUint8(t + 48, 0); + } + }, + "syscall/js.valueLength": (t) => { + (t >>>= 0), h(t + 16, parseInt(i(t + 8).length)); + }, + "syscall/js.valuePrepareString": (t) => { + t >>>= 0; + const e = g.encode(String(i(t + 8))); + r(t + 16, e), h(t + 24, e.length); + }, + "syscall/js.valueLoadString": (t) => { + t >>>= 0; + const e = i(t + 8); + f(t + 16).set(e); + }, + "syscall/js.valueInstanceOf": (t) => { + (t >>>= 0), this.mem.setUint8(t + 24, i(t + 8) instanceof i(t + 16) ? 1 : 0); + }, + "syscall/js.copyBytesToGo": (t) => { + t >>>= 0; + const e = f(t + 8), + l = i(t + 32); + if (!(l instanceof Uint8Array || l instanceof Uint8ClampedArray)) { + this.mem.setUint8(t + 48, 0); + return; + } + const a = l.subarray(0, e.length); + e.set(a), h(t + 40, a.length), this.mem.setUint8(t + 48, 1); + }, + "syscall/js.copyBytesToJS": (t) => { + t >>>= 0; + const e = i(t + 8), + l = f(t + 16); + if (!(e instanceof Uint8Array || e instanceof Uint8ClampedArray)) { + this.mem.setUint8(t + 48, 0); + return; + } + const a = l.subarray(0, e.length); + e.set(a), h(t + 40, a.length), this.mem.setUint8(t + 48, 1); + }, + debug: (t) => { + console.log(t); + }, + }, + }; + } + async run(h) { + if (!(h instanceof WebAssembly.Instance)) + throw new Error("Go.run: WebAssembly.Instance expected"); + (this._inst = h), + (this.mem = new DataView(this._inst.exports.mem.buffer)), + (this._values = [NaN, 0, null, !0, !1, globalThis, this]), + (this._goRefCounts = new Array(this._values.length).fill(1 / 0)), + (this._ids = new Map([ + [0, 1], + [null, 2], + [!0, 3], + [!1, 4], + [globalThis, 5], + [this, 6], + ])), + (this._idPool = []), + (this.exited = !1); + let n = 4096; + const s = (m) => { + const t = n, + e = g.encode(m + "\0"); + return ( + new Uint8Array(this.mem.buffer, n, e.length).set(e), + (n += e.length), + n % 8 !== 0 && (n += 8 - (n % 8)), + t + ); + }, + i = this.argv.length, + r = []; + this.argv.forEach((m) => { + r.push(s(m)); + }), + r.push(0), + Object.keys(this.env) + .sort() + .forEach((m) => { + r.push(s(`${m}=${this.env[m]}`)); + }), + r.push(0); + const u = n; + if ( + (r.forEach((m) => { + this.mem.setUint32(n, m, !0), this.mem.setUint32(n + 4, 0, !0), (n += 8); + }), + n >= 12288) + ) + throw new Error( + "total length of command line and environment variables exceeds limit" + ); + this._inst.exports.run(i, u), + this.exited && this._resolveExitPromise(), + await this._exitPromise; + } + _resume() { + if (this.exited) throw new Error("Go program has already exited"); + this._inst.exports.resume(), this.exited && this._resolveExitPromise(); + } + _makeFuncWrapper(h) { + const n = this; + return function () { + const s = { id: h, this: this, args: arguments }; + return (n._pendingEvent = s), n._resume(), s.result; + }; + } + }; +})();