From 6e668ab6fc40923d028117085d14f5e2a132b10d Mon Sep 17 00:00:00 2001 From: Robin Date: Wed, 10 Jun 2026 13:03:52 +0200 Subject: [PATCH] feat: add LLRT native executor prototype Rationale: Explore LLRT as the preferred lightweight execution runtime by adding a standalone TypeScript-friendly @robinbraemer/llrt package, napi-rs native bridge, codemode executor adapter, native packaging workflow, and benchmark/stress evidence. Rejected: Do not replace the fallback executors yet; LLRT still needs a real GitHub Actions native matrix run and artifact inspection before production-default adoption. Do not embed LLRT directly into codemode; the standalone package keeps native packaging and runtime API reusable. Risk: Introduces a new Rust/native package and release workflow. The prototype is intentionally JSON-safe and fresh-VM-per-call, which is safer but may leave reusable-VM performance for later. Tested: mise exec -- task ci; mise exec actionlint -- actionlint .github/workflows/*.yml; mise exec -- pnpm --filter @robinbraemer/llrt run verify:native-artifacts; mise exec -- npm pack --dry-run from packages/llrt; benchmark:executors report generation. Not-tested: Real GitHub Actions native matrix run across macOS and Linux runners; publishing to npm; CNAP repository consumption of the published package. --- .github/workflows/llrt-native.yml | 146 + .gitignore | 5 + Taskfile.yml | 2 +- .../2026-06-10-standalone-llrt-calljson.md | 180 + .../2026-06-10-llrt-executor-benchmark.json | 74 + .../2026-06-10-llrt-executor-benchmark.md | 31 + ...andalone-llrt-typescript-runtime-design.md | 326 ++ package.json | 6 +- packages/codemode/package.json | 7 + .../codemode/scripts/benchmark-executors.ts | 188 + packages/codemode/src/executor/auto.ts | 60 +- .../codemode/src/executor/benchmark-cli.ts | 40 + .../codemode/src/executor/benchmark-report.ts | 116 + packages/codemode/src/executor/llrt-native.ts | 207 + .../codemode/src/executor/llrt-process.ts | 164 + packages/codemode/src/index.ts | 3 + packages/codemode/src/types.ts | 7 +- packages/codemode/test/auto-executor.test.ts | 37 + packages/codemode/test/benchmark-cli.test.ts | 34 + .../codemode/test/benchmark-report.test.ts | 96 + .../test/llrt-native-executor.test.ts | 58 + .../test/llrt-process-executor.test.ts | 74 + packages/codemode/tsconfig.json | 1 + packages/codemode/tsup.config.ts | 2 +- packages/llrt/LICENSE | 21 + packages/llrt/README.md | 122 + packages/llrt/native/Cargo.lock | 3358 +++++++++++++++++ packages/llrt/native/Cargo.toml | 21 + packages/llrt/native/build.rs | 3 + packages/llrt/native/src/lib.rs | 8 + packages/llrt/native/src/runtime.rs | 347 ++ packages/llrt/npm/darwin-arm64/README.md | 3 + packages/llrt/npm/darwin-arm64/package.json | 27 + packages/llrt/npm/darwin-x64/README.md | 3 + packages/llrt/npm/darwin-x64/package.json | 27 + packages/llrt/npm/linux-arm64-gnu/README.md | 3 + .../llrt/npm/linux-arm64-gnu/package.json | 30 + packages/llrt/npm/linux-x64-gnu/README.md | 3 + packages/llrt/npm/linux-x64-gnu/package.json | 30 + packages/llrt/package.json | 64 + packages/llrt/scripts/build-native-target.mjs | 31 + packages/llrt/scripts/prepare-llrt-source.mjs | 76 + .../llrt/scripts/smoke-packed-install.mjs | 82 + .../llrt/scripts/verify-native-artifacts.mjs | 141 + packages/llrt/src/errors.ts | 33 + packages/llrt/src/index.ts | 16 + packages/llrt/src/native.ts | 137 + packages/llrt/src/runtime.ts | 170 + packages/llrt/src/types.ts | 54 + packages/llrt/test/call-json.test.ts | 190 + .../test/native-artifact-verifier.test.ts | 79 + packages/llrt/test/native-loader.test.ts | 40 + .../test/native-prebuild-workflow.test.ts | 75 + packages/llrt/test/native-smoke.test.ts | 10 + .../llrt/test/package-publication.test.ts | 37 + packages/llrt/test/runtime.test.ts | 120 + packages/llrt/test/stress.test.ts | 101 + packages/llrt/tsconfig.json | 14 + packages/llrt/tsup.config.ts | 11 + packages/llrt/vitest.config.ts | 7 + pnpm-lock.yaml | 1185 +++++- tsconfig.json | 3 +- 62 files changed, 8522 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/llrt-native.yml create mode 100644 internal/superpowers/plans/2026-06-10-standalone-llrt-calljson.md create mode 100644 internal/superpowers/reports/2026-06-10-llrt-executor-benchmark.json create mode 100644 internal/superpowers/reports/2026-06-10-llrt-executor-benchmark.md create mode 100644 internal/superpowers/specs/2026-06-10-standalone-llrt-typescript-runtime-design.md create mode 100644 packages/codemode/scripts/benchmark-executors.ts create mode 100644 packages/codemode/src/executor/benchmark-cli.ts create mode 100644 packages/codemode/src/executor/benchmark-report.ts create mode 100644 packages/codemode/src/executor/llrt-native.ts create mode 100644 packages/codemode/src/executor/llrt-process.ts create mode 100644 packages/codemode/test/auto-executor.test.ts create mode 100644 packages/codemode/test/benchmark-cli.test.ts create mode 100644 packages/codemode/test/benchmark-report.test.ts create mode 100644 packages/codemode/test/llrt-native-executor.test.ts create mode 100644 packages/codemode/test/llrt-process-executor.test.ts create mode 100644 packages/llrt/LICENSE create mode 100644 packages/llrt/README.md create mode 100644 packages/llrt/native/Cargo.lock create mode 100644 packages/llrt/native/Cargo.toml create mode 100644 packages/llrt/native/build.rs create mode 100644 packages/llrt/native/src/lib.rs create mode 100644 packages/llrt/native/src/runtime.rs create mode 100644 packages/llrt/npm/darwin-arm64/README.md create mode 100644 packages/llrt/npm/darwin-arm64/package.json create mode 100644 packages/llrt/npm/darwin-x64/README.md create mode 100644 packages/llrt/npm/darwin-x64/package.json create mode 100644 packages/llrt/npm/linux-arm64-gnu/README.md create mode 100644 packages/llrt/npm/linux-arm64-gnu/package.json create mode 100644 packages/llrt/npm/linux-x64-gnu/README.md create mode 100644 packages/llrt/npm/linux-x64-gnu/package.json create mode 100644 packages/llrt/package.json create mode 100644 packages/llrt/scripts/build-native-target.mjs create mode 100644 packages/llrt/scripts/prepare-llrt-source.mjs create mode 100644 packages/llrt/scripts/smoke-packed-install.mjs create mode 100644 packages/llrt/scripts/verify-native-artifacts.mjs create mode 100644 packages/llrt/src/errors.ts create mode 100644 packages/llrt/src/index.ts create mode 100644 packages/llrt/src/native.ts create mode 100644 packages/llrt/src/runtime.ts create mode 100644 packages/llrt/src/types.ts create mode 100644 packages/llrt/test/call-json.test.ts create mode 100644 packages/llrt/test/native-artifact-verifier.test.ts create mode 100644 packages/llrt/test/native-loader.test.ts create mode 100644 packages/llrt/test/native-prebuild-workflow.test.ts create mode 100644 packages/llrt/test/native-smoke.test.ts create mode 100644 packages/llrt/test/package-publication.test.ts create mode 100644 packages/llrt/test/runtime.test.ts create mode 100644 packages/llrt/test/stress.test.ts create mode 100644 packages/llrt/tsconfig.json create mode 100644 packages/llrt/tsup.config.ts create mode 100644 packages/llrt/vitest.config.ts diff --git a/.github/workflows/llrt-native.yml b/.github/workflows/llrt-native.yml new file mode 100644 index 0000000..cf16e77 --- /dev/null +++ b/.github/workflows/llrt-native.yml @@ -0,0 +1,146 @@ +name: LLRT Native Packages + +on: + workflow_dispatch: + pull_request: + branches: [main] + paths: + - ".github/workflows/llrt-native.yml" + - "packages/llrt/**" + - "package.json" + - "pnpm-lock.yaml" + - "pnpm-workspace.yaml" + - "Taskfile.yml" + push: + branches: [main] + paths: + - ".github/workflows/llrt-native.yml" + - "packages/llrt/**" + - "package.json" + - "pnpm-lock.yaml" + - "pnpm-workspace.yaml" + - "Taskfile.yml" + release: + types: [published] + +permissions: + contents: read + id-token: write + +jobs: + build-native: + name: Build ${{ matrix.platform_arch_abi }} + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - target: aarch64-apple-darwin + platform_arch_abi: darwin-arm64 + runner: macos-15 + - target: x86_64-apple-darwin + platform_arch_abi: darwin-x64 + runner: macos-15-intel + - target: x86_64-unknown-linux-gnu + platform_arch_abi: linux-x64-gnu + runner: ubuntu-24.04 + - target: aarch64-unknown-linux-gnu + platform_arch_abi: linux-arm64-gnu + runner: ubuntu-24.04-arm + + steps: + - uses: actions/checkout@v6 + + - uses: jdx/mise-action@v4 + + - run: task install + + - run: rustup target add ${{ matrix.target }} + + - run: pnpm --filter @robinbraemer/llrt run create:native-packages + + - run: pnpm --filter @robinbraemer/llrt run verify:native-artifacts + + - run: pnpm --filter @robinbraemer/llrt run prepare:llrt-source + + - run: pnpm --filter @robinbraemer/llrt run build:native:target + env: + LLRT_TARGET: ${{ matrix.target }} + + - run: pnpm --filter @robinbraemer/llrt run collect:native-artifacts + + - name: Verify optional native package + working-directory: packages/llrt/npm/${{ matrix.platform_arch_abi }} + run: npm pack --dry-run + + - run: pnpm --filter @robinbraemer/llrt run build + + - name: Smoke-test packed install on native runner + run: pnpm --filter @robinbraemer/llrt run smoke:packed-install + + - uses: actions/upload-artifact@v4 + with: + name: llrt-native-${{ matrix.platform_arch_abi }} + path: packages/llrt/npm/${{ matrix.platform_arch_abi }}/llrt_node.${{ matrix.platform_arch_abi }}.node + if-no-files-found: error + + package: + name: Package LLRT + runs-on: ubuntu-24.04 + needs: build-native + steps: + - uses: actions/checkout@v6 + + - uses: jdx/mise-action@v4 + + - run: task install + + - run: pnpm --filter @robinbraemer/llrt run create:native-packages + + - run: pnpm --filter @robinbraemer/llrt run verify:native-artifacts + + - uses: actions/download-artifact@v4 + with: + pattern: llrt-native-* + path: artifacts/llrt-native + + - name: Place native artifacts into npm package directories + run: | + for artifact_dir in artifacts/llrt-native/llrt-native-*; do + platform_arch_abi="${artifact_dir##*/llrt-native-}" + mkdir -p "packages/llrt/npm/${platform_arch_abi}" + cp "${artifact_dir}/llrt_node.${platform_arch_abi}.node" "packages/llrt/npm/${platform_arch_abi}/" + done + + - run: pnpm --filter @robinbraemer/llrt run verify:native-artifacts:strict + + - run: pnpm --filter @robinbraemer/llrt run build + + - name: Verify main package + working-directory: packages/llrt + run: npm pack --dry-run + + - name: Verify optional native packages + run: | + for package_dir in packages/llrt/npm/*; do + (cd "${package_dir}" && npm pack --dry-run) + done + + - run: pnpm --filter @robinbraemer/llrt run smoke:packed-install + + - name: Prepare native package metadata + if: github.event_name == 'release' + env: + GITHUB_REPOSITORY: "" + run: pnpm --filter @robinbraemer/llrt run prepare:native-publish + + - name: Publish optional native packages + if: github.event_name == 'release' + run: | + for package_dir in packages/llrt/npm/*; do + (cd "${package_dir}" && npm publish --access public --no-git-checks) + done + + - name: Publish main LLRT package + if: github.event_name == 'release' + run: pnpm --filter @robinbraemer/llrt publish --access public --no-git-checks diff --git a/.gitignore b/.gitignore index f5d9e55..a43a959 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,9 @@ node_modules/ dist/ +target/ +*.node *.tgz .DS_Store +packages/llrt/native/index.d.ts +packages/llrt/native/index.js +packages/llrt/vendor/ diff --git a/Taskfile.yml b/Taskfile.yml index 3e70a0e..da4b4c0 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -26,7 +26,7 @@ tasks: test: desc: Run tests cmds: - - pnpm --filter @robinbraemer/codemode test {{.CLI_ARGS}} + - pnpm test {{.CLI_ARGS}} test:watch: desc: Run tests in watch mode diff --git a/internal/superpowers/plans/2026-06-10-standalone-llrt-calljson.md b/internal/superpowers/plans/2026-06-10-standalone-llrt-calljson.md new file mode 100644 index 0000000..f5b58b0 --- /dev/null +++ b/internal/superpowers/plans/2026-06-10-standalone-llrt-calljson.md @@ -0,0 +1,180 @@ +# Standalone LLRT Runtime Adoption Plan + +**Date:** 2026-06-10 + +**Status:** Prototype implemented; release and promotion gates remain. + +**Working directory:** `/Users/robin/Developer/cnap-tech/codemode` + +## Objective + +Move codemode toward LLRT as the preferred JavaScript execution engine by building a standalone `@robinbraemer/llrt` package, wiring codemode to use it experimentally, and proving the safety/performance properties needed before CNAP adopts it as the default. + +## Completed Prototype Work + +- [x] Created `packages/llrt` as a standalone TypeScript package. +- [x] Added a Rust `napi-rs` native crate in `packages/llrt/native`. +- [x] Built a native smoke export. +- [x] Implemented `LlrtRuntime.callJson()`. +- [x] Added structured result/error types. +- [x] Added JSON input serialization and output parsing. +- [x] Added fresh LLRT VM per call. +- [x] Added wall-time timeout with rquickjs interrupt handler plus outer Tokio timeout. +- [x] Added memory limit via LLRT/rquickjs runtime memory limit. +- [x] Added async host callbacks through a JSON-safe N-API dispatcher. +- [x] Added standalone tests for execution, errors, isolation, timeout, memory pressure, and host callbacks. +- [x] Added LLRT stress tests for per-call memory overrides, repeated isolation, concurrent host callbacks, and concurrent stalled callback timeouts. +- [x] Made root `pnpm test` run both `@robinbraemer/llrt` and `@robinbraemer/codemode`. +- [x] Added `LlrtNativeExecutor` in codemode. +- [x] Ran the shared codemode executor contract against `LlrtNativeExecutor`. +- [x] Made `createExecutor()` prefer LLRT when `@robinbraemer/llrt` is installed. +- [x] Kept fallback executors available. +- [x] Tightened fallback policy so broken installed LLRT does not silently fall back. +- [x] Added `benchmark:executors` to collect local LLRT vs `isolated-vm` vs QuickJS WASM evidence. +- [x] Replaced absolute local LLRT Rust paths with a package-local `prepare:llrt-source` step and relative Cargo paths. +- [x] Added napi-rs optional platform package manifests for darwin arm64/x64 and linux glibc arm64/x64. +- [x] Taught the native loader to try the current platform's optional native package. +- [x] Verified `npm pack --dry-run` keeps the main package JS/types-only and puts the current platform native artifact in its optional package. +- [x] Added `.github/workflows/llrt-native.yml` to build every declared native target, dry-run-pack optional packages, assemble artifacts, and publish the LLRT package family on releases. +- [x] Added a workflow/manifest guard test so LLRT tests fail if the native target matrix or main package prepublish hook regresses. +- [x] Added an explicit `LLRT_TARGET` native build script so CI passes native targets directly to napi-rs instead of relying on package-manager argument forwarding. +- [x] Added `smoke:packed-install` to pack the main and current optional native package, install them into a temporary consumer project, and execute LLRT from that install. + +## Current Known Rough Edges + +- [ ] Source builds still need network access, `yarn install`, and upstream `make js` until native prebuilds or upstream crates exist. +- [ ] Native prebuild CI is implemented but not yet proven by a GitHub Actions run across all target runners. +- [ ] Cross-platform package artifact strategy is specified and locally smoke-tested for the current platform, but full target tarballs still need GitHub matrix evidence. +- [ ] Stress coverage exists for core LLRT safety properties; representative CNAP/codemode workload stress still needs a larger report. +- [ ] The standalone runtime security policy is not final; codemode masks `require`, `process`, and `fetch` in its adapter. +- [ ] `LlrtProcessExecutor` remains as a proof-of-concept fallback; decide later whether to keep, hide, or remove it. +- [ ] Full repo lint/preflight has not been run for this branch. + +## Next Engineering Steps + +1. **Make the native dependency reproducible** + - Status: implemented for local/source builds. + - `prepare:llrt-source` shallow-fetches `awslabs/llrt` at `80c113ddee03ff1926068193f50fe35f41ca2105`. + - The script runs upstream `yarn install --immutable` and `make js` so `llrt_core` has the generated `bundle/js` assets its build script requires. + - Cargo now uses relative paths under `packages/llrt/vendor/llrt`. + - Remaining release question: whether source builds are acceptable, or whether npm should rely only on prebuilt native artifacts. + +2. **Add benchmark and stress evidence** + - Status: baseline benchmarks and core LLRT stress tests implemented. + - Measured cold simple execution. + - Measured JSON-heavy OpenAPI-spec execution. + - Measured async host callback chains. + - Stress-tested repeated fresh VM execution. + - Stress-tested concurrent host callback traffic. + - Stress-tested stalled host callback timeouts. + - Stress-tested per-call memory overrides. + - Compare native LLRT, `isolated-vm`, and QuickJS WASM. + - Remaining: run/report representative CNAP/codemode workloads, preferably in CI. + +3. **Harden the native package release path** + - Status: local package layout implemented; native prebuild workflow added. + - Generated napi-rs platform packages under `packages/llrt/npm`. + - Main package ships JS/types only. + - Platform packages ship `.node` binaries with OS/CPU/libc gates. + - Loader tries local development binaries first, then the current optional platform package. + - Prebuild matrix covers darwin arm64/x64 and linux glibc arm64/x64. + - Workflow dry-run-packs the main package and every optional package before release publishing. + - Targeted native builds require `LLRT_TARGET` and fail fast when it is missing. + - Packed-install smoke test imports and executes LLRT from a temporary consumer project. + - Document supported Node/platform combinations. + - Publish with `napi pre-publish` so optional dependencies are injected at the release version. + - Remaining: run the workflow on GitHub and inspect artifacts/logs from all runners. + +4. **Finalize codemode default behavior** + - Keep LLRT first in `createExecutor()` when installed. + - Keep explicit fallback engine classes exported. + - Add user-facing docs for selecting a non-default engine if needed. + - Keep installed-but-broken LLRT failures loud. + +5. **Prepare CNAP adoption** + - Release or locally link the codemode package. + - Update CNAP dependency. + - Run CNAP-specific codemode flows against LLRT. + - Keep rollback path to `isolated-vm` or QuickJS. + +## Verification Commands + +Use Node 24 and pnpm 10: + +```bash +PATH=/Users/robin/.local/share/mise/installs/node/24/bin:$PATH \ +/Users/robin/.local/share/mise/installs/pnpm/10.25.0/pnpm \ + --filter @robinbraemer/llrt run dev:native +``` + +```bash +PATH=/Users/robin/.local/share/mise/installs/node/24/bin:$PATH \ +/Users/robin/.local/share/mise/installs/pnpm/10.25.0/pnpm \ + --filter @robinbraemer/llrt exec vitest run test/native-loader.test.ts --pool=forks --maxWorkers=1 --testTimeout=15000 +``` + +```bash +PATH=/Users/robin/.local/share/mise/installs/node/24/bin:$PATH \ +/Users/robin/.local/share/mise/installs/pnpm/10.25.0/pnpm \ + --filter @robinbraemer/llrt exec vitest run test/call-json.test.ts test/runtime.test.ts test/native-smoke.test.ts test/stress.test.ts --pool=forks --maxWorkers=1 --testTimeout=20000 +``` + +```bash +cd /Users/robin/Developer/cnap-tech/codemode/packages/llrt && npm pack --dry-run +cd /Users/robin/Developer/cnap-tech/codemode/packages/llrt/npm/darwin-arm64 && npm pack --dry-run +cd /Users/robin/Developer/cnap-tech/codemode && mise exec -- pnpm --filter @robinbraemer/llrt run smoke:packed-install +``` + +```bash +cd /Users/robin/Developer/cnap-tech/codemode && mise exec actionlint -- actionlint .github/workflows/*.yml +cd /Users/robin/Developer/cnap-tech/codemode && mise exec -- task ci +``` + +```bash +PATH=/Users/robin/.local/share/mise/installs/node/24/bin:$PATH \ +/Users/robin/.local/share/mise/installs/pnpm/10.25.0/pnpm \ + test +``` + +```bash +PATH=/Users/robin/.local/share/mise/installs/node/24/bin:$PATH \ +/Users/robin/.local/share/mise/installs/pnpm/10.25.0/pnpm \ + --filter @robinbraemer/llrt run typecheck +``` + +```bash +PATH=/Users/robin/.local/share/mise/installs/node/24/bin:$PATH \ +/Users/robin/.local/share/mise/installs/pnpm/10.25.0/pnpm \ + --filter @robinbraemer/codemode run typecheck +``` + +```bash +PATH=/Users/robin/.local/share/mise/installs/node/24/bin:$PATH \ +/Users/robin/.local/share/mise/installs/pnpm/10.25.0/pnpm \ + --filter @robinbraemer/codemode run benchmark:executors +``` + +Latest local sample on 2026-06-10: + +| Engine | Scenario | Mean ms | Errors | +| --- | --- | ---: | ---: | +| `llrt-native` | `cold-simple` | 2.47 | 0 | +| `llrt-native` | `openapi-json-scan` | 3.74 | 0 | +| `llrt-native` | `host-callbacks-parallel` | 2.24 | 0 | +| `isolated-vm` | `cold-simple` | 1.01 | 0 | +| `isolated-vm` | `openapi-json-scan` | 1.32 | 0 | +| `isolated-vm` | `host-callbacks-parallel` | 0.83 | 0 | +| `quickjs-wasm` | `cold-simple` | 5.50 | 0 | +| `quickjs-wasm` | `openapi-json-scan` | 4.92 | 0 | +| `quickjs-wasm` | `host-callbacks-parallel` | 3.61 | 0 | + +## Promotion Standard + +Promote LLRT from default candidate to production default only when the evidence proves: + +- fresh install can build or install `@robinbraemer/llrt`; +- supported platform native artifacts are available; +- codemode executor contract passes in CI; +- safety stress tests cover memory, timeout, callback stalls, and isolation; +- performance is materially better or operationally simpler than the current default for representative codemode workloads; +- fallback engine selection is documented and tested. diff --git a/internal/superpowers/reports/2026-06-10-llrt-executor-benchmark.json b/internal/superpowers/reports/2026-06-10-llrt-executor-benchmark.json new file mode 100644 index 0000000..54ddab3 --- /dev/null +++ b/internal/superpowers/reports/2026-06-10-llrt-executor-benchmark.json @@ -0,0 +1,74 @@ +[ + { + "engine": "llrt-native", + "scenario": "cold-simple", + "iterations": 25, + "totalMs": 22.67, + "meanMs": 0.91, + "errors": 0 + }, + { + "engine": "llrt-native", + "scenario": "openapi-json-scan", + "iterations": 25, + "totalMs": 22.93, + "meanMs": 0.92, + "errors": 0 + }, + { + "engine": "llrt-native", + "scenario": "host-callbacks-parallel", + "iterations": 25, + "totalMs": 17.74, + "meanMs": 0.71, + "errors": 0 + }, + { + "engine": "isolated-vm", + "scenario": "cold-simple", + "iterations": 25, + "totalMs": 22.58, + "meanMs": 0.9, + "errors": 0 + }, + { + "engine": "isolated-vm", + "scenario": "openapi-json-scan", + "iterations": 25, + "totalMs": 29.72, + "meanMs": 1.19, + "errors": 0 + }, + { + "engine": "isolated-vm", + "scenario": "host-callbacks-parallel", + "iterations": 25, + "totalMs": 20.4, + "meanMs": 0.82, + "errors": 0 + }, + { + "engine": "quickjs-wasm", + "scenario": "cold-simple", + "iterations": 25, + "totalMs": 128.84, + "meanMs": 5.15, + "errors": 0 + }, + { + "engine": "quickjs-wasm", + "scenario": "openapi-json-scan", + "iterations": 25, + "totalMs": 126.12, + "meanMs": 5.04, + "errors": 0 + }, + { + "engine": "quickjs-wasm", + "scenario": "host-callbacks-parallel", + "iterations": 25, + "totalMs": 98.13, + "meanMs": 3.93, + "errors": 0 + } +] diff --git a/internal/superpowers/reports/2026-06-10-llrt-executor-benchmark.md b/internal/superpowers/reports/2026-06-10-llrt-executor-benchmark.md new file mode 100644 index 0000000..acc467e --- /dev/null +++ b/internal/superpowers/reports/2026-06-10-llrt-executor-benchmark.md @@ -0,0 +1,31 @@ +# Executor Benchmark Report + +Generated: 2026-06-10T10:53:10.729Z +Command: `pnpm benchmark:executors -- --report internal/superpowers/reports/2026-06-10-llrt-executor-benchmark.md --json internal/superpowers/reports/2026-06-10-llrt-executor-benchmark.json` +Runtime: Node v24.13.1 on darwin/arm64 + +## Raw Results + +| Scenario | Engine | Iterations | Total ms | Mean ms | Errors | First error | +| --- | --- | ---: | ---: | ---: | ---: | --- | +| cold-simple | isolated-vm | 25 | 22.58 | 0.9 | 0 | | +| cold-simple | llrt-native | 25 | 22.67 | 0.91 | 0 | | +| cold-simple | quickjs-wasm | 25 | 128.84 | 5.15 | 0 | | +| host-callbacks-parallel | isolated-vm | 25 | 20.4 | 0.82 | 0 | | +| host-callbacks-parallel | llrt-native | 25 | 17.74 | 0.71 | 0 | | +| host-callbacks-parallel | quickjs-wasm | 25 | 98.13 | 3.93 | 0 | | +| openapi-json-scan | isolated-vm | 25 | 29.72 | 1.19 | 0 | | +| openapi-json-scan | llrt-native | 25 | 22.93 | 0.92 | 0 | | +| openapi-json-scan | quickjs-wasm | 25 | 126.12 | 5.04 | 0 | | + +## Scenario Summary + +| Scenario | Fastest zero-error engine | LLRT rank | LLRT mean ms | Fastest mean ms | Zero-error engines | +| --- | --- | ---: | ---: | ---: | --- | +| cold-simple | isolated-vm | 2 | 0.91 | 0.9 | isolated-vm, llrt-native, quickjs-wasm | +| host-callbacks-parallel | llrt-native | 1 | 0.71 | 0.71 | llrt-native, isolated-vm, quickjs-wasm | +| openapi-json-scan | llrt-native | 1 | 0.92 | 0.92 | llrt-native, isolated-vm, quickjs-wasm | + +## Interpretation + +These numbers are a local signal, not a universal performance claim. LLRT should become the default only when the native package is reproducibly installable, executor contract tests pass, stress tests stay green, and representative codemode snippets are inside the accepted performance envelope. diff --git a/internal/superpowers/specs/2026-06-10-standalone-llrt-typescript-runtime-design.md b/internal/superpowers/specs/2026-06-10-standalone-llrt-typescript-runtime-design.md new file mode 100644 index 0000000..d26cdd6 --- /dev/null +++ b/internal/superpowers/specs/2026-06-10-standalone-llrt-typescript-runtime-design.md @@ -0,0 +1,326 @@ +# Standalone LLRT TypeScript Runtime Design + +**Date:** 2026-06-10 + +**Status:** Prototype implemented; release gates still open. + +**Goal:** Publish a standalone TypeScript-friendly LLRT package and make codemode prefer it as the default executor once packaging, safety, and performance gates are proven. + +## Decision + +Build `@robinbraemer/llrt` as a general Node/TypeScript package backed by a Rust `napi-rs` binding around LLRT's Rust VM API. Codemode consumes that package through a thin executor adapter. + +This is better than embedding LLRT directly in codemode because: + +- the runtime package is useful outside codemode; +- codemode-specific OpenAPI and MCP behavior stays out of the LLRT layer; +- native packaging and prebuilds can be solved once; +- codemode can keep `isolated-vm`, QuickJS WASM, and the LLRT process proof of concept as fallback executors. + +## Confirmed Source Findings + +The upstream `awslabs/llrt` project is a runtime and Lambda-oriented binary distribution, not an importable Node package. The embeddable API is Rust-level: + +- `llrt_core::vm::Vm` +- `VmOptions` +- `rquickjs::AsyncRuntime` +- `rquickjs::AsyncContext` +- `Ctx::globals().set(...)` +- `Function::call(...)` +- `Promise::into_future(...)` + +That makes `napi-rs` the right path for a TypeScript package. There is no mature existing TypeScript/Node binding to reuse from the earlier web and source scan. + +## Package Shape + +```text +packages/llrt/ + package.json + tsconfig.json + tsup.config.ts + vitest.config.ts + src/ + errors.ts + index.ts + native.ts + runtime.ts + types.ts + native/ + Cargo.toml + build.rs + src/ + lib.rs + runtime.rs + scripts/ + prepare-llrt-source.mjs + npm/ + darwin-arm64/ + darwin-x64/ + linux-x64-gnu/ + linux-arm64-gnu/ + test/ + call-json.test.ts + native-loader.test.ts + native-prebuild-workflow.test.ts + native-smoke.test.ts + runtime.test.ts + stress.test.ts +``` + +Public package name: + +```text +@robinbraemer/llrt +``` + +Codemode integration: + +```text +packages/codemode/src/executor/llrt-native.ts +``` + +## Public API + +The first API is JSON-safe and generic: + +```ts +const runtime = new LlrtRuntime({ + memoryMB: 64, + wallTimeMs: 1000, +}); + +const result = await runtime.callJson( + `async ({ input, host }) => { + const response = await host.request({ path: input.path }); + return response.body; + }`, + { path: "/v1/clusters" }, + { + memoryMB: 64, + wallTimeMs: 500, + functions: { + request: async (request) => requestBridge(request), + }, + }, +); +``` + +Types: + +- `LlrtRuntimeOptions` +- `LlrtCallOptions` +- `LlrtHostFunction` +- `LlrtStats` +- `LlrtExecutionErrorInfo` +- `LlrtResult` + +The boundary is deliberately JSON-safe. The package does not emulate `isolated-vm` references or expose arbitrary live host objects. Host functions are async, named, explicit, and pass JSON arguments/results through the native bridge. + +Constructor options define runtime defaults. Per-call options can override +memory, wall time, CPU-time placeholder, and stack limits for the individual +fresh VM invocation. + +## Isolation Model + +The prototype creates a fresh LLRT VM per `callJson()` invocation. + +This is the safest default for untrusted snippets: + +- no guest global state leaks across calls; +- memory limit is per VM; +- interrupt handler and wall timeout are scoped per call; +- disposal is simple. + +A reusable VM mode can be considered later, but only behind an explicit option and separate state-leak tests. + +## Timeout Model + +The native binding enforces wall time in two layers: + +- an rquickjs interrupt handler for running guest bytecode; +- an outer `tokio::time::timeout` for stalled async paths, including host callbacks. + +Codemode maps LLRT timeout failures to an executor error containing `Wall-clock timeout` so the existing executor contract remains portable. + +`cpuTimeMs` remains part of the TypeScript API for future parity, but the current LLRT stats do not expose reliable separate CPU time. Stats use `null` when a value is not known. + +## Memory Model + +The native binding calls `vm.runtime.set_memory_limit(...)` and reports: + +- `memoryUsedBytes` +- `memoryLimitBytes` +- `maxStackBytes` + +The current stress tests prove that memory pressure returns a typed `MEMORY_LIMIT` failure for the exercised cases. Before production default, we still need broader stress tests with larger snippets, concurrent executions, and host callback traffic. + +The stress suite now also verifies per-call memory overrides, repeated +fresh-VM isolation on one `LlrtRuntime` instance, concurrent calls with host +callback traffic, and concurrent stalled host callbacks returning typed +timeouts. + +The benchmark runner can now emit both raw JSON and Markdown reports: + +```bash +pnpm benchmark:executors -- \ + --report internal/superpowers/reports/2026-06-10-llrt-executor-benchmark.md \ + --json internal/superpowers/reports/2026-06-10-llrt-executor-benchmark.json +``` + +The current local report is saved at +`internal/superpowers/reports/2026-06-10-llrt-executor-benchmark.md`. +It compares `llrt-native`, `isolated-vm`, and `quickjs-wasm` on cold +execution, OpenAPI JSON scanning, and parallel host callbacks. + +The standalone package README now documents install commands, supported Node +version, unsupported runtimes, native targets, JSON-safe host functions, fresh +VM isolation, memory and timeout controls, and default safety boundaries. The +package also includes its own MIT `LICENSE` file so the manifest's `files` +entry is backed by a real package artifact. + +## Codemode Default Policy + +`createExecutor()` now tries LLRT first when `@robinbraemer/llrt` is installed. + +Fallback rule: + +- if `@robinbraemer/llrt` itself is missing, try the next backend; +- if the installed LLRT package or adapter is broken, fail loudly instead of silently falling back. + +Fallback engines remain: + +- `isolated-vm` +- `quickjs-emscripten` +- `LlrtProcessExecutor` as a process-oriented proof of concept. + +## Native Packaging + +The package now follows napi-rs's optional platform package layout: + +- main package: `@robinbraemer/llrt`, shipping JavaScript and TypeScript declarations; +- native packages: + - `@robinbraemer/llrt-darwin-arm64` + - `@robinbraemer/llrt-darwin-x64` + - `@robinbraemer/llrt-linux-x64-gnu` + - `@robinbraemer/llrt-linux-arm64-gnu` + +The loader tries package-local development binaries first, then the current +platform's optional native package. This keeps source checkouts ergonomic while +letting published installs use small OS/CPU/libc-gated native packages. + +Release commands: + +```bash +pnpm --filter @robinbraemer/llrt run create:native-packages +pnpm --filter @robinbraemer/llrt run prepare:llrt-source +LLRT_TARGET=aarch64-apple-darwin pnpm --filter @robinbraemer/llrt run build:native:target +pnpm --filter @robinbraemer/llrt run collect:native-artifacts +pnpm --filter @robinbraemer/llrt run smoke:packed-install +pnpm --filter @robinbraemer/llrt run prepublish:native-packages:dry-run +pnpm --filter @robinbraemer/llrt run prepare:native-publish +``` + +`napi pre-publish` is the step that injects `optionalDependencies` into the +main package at the release version. We intentionally do not commit placeholder +`0.0.0` optional dependencies before those native packages exist in a registry. + +Native prebuild CI is defined in `.github/workflows/llrt-native.yml`. +It builds every declared napi target on an explicit GitHub-hosted runner: + +- `aarch64-apple-darwin` on `macos-15`; +- `x86_64-apple-darwin` on `macos-15-intel`; +- `x86_64-unknown-linux-gnu` on `ubuntu-24.04`; +- `aarch64-unknown-linux-gnu` on `ubuntu-24.04-arm`. + +The workflow dry-run-packs each optional native package, uploads the native +artifact, assembles all artifacts in a packaging job, dry-run-packs the main +package and optional packages together, and publishes the LLRT package family +only for release events. + +`pnpm --filter @robinbraemer/llrt run verify:native-artifacts` verifies that +the optional native package manifests match the root package's declared +`napi.targets`. The packaging job runs +`verify:native-artifacts:strict` after downloading artifacts and placing them +into `packages/llrt/npm/*`, so release CI fails if any expected `.node` file is +missing before dry-run packing or publishing. + +Targeted native builds use an explicit `LLRT_TARGET` environment variable so +the package script validates the target and passes `--target` directly to +`napi build`; the workflow does not rely on package-manager argument +forwarding. + +After the packaging job assembles downloaded native artifacts, it runs a +packed-install smoke test: `npm pack` the main package and current platform's +optional native package, install both tarballs into a temporary consumer +project, import `@robinbraemer/llrt`, and execute `LlrtRuntime.callJson()`. +That catches missing files, incorrect optional package names, and loader +resolution regressions before release publishing. + +## Non-Goals + +The standalone package does not: + +- implement Node.js compatibility inside guest code; +- expose filesystem, process, require, or fetch by default; +- expose OpenAPI-specific behavior; +- expose MCP-specific behavior; +- guarantee browser, Bun, or Workers support; +- publish until native dependency and prebuild strategy are solved. + +## Release Gates + +Before publishing `@robinbraemer/llrt`: + +1. Run native prebuild CI for supported targets on GitHub-hosted runners. +2. Document supported Node versions and unsupported runtimes. +3. Confirm main and optional native package tarballs for all release targets. +4. Add benchmark/stress report comparing LLRT, `isolated-vm`, and QuickJS WASM. +5. Decide whether security policy belongs in the standalone runtime or only in codemode's adapter. +6. Decide whether source builds should keep using the self-prepared upstream checkout or move to an upstream crate/fork once available. + +Before making LLRT the production default in CNAP: + +1. Run codemode executor contract on LLRT in CI. +2. Run memory, timeout, stalled-host-callback, and concurrency stress tests. +3. Benchmark representative CNAP/codemode snippets. +4. Publish or otherwise consume a reproducible `@robinbraemer/llrt` package. +5. Keep explicit fallback engine selection for emergency rollback. + +## Current Verdict + +LLRT is a strong default candidate, not yet the proven production default. + +The prototype now shows the important properties we needed to see: + +- importable TypeScript API is feasible; +- a Rust `napi-rs` bridge is practical; +- a standalone package appears novel: the current web/source sweep found + Lambda deployment helpers and LLRT type packages, but no existing true + Node/TypeScript embedded LLRT binding; +- native builds no longer depend on an absolute local LLRT checkout; +- the standalone package README documents supported Node/runtime boundaries; +- napi-rs optional platform packages are generated and loadable by name; +- native package manifests and downloaded native artifact presence are verified + by an explicit script in CI; +- native prebuild CI is specified and guarded by a workflow/manifest test; +- packed package install smoke test proves the current-platform optional + native package can be consumed by a fresh Node project; +- JSON input/output works; +- async host callbacks work; +- fresh VM isolation works; +- memory limit and wall-time timeout are enforceable in tested cases; +- per-call resource overrides work for memory and timeout inputs; +- stress tests cover repeated isolation, concurrent host callbacks, and + concurrent stalled callback timeouts; +- codemode's shared executor contract passes against the native adapter. + +The current local benchmark report shows zero errors across LLRT, +`isolated-vm`, and `quickjs-emscripten`. On this darwin/arm64 Node 24 machine, +LLRT is fastest in the OpenAPI JSON scan and parallel host-callback scenarios +and effectively tied with `isolated-vm` in the tiny cold execution scenario +(`0.91ms` vs `0.90ms` mean). Treat that as a positive local signal, not a +universal raw-speed claim. The strongest current argument for LLRT is the +combination of lightweight packaging, fresh-VM isolation, typed JSON-safe host +callbacks, and competitive performance on representative snippets. The +remaining blockers are release engineering and broader CI/runtime evidence, not +basic feasibility. diff --git a/package.json b/package.json index d90fb8c..a5f0a9b 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,12 @@ "scripts": { "build": "pnpm -r run build", "build:codemode": "pnpm --filter @robinbraemer/codemode build", + "build:llrt": "pnpm --filter @robinbraemer/llrt build", + "build:llrt:native": "pnpm --filter @robinbraemer/llrt build:native", + "benchmark:executors": "pnpm --filter @robinbraemer/codemode benchmark:executors", "dev": "pnpm --filter @robinbraemer/codemode dev", - "test": "pnpm --filter @robinbraemer/codemode test", + "test": "pnpm -r run test", + "test:llrt": "pnpm --filter @robinbraemer/llrt test", "test:watch": "pnpm --filter @robinbraemer/codemode test:watch", "lint": "oxlint && pnpm -r run typecheck", "ci": "pnpm run lint && pnpm run test && pnpm run build" diff --git a/packages/codemode/package.json b/packages/codemode/package.json index 1e7e1a4..b338221 100644 --- a/packages/codemode/package.json +++ b/packages/codemode/package.json @@ -21,6 +21,7 @@ "LICENSE" ], "scripts": { + "benchmark:executors": "tsx scripts/benchmark-executors.ts", "build": "tsup", "dev": "tsup --watch", "test": "vitest run", @@ -46,10 +47,14 @@ "url": "https://github.com/cnap-tech/codemode.git" }, "peerDependencies": { + "@robinbraemer/llrt": "0.0.0", "isolated-vm": "6", "quickjs-emscripten": ">=0.31" }, "peerDependenciesMeta": { + "@robinbraemer/llrt": { + "optional": true + }, "isolated-vm": { "optional": true }, @@ -58,7 +63,9 @@ } }, "devDependencies": { + "@robinbraemer/llrt": "workspace:*", "@modelcontextprotocol/sdk": "^1.12.1", + "@types/node": "^24.13.1", "hono": "^4.7.6", "isolated-vm": "^6.0.2", "quickjs-emscripten": "^0.32.0", diff --git a/packages/codemode/scripts/benchmark-executors.ts b/packages/codemode/scripts/benchmark-executors.ts new file mode 100644 index 0000000..f62c69f --- /dev/null +++ b/packages/codemode/scripts/benchmark-executors.ts @@ -0,0 +1,188 @@ +import { mkdir, writeFile } from "node:fs/promises"; +import { dirname } from "node:path"; +import { performance } from "node:perf_hooks"; +import { arch, argv, cwd, env, platform, version } from "node:process"; +import { + parseBenchmarkCliArgs, + resolveBenchmarkOutputPath, +} from "../src/executor/benchmark-cli.js"; +import { renderBenchmarkMarkdown, type BenchmarkResult } from "../src/executor/benchmark-report.js"; +import { IsolatedVMExecutor } from "../src/executor/isolated-vm.js"; +import { LlrtNativeExecutor } from "../src/executor/llrt-native.js"; +import { QuickJSExecutor } from "../src/executor/quickjs.js"; +import type { Executor, SandboxOptions } from "../src/types.js"; + +type ExecutorFactory = (options: SandboxOptions) => Executor; + +interface Engine { + name: string; + create: ExecutorFactory; +} + +interface Scenario { + name: string; + iterations: number; + code: string; + globals: Record; +} + +const engines: Engine[] = [ + { + name: "llrt-native", + create: (options) => new LlrtNativeExecutor(options), + }, + { + name: "isolated-vm", + create: (options) => new IsolatedVMExecutor(options), + }, + { + name: "quickjs-wasm", + create: (options) => new QuickJSExecutor(options), + }, +]; + +const scenarios: Scenario[] = [ + { + name: "cold-simple", + iterations: 25, + code: `async () => ({ value: 1 + 2 })`, + globals: {}, + }, + { + name: "openapi-json-scan", + iterations: 25, + code: `async () => Object.keys(spec.paths).filter((path) => path.includes("cluster")).length`, + globals: { + spec: createOpenApiFixture(250), + }, + }, + { + name: "host-callbacks-parallel", + iterations: 25, + code: `async () => { + const values = await Promise.all([ + add(1, 2), + add(3, 4), + add(5, 6), + ]); + return values.reduce((sum, value) => sum + value, 0); + }`, + globals: { + add: async (left: number, right: number) => left + right, + }, + }, +]; + +const cliOptions = parseBenchmarkCliArgs(argv.slice(2)); +const results: BenchmarkResult[] = []; +const invocationDir = env.INIT_CWD ?? cwd(); + +for (const engine of engines) { + for (const scenario of scenarios) { + // Benchmark scenarios run sequentially to avoid cross-engine interference. + // oxlint-disable-next-line no-await-in-loop + const result = await runScenario(engine, scenario); + results.push(result); + console.log(JSON.stringify(result)); + } +} + +if (cliOptions.jsonPath) { + await writeTextFile( + resolveBenchmarkOutputPath(cliOptions.jsonPath, invocationDir), + `${JSON.stringify(results, null, 2)}\n`, + ); +} + +if (cliOptions.reportPath) { + await writeTextFile( + resolveBenchmarkOutputPath(cliOptions.reportPath, invocationDir), + renderBenchmarkMarkdown(results, { + generatedAt: new Date().toISOString(), + command: `pnpm benchmark:executors${argv.slice(2).length > 0 ? ` ${argv.slice(2).join(" ")}` : ""}`, + nodeVersion: version, + platform, + arch, + }), + ); +} + +async function runScenario( + engine: Engine, + scenario: Scenario, +): Promise { + const start = performance.now(); + let errors = 0; + let firstError: string | undefined; + + for (let index = 0; index < scenario.iterations; index++) { + const executor = engine.create({ + memoryMB: 64, + timeoutMs: 5_000, + wallTimeMs: 5_000, + }); + + try { + // Each iteration creates and exercises one fresh executor by design. + // oxlint-disable-next-line no-await-in-loop + const result = await executor.execute(scenario.code, scenario.globals); + if (result.error) { + errors++; + firstError ??= result.error; + } + } catch (error) { + errors++; + firstError ??= error instanceof Error ? error.message : String(error); + } finally { + executor.dispose?.(); + } + } + + const totalMs = performance.now() - start; + + return { + engine: engine.name, + scenario: scenario.name, + iterations: scenario.iterations, + totalMs: round(totalMs), + meanMs: round(totalMs / scenario.iterations), + errors, + ...(firstError ? { firstError } : {}), + }; +} + +function createOpenApiFixture(pathCount: number): Record { + const paths: Record = {}; + + for (let index = 0; index < pathCount; index++) { + const resource = index % 5 === 0 ? "clusters" : "products"; + paths[`/v1/${resource}/${index}`] = { + get: { + operationId: `get${resource}${index}`, + responses: { + "200": { + description: "OK", + }, + }, + }, + }; + } + + return { + openapi: "3.1.0", + info: { + title: "Benchmark API", + version: "1.0.0", + }, + paths, + }; +} + +function round(value: number): number { + return Math.round(value * 100) / 100; +} + +async function writeTextFile(path: string, contents: string): Promise { + await mkdir(dirname(path), { recursive: true }); + await writeFile(path, contents); +} diff --git a/packages/codemode/src/executor/auto.ts b/packages/codemode/src/executor/auto.ts index 4a97aa0..6659072 100644 --- a/packages/codemode/src/executor/auto.ts +++ b/packages/codemode/src/executor/auto.ts @@ -22,28 +22,44 @@ function isBun(): boolean { * Pick a sandbox runtime automatically. * * Order of preference: + * - **LLRT native** → first when `@robinbraemer/llrt` is installed. This is + * the lightweight default candidate and satisfies the shared executor + * contract, including host callbacks. * - **Bun** → QuickJS first (isolated-vm cannot load native bindings under * JavaScriptCore), fall back to isolated-vm only if QuickJS isn't * installed. - * - **Node** → isolated-vm first (V8 JIT is faster, mature, no upstream - * async bugs), fall back to QuickJS if isolated-vm isn't installed (e.g. - * ARM Linux without build tools, or a Node minor without a prebuild). + * - **Node without LLRT** → isolated-vm first (mature V8 isolates), then + * QuickJS if isolated-vm isn't installed (e.g. ARM Linux without build + * tools, or a Node minor without a prebuild). * - * Production deployments on Node should always have `isolated-vm` installed - * — QuickJS is a compatibility fallback, not a recommended production - * backend. See `QuickJSExecutor`'s docstring for the upstream - * `quickjs-emscripten` bugs it inherits. + * QuickJS is a compatibility fallback, not a recommended production backend. + * See `QuickJSExecutor`'s docstring for the upstream `quickjs-emscripten` + * bugs it inherits. * - * Both `isolated-vm` and `quickjs-emscripten` are optional peer dependencies. + * All sandbox runtimes are optional peer dependencies. */ export async function createExecutor( options: SandboxOptions = {}, ): Promise { - const order = isBun() ? (["quickjs", "isolated-vm"] as const) : (["isolated-vm", "quickjs"] as const); + const order = isBun() + ? (["llrt", "quickjs", "isolated-vm"] as const) + : (["llrt", "isolated-vm", "quickjs"] as const); /* oxlint-disable no-await-in-loop */ for (const backend of order) { - if (backend === "isolated-vm") { + if (backend === "llrt") { + try { + await import("@robinbraemer/llrt"); + } catch (error) { + if (isMissingOptionalDependency(error, "@robinbraemer/llrt")) { + continue; + } + throw error; + } + + const { LlrtNativeExecutor } = await import("./llrt-native.js"); + return new LlrtNativeExecutor(options); + } else if (backend === "isolated-vm") { try { // @ts-ignore — optional peer dependency await import("isolated-vm"); @@ -67,7 +83,29 @@ export async function createExecutor( throw new Error( "No sandbox runtime found. Install one of:\n" + - " npm install isolated-vm # V8 isolates (Node.js, fastest)\n" + + " npm install @robinbraemer/llrt # Native LLRT (default candidate)\n" + + " npm install isolated-vm # V8 isolates (Node.js fallback)\n" + " npm install quickjs-emscripten # WASM QuickJS (Bun, Workers, browser)", ); } + +export function isMissingOptionalDependency( + error: unknown, + dependency: string, +): boolean { + const escapedDependency = escapeRegExp(dependency); + const missingDependencyPattern = new RegExp( + `Cannot find (?:package|module) ['"]${escapedDependency}['"]`, + ); + + return ( + error instanceof Error && + "code" in error && + error.code === "ERR_MODULE_NOT_FOUND" && + missingDependencyPattern.test(error.message) + ); +} + +function escapeRegExp(value: string): string { + return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} diff --git a/packages/codemode/src/executor/benchmark-cli.ts b/packages/codemode/src/executor/benchmark-cli.ts new file mode 100644 index 0000000..6b22798 --- /dev/null +++ b/packages/codemode/src/executor/benchmark-cli.ts @@ -0,0 +1,40 @@ +import { isAbsolute, join } from "node:path"; + +export interface BenchmarkCliOptions { + reportPath?: string; + jsonPath?: string; +} + +export function parseBenchmarkCliArgs(args: string[]): BenchmarkCliOptions { + const options: BenchmarkCliOptions = {}; + + for (let index = 0; index < args.length; index++) { + const arg = args[index]; + if (arg === "--") { + continue; + } + + if (arg === "--report") { + options.reportPath = requiredValue(args, ++index, arg); + } else if (arg === "--json") { + options.jsonPath = requiredValue(args, ++index, arg); + } else { + throw new Error(`Unknown benchmark option: ${arg}`); + } + } + + return options; +} + +function requiredValue(args: string[], index: number, flag: string): string { + const value = args[index]; + if (!value || value.startsWith("--")) { + throw new Error(`Missing value for ${flag}`); + } + + return value; +} + +export function resolveBenchmarkOutputPath(path: string, baseDir: string): string { + return isAbsolute(path) ? path : join(baseDir, path); +} diff --git a/packages/codemode/src/executor/benchmark-report.ts b/packages/codemode/src/executor/benchmark-report.ts new file mode 100644 index 0000000..d668ffb --- /dev/null +++ b/packages/codemode/src/executor/benchmark-report.ts @@ -0,0 +1,116 @@ +export interface BenchmarkResult { + engine: string; + scenario: string; + iterations: number; + totalMs: number; + meanMs: number; + errors: number; + firstError?: string; +} + +export interface BenchmarkScenarioSummary { + scenario: string; + fastestEngine: string | null; + fastestMeanMs: number | null; + llrtMeanMs: number | null; + llrtRank: number | null; + zeroErrorEngines: string[]; +} + +export interface BenchmarkReportContext { + generatedAt: string; + command: string; + nodeVersion: string; + platform: string; + arch: string; +} + +export function summarizeBenchmarkResults( + results: BenchmarkResult[], +): BenchmarkScenarioSummary[] { + return scenarioNames(results).map((scenario) => { + const zeroErrorResults = results + .filter((result) => result.scenario === scenario && result.errors === 0) + .toSorted(compareMeanThenEngine); + const llrtIndex = zeroErrorResults.findIndex((result) => result.engine === "llrt-native"); + const fastest = zeroErrorResults[0]; + const llrt = llrtIndex >= 0 ? zeroErrorResults[llrtIndex] : undefined; + + return { + scenario, + fastestEngine: fastest?.engine ?? null, + fastestMeanMs: fastest?.meanMs ?? null, + llrtMeanMs: llrt?.meanMs ?? null, + llrtRank: llrt ? llrtIndex + 1 : null, + zeroErrorEngines: zeroErrorResults.map((result) => result.engine), + }; + }); +} + +export function renderBenchmarkMarkdown( + results: BenchmarkResult[], + context: BenchmarkReportContext, +): string { + const summaries = summarizeBenchmarkResults(results); + const lines = [ + "# Executor Benchmark Report", + "", + `Generated: ${context.generatedAt}`, + `Command: \`${context.command}\``, + `Runtime: Node ${context.nodeVersion} on ${context.platform}/${context.arch}`, + "", + "## Raw Results", + "", + "| Scenario | Engine | Iterations | Total ms | Mean ms | Errors | First error |", + "| --- | --- | ---: | ---: | ---: | ---: | --- |", + ...results + .toSorted(compareScenarioThenEngine) + .map( + (result) => + `| ${escapeCell(result.scenario)} | ${escapeCell(result.engine)} | ${result.iterations} | ${formatNumber(result.totalMs)} | ${formatNumber(result.meanMs)} | ${result.errors} | ${escapeCell(result.firstError ?? "")} |`, + ), + "", + "## Scenario Summary", + "", + "| Scenario | Fastest zero-error engine | LLRT rank | LLRT mean ms | Fastest mean ms | Zero-error engines |", + "| --- | --- | ---: | ---: | ---: | --- |", + ...summaries.map( + (summary) => + `| ${escapeCell(summary.scenario)} | ${escapeCell(summary.fastestEngine ?? "none")} | ${summary.llrtRank ?? "n/a"} | ${formatNullableNumber(summary.llrtMeanMs)} | ${formatNullableNumber(summary.fastestMeanMs)} | ${escapeCell(summary.zeroErrorEngines.join(", "))} |`, + ), + "", + "## Interpretation", + "", + "These numbers are a local signal, not a universal performance claim. LLRT should become the default only when the native package is reproducibly installable, executor contract tests pass, stress tests stay green, and representative codemode snippets are inside the accepted performance envelope.", + "", + ]; + + return lines.join("\n"); +} + +function scenarioNames(results: BenchmarkResult[]): string[] { + return [...new Set(results.map((result) => result.scenario))].toSorted(); +} + +function compareMeanThenEngine(left: BenchmarkResult, right: BenchmarkResult): number { + return left.meanMs - right.meanMs || left.engine.localeCompare(right.engine); +} + +function compareScenarioThenEngine(left: BenchmarkResult, right: BenchmarkResult): number { + return ( + left.scenario.localeCompare(right.scenario) || + left.engine.localeCompare(right.engine) + ); +} + +function formatNullableNumber(value: number | null): string { + return value === null ? "n/a" : formatNumber(value); +} + +function formatNumber(value: number): string { + return Number.isInteger(value) ? String(value) : value.toFixed(2).replace(/0+$/, "").replace(/\.$/, ""); +} + +function escapeCell(value: string): string { + return value.replaceAll("|", "\\|").replaceAll("\n", " "); +} diff --git a/packages/codemode/src/executor/llrt-native.ts b/packages/codemode/src/executor/llrt-native.ts new file mode 100644 index 0000000..845cd8d --- /dev/null +++ b/packages/codemode/src/executor/llrt-native.ts @@ -0,0 +1,207 @@ +import type { Executor, ExecuteResult, ExecuteStats, SandboxOptions } from "../types.js"; + +/** + * Experimental in-process LLRT executor backed by `@robinbraemer/llrt`. + * + * Plain globals cross as JSON. Function globals and one-level namespace + * methods cross through the LLRT host callback bridge. + */ +export class LlrtNativeExecutor implements Executor { + private readonly memoryMB: number; + private readonly timeoutMs: number; + private readonly wallTimeMs: number; + + constructor(options: SandboxOptions = {}) { + this.memoryMB = options.memoryMB ?? 64; + this.timeoutMs = options.timeoutMs ?? 30_000; + this.wallTimeMs = options.wallTimeMs ?? 60_000; + } + + async execute( + code: string, + globals: Record, + ): Promise { + const start = Date.now(); + + try { + const { LlrtRuntime } = await import("@robinbraemer/llrt"); + const runtime = new LlrtRuntime({ + memoryMB: this.memoryMB, + wallTimeMs: Math.min(this.timeoutMs, this.wallTimeMs), + }); + const bindings = buildHostBindings(globals); + const result = await runtime.callJson( + wrapCode(code), + bindings.input, + { functions: bindings.functions }, + ); + + if (!result.ok) { + return { + result: undefined, + error: formatLlrtError(result.error), + stats: statsFromLlrt(result.stats, start, this.memoryMB), + }; + } + + return { + result: result.value, + stats: statsFromLlrt(result.stats, start, this.memoryMB), + }; + } catch (error) { + return { + result: undefined, + error: error instanceof Error ? error.message : String(error), + stats: emptyStats(Date.now() - start, this.memoryMB), + }; + } + } +} + +type HostCallable = (...args: unknown[]) => unknown | Promise; + +interface ExecutionInput { + globals: Record; + globalFunctions: Record; + namespaceFunctions: Record>; +} + +function buildHostBindings(globals: Record): { + input: ExecutionInput; + functions: Record; +} { + const input: ExecutionInput = { + globals: {}, + globalFunctions: {}, + namespaceFunctions: {}, + }; + const functions: Record = {}; + + for (const [name, value] of Object.entries(globals)) { + if (isHostCallable(value)) { + input.globalFunctions[name] = name; + functions[name] = value; + continue; + } + + if (isNamespace(value)) { + const namespaceData: Record = {}; + const namespaceFunctions: Record = {}; + + for (const [key, entry] of Object.entries(value)) { + if (isHostCallable(entry)) { + const hostName = `${name}.${key}`; + namespaceFunctions[key] = hostName; + functions[hostName] = entry; + } else { + namespaceData[key] = entry; + } + } + + input.globals[name] = namespaceData; + if (Object.keys(namespaceFunctions).length > 0) { + input.namespaceFunctions[name] = namespaceFunctions; + } + continue; + } + + input.globals[name] = value; + } + + return { input, functions }; +} + +function isHostCallable(value: unknown): value is HostCallable { + return typeof value === "function"; +} + +function isNamespace(value: unknown): value is Record { + return value !== null && typeof value === "object" && !Array.isArray(value); +} + +function wrapCode(code: string): string { + return `async ({ input, host }) => { + globalThis.require = undefined; + globalThis.process = undefined; + globalThis.fetch = undefined; + globalThis.console = { + log: () => {}, + warn: () => {}, + error: () => {}, + }; + + for (const [name, value] of Object.entries(input.globals)) { + globalThis[name] = value; + } + + for (const [name, hostName] of Object.entries(input.globalFunctions)) { + globalThis[name] = (...args) => host[hostName](...args); + } + + for (const [namespace, methods] of Object.entries(input.namespaceFunctions)) { + const namespaceValue = globalThis[namespace] ?? {}; + for (const [methodName, hostName] of Object.entries(methods)) { + namespaceValue[methodName] = (...args) => host[hostName](...args); + } + globalThis[namespace] = namespaceValue; + } + + return await (${code})(); + }`; +} + +function formatLlrtError(error: { + code: string; + name: string; + message: string; +}): string { + if (error.code === "TIMEOUT") { + return `Wall-clock timeout exceeded: ${error.name}: ${error.message}`; + } + return `${error.code}: ${error.name}: ${error.message}`; +} + +function statsFromLlrt( + stats: { + wallTimeMs: number; + cpuTimeMs: number | null; + memoryUsedBytes: number | null; + memoryLimitBytes: number | null; + }, + start: number, + memoryMB: number, +): ExecuteStats { + const wallTimeMs = stats.wallTimeMs || Date.now() - start; + const heapUsedBytes = stats.memoryUsedBytes ?? 0; + const heapSizeLimitBytes = stats.memoryLimitBytes ?? memoryMB * 1024 * 1024; + + return { + cpuTimeMs: stats.cpuTimeMs ?? wallTimeMs, + wallTimeMs, + heapUsedBytes, + heapTotalBytes: heapUsedBytes, + externalBytes: 0, + heapSizeLimitBytes, + totalPhysicalBytes: heapUsedBytes, + availableBytes: Math.max(0, heapSizeLimitBytes - heapUsedBytes), + executableBytes: 0, + mallocedBytes: 0, + peakMallocedBytes: 0, + }; +} + +function emptyStats(wallTimeMs: number, memoryMB: number): ExecuteStats { + return { + cpuTimeMs: wallTimeMs, + wallTimeMs, + heapUsedBytes: 0, + heapTotalBytes: 0, + externalBytes: 0, + heapSizeLimitBytes: memoryMB * 1024 * 1024, + totalPhysicalBytes: 0, + availableBytes: memoryMB * 1024 * 1024, + executableBytes: 0, + mallocedBytes: 0, + peakMallocedBytes: 0, + }; +} diff --git a/packages/codemode/src/executor/llrt-process.ts b/packages/codemode/src/executor/llrt-process.ts new file mode 100644 index 0000000..f8bede8 --- /dev/null +++ b/packages/codemode/src/executor/llrt-process.ts @@ -0,0 +1,164 @@ +import { spawn } from "node:child_process"; +import type { Executor, ExecuteResult, ExecuteStats, SandboxOptions } from "../types.js"; + +export interface LlrtProcessExecutorOptions extends SandboxOptions { + binaryPath?: string; +} + +/** + * Experimental LLRT process executor. + * + * This is intentionally a JSON-safe POC, not the production backend. It proves + * that codemode can drive an LLRT-compatible runtime and collect the same + * high-level ExecuteResult shape before we invest in a native napi-rs addon. + */ +export class LlrtProcessExecutor implements Executor { + private readonly binaryPath: string; + private readonly wallTimeMs: number; + + constructor(options: LlrtProcessExecutorOptions = {}) { + this.binaryPath = options.binaryPath ?? process.env.LLRT_BINARY ?? "llrt"; + this.wallTimeMs = options.wallTimeMs ?? 60_000; + } + + async execute( + code: string, + globals: Record, + ): Promise { + const start = Date.now(); + + if (Object.keys(globals).length > 0) { + return { + result: undefined, + error: "LlrtProcessExecutor POC does not support globals or host callbacks yet", + stats: captureStats(start), + }; + } + + try { + const stdout = await runLlrt(this.binaryPath, wrapCode(code), this.wallTimeMs); + const encoded = lastJsonLine(stdout); + const envelope = JSON.parse(encoded) as + | { ok: true; value?: unknown } + | { ok: false; error: string }; + + if (!envelope.ok) { + return { + result: undefined, + error: envelope.error, + stats: captureStats(start), + }; + } + + return { + result: envelope.value, + stats: captureStats(start), + }; + } catch (err) { + return { + result: undefined, + error: err instanceof Error ? err.message : String(err), + stats: captureStats(start), + }; + } + } +} + +function wrapCode(code: string): string { + return ` +(async () => { + try { + const value = await (${code})(); + console.log(JSON.stringify({ ok: true, value })); + } catch (error) { + console.log(JSON.stringify({ + ok: false, + error: error instanceof Error ? error.message : String(error), + })); + } +})() +`; +} + +async function runLlrt( + binaryPath: string, + source: string, + wallTimeMs: number, +): Promise { + return await new Promise((resolve, reject) => { + const child = spawn(binaryPath, ["-e", source], { + stdio: ["ignore", "pipe", "pipe"], + }); + + let stdout = ""; + let stderr = ""; + let settled = false; + + const timer = setTimeout(() => { + if (settled) return; + settled = true; + child.kill("SIGKILL"); + reject(new Error(`Wall-clock timeout exceeded after ${wallTimeMs}ms`)); + }, wallTimeMs); + timer.unref(); + + child.stdout.setEncoding("utf8"); + child.stdout.on("data", (chunk: string) => { + stdout += chunk; + }); + + child.stderr.setEncoding("utf8"); + child.stderr.on("data", (chunk: string) => { + stderr += chunk; + }); + + child.on("error", (err) => { + if (settled) return; + settled = true; + clearTimeout(timer); + reject(err); + }); + + child.on("close", (code, signal) => { + if (settled) return; + settled = true; + clearTimeout(timer); + + if (code === 0) { + resolve(stdout); + return; + } + + const detail = stderr.trim() || stdout.trim() || `signal ${signal ?? "unknown"}`; + reject(new Error(`LLRT process exited with code ${code ?? "null"}: ${detail}`)); + }); + }); +} + +function lastJsonLine(stdout: string): string { + const line = stdout + .split(/\r?\n/) + .map((entry) => entry.trim()) + .findLast((entry) => entry.length > 0); + if (!line) { + throw new Error("LLRT process produced no JSON result"); + } + return line; +} + +function captureStats(start: number): ExecuteStats { + const wallTimeMs = Date.now() - start; + return { + cpuTimeMs: wallTimeMs, + wallTimeMs, + heapUsedBytes: 0, + heapTotalBytes: 0, + externalBytes: 0, + heapSizeLimitBytes: 0, + totalPhysicalBytes: 0, + availableBytes: 0, + executableBytes: 0, + mallocedBytes: 0, + peakMallocedBytes: 0, + }; +} diff --git a/packages/codemode/src/index.ts b/packages/codemode/src/index.ts index b3a4f18..be40e71 100644 --- a/packages/codemode/src/index.ts +++ b/packages/codemode/src/index.ts @@ -17,6 +17,9 @@ export type { // Executors (for advanced usage / custom executor selection) export { IsolatedVMExecutor } from "./executor/isolated-vm.js"; +export { LlrtNativeExecutor } from "./executor/llrt-native.js"; +export { LlrtProcessExecutor } from "./executor/llrt-process.js"; +export type { LlrtProcessExecutorOptions } from "./executor/llrt-process.js"; export { QuickJSExecutor } from "./executor/quickjs.js"; export { createExecutor } from "./executor/auto.js"; diff --git a/packages/codemode/src/types.ts b/packages/codemode/src/types.ts index ba48f89..cd7aac9 100644 --- a/packages/codemode/src/types.ts +++ b/packages/codemode/src/types.ts @@ -39,8 +39,10 @@ export interface ExecuteResult { /** * Sandbox executor interface. Implement this to use a custom sandbox runtime. * - * Built-in implementation: + * Built-in implementations: + * - `LlrtNativeExecutor` (requires `@robinbraemer/llrt` peer dependency) * - `IsolatedVMExecutor` (requires `isolated-vm` peer dependency) + * - `QuickJSExecutor` (requires `quickjs-emscripten` peer dependency) */ export interface Executor { /** @@ -139,7 +141,8 @@ export interface CodeModeOptions { sandbox?: SandboxOptions; /** - * Custom executor instance. If not provided, uses isolated-vm. + * Custom executor instance. If not provided, `createExecutor()` chooses + * the best installed runtime. */ executor?: Executor; diff --git a/packages/codemode/test/auto-executor.test.ts b/packages/codemode/test/auto-executor.test.ts new file mode 100644 index 0000000..54901aa --- /dev/null +++ b/packages/codemode/test/auto-executor.test.ts @@ -0,0 +1,37 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { LlrtNativeExecutor } from "../src/executor/llrt-native.js"; +import { isMissingOptionalDependency } from "../src/executor/auto.js"; + +afterEach(() => { + vi.doUnmock("@robinbraemer/llrt"); + vi.resetModules(); +}); + +describe("createExecutor", () => { + it("prefers native LLRT when the optional package is installed", async () => { + const { createExecutor } = await import("../src/executor/auto.js"); + + const executor = await createExecutor(); + + expect(executor).toBeInstanceOf(LlrtNativeExecutor); + }); + + it("only treats the selected optional dependency itself as safely missing", () => { + const missingLlrt = Object.assign( + new Error("Cannot find package '@robinbraemer/llrt' imported from auto.ts"), + { code: "ERR_MODULE_NOT_FOUND" }, + ); + const missingNestedDependency = Object.assign( + new Error("Cannot find package 'nested-native-helper' imported from @robinbraemer/llrt"), + { code: "ERR_MODULE_NOT_FOUND" }, + ); + + expect(isMissingOptionalDependency(missingLlrt, "@robinbraemer/llrt")).toBe(true); + expect( + isMissingOptionalDependency(missingNestedDependency, "@robinbraemer/llrt"), + ).toBe(false); + expect( + isMissingOptionalDependency(new Error("broken llrt package"), "@robinbraemer/llrt"), + ).toBe(false); + }); +}); diff --git a/packages/codemode/test/benchmark-cli.test.ts b/packages/codemode/test/benchmark-cli.test.ts new file mode 100644 index 0000000..d3439c2 --- /dev/null +++ b/packages/codemode/test/benchmark-cli.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from "vitest"; +import { + parseBenchmarkCliArgs, + resolveBenchmarkOutputPath, +} from "../src/executor/benchmark-cli.js"; + +describe("benchmark CLI args", () => { + it("parses report and json output paths", () => { + expect(parseBenchmarkCliArgs(["--report", "report.md", "--json", "report.json"])).toEqual({ + reportPath: "report.md", + jsonPath: "report.json", + }); + }); + + it("ignores a package-manager argument separator", () => { + expect( + parseBenchmarkCliArgs(["--", "--report", "report.md", "--json", "report.json"]), + ).toEqual({ + reportPath: "report.md", + jsonPath: "report.json", + }); + }); + + it("rejects unknown options", () => { + expect(() => parseBenchmarkCliArgs(["--wat"])).toThrow("Unknown benchmark option: --wat"); + }); + + it("resolves relative output paths from the invocation directory", () => { + expect(resolveBenchmarkOutputPath("reports/bench.md", "/repo")).toBe( + "/repo/reports/bench.md", + ); + expect(resolveBenchmarkOutputPath("/tmp/bench.md", "/repo")).toBe("/tmp/bench.md"); + }); +}); diff --git a/packages/codemode/test/benchmark-report.test.ts b/packages/codemode/test/benchmark-report.test.ts new file mode 100644 index 0000000..02ccc5c --- /dev/null +++ b/packages/codemode/test/benchmark-report.test.ts @@ -0,0 +1,96 @@ +import { describe, expect, it } from "vitest"; +import { + renderBenchmarkMarkdown, + summarizeBenchmarkResults, + type BenchmarkResult, +} from "../src/executor/benchmark-report.js"; + +const sampleResults: BenchmarkResult[] = [ + { + engine: "llrt-native", + scenario: "cold-simple", + iterations: 25, + totalMs: 50, + meanMs: 2, + errors: 0, + }, + { + engine: "isolated-vm", + scenario: "cold-simple", + iterations: 25, + totalMs: 25, + meanMs: 1, + errors: 0, + }, + { + engine: "quickjs-wasm", + scenario: "cold-simple", + iterations: 25, + totalMs: 100, + meanMs: 4, + errors: 1, + firstError: "boom", + }, + { + engine: "llrt-native", + scenario: "host-callbacks-parallel", + iterations: 25, + totalMs: 80, + meanMs: 3.2, + errors: 0, + }, + { + engine: "isolated-vm", + scenario: "host-callbacks-parallel", + iterations: 25, + totalMs: 120, + meanMs: 4.8, + errors: 0, + }, +]; + +describe("benchmark report helpers", () => { + it("summarizes the fastest zero-error engine per scenario", () => { + expect(summarizeBenchmarkResults(sampleResults)).toEqual([ + { + scenario: "cold-simple", + fastestEngine: "isolated-vm", + fastestMeanMs: 1, + llrtMeanMs: 2, + llrtRank: 2, + zeroErrorEngines: ["isolated-vm", "llrt-native"], + }, + { + scenario: "host-callbacks-parallel", + fastestEngine: "llrt-native", + fastestMeanMs: 3.2, + llrtMeanMs: 3.2, + llrtRank: 1, + zeroErrorEngines: ["llrt-native", "isolated-vm"], + }, + ]); + }); + + it("renders a markdown report with environment context and error visibility", () => { + const report = renderBenchmarkMarkdown(sampleResults, { + generatedAt: "2026-06-10T10:00:00.000Z", + command: "pnpm benchmark:executors", + nodeVersion: "v24.0.0", + platform: "darwin", + arch: "arm64", + }); + + expect(report).toContain("# Executor Benchmark Report"); + expect(report).toContain("Generated: 2026-06-10T10:00:00.000Z"); + expect(report).toContain("Runtime: Node v24.0.0 on darwin/arm64"); + expect(report).toContain( + "| cold-simple | isolated-vm | 25 | 25 | 1 | 0 | |", + ); + expect(report).toContain( + "| cold-simple | quickjs-wasm | 25 | 100 | 4 | 1 | boom |", + ); + expect(report).toContain( + "| host-callbacks-parallel | llrt-native | 1 | 3.2 | 3.2 | llrt-native, isolated-vm |", + ); + }); +}); diff --git a/packages/codemode/test/llrt-native-executor.test.ts b/packages/codemode/test/llrt-native-executor.test.ts new file mode 100644 index 0000000..0c39496 --- /dev/null +++ b/packages/codemode/test/llrt-native-executor.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it } from "vitest"; +import { LlrtNativeExecutor } from "../src/executor/llrt-native.js"; +import { executorContract } from "./executor-contract.js"; + +executorContract( + "LlrtNativeExecutor", + (opts) => new LlrtNativeExecutor(opts), + { memoryStress: { memoryMB: 1, iterations: 100_000 } }, +); + +describe("LlrtNativeExecutor", () => { + it("executes code with JSON-safe globals through the native LLRT runtime", async () => { + const executor = new LlrtNativeExecutor({ memoryMB: 8, wallTimeMs: 1000 }); + + const result = await executor.execute( + `async () => spec.info.title.toUpperCase()`, + { spec: { info: { title: "Petstore" } } }, + ); + + expect(result.error).toBeUndefined(); + expect(result.result).toBe("PETSTORE"); + expect(result.stats.heapSizeLimitBytes).toBe(8 * 1024 * 1024); + }); + + it("returns runtime errors as ExecuteResult errors", async () => { + const executor = new LlrtNativeExecutor({ memoryMB: 8, wallTimeMs: 1000 }); + + const result = await executor.execute( + `async () => { throw new Error("guest exploded"); }`, + {}, + ); + + expect(result.result).toBeUndefined(); + expect(result.error).toContain("guest exploded"); + }); + + it("executes async host functions in namespaces", async () => { + const executor = new LlrtNativeExecutor({ memoryMB: 8, wallTimeMs: 1000 }); + + const result = await executor.execute( + `async () => { + const response = await api.request({ path: "/v1/pets" }); + return response.body; + }`, + { + api: { + request: async (request: { path: string }) => ({ + status: 200, + body: { title: "Petstore", path: request.path }, + }), + }, + }, + ); + + expect(result.error).toBeUndefined(); + expect(result.result).toEqual({ title: "Petstore", path: "/v1/pets" }); + }); +}); diff --git a/packages/codemode/test/llrt-process-executor.test.ts b/packages/codemode/test/llrt-process-executor.test.ts new file mode 100644 index 0000000..9e6e7e7 --- /dev/null +++ b/packages/codemode/test/llrt-process-executor.test.ts @@ -0,0 +1,74 @@ +import { mkdtemp, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { LlrtProcessExecutor } from "../src/executor/llrt-process.js"; + +async function createFakeLlrtBinary(): Promise { + const dir = await mkdtemp(join(tmpdir(), "codemode-llrt-")); + const binary = join(dir, "fake-llrt.mjs"); + await writeFile( + binary, + `#!/usr/bin/env node +const evalIndex = process.argv.indexOf("-e"); +if (evalIndex === -1) { + console.error("expected -e"); + process.exit(64); +} +const source = process.argv[evalIndex + 1]; +try { + const output = await (0, eval)(source); + if (output !== undefined) { + process.stdout.write(String(output)); + } +} catch (error) { + console.error(error instanceof Error ? error.message : String(error)); + process.exit(1); +} +`, + { mode: 0o755 }, + ); + return binary; +} + +describe("LlrtProcessExecutor", () => { + it("executes JSON-safe async functions through an LLRT-compatible binary", async () => { + const executor = new LlrtProcessExecutor({ binaryPath: await createFakeLlrtBinary() }); + + const result = await executor.execute( + `async () => ({ ok: true, answer: 42 })`, + {}, + ); + + expect(result.error).toBeUndefined(); + expect(result.result).toEqual({ ok: true, answer: 42 }); + expect(result.stats.wallTimeMs).toBeGreaterThanOrEqual(0); + }); + + it("returns a structured error when globals are requested", async () => { + const executor = new LlrtProcessExecutor({ binaryPath: await createFakeLlrtBinary() }); + + const result = await executor.execute( + `async () => spec.info.title`, + { spec: { info: { title: "API" } } }, + ); + + expect(result.result).toBeUndefined(); + expect(result.error).toContain("does not support globals"); + }); + + it("enforces wall-clock timeout for a stuck process", async () => { + const executor = new LlrtProcessExecutor({ + binaryPath: await createFakeLlrtBinary(), + wallTimeMs: 50, + }); + + const result = await executor.execute( + `async () => { while (true) {} }`, + {}, + ); + + expect(result.result).toBeUndefined(); + expect(result.error).toContain("Wall-clock timeout"); + }); +}); diff --git a/packages/codemode/tsconfig.json b/packages/codemode/tsconfig.json index e548831..492415d 100644 --- a/packages/codemode/tsconfig.json +++ b/packages/codemode/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "lib": ["ES2023", "DOM"], + "types": ["node"], "outDir": "dist", "rootDir": "src", "declaration": true, diff --git a/packages/codemode/tsup.config.ts b/packages/codemode/tsup.config.ts index 6431706..571dad3 100644 --- a/packages/codemode/tsup.config.ts +++ b/packages/codemode/tsup.config.ts @@ -9,5 +9,5 @@ export default defineConfig({ dts: true, sourcemap: true, clean: true, - external: ["isolated-vm", "quickjs-emscripten"], + external: ["@robinbraemer/llrt", "isolated-vm", "quickjs-emscripten"], }); diff --git a/packages/llrt/LICENSE b/packages/llrt/LICENSE new file mode 100644 index 0000000..b03c69e --- /dev/null +++ b/packages/llrt/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Robin Braemer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/llrt/README.md b/packages/llrt/README.md new file mode 100644 index 0000000..a2e0750 --- /dev/null +++ b/packages/llrt/README.md @@ -0,0 +1,122 @@ +# @robinbraemer/llrt + +TypeScript-friendly Node bindings for AWS LLRT. + +This package exposes LLRT as an embedded runtime through a napi-rs native addon. +The public API is intentionally small while the proof of concept hardens. + +```sh +npm install @robinbraemer/llrt +``` + +## Usage + +`LlrtRuntime.callJson()` runs an async JavaScript function in a fresh LLRT VM and +returns a typed result envelope instead of throwing for guest failures: + +```ts +import { LlrtRuntime } from "@robinbraemer/llrt"; + +const runtime = new LlrtRuntime({ memoryMB: 64, wallTimeMs: 1000 }); + +const result = await runtime.callJson<{ name: string }, { greeting: string }>( + `async ({ input }) => ({ greeting: "Hello " + input.name })`, + { name: "Ada" }, +); +``` + +The boundary is JSON-safe: + +- the `input` value must serialize to JSON; +- the guest return value is serialized back through JSON; +- Host functions are explicit async functions passed per call through + `functions`; +- each call creates a fresh LLRT VM, so guest globals do not persist across + calls. + +Resource controls can be set on the runtime or per call: + +```ts +const runtime = new LlrtRuntime({ + memoryMB: 64, + wallTimeMs: 1000, + maxStackBytes: 512 * 1024, +}); + +const result = await runtime.callJson( + `async ({ input, host }) => host.echo(input)`, + { ok: true }, + { + memoryMB: 32, + wallTimeMs: 500, + functions: { + echo: async (value) => value, + }, + }, +); +``` + +The package reports `TIMEOUT`, `MEMORY_LIMIT`, `SERIALIZATION_ERROR`, +`EVALUATION_ERROR`, `NATIVE_LOAD_ERROR`, `RUNTIME_DISPOSED`, and `UNSUPPORTED` +as typed error codes. + +## Runtime Support + +Supported Node version: + +- Node.js 24 + +Supported native targets: + +- `aarch64-apple-darwin` +- `x86_64-apple-darwin` +- `x86_64-unknown-linux-gnu` +- `aarch64-unknown-linux-gnu` + +Unsupported runtimes: + +- Bun +- Cloudflare Workers +- browser environments + +This package is a Node native addon and does not guarantee support in non-Node +JavaScript runtimes. + +## Safety Boundaries + +Guest code does not receive Node.js compatibility APIs by default. The runtime +does not expose filesystem, process, require, or fetch unless a caller +deliberately provides equivalent behavior through Host functions. + +The isolation model is intentionally simple: every `callJson()` invocation uses +a fresh LLRT VM with its own memory and wall-time limits. This is slower than a +reused VM could be, but it avoids cross-call global state leaks and keeps +resource limits scoped to one execution. + +## Native Packages + +The main package ships JavaScript and TypeScript declarations. Platform-specific +native binaries are published as optional packages generated by napi-rs: + +- `@robinbraemer/llrt-darwin-arm64` +- `@robinbraemer/llrt-darwin-x64` +- `@robinbraemer/llrt-linux-x64-gnu` +- `@robinbraemer/llrt-linux-arm64-gnu` + +For local development, build the native addon with: + +```sh +pnpm --filter @robinbraemer/llrt run dev:native +``` + +Release preparation uses the napi-rs package layout: + +```sh +pnpm --filter @robinbraemer/llrt run create:native-packages +pnpm --filter @robinbraemer/llrt run prepare:llrt-source +LLRT_TARGET=aarch64-apple-darwin pnpm --filter @robinbraemer/llrt run build:native:target +pnpm --filter @robinbraemer/llrt run collect:native-artifacts +pnpm --filter @robinbraemer/llrt run smoke:packed-install +pnpm --filter @robinbraemer/llrt run prepublish:native-packages:dry-run +pnpm --filter @robinbraemer/llrt run prepare:native-publish +``` diff --git a/packages/llrt/native/Cargo.lock b/packages/llrt/native/Cargo.lock new file mode 100644 index 0000000..2eb79ae --- /dev/null +++ b/packages/llrt/native/Cargo.lock @@ -0,0 +1,3358 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aead" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef60ac202874e574ce7a7158cc8bca7313dd344322482e4fadee288bf4a306b8" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "aes" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" +dependencies = [ + "cipher", + "cpubits", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.11.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da8c919c118108f144adecad74b425b804ad075580d605d9b33c2d6d1c62a2f8" +dependencies = [ + "aead", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aes-kw" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ac571010bd60765c56085a4f1d412012a9be2663b1a2f2b19b49318653fd0d" +dependencies = [ + "aes", + "const-oid", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base16ct" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "block-padding" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "brotlic" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f552f56f302af0006c32b50bfa2bdb4696fd6ba33c3ab9f6225fefdb1efdc680" +dependencies = [ + "brotlic-sys", +] + +[[package]] +name = "brotlic-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afdec5c62bc97b56349053cf66ba503af5c2448591be61c3ad70a5f11b57e574" +dependencies = [ + "cc", +] + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cbc" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.2.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core", +] + +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "block-buffer", + "crypto-common", + "inout", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "convert_case" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32c" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-bigint" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a0d26b245348befa0c121944541476763dcc46ede886c88f9d12e1697d27c3" +dependencies = [ + "cpubits", + "ctutils", + "hybrid-array", + "num-traits", + "rand_core", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", + "rand_core", +] + +[[package]] +name = "crypto-primes" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34caa94a67aadf36b12807811390033f73eff0e3789feb596925ace787b13efe" +dependencies = [ + "crypto-bigint", + "libm", + "rand_core", +] + +[[package]] +name = "ctor" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01334b89b69ff726750c5ce5073fc8bd860e99aa9a8fc5ca11b04730e3aee97a" + +[[package]] +name = "ctr" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" +dependencies = [ + "cipher", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "5.0.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f359e08ca85e7bd759e1fd933ff2bccd81864c60a8fba0e259c7f822b0924bf" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" +dependencies = [ + "const-oid", + "der_derive", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der_derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59600e2c2d636fde9b65e99cc6445ac770c63d3628195ff39932b8d6d7409903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "ctutils", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ecdsa" +version = "0.17.0-rc.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54fb064faabbee66e1fc8e5c5a9458d4269dc2d8b638fe86a425adb2510d1a96" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "zeroize", +] + +[[package]] +name = "ed25519" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "3.0.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b011170fe4f04665565b4110afef66774fe9ffff278f3eb5b81cc73d26e27d60" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core", + "serde", + "sha2", + "signature", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "elliptic-curve" +version = "0.14.0-rc.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "102d3643d30dd8b559613c5cced68317199597fffb278cdc88daa2ef7fafc935" +dependencies = [ + "base16ct", + "crypto-bigint", + "crypto-common", + "digest", + "ff", + "group", + "hkdf", + "hybrid-array", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "ff" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "libz-ng-sys", + "miniz_oxide", +] + +[[package]] +name = "float-cmp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core", + "wasip2", + "wasip3", +] + +[[package]] +name = "ghash" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" +dependencies = [ + "polyval", +] + +[[package]] +name = "group" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "h2" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "halfbrown" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7ed2f2edad8a14c8186b847909a41fbb9c3eafa44f88bd891114ed5019da09" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f7685beb53fc20efc2605f32f5d51e9ba18b8ef237961d1760169d2290d3bee" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "hkdf" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "http" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hybrid-array" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +dependencies = [ + "subtle", + "typenum", + "zeroize", +] + +[[package]] +name = "hyper" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "block-padding", + "hybrid-array", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" +dependencies = [ + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link", +] + +[[package]] +name = "jiff-static" +version = "0.2.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c900ef84826f1338a557697dc8fc601df9ca9af4ac137c7fb61d4c6f2dfd3076" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "junction" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "160f2eade097f30263b548aae5deb12ad349c909baa710fa24b92c9090b2e006" +dependencies = [ + "scopeguard", + "windows-sys 0.61.2", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libloading" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libz-ng-sys" +version = "1.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879917b256f6317769b9f374b435805a8697013098aacce5a38ac106cd6a9469" +dependencies = [ + "cmake", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "llrt_abort" +version = "0.8.1-beta" +dependencies = [ + "llrt_async_hooks", + "llrt_events", + "llrt_exceptions", + "llrt_timers", + "llrt_utils", + "rquickjs", +] + +[[package]] +name = "llrt_assert" +version = "0.8.1-beta" +dependencies = [ + "llrt_utils", + "rquickjs", +] + +[[package]] +name = "llrt_async_hooks" +version = "0.8.1-beta" +dependencies = [ + "llrt_hooking", + "llrt_utils", + "rquickjs", + "tracing", +] + +[[package]] +name = "llrt_buffer" +version = "0.8.1-beta" +dependencies = [ + "itoa", + "llrt_encoding", + "llrt_stream_web", + "llrt_utils", + "rquickjs", + "ryu", +] + +[[package]] +name = "llrt_build" +version = "0.8.1-beta" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "llrt_child_process" +version = "0.8.1-beta" +dependencies = [ + "itoa", + "libc", + "llrt_buffer", + "llrt_context", + "llrt_events", + "llrt_stream", + "llrt_utils", + "rquickjs", + "tokio", +] + +[[package]] +name = "llrt_compression" +version = "0.8.1-beta" +dependencies = [ + "brotlic", + "flate2", + "zstd", +] + +[[package]] +name = "llrt_console" +version = "0.8.1-beta" +dependencies = [ + "llrt_logging", + "llrt_utils", + "rquickjs", +] + +[[package]] +name = "llrt_context" +version = "0.8.1-beta" +dependencies = [ + "llrt_build", + "llrt_utils", + "rquickjs", + "tokio", + "tracing", +] + +[[package]] +name = "llrt_core" +version = "0.8.1-beta" +dependencies = [ + "bytes", + "home", + "http-body-util", + "hyper", + "itoa", + "jiff", + "libc", + "llrt_build", + "llrt_context", + "llrt_encoding", + "llrt_hooking", + "llrt_json", + "llrt_logging", + "llrt_modules", + "llrt_numbers", + "llrt_utils", + "md-5", + "once_cell", + "phf", + "phf_codegen", + "quick-xml", + "rand", + "rquickjs", + "rustls", + "rustls-pemfile", + "ryu", + "simd-json", + "terminal_size", + "tokio", + "tracing", + "uuid", + "walkdir", + "zstd", +] + +[[package]] +name = "llrt_crypto" +version = "0.8.1-beta" +dependencies = [ + "aes", + "aes-gcm", + "aes-kw", + "cbc", + "const-oid", + "crc32c", + "crc32fast", + "ctr", + "der", + "ecdsa", + "ed25519-dalek", + "elliptic-curve", + "hkdf", + "hmac", + "llrt_buffer", + "llrt_context", + "llrt_encoding", + "llrt_json", + "llrt_utils", + "md-5", + "once_cell", + "p256", + "p384", + "p521", + "pbkdf2", + "pkcs8", + "rand", + "rquickjs", + "rsa", + "sha1", + "spki", + "x25519-dalek", +] + +[[package]] +name = "llrt_dgram" +version = "0.8.1-beta" +dependencies = [ + "itoa", + "llrt_buffer", + "llrt_context", + "llrt_events", + "llrt_utils", + "rquickjs", + "tokio", + "tracing", +] + +[[package]] +name = "llrt_dns" +version = "0.8.1-beta" +dependencies = [ + "either", + "llrt_context", + "llrt_hooking", + "llrt_utils", + "rquickjs", + "tokio", +] + +[[package]] +name = "llrt_dns_cache" +version = "0.8.1-beta" +dependencies = [ + "hyper-util", + "llrt_context", + "llrt_utils", + "quick_cache", + "tokio", + "tower-service", +] + +[[package]] +name = "llrt_encoding" +version = "0.8.1-beta" +dependencies = [ + "base64-simd", + "hex-simd", + "llrt_build", + "memchr", + "phf", +] + +[[package]] +name = "llrt_events" +version = "0.8.1-beta" +dependencies = [ + "llrt_utils", + "rquickjs", + "tracing", +] + +[[package]] +name = "llrt_exceptions" +version = "0.8.1-beta" +dependencies = [ + "llrt_utils", + "rquickjs", +] + +[[package]] +name = "llrt_fetch" +version = "0.8.1-beta" +dependencies = [ + "bytes", + "either", + "http-body", + "http-body-util", + "hyper", + "itoa", + "llrt_abort", + "llrt_buffer", + "llrt_compression", + "llrt_context", + "llrt_encoding", + "llrt_http", + "llrt_json", + "llrt_stream_web", + "llrt_url", + "llrt_utils", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rand", + "rquickjs", + "sha2", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "llrt_fs" +version = "0.8.1-beta" +dependencies = [ + "either", + "junction", + "llrt_buffer", + "llrt_encoding", + "llrt_path", + "llrt_utils", + "rand", + "rquickjs", + "tokio", +] + +[[package]] +name = "llrt_hooking" +version = "0.8.1-beta" +dependencies = [ + "llrt_utils", + "once_cell", + "rquickjs", +] + +[[package]] +name = "llrt_http" +version = "0.8.1-beta" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "llrt_dns_cache", + "llrt_tls", + "llrt_utils", + "once_cell", + "rquickjs", + "rustls", +] + +[[package]] +name = "llrt_intl" +version = "0.8.1-beta" +dependencies = [ + "itoa", + "jiff", + "llrt_utils", + "rquickjs", +] + +[[package]] +name = "llrt_json" +version = "0.8.1-beta" +dependencies = [ + "itoa", + "llrt_build", + "llrt_utils", + "rquickjs", + "ryu", + "simd-json", +] + +[[package]] +name = "llrt_logging" +version = "0.8.1-beta" +dependencies = [ + "itoa", + "llrt_context", + "llrt_encoding", + "llrt_json", + "llrt_numbers", + "llrt_utils", + "rquickjs", + "ryu", +] + +[[package]] +name = "llrt_modules" +version = "0.8.1-beta" +dependencies = [ + "home", + "llrt_abort", + "llrt_assert", + "llrt_async_hooks", + "llrt_buffer", + "llrt_child_process", + "llrt_console", + "llrt_crypto", + "llrt_dgram", + "llrt_dns", + "llrt_events", + "llrt_exceptions", + "llrt_fetch", + "llrt_fs", + "llrt_hooking", + "llrt_http", + "llrt_intl", + "llrt_json", + "llrt_navigator", + "llrt_net", + "llrt_os", + "llrt_path", + "llrt_perf_hooks", + "llrt_process", + "llrt_stream_web", + "llrt_string_decoder", + "llrt_temporal", + "llrt_timers", + "llrt_tls", + "llrt_tty", + "llrt_url", + "llrt_util", + "llrt_utils", + "llrt_zlib", + "once_cell", + "rquickjs", + "simd-json", + "tokio", + "tracing", +] + +[[package]] +name = "llrt_navigator" +version = "0.8.1-beta" +dependencies = [ + "rquickjs", +] + +[[package]] +name = "llrt_net" +version = "0.8.1-beta" +dependencies = [ + "itoa", + "llrt_buffer", + "llrt_context", + "llrt_events", + "llrt_stream", + "llrt_utils", + "rquickjs", + "tokio", + "tracing", +] + +[[package]] +name = "llrt_node" +version = "0.0.0" +dependencies = [ + "llrt_core", + "llrt_json", + "napi", + "napi-build", + "napi-derive", + "rquickjs", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "llrt_numbers" +version = "0.8.1-beta" +dependencies = [ + "itoa", + "llrt_utils", + "rquickjs", + "ryu", +] + +[[package]] +name = "llrt_os" +version = "0.8.1-beta" +dependencies = [ + "home", + "libc", + "llrt_utils", + "num_cpus", + "once_cell", + "rquickjs", + "users", + "whoami", + "windows-registry", + "windows-result", + "windows-version", +] + +[[package]] +name = "llrt_path" +version = "0.8.1-beta" +dependencies = [ + "llrt_utils", + "memchr", + "rquickjs", +] + +[[package]] +name = "llrt_perf_hooks" +version = "0.8.1-beta" +dependencies = [ + "llrt_events", + "llrt_utils", + "rquickjs", +] + +[[package]] +name = "llrt_process" +version = "0.8.1-beta" +dependencies = [ + "libc", + "llrt_utils", + "rquickjs", +] + +[[package]] +name = "llrt_stream" +version = "0.8.1-beta" +dependencies = [ + "llrt_buffer", + "llrt_context", + "llrt_events", + "llrt_utils", + "rquickjs", + "tokio", +] + +[[package]] +name = "llrt_stream_web" +version = "0.8.1-beta" +dependencies = [ + "llrt_abort", + "llrt_utils", + "rquickjs", +] + +[[package]] +name = "llrt_string_decoder" +version = "0.8.1-beta" +dependencies = [ + "llrt_buffer", + "llrt_encoding", + "llrt_utils", + "rquickjs", +] + +[[package]] +name = "llrt_temporal" +version = "0.8.1-beta" +dependencies = [ + "jiff", + "llrt_utils", + "rquickjs", +] + +[[package]] +name = "llrt_timers" +version = "0.8.1-beta" +dependencies = [ + "llrt_context", + "llrt_hooking", + "llrt_utils", + "once_cell", + "rquickjs", + "tokio", +] + +[[package]] +name = "llrt_tls" +version = "0.8.1-beta" +dependencies = [ + "once_cell", + "rustls", + "tracing", + "webpki-roots", +] + +[[package]] +name = "llrt_tty" +version = "0.8.1-beta" +dependencies = [ + "libc", + "llrt_utils", + "rquickjs", +] + +[[package]] +name = "llrt_url" +version = "0.8.1-beta" +dependencies = [ + "llrt_utils", + "rquickjs", + "url", +] + +[[package]] +name = "llrt_util" +version = "0.8.1-beta" +dependencies = [ + "llrt_context", + "llrt_encoding", + "llrt_logging", + "llrt_utils", + "rquickjs", +] + +[[package]] +name = "llrt_utils" +version = "0.8.1-beta" +dependencies = [ + "libc", + "llrt_build", + "memchr", + "rquickjs", + "simdutf8", + "tokio", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "llrt_zlib" +version = "0.8.1-beta" +dependencies = [ + "llrt_buffer", + "llrt_compression", + "llrt_context", + "llrt_utils", + "rquickjs", +] + +[[package]] +name = "log" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" + +[[package]] +name = "md-5" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "napi" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad513ff22558f1830b595ea6eb4091da48145d09a222ce157e781896f78be0b9" +dependencies = [ + "bitflags", + "ctor", + "futures", + "napi-build", + "napi-sys", + "nohash-hasher", + "rustc-hash", + "tokio", +] + +[[package]] +name = "napi-build" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9c366d2c8c60b86fa632df75f745509b52f9128f91a6bad4c796e44abb505e1" + +[[package]] +name = "napi-derive" +version = "3.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b3f766e04667e6da0e181e2da4f85475d5a6513b7cf6a80bea184e224a5b42" +dependencies = [ + "convert_case", + "ctor", + "napi-derive-backend", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "napi-derive-backend" +version = "5.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d5af30503edf933ce7377cf6d4c877a62b0f1107ea05585f1b5e430e88d5baf" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "semver", + "syn", +] + +[[package]] +name = "napi-sys" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f5bcdf71abd3a50d00b49c1c2c75251cb3c913777d6139cd37dabc093a5e400" +dependencies = [ + "libloading", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "p256" +version = "0.14.0-rc.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41adc63effe99d48837a8cc0e6d7a77e32ae6a07f6000df466178dbc2193093e" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primefield", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.14.0-rc.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd5333afa5ae0347f39e6a0f2c9c155da431583fd71fe5555bd0521b4ccaf02" +dependencies = [ + "ecdsa", + "elliptic-curve", + "fiat-crypto", + "primefield", + "primeorder", + "sha2", +] + +[[package]] +name = "p521" +version = "0.14.0-rc.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3a5297f53dc16d35909060ba3032cff7867e8809f01e273ff325579d5f0ceae" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primefield", + "primeorder", + "sha2", +] + +[[package]] +name = "pbkdf2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" +dependencies = [ + "digest", +] + +[[package]] +name = "pem-rfc7468" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs1" +version = "0.8.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "polyval" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfc63250416fea14f5749b90725916a6c903f599d51cb635aa7a52bfd03eede" +dependencies = [ + "cpubits", + "cpufeatures", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "primefield" +version = "0.14.0-rc.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f845ec3240cd5ed5e1e31cf3ff633a5bf47c698dc4092ba9e767415b3d393406" +dependencies = [ + "crypto-bigint", + "crypto-common", + "ff", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "primeorder" +version = "0.14.0-rc.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d2793f22b9b6fd11ef3ac1d59bf003c2573593e4968702341605c2748fd90bf" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2474bd2e5029e7ccb6abb2ba48cf2383a333851dedf495901544281590c7da7f" +dependencies = [ + "memchr", +] + +[[package]] +name = "quick_cache" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3db184a8b66cfe87f0263a1de147a6b554c864d1767c6f7fa4eb0e5497b565" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "relative-path" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca40a312222d8ba74837cb474edef44b37f561da5f773981007a10bbaa992b0" +dependencies = [ + "serde", +] + +[[package]] +name = "rfc6979" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5236ce872cac07e0fb3969b0cbf468c7d2f37d432f1b627dcb7b8d34563fb0c3" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rquickjs" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0688f8b0192998cca685adefdfad3483da295fa40a0ec406b4c14ecd729e858" +dependencies = [ + "either", + "rquickjs-core", + "rquickjs-macro", +] + +[[package]] +name = "rquickjs-core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fee8c5383f0cfda3b980a80ca4520e726e09b593c59562f579daa51b6c20411" +dependencies = [ + "async-lock", + "either", + "hashbrown 0.17.1", + "relative-path", + "rquickjs-sys", +] + +[[package]] +name = "rquickjs-macro" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e6cf4b6e695b526cb430a6f445d3ccb9908696b99f1c1f8a1480af38bed5e6" +dependencies = [ + "convert_case", + "fnv", + "ident_case", + "indexmap", + "proc-macro-crate", + "proc-macro2", + "quote", + "rquickjs-core", + "syn", +] + +[[package]] +name = "rquickjs-sys" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "698077537c286a169de8693b216672bcef148bf2e2e112ebf50758c68e9afa09" +dependencies = [ + "cc", +] + +[[package]] +name = "rsa" +version = "0.10.0-rc.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b2aa4ba0d89f73d1e332df05be0eeab8840351c36ca5654341dfdb57bb3caf" +dependencies = [ + "const-oid", + "crypto-bigint", + "crypto-primes", + "digest", + "pkcs1", + "pkcs8", + "rand_core", + "sha2", + "signature", + "spki", + "zeroize", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sec1" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" +dependencies = [ + "base16ct", + "ctutils", + "der", + "hybrid-array", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serdect" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "simd-json" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4255126f310d2ba20048db6321c81ab376f6a6735608bf11f0785c41f01f64e3" +dependencies = [ + "halfbrown", + "ref-cast", + "simdutf8", + "value-trait", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spki" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "terminal_size" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" +dependencies = [ + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" +dependencies = [ + "crypto-common", + "ctutils", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "users" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" +dependencies = [ + "libc", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" +dependencies = [ + "getrandom 0.4.2", +] + +[[package]] +name = "value-trait" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e80f0c733af0720a501b3905d22e2f97662d8eacfe082a75ed7ffb5ab08cb59" +dependencies = [ + "float-cmp", + "halfbrown", + "itoa", + "ryu", +] + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "webpki-roots" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "whoami" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998767ef88740d1f5b0682a9c53c24431453923962269c2db68ee43788c5a40d" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "x25519-dalek" +version = "3.0.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17b575e04fcdb37e5509a85a14ff08116678a1c9724befb8b571db742dbdbb0" +dependencies = [ + "curve25519-dalek", + "rand_core", + "zeroize", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/packages/llrt/native/Cargo.toml b/packages/llrt/native/Cargo.toml new file mode 100644 index 0000000..9bd9fb1 --- /dev/null +++ b/packages/llrt/native/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "llrt_node" +version = "0.0.0" +edition = "2021" +license = "MIT" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +llrt_core = { path = "../vendor/llrt/llrt_core", default-features = false, features = ["macro", "tls-ring", "crypto-rust"] } +llrt_json = { path = "../vendor/llrt/libs/llrt_json" } +napi = { version = "3", features = ["async"] } +napi-derive = "3" +rquickjs = { version = "0.12", features = ["futures", "parallel", "rust-alloc"], default-features = false } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] } + +[build-dependencies] +napi-build = "2" diff --git a/packages/llrt/native/build.rs b/packages/llrt/native/build.rs new file mode 100644 index 0000000..0f1b010 --- /dev/null +++ b/packages/llrt/native/build.rs @@ -0,0 +1,3 @@ +fn main() { + napi_build::setup(); +} diff --git a/packages/llrt/native/src/lib.rs b/packages/llrt/native/src/lib.rs new file mode 100644 index 0000000..a64b31e --- /dev/null +++ b/packages/llrt/native/src/lib.rs @@ -0,0 +1,8 @@ +pub mod runtime; + +use napi_derive::napi; + +#[napi] +pub fn native_smoke() -> String { + "llrt-native-ok".to_string() +} diff --git a/packages/llrt/native/src/runtime.rs b/packages/llrt/native/src/runtime.rs new file mode 100644 index 0000000..7dcd204 --- /dev/null +++ b/packages/llrt/native/src/runtime.rs @@ -0,0 +1,347 @@ +use std::{ + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + time::{Duration, Instant}, +}; + +use llrt_core::vm::{Vm, VmOptions}; +use llrt_json::{parse::json_parse, stringify::json_stringify}; +use napi::{ + bindgen_prelude::Promise as NapiPromise, bindgen_prelude::*, + threadsafe_function::ThreadsafeFunction, Status, +}; +use napi_derive::napi; +use rquickjs::{ + atom::PredefinedAtom, + function::This, + prelude::{Async, Func}, + CatchResultExt, CaughtError, Ctx, Function as QuickFunction, Object, Promise as QuickPromise, + Value, +}; + +type HostDispatcher = ThreadsafeFunction, String, Status, false>; + +#[napi(object)] +pub struct NativeStats { + pub wall_time_ms: f64, + pub cpu_time_ms: Option, + pub memory_used_bytes: Option, + pub memory_limit_bytes: Option, + pub max_stack_bytes: Option, +} + +#[napi(object)] +pub struct NativeRuntimeOptions { + pub memory_mb: Option, + pub wall_time_ms: Option, + pub cpu_time_ms: Option, + pub max_stack_bytes: Option, +} + +#[napi(object)] +pub struct NativeErrorInfo { + pub name: String, + pub message: String, + pub stack: Option, + pub code: String, +} + +#[napi(object)] +pub struct NativeCallResult { + pub ok: bool, + pub value_json: Option, + pub error: Option, + pub stats: NativeStats, +} + +#[napi] +pub fn call_json<'env>( + env: &'env Env, + source: String, + input_json: String, + options: NativeRuntimeOptions, + host_dispatcher: Option>>, +) -> Result> { + let host_dispatcher = host_dispatcher + .map(|dispatcher| { + dispatcher + .build_threadsafe_function() + .build_callback(|ctx| Ok(ctx.value)) + }) + .transpose() + .map(|dispatcher| dispatcher.map(Arc::new))?; + + env.spawn_future( + async move { call_json_inner(source, input_json, options, host_dispatcher).await }, + ) +} + +async fn call_json_inner( + source: String, + input_json: String, + options: NativeRuntimeOptions, + host_dispatcher: Option>, +) -> Result { + let start = Instant::now(); + let max_stack_bytes = options + .max_stack_bytes + .map(|value| value as usize) + .unwrap_or_else(|| VmOptions::default().max_stack_size); + let memory_limit_bytes = options + .memory_mb + .map(|value| (value * 1024.0 * 1024.0) as usize) + .unwrap_or(64 * 1024 * 1024); + + let vm = Vm::from_options(VmOptions { + max_stack_size: max_stack_bytes, + ..VmOptions::default() + }) + .await + .map_err(|error| Error::from_reason(error.to_string()))?; + vm.runtime.set_memory_limit(memory_limit_bytes).await; + + let wall_timeout = wall_time_duration(options.wall_time_ms); + let timeout_flag = configure_wall_time_limit(&vm, wall_timeout).await; + let result = execute_with_wall_timeout( + execute_function(&vm, source, input_json, host_dispatcher), + wall_timeout, + ) + .await; + vm.runtime.set_interrupt_handler(None).await; + let memory_usage = vm.runtime.memory_usage().await; + if !matches!(&result, Err(error) if error.code == "TIMEOUT") { + vm.idle() + .await + .map_err(|error| Error::from_reason(error.to_string()))?; + } + + let stats = NativeStats { + wall_time_ms: start.elapsed().as_secs_f64() * 1000.0, + cpu_time_ms: None, + memory_used_bytes: Some(memory_usage.memory_used_size as f64), + memory_limit_bytes: Some(memory_limit_bytes as f64), + max_stack_bytes: Some(max_stack_bytes as f64), + }; + + let value_json = match result { + Ok(value_json) => value_json, + Err(mut error) => { + if error.message.contains("out of memory") { + error.code = "MEMORY_LIMIT".to_string(); + error.name = "LlrtMemoryLimitError".to_string(); + } + + if timeout_flag + .as_ref() + .map(|flag| flag.load(Ordering::Relaxed)) + .unwrap_or_default() + { + error.code = "TIMEOUT".to_string(); + error.name = "LlrtTimeoutError".to_string(); + error.message = "Execution exceeded wall-time limit".to_string(); + } + + return Ok(NativeCallResult { + ok: false, + value_json: None, + error: Some(error), + stats, + }); + } + }; + + Ok(NativeCallResult { + ok: true, + value_json: Some(value_json), + error: None, + stats, + }) +} + +fn wall_time_duration(wall_time_ms: Option) -> Option { + let wall_time_ms = wall_time_ms?; + if !wall_time_ms.is_finite() || wall_time_ms < 0.0 { + return None; + } + Some(Duration::from_secs_f64(wall_time_ms / 1000.0)) +} + +async fn configure_wall_time_limit( + vm: &Vm, + wall_timeout: Option, +) -> Option> { + let wall_timeout = wall_timeout?; + let timeout = Arc::new(AtomicBool::new(false)); + let timeout_for_handler = Arc::clone(&timeout); + let deadline = Instant::now() + wall_timeout; + vm.runtime + .set_interrupt_handler(Some(Box::new(move || { + let should_interrupt = Instant::now() >= deadline; + if should_interrupt { + timeout_for_handler.store(true, Ordering::Relaxed); + } + should_interrupt + }))) + .await; + + Some(timeout) +} + +async fn execute_with_wall_timeout( + execution: F, + wall_timeout: Option, +) -> std::result::Result +where + F: std::future::Future>, +{ + let Some(wall_timeout) = wall_timeout else { + return execution.await; + }; + + match tokio::time::timeout(wall_timeout, execution).await { + Ok(result) => result, + Err(_) => Err(timeout_error()), + } +} + +fn timeout_error() -> NativeErrorInfo { + NativeErrorInfo { + code: "TIMEOUT".to_string(), + name: "LlrtTimeoutError".to_string(), + message: "Execution exceeded wall-time limit".to_string(), + stack: None, + } +} + +async fn execute_function( + vm: &Vm, + source: String, + input_json: String, + host_dispatcher: Option>, +) -> std::result::Result { + vm.ctx + .async_with(async |ctx| execute_in_context(ctx, source, input_json, host_dispatcher).await) + .await +} + +async fn execute_in_context<'js>( + ctx: Ctx<'js>, + source: String, + input_json: String, + host_dispatcher: Option>, +) -> std::result::Result { + execute_in_context_inner(ctx.clone(), source, input_json, host_dispatcher) + .await + .catch(&ctx) + .map_err(|error| native_error_from_caught(&ctx, error)) +} + +async fn execute_in_context_inner<'js>( + ctx: Ctx<'js>, + source: String, + input_json: String, + host_dispatcher: Option>, +) -> rquickjs::Result { + if let Some(host_dispatcher) = host_dispatcher { + let host_function = Func::from(Async(move |name: String, args_json: String| { + call_host_function(Arc::clone(&host_dispatcher), name, args_json) + })); + ctx.globals().set("__llrtHostCall", host_function)?; + } + + let function: QuickFunction = ctx.eval(format!("({source})"))?; + let input = json_parse(&ctx, input_json.into_bytes())?; + let argument = Object::new(ctx.clone())?; + argument.set("input", input)?; + + let result = function.call::<_, Value>((This(ctx.globals()), argument))?; + let promise_constructor: Value = ctx.globals().get(PredefinedAtom::Promise)?; + let result = match result.as_object() { + Some(object) if object.is_instance_of(&promise_constructor) => { + result.get::()?.into_future::().await? + } + _ => result, + }; + + Ok(json_stringify(&ctx, result)?.unwrap_or_default()) +} + +async fn call_host_function( + dispatcher: Arc, + name: String, + args_json: String, +) -> rquickjs::Result { + let payload_json = serde_json::json!({ + "name": name, + "argsJson": args_json, + }) + .to_string(); + + dispatcher + .call_async_catch(payload_json) + .await + .map_err(|error| { + rquickjs::Error::new_from_js_message( + "host function", + "JSON string promise", + error.to_string(), + ) + })? + .await + .map_err(|error| { + rquickjs::Error::new_from_js_message("host function", "JSON string", error.to_string()) + }) +} + +fn native_error_from_caught<'js>(ctx: &Ctx<'js>, error: CaughtError<'js>) -> NativeErrorInfo { + match error { + CaughtError::Exception(exception) => NativeErrorInfo { + code: "EVALUATION_ERROR".to_string(), + name: exception_name(&exception).unwrap_or_else(|| "Error".to_string()), + message: exception.message().unwrap_or_default(), + stack: exception.stack(), + }, + CaughtError::Value(value) => NativeErrorInfo { + code: "EVALUATION_ERROR".to_string(), + name: value.type_name().to_string(), + message: json_stringify(ctx, value) + .ok() + .flatten() + .unwrap_or_else(|| "Non-Error JavaScript exception".to_string()), + stack: None, + }, + CaughtError::Error(error) => NativeErrorInfo { + code: "EVALUATION_ERROR".to_string(), + name: "Error".to_string(), + message: error.to_string(), + stack: None, + }, + } +} + +fn exception_name(exception: &rquickjs::Exception<'_>) -> Option { + exception + .as_object() + .get::<_, Option>(PredefinedAtom::Constructor) + .ok() + .flatten() + .and_then(|constructor| { + constructor + .get::<_, Option>(PredefinedAtom::Name) + .ok() + .flatten() + }) +} + +#[cfg(test)] +mod tests { + #[tokio::test] + async fn creates_llrt_vm() { + let vm = llrt_core::vm::Vm::new() + .await + .expect("LLRT VM should initialize"); + vm.idle().await.expect("LLRT VM should idle cleanly"); + } +} diff --git a/packages/llrt/npm/darwin-arm64/README.md b/packages/llrt/npm/darwin-arm64/README.md new file mode 100644 index 0000000..f5bc77b --- /dev/null +++ b/packages/llrt/npm/darwin-arm64/README.md @@ -0,0 +1,3 @@ +# `@robinbraemer/llrt-darwin-arm64` + +This is the **aarch64-apple-darwin** binary for `@robinbraemer/llrt` diff --git a/packages/llrt/npm/darwin-arm64/package.json b/packages/llrt/npm/darwin-arm64/package.json new file mode 100644 index 0000000..ebf9da5 --- /dev/null +++ b/packages/llrt/npm/darwin-arm64/package.json @@ -0,0 +1,27 @@ +{ + "name": "@robinbraemer/llrt-darwin-arm64", + "version": "0.0.0", + "cpu": [ + "arm64" + ], + "main": "llrt_node.darwin-arm64.node", + "files": [ + "llrt_node.darwin-arm64.node" + ], + "description": "TypeScript-friendly Node bindings for AWS LLRT.", + "keywords": [ + "llrt", + "quickjs", + "runtime", + "napi", + "typescript" + ], + "author": "Robin Braemer", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "os": [ + "darwin" + ] +} diff --git a/packages/llrt/npm/darwin-x64/README.md b/packages/llrt/npm/darwin-x64/README.md new file mode 100644 index 0000000..12e5722 --- /dev/null +++ b/packages/llrt/npm/darwin-x64/README.md @@ -0,0 +1,3 @@ +# `@robinbraemer/llrt-darwin-x64` + +This is the **x86_64-apple-darwin** binary for `@robinbraemer/llrt` diff --git a/packages/llrt/npm/darwin-x64/package.json b/packages/llrt/npm/darwin-x64/package.json new file mode 100644 index 0000000..ea4f198 --- /dev/null +++ b/packages/llrt/npm/darwin-x64/package.json @@ -0,0 +1,27 @@ +{ + "name": "@robinbraemer/llrt-darwin-x64", + "version": "0.0.0", + "cpu": [ + "x64" + ], + "main": "llrt_node.darwin-x64.node", + "files": [ + "llrt_node.darwin-x64.node" + ], + "description": "TypeScript-friendly Node bindings for AWS LLRT.", + "keywords": [ + "llrt", + "quickjs", + "runtime", + "napi", + "typescript" + ], + "author": "Robin Braemer", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "os": [ + "darwin" + ] +} diff --git a/packages/llrt/npm/linux-arm64-gnu/README.md b/packages/llrt/npm/linux-arm64-gnu/README.md new file mode 100644 index 0000000..e6e0710 --- /dev/null +++ b/packages/llrt/npm/linux-arm64-gnu/README.md @@ -0,0 +1,3 @@ +# `@robinbraemer/llrt-linux-arm64-gnu` + +This is the **aarch64-unknown-linux-gnu** binary for `@robinbraemer/llrt` diff --git a/packages/llrt/npm/linux-arm64-gnu/package.json b/packages/llrt/npm/linux-arm64-gnu/package.json new file mode 100644 index 0000000..e67376e --- /dev/null +++ b/packages/llrt/npm/linux-arm64-gnu/package.json @@ -0,0 +1,30 @@ +{ + "name": "@robinbraemer/llrt-linux-arm64-gnu", + "version": "0.0.0", + "cpu": [ + "arm64" + ], + "main": "llrt_node.linux-arm64-gnu.node", + "files": [ + "llrt_node.linux-arm64-gnu.node" + ], + "description": "TypeScript-friendly Node bindings for AWS LLRT.", + "keywords": [ + "llrt", + "quickjs", + "runtime", + "napi", + "typescript" + ], + "author": "Robin Braemer", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "os": [ + "linux" + ], + "libc": [ + "glibc" + ] +} diff --git a/packages/llrt/npm/linux-x64-gnu/README.md b/packages/llrt/npm/linux-x64-gnu/README.md new file mode 100644 index 0000000..4b2625a --- /dev/null +++ b/packages/llrt/npm/linux-x64-gnu/README.md @@ -0,0 +1,3 @@ +# `@robinbraemer/llrt-linux-x64-gnu` + +This is the **x86_64-unknown-linux-gnu** binary for `@robinbraemer/llrt` diff --git a/packages/llrt/npm/linux-x64-gnu/package.json b/packages/llrt/npm/linux-x64-gnu/package.json new file mode 100644 index 0000000..6d131a5 --- /dev/null +++ b/packages/llrt/npm/linux-x64-gnu/package.json @@ -0,0 +1,30 @@ +{ + "name": "@robinbraemer/llrt-linux-x64-gnu", + "version": "0.0.0", + "cpu": [ + "x64" + ], + "main": "llrt_node.linux-x64-gnu.node", + "files": [ + "llrt_node.linux-x64-gnu.node" + ], + "description": "TypeScript-friendly Node bindings for AWS LLRT.", + "keywords": [ + "llrt", + "quickjs", + "runtime", + "napi", + "typescript" + ], + "author": "Robin Braemer", + "license": "MIT", + "publishConfig": { + "access": "public" + }, + "os": [ + "linux" + ], + "libc": [ + "glibc" + ] +} diff --git a/packages/llrt/package.json b/packages/llrt/package.json new file mode 100644 index 0000000..3d9b81b --- /dev/null +++ b/packages/llrt/package.json @@ -0,0 +1,64 @@ +{ + "name": "@robinbraemer/llrt", + "version": "0.0.0", + "description": "TypeScript-friendly Node bindings for AWS LLRT.", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "files": [ + "dist", + "README.md", + "LICENSE" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsup", + "build:native": "pnpm run prepare:llrt-source && napi build --manifest-path native/Cargo.toml --platform --release", + "build:native:target": "node scripts/build-native-target.mjs", + "collect:native-artifacts": "napi artifacts --npm-dir npm --output-dir native", + "create:native-packages": "napi create-npm-dirs --npm-dir npm", + "dev:native": "pnpm run prepare:llrt-source && napi build --manifest-path native/Cargo.toml --platform", + "prepare:llrt-source": "node scripts/prepare-llrt-source.mjs", + "prepare:native-publish": "napi pre-publish --npm-dir npm --tag-style npm --skip-optional-publish", + "prepublish:native-packages:dry-run": "napi pre-publish --npm-dir npm --tag-style npm --dry-run", + "smoke:packed-install": "node scripts/smoke-packed-install.mjs", + "test": "vitest run", + "typecheck": "tsc --noEmit", + "verify:native-artifacts": "node scripts/verify-native-artifacts.mjs", + "verify:native-artifacts:strict": "node scripts/verify-native-artifacts.mjs --require-binaries", + "prepublishOnly": "pnpm run build" + }, + "keywords": [ + "llrt", + "quickjs", + "runtime", + "napi", + "typescript" + ], + "author": "Robin Braemer", + "license": "MIT", + "napi": { + "binaryName": "llrt_node", + "targets": [ + "aarch64-apple-darwin", + "x86_64-apple-darwin", + "x86_64-unknown-linux-gnu", + "aarch64-unknown-linux-gnu" + ] + }, + "devDependencies": { + "@napi-rs/cli": "^3.0.0", + "@types/node": "^24.13.1", + "tsup": "^8.4.0", + "typescript": "^6.0.0", + "vitest": "^4.0.0" + } +} diff --git a/packages/llrt/scripts/build-native-target.mjs b/packages/llrt/scripts/build-native-target.mjs new file mode 100644 index 0000000..0bca4ba --- /dev/null +++ b/packages/llrt/scripts/build-native-target.mjs @@ -0,0 +1,31 @@ +import { spawn } from "node:child_process"; + +const target = process.env.LLRT_TARGET; + +if (!target) { + throw new Error("LLRT_TARGET is required, for example LLRT_TARGET=aarch64-apple-darwin"); +} + +const args = [ + "build", + "--manifest-path", + "native/Cargo.toml", + "--platform", + "--release", + "--target", + target, +]; + +const child = spawn("napi", args, { + stdio: "inherit", + shell: process.platform === "win32", +}); + +child.on("exit", (code, signal) => { + if (signal) { + process.kill(process.pid, signal); + return; + } + + process.exit(code ?? 1); +}); diff --git a/packages/llrt/scripts/prepare-llrt-source.mjs b/packages/llrt/scripts/prepare-llrt-source.mjs new file mode 100644 index 0000000..00593eb --- /dev/null +++ b/packages/llrt/scripts/prepare-llrt-source.mjs @@ -0,0 +1,76 @@ +import { existsSync } from "node:fs"; +import { mkdir, rm } from "node:fs/promises"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { spawnSync } from "node:child_process"; + +const LLRT_REPOSITORY = "https://github.com/awslabs/llrt.git"; +const LLRT_REVISION = "80c113ddee03ff1926068193f50fe35f41ca2105"; + +const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const vendorRoot = resolve(packageRoot, "vendor"); +const llrtRoot = resolve(vendorRoot, "llrt"); + +await mkdir(vendorRoot, { recursive: true }); +await ensureCheckout(); +await ensureGeneratedBundle(); + +async function ensureCheckout() { + if (!existsSync(resolve(llrtRoot, ".git"))) { + await rm(llrtRoot, { force: true, recursive: true }); + await mkdir(llrtRoot, { recursive: true }); + run("git", ["init", "-q"], llrtRoot); + run("git", ["remote", "add", "origin", LLRT_REPOSITORY], llrtRoot); + } + + const currentRevision = git(["rev-parse", "--verify", "HEAD"], llrtRoot, { + allowFailure: true, + }); + if (currentRevision === LLRT_REVISION) { + return; + } + + run("git", ["fetch", "--depth=1", "origin", LLRT_REVISION], llrtRoot); + run("git", ["checkout", "--detach", LLRT_REVISION], llrtRoot); +} + +async function ensureGeneratedBundle() { + if (existsSync(resolve(llrtRoot, "bundle/js/@llrt/std.js"))) { + return; + } + + if (!existsSync(resolve(llrtRoot, "node_modules/esbuild"))) { + run("yarn", ["install", "--immutable"], llrtRoot); + } + run("make", ["js"], llrtRoot); +} + +function git(args, cwd, options = {}) { + const result = spawnSync("git", args, { + cwd, + encoding: "utf8", + stdio: ["ignore", "pipe", options.allowFailure ? "pipe" : "inherit"], + }); + + if (result.status !== 0) { + if (options.allowFailure) { + return undefined; + } + throw new Error(`git ${args.join(" ")} failed with exit code ${result.status}`); + } + + return result.stdout.trim(); +} + +function run(command, args, cwd) { + const result = spawnSync(command, args, { + cwd, + stdio: "inherit", + }); + + if (result.status !== 0) { + throw new Error( + `${command} ${args.join(" ")} failed with exit code ${result.status}`, + ); + } +} diff --git a/packages/llrt/scripts/smoke-packed-install.mjs b/packages/llrt/scripts/smoke-packed-install.mjs new file mode 100644 index 0000000..6b2c023 --- /dev/null +++ b/packages/llrt/scripts/smoke-packed-install.mjs @@ -0,0 +1,82 @@ +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import { spawnSync } from "node:child_process"; + +const packageRoot = resolve(import.meta.dirname, ".."); + +function platformArchAbi() { + if (process.platform === "darwin" && process.arch === "arm64") return "darwin-arm64"; + if (process.platform === "darwin" && process.arch === "x64") return "darwin-x64"; + if (process.platform === "linux" && process.arch === "x64") return "linux-x64-gnu"; + if (process.platform === "linux" && process.arch === "arm64") return "linux-arm64-gnu"; + + throw new Error(`Unsupported LLRT smoke platform: ${process.platform}/${process.arch}`); +} + +function run(command, args, options = {}) { + const result = spawnSync(command, args, { + cwd: packageRoot, + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + ...options, + }); + + if (result.status !== 0) { + throw new Error([ + `Command failed: ${command} ${args.join(" ")}`, + result.stdout, + result.stderr, + ].join("\n")); + } + + return result.stdout; +} + +function pack(cwd) { + const stdout = run("npm", ["pack", "--json", "--pack-destination", packDir], { cwd }); + const [packed] = JSON.parse(stdout); + if (!packed?.filename) { + throw new Error(`npm pack did not return a filename for ${cwd}`); + } + + return join(packDir, packed.filename); +} + +const nativePackageDir = join(packageRoot, "npm", platformArchAbi()); +const consumerDir = mkdtempSync(join(tmpdir(), "llrt-packed-install-")); +const packDir = join(consumerDir, "packs"); +mkdirSync(packDir); +const mainTarball = pack(packageRoot); +const nativeTarball = pack(nativePackageDir); + +try { + writeFileSync( + join(consumerDir, "package.json"), + JSON.stringify({ type: "module", private: true }, null, 2), + ); + run("npm", ["install", "--ignore-scripts", mainTarball, nativeTarball], { + cwd: consumerDir, + }); + run( + process.execPath, + [ + "--input-type=module", + "--eval", + ` + import { LlrtRuntime } from "@robinbraemer/llrt"; + const runtime = new LlrtRuntime({ memoryMB: 8, wallTimeMs: 1000 }); + const result = await runtime.callJson( + "async ({ input }) => ({ ok: true, value: input.value * 2 })", + { value: 21 } + ); + if (!result.ok || result.value.value !== 42) { + throw new Error("Packed LLRT execution failed: " + JSON.stringify(result)); + } + `, + ], + { cwd: consumerDir }, + ); +} finally { + rmSync(consumerDir, { recursive: true, force: true }); +} diff --git a/packages/llrt/scripts/verify-native-artifacts.mjs b/packages/llrt/scripts/verify-native-artifacts.mjs new file mode 100644 index 0000000..45381b1 --- /dev/null +++ b/packages/llrt/scripts/verify-native-artifacts.mjs @@ -0,0 +1,141 @@ +import { existsSync } from "node:fs"; +import { readFile } from "node:fs/promises"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +const defaultPackageRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); + +const targetInfo = { + "aarch64-apple-darwin": { + cpu: "arm64", + dir: "darwin-arm64", + os: "darwin", + }, + "x86_64-apple-darwin": { + cpu: "x64", + dir: "darwin-x64", + os: "darwin", + }, + "x86_64-unknown-linux-gnu": { + cpu: "x64", + dir: "linux-x64-gnu", + libc: "glibc", + os: "linux", + }, + "aarch64-unknown-linux-gnu": { + cpu: "arm64", + dir: "linux-arm64-gnu", + libc: "glibc", + os: "linux", + }, +}; + +export function expectedNativePackages(targets) { + return targets.map((target) => { + const info = targetInfo[target]; + if (!info) { + throw new Error(`Unsupported napi target in package manifest: ${target}`); + } + + return { + target, + dir: info.dir, + packageName: `@robinbraemer/llrt-${info.dir}`, + nodeFile: `llrt_node.${info.dir}.node`, + os: info.os, + cpu: info.cpu, + libc: info.libc, + }; + }); +} + +export async function verifyNativeArtifacts(options = {}) { + const packageRoot = options.packageRoot ?? defaultPackageRoot; + const requireBinaries = options.requireBinaries ?? false; + const rootPackageJson = await readJson(join(packageRoot, "package.json")); + const targets = rootPackageJson.napi?.targets; + if (!Array.isArray(targets) || targets.length === 0) { + throw new Error("Root package.json must declare napi.targets"); + } + + const expectedPackages = expectedNativePackages(targets); + await Promise.all( + expectedPackages.map((expectedPackage) => + verifyNativePackage(packageRoot, rootPackageJson, expectedPackage, requireBinaries), + ), + ); + + return expectedPackages.map((expectedPackage) => expectedPackage.dir).toSorted(); +} + +async function verifyNativePackage( + packageRoot, + rootPackageJson, + expectedPackage, + requireBinaries, +) { + const relativePackageDir = join("npm", expectedPackage.dir); + const packageDir = join(packageRoot, relativePackageDir); + const packageJsonPath = join(packageDir, "package.json"); + + if (!existsSync(packageJsonPath)) { + throw new Error(`Missing native package manifest: ${relativePackageDir}/package.json`); + } + + const packageJson = await readJson(packageJsonPath); + expectEqual(packageJson.name, expectedPackage.packageName, `${relativePackageDir} name`); + expectEqual(packageJson.version, rootPackageJson.version, `${relativePackageDir} version`); + expectEqual(packageJson.main, expectedPackage.nodeFile, `${relativePackageDir} main`); + expectArrayEqual(packageJson.files, [expectedPackage.nodeFile], `${relativePackageDir} files`); + expectArrayEqual(packageJson.os, [expectedPackage.os], `${relativePackageDir} os`); + expectArrayEqual(packageJson.cpu, [expectedPackage.cpu], `${relativePackageDir} cpu`); + expectEqual(packageJson.license, rootPackageJson.license, `${relativePackageDir} license`); + expectEqual( + packageJson.publishConfig?.access, + rootPackageJson.publishConfig?.access, + `${relativePackageDir} publishConfig.access`, + ); + + if (expectedPackage.libc) { + expectArrayEqual(packageJson.libc, [expectedPackage.libc], `${relativePackageDir} libc`); + } else if ("libc" in packageJson) { + throw new Error(`${relativePackageDir} must not declare libc`); + } + + if (requireBinaries) { + const binaryPath = join(packageDir, expectedPackage.nodeFile); + if (!existsSync(binaryPath)) { + throw new Error(`Missing native artifact: ${relativePackageDir}/${expectedPackage.nodeFile}`); + } + } +} + +async function readJson(path) { + return JSON.parse(await readFile(path, "utf8")); +} + +function expectEqual(actual, expected, label) { + if (actual !== expected) { + throw new Error(`${label}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`); + } +} + +function expectArrayEqual(actual, expected, label) { + if (!Array.isArray(actual)) { + throw new Error(`${label}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`); + } + + expectEqual(JSON.stringify(actual), JSON.stringify(expected), label); +} + +function parseArgs(args) { + return { + requireBinaries: args.includes("--require-binaries"), + }; +} + +if (import.meta.url === pathToFileURL(process.argv[1]).href) { + const options = parseArgs(process.argv.slice(2)); + const verified = await verifyNativeArtifacts(options); + console.log(`Verified LLRT native packages: ${verified.join(", ")}`); +} diff --git a/packages/llrt/src/errors.ts b/packages/llrt/src/errors.ts new file mode 100644 index 0000000..3eb98dd --- /dev/null +++ b/packages/llrt/src/errors.ts @@ -0,0 +1,33 @@ +import type { + LlrtExecutionErrorCode, + LlrtExecutionErrorInfo, + LlrtStats, +} from "./types.js"; + +export const emptyStats: LlrtStats = { + wallTimeMs: 0, + cpuTimeMs: null, + memoryUsedBytes: null, + memoryLimitBytes: null, + maxStackBytes: null, +}; + +export function errorInfo( + code: LlrtExecutionErrorCode, + error: unknown, +): LlrtExecutionErrorInfo { + if (error instanceof Error) { + return { + code, + name: error.name, + message: error.message, + stack: error.stack, + }; + } + + return { + code, + name: "Error", + message: String(error), + }; +} diff --git a/packages/llrt/src/index.ts b/packages/llrt/src/index.ts new file mode 100644 index 0000000..d84f619 --- /dev/null +++ b/packages/llrt/src/index.ts @@ -0,0 +1,16 @@ +export { LlrtRuntime } from "./runtime.js"; +export type { + NativeCallFailure, + NativeCallResult, + NativeCallSuccess, +} from "./native.js"; +export type { + LlrtCallFailure, + LlrtCallOptions, + LlrtCallResult, + LlrtExecutionErrorCode, + LlrtExecutionErrorInfo, + LlrtResult, + LlrtRuntimeOptions, + LlrtStats, +} from "./types.js"; diff --git a/packages/llrt/src/native.ts b/packages/llrt/src/native.ts new file mode 100644 index 0000000..729bdb1 --- /dev/null +++ b/packages/llrt/src/native.ts @@ -0,0 +1,137 @@ +import { createRequire } from "node:module"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { errorInfo } from "./errors.js"; +import type { + LlrtExecutionErrorInfo, + LlrtStats, +} from "./types.js"; + +export interface NativeCallSuccess { + ok: true; + valueJson: string; + stats: LlrtStats; +} + +export interface NativeCallFailure { + ok: false; + error: LlrtExecutionErrorInfo; + stats: LlrtStats; +} + +export type NativeCallResult = NativeCallSuccess | NativeCallFailure; + +export interface NativeRuntimeOptions { + memoryMb?: number; + wallTimeMs?: number; + cpuTimeMs?: number; + maxStackBytes?: number; +} + +export interface NativeBinding { + nativeSmoke(): string; + callJson( + source: string, + inputJson: string, + options: NativeRuntimeOptions, + hostDispatcher?: (payloadJson: string) => Promise, + ): Promise; + dispose(): void; +} + +export type NativeBindingCandidate = + | { kind: "path"; specifier: string } + | { kind: "package"; specifier: string }; + +let testBinding: NativeBinding | null | undefined; + +export function setNativeBindingForTest(binding: NativeBinding | null | undefined): void { + testBinding = binding; +} + +function nativePlatformArchAbi( + platform: NodeJS.Platform, + arch: NodeJS.Architecture, +): string | undefined { + if (platform === "darwin" && arch === "arm64") return "darwin-arm64"; + if (platform === "darwin" && arch === "x64") return "darwin-x64"; + if (platform === "linux" && arch === "x64") return "linux-x64-gnu"; + if (platform === "linux" && arch === "arm64") return "linux-arm64-gnu"; + + return undefined; +} + +function localNativeCandidates( + platform: NodeJS.Platform, + arch: NodeJS.Architecture, +): NativeBindingCandidate[] { + const here = dirname(fileURLToPath(import.meta.url)); + const packageRoot = join(here, ".."); + const platformArchAbi = nativePlatformArchAbi(platform, arch); + const platformSpecific = platformArchAbi + ? [ + join(packageRoot, "native", `llrt_node.${platformArchAbi}.node`), + join(packageRoot, `llrt_node.${platformArchAbi}.node`), + ] + : []; + + return [ + ...platformSpecific, + join(packageRoot, "native", "llrt_node.node"), + join(packageRoot, "llrt_node.node"), + ].map((specifier) => ({ kind: "path", specifier })); +} + +export function nativePackageNameForPlatform( + platform: NodeJS.Platform, + arch: NodeJS.Architecture, +): string | undefined { + const platformArchAbi = nativePlatformArchAbi(platform, arch); + + return platformArchAbi ? `@robinbraemer/llrt-${platformArchAbi}` : undefined; +} + +export function nativeBindingCandidates( + options: { + platform?: NodeJS.Platform; + arch?: NodeJS.Architecture; + } = {}, +): NativeBindingCandidate[] { + const platform = options.platform ?? process.platform; + const arch = options.arch ?? process.arch; + const optionalPackageName = nativePackageNameForPlatform(platform, arch); + const packageCandidates: NativeBindingCandidate[] = optionalPackageName + ? [{ kind: "package", specifier: optionalPackageName }] + : []; + + return [ + ...localNativeCandidates(platform, arch), + ...packageCandidates, + { kind: "path", specifier: "../llrt_node.node" }, + ]; +} + +export function loadNativeBinding(): NativeBinding { + if (testBinding === null) { + throw Object.assign(new Error("Native binding disabled for test"), { + llrtError: errorInfo("NATIVE_LOAD_ERROR", "Native binding disabled for test"), + }); + } + if (testBinding) return testBinding; + + const require = createRequire(import.meta.url); + const errors: unknown[] = []; + + for (const candidate of nativeBindingCandidates()) { + try { + return require(candidate.specifier) as NativeBinding; + } catch (error) { + errors.push(error); + } + } + + throw Object.assign(new Error("Unable to load @robinbraemer/llrt native binding"), { + cause: errors, + llrtError: errorInfo("NATIVE_LOAD_ERROR", errors.at(-1) ?? "No native candidates tried"), + }); +} diff --git a/packages/llrt/src/runtime.ts b/packages/llrt/src/runtime.ts new file mode 100644 index 0000000..34dd52f --- /dev/null +++ b/packages/llrt/src/runtime.ts @@ -0,0 +1,170 @@ +import { emptyStats, errorInfo } from "./errors.js"; +import { loadNativeBinding } from "./native.js"; +import type { + LlrtCallOptions, + LlrtExecutionErrorInfo, + LlrtHostFunction, + LlrtResult, + LlrtRuntimeOptions, + LlrtStats, +} from "./types.js"; + +function hasNativeLoadError(error: unknown): error is { llrtError: LlrtExecutionErrorInfo } { + if (!(error instanceof Error) || !("llrtError" in error)) { + return false; + } + + const maybeInfo = error.llrtError; + return ( + typeof maybeInfo === "object" && + maybeInfo !== null && + "code" in maybeInfo && + "name" in maybeInfo && + "message" in maybeInfo + ); +} + +function normalizeStats(stats: LlrtStats): LlrtStats { + return { + wallTimeMs: stats.wallTimeMs ?? 0, + cpuTimeMs: stats.cpuTimeMs ?? null, + memoryUsedBytes: stats.memoryUsedBytes ?? null, + memoryLimitBytes: stats.memoryLimitBytes ?? null, + maxStackBytes: stats.maxStackBytes ?? null, + }; +} + +export class LlrtRuntime { + private disposed = false; + + constructor(private readonly options: LlrtRuntimeOptions = {}) {} + + async callJson( + source: string, + input: TInput, + options: LlrtCallOptions = {}, + ): Promise> { + if (this.disposed) { + return { + ok: false, + error: { + code: "RUNTIME_DISPOSED", + name: "LlrtRuntimeDisposedError", + message: "LlrtRuntime has been disposed", + }, + stats: emptyStats, + }; + } + + const inputJson = this.stringifyInput(input); + if (!inputJson.ok) { + return inputJson; + } + + try { + const binding = loadNativeBinding(); + const hostDispatcher = options.functions + ? createHostDispatcher(options.functions) + : undefined; + const result = await binding.callJson( + hostDispatcher ? wrapSourceForHostFunctions(source) : source, + inputJson.value, + { + memoryMb: options.memoryMB ?? this.options.memoryMB, + wallTimeMs: options.wallTimeMs ?? this.options.wallTimeMs, + cpuTimeMs: options.cpuTimeMs ?? this.options.cpuTimeMs, + maxStackBytes: options.maxStackBytes ?? this.options.maxStackBytes, + }, + hostDispatcher, + ); + + if (!result.ok) { + return { + ...result, + stats: normalizeStats(result.stats), + }; + } + + return { + ok: true, + value: JSON.parse(result.valueJson) as TOutput, + stats: normalizeStats(result.stats), + }; + } catch (error) { + return { + ok: false, + error: hasNativeLoadError(error) + ? error.llrtError + : errorInfo("NATIVE_LOAD_ERROR", error), + stats: emptyStats, + }; + } + } + + dispose(): void { + this.disposed = true; + } + + private stringifyInput(input: unknown): + | { ok: true; value: string } + | { ok: false; error: LlrtExecutionErrorInfo; stats: typeof emptyStats } { + try { + const inputJson = JSON.stringify(input); + if (inputJson !== undefined) { + return { ok: true, value: inputJson }; + } + + return { + ok: false, + error: { + code: "SERIALIZATION_ERROR", + name: "LlrtSerializationError", + message: "Input must serialize to a JSON value", + }, + stats: emptyStats, + }; + } catch (error) { + return { + ok: false, + error: errorInfo("SERIALIZATION_ERROR", error), + stats: emptyStats, + }; + } + } +} + +function createHostDispatcher( + functions: Record, +): (payloadJson: string) => Promise { + return async (payloadJson) => { + const { name, argsJson } = JSON.parse(payloadJson) as { + name: string; + argsJson: string; + }; + const hostFunction = functions[name]; + if (!hostFunction) { + throw new Error(`Unknown LLRT host function: ${name}`); + } + + const args = JSON.parse(argsJson) as unknown[]; + const result = await hostFunction(...args); + const resultJson = JSON.stringify(result); + if (resultJson === undefined) { + return "null"; + } + return resultJson; + }; +} + +function wrapSourceForHostFunctions(source: string): string { + return `async ({ input }) => { + const host = new Proxy({}, { + get(_target, property) { + if (typeof property !== "string") return undefined; + return async (...args) => JSON.parse(await globalThis.__llrtHostCall(property, JSON.stringify(args))); + }, + }); + + return await (${source})({ input, host }); + }`; +} diff --git a/packages/llrt/src/types.ts b/packages/llrt/src/types.ts new file mode 100644 index 0000000..a171a28 --- /dev/null +++ b/packages/llrt/src/types.ts @@ -0,0 +1,54 @@ +export interface LlrtRuntimeOptions { + memoryMB?: number; + wallTimeMs?: number; + cpuTimeMs?: number; + maxStackBytes?: number; +} + +export interface LlrtCallOptions { + memoryMB?: number; + wallTimeMs?: number; + cpuTimeMs?: number; + maxStackBytes?: number; + functions?: Record; +} + +export type LlrtHostFunction = (...args: unknown[]) => unknown | Promise; + +export interface LlrtStats { + wallTimeMs: number; + cpuTimeMs: number | null; + memoryUsedBytes: number | null; + memoryLimitBytes: number | null; + maxStackBytes: number | null; +} + +export type LlrtExecutionErrorCode = + | "EVALUATION_ERROR" + | "SERIALIZATION_ERROR" + | "TIMEOUT" + | "MEMORY_LIMIT" + | "RUNTIME_DISPOSED" + | "NATIVE_LOAD_ERROR" + | "UNSUPPORTED"; + +export interface LlrtExecutionErrorInfo { + name: string; + message: string; + stack?: string; + code: LlrtExecutionErrorCode; +} + +export interface LlrtCallResult { + ok: true; + value: TOutput; + stats: LlrtStats; +} + +export interface LlrtCallFailure { + ok: false; + error: LlrtExecutionErrorInfo; + stats: LlrtStats; +} + +export type LlrtResult = LlrtCallResult | LlrtCallFailure; diff --git a/packages/llrt/test/call-json.test.ts b/packages/llrt/test/call-json.test.ts new file mode 100644 index 0000000..b4fdd6c --- /dev/null +++ b/packages/llrt/test/call-json.test.ts @@ -0,0 +1,190 @@ +import { describe, expect, it } from "vitest"; +import { LlrtRuntime } from "../src/index.js"; + +describe("LlrtRuntime.callJson native execution", () => { + it("executes an async guest function with JSON input and output", async () => { + const runtime = new LlrtRuntime({ wallTimeMs: 1000, memoryMB: 8 }); + + const result = await runtime.callJson< + { spec: { info: { title: string } } }, + { title: string; upper: string } + >( + `async ({ input }) => ({ + title: input.spec.info.title, + upper: input.spec.info.title.toUpperCase(), + })`, + { spec: { info: { title: "Petstore" } } }, + ); + + expect(result).toEqual({ + ok: true, + value: { title: "Petstore", upper: "PETSTORE" }, + stats: { + wallTimeMs: expect.any(Number), + cpuTimeMs: null, + memoryUsedBytes: expect.any(Number), + memoryLimitBytes: 8 * 1024 * 1024, + maxStackBytes: expect.any(Number), + }, + }); + }); + + it("returns a typed evaluation failure when guest code throws", async () => { + const runtime = new LlrtRuntime({ wallTimeMs: 1000, memoryMB: 64 }); + + const result = await runtime.callJson( + `async () => { + throw new Error("guest exploded"); + }`, + {}, + ); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toMatchObject({ + code: "EVALUATION_ERROR", + name: "Error", + message: "guest exploded", + }); + expect(result.stats).toEqual({ + wallTimeMs: expect.any(Number), + cpuTimeMs: null, + memoryUsedBytes: expect.any(Number), + memoryLimitBytes: expect.any(Number), + maxStackBytes: expect.any(Number), + }); + } + }); + + it("returns a typed evaluation failure when guest code rejects", async () => { + const runtime = new LlrtRuntime({ wallTimeMs: 1000, memoryMB: 64 }); + + const result = await runtime.callJson( + `async () => Promise.reject(new TypeError("guest rejected"))`, + {}, + ); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toMatchObject({ + code: "EVALUATION_ERROR", + name: "TypeError", + message: "guest rejected", + }); + } + }); + + it("does not share guest globals across calls", async () => { + const runtime = new LlrtRuntime({ wallTimeMs: 1000, memoryMB: 8 }); + + const first = await runtime.callJson( + `async () => { + globalThis.__codemodeProbe = "leaked"; + return globalThis.__codemodeProbe; + }`, + {}, + ); + const second = await runtime.callJson( + `async () => globalThis.__codemodeProbe ?? "clean"`, + {}, + ); + + expect(first).toMatchObject({ ok: true, value: "leaked" }); + expect(second).toMatchObject({ ok: true, value: "clean" }); + }); + + it("returns a typed timeout when guest code exceeds the wall-time limit", async () => { + const runtime = new LlrtRuntime({ wallTimeMs: 1, memoryMB: 8 }); + + const result = await runtime.callJson( + `async () => { + let value = 0; + for (let index = 0; index < 100_000_000; index++) { + value += index; + } + return value; + }`, + {}, + ); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error.code).toBe("TIMEOUT"); + expect(result.stats.wallTimeMs).toBeGreaterThanOrEqual(1); + } + }); + + it("returns a typed memory-limit failure when guest code exhausts the heap", async () => { + const runtime = new LlrtRuntime({ wallTimeMs: 1000, memoryMB: 1 }); + + const result = await runtime.callJson( + `async () => { + const chunks = []; + for (let index = 0; index < 100; index++) { + chunks.push("x".repeat(1024 * 1024)); + } + return chunks.length; + }`, + {}, + ); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error.code).toBe("MEMORY_LIMIT"); + expect(result.stats.memoryLimitBytes).toBe(1024 * 1024); + } + }); + + it("calls async host functions with JSON arguments and results", async () => { + const runtime = new LlrtRuntime({ wallTimeMs: 1000, memoryMB: 8 }); + + const result = await runtime.callJson< + { petId: string }, + { title: string; path: string } + >( + `async ({ input, host }) => { + const pet = await host.lookupPet(input.petId); + return { + title: pet.title, + path: pet.path, + }; + }`, + { petId: "pet_123" }, + { + functions: { + lookupPet: async (petId: string) => ({ + title: "Petstore", + path: `/pets/${petId}`, + }), + }, + }, + ); + + expect(result).toMatchObject({ + ok: true, + value: { + title: "Petstore", + path: "/pets/pet_123", + }, + }); + }); + + it("returns a typed timeout when an async host function stalls", async () => { + const runtime = new LlrtRuntime({ wallTimeMs: 50, memoryMB: 8 }); + + const result = await runtime.callJson( + `async ({ host }) => await host.never()`, + {}, + { + functions: { + never: () => new Promise(() => {}), + }, + }, + ); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error.code).toBe("TIMEOUT"); + } + }); +}); diff --git a/packages/llrt/test/native-artifact-verifier.test.ts b/packages/llrt/test/native-artifact-verifier.test.ts new file mode 100644 index 0000000..956f48d --- /dev/null +++ b/packages/llrt/test/native-artifact-verifier.test.ts @@ -0,0 +1,79 @@ +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { + expectedNativePackages, + verifyNativeArtifacts, +} from "../scripts/verify-native-artifacts.mjs"; + +describe("native artifact verifier", () => { + it("derives the expected optional native packages from napi targets", () => { + expect(expectedNativePackages(["aarch64-apple-darwin", "x86_64-unknown-linux-gnu"])).toEqual([ + { + cpu: "arm64", + dir: "darwin-arm64", + libc: undefined, + nodeFile: "llrt_node.darwin-arm64.node", + os: "darwin", + packageName: "@robinbraemer/llrt-darwin-arm64", + target: "aarch64-apple-darwin", + }, + { + cpu: "x64", + dir: "linux-x64-gnu", + libc: "glibc", + nodeFile: "llrt_node.linux-x64-gnu.node", + os: "linux", + packageName: "@robinbraemer/llrt-linux-x64-gnu", + target: "x86_64-unknown-linux-gnu", + }, + ]); + }); + + it("passes manifest verification for the checked-in native package layout", async () => { + await expect(verifyNativeArtifacts({ requireBinaries: false })).resolves.toEqual([ + "darwin-arm64", + "darwin-x64", + "linux-arm64-gnu", + "linux-x64-gnu", + ]); + }); + + it("fails strict verification when a native binary artifact is missing", async () => { + const root = mkdtempSync(join(tmpdir(), "llrt-artifact-verify-")); + try { + writeJson(join(root, "package.json"), { + license: "MIT", + publishConfig: { access: "public" }, + version: "0.0.0", + napi: { + targets: ["aarch64-apple-darwin"], + }, + }); + const packageDir = join(root, "npm", "darwin-arm64"); + mkdirSync(packageDir, { recursive: true }); + writeJson(join(packageDir, "package.json"), { + name: "@robinbraemer/llrt-darwin-arm64", + version: "0.0.0", + cpu: ["arm64"], + os: ["darwin"], + main: "llrt_node.darwin-arm64.node", + files: ["llrt_node.darwin-arm64.node"], + license: "MIT", + publishConfig: { access: "public" }, + }); + + await expect( + verifyNativeArtifacts({ packageRoot: root, requireBinaries: true }), + ).rejects.toThrow("Missing native artifact: npm/darwin-arm64/llrt_node.darwin-arm64.node"); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + +}); + +function writeJson(path: string, value: unknown): void { + writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`); +} diff --git a/packages/llrt/test/native-loader.test.ts b/packages/llrt/test/native-loader.test.ts new file mode 100644 index 0000000..a376be2 --- /dev/null +++ b/packages/llrt/test/native-loader.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from "vitest"; +import { + nativeBindingCandidates, + nativePackageNameForPlatform, +} from "../src/native.js"; + +describe("native loader", () => { + it("uses napi-rs platform package names for optional native installs", () => { + expect(nativePackageNameForPlatform("darwin", "arm64")).toBe( + "@robinbraemer/llrt-darwin-arm64", + ); + expect(nativePackageNameForPlatform("darwin", "x64")).toBe( + "@robinbraemer/llrt-darwin-x64", + ); + expect(nativePackageNameForPlatform("linux", "x64")).toBe( + "@robinbraemer/llrt-linux-x64-gnu", + ); + expect(nativePackageNameForPlatform("linux", "arm64")).toBe( + "@robinbraemer/llrt-linux-arm64-gnu", + ); + }); + + it("tries local development binaries before optional platform packages", () => { + const candidates = nativeBindingCandidates({ + platform: "darwin", + arch: "arm64", + }); + + const firstPackageCandidate = candidates.findIndex( + (candidate) => candidate.kind === "package", + ); + + expect(candidates[0]).toMatchObject({ kind: "path" }); + expect(firstPackageCandidate).toBeGreaterThan(0); + expect(candidates[firstPackageCandidate]).toEqual({ + kind: "package", + specifier: "@robinbraemer/llrt-darwin-arm64", + }); + }); +}); diff --git a/packages/llrt/test/native-prebuild-workflow.test.ts b/packages/llrt/test/native-prebuild-workflow.test.ts new file mode 100644 index 0000000..fa8fac2 --- /dev/null +++ b/packages/llrt/test/native-prebuild-workflow.test.ts @@ -0,0 +1,75 @@ +import { readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; +import packageJson from "../package.json" with { type: "json" }; + +const root = join(dirname(fileURLToPath(import.meta.url)), "../../.."); +const workflowPath = join(root, ".github/workflows/llrt-native.yml"); + +describe("LLRT native prebuild workflow", () => { + it("builds every napi target declared by the package manifest", () => { + const workflow = readFileSync(workflowPath, "utf8"); + + for (const target of packageJson.napi.targets) { + expect(workflow).toContain(`target: ${target}`); + } + }); + + it("uses explicit runner labels for each supported native target", () => { + const workflow = readFileSync(workflowPath, "utf8"); + + expect(workflow).toContain("runner: macos-15"); + expect(workflow).toContain("runner: macos-15-intel"); + expect(workflow).toContain("runner: ubuntu-24.04"); + expect(workflow).toContain("runner: ubuntu-24.04-arm"); + }); + + it("packages artifacts before publishing the LLRT package family", () => { + const workflow = readFileSync(workflowPath, "utf8"); + + expect(workflow).toContain("pnpm --filter @robinbraemer/llrt run create:native-packages"); + expect(workflow).toContain("pnpm --filter @robinbraemer/llrt run prepare:native-publish"); + expect(workflow).toContain("npm publish --access public --no-git-checks"); + expect(workflow).toContain("pnpm --filter @robinbraemer/llrt publish --access public --no-git-checks"); + }); + + it("passes the native build target through an explicit environment variable", () => { + const workflow = readFileSync(workflowPath, "utf8"); + + expect(packageJson.scripts["build:native:target"]).toBe("node scripts/build-native-target.mjs"); + expect(workflow).toContain("LLRT_TARGET: ${{ matrix.target }}"); + expect(workflow).toContain("pnpm --filter @robinbraemer/llrt run build:native:target"); + expect(workflow).not.toContain("run build:native:target -- --target"); + }); + + it("smoke-tests a consumer install from packed main and native tarballs", () => { + const workflow = readFileSync(workflowPath, "utf8"); + const smokeCommand = "pnpm --filter @robinbraemer/llrt run smoke:packed-install"; + + expect(packageJson.scripts["smoke:packed-install"]).toBe( + "node scripts/smoke-packed-install.mjs", + ); + expect(workflow).toContain(smokeCommand); + expect([...workflow.matchAll(new RegExp(smokeCommand, "g"))]).toHaveLength(2); + }); + + it("verifies native package manifests locally and downloaded native artifacts in CI", () => { + const workflow = readFileSync(workflowPath, "utf8"); + + expect(packageJson.scripts["verify:native-artifacts"]).toBe( + "node scripts/verify-native-artifacts.mjs", + ); + expect(packageJson.scripts["verify:native-artifacts:strict"]).toBe( + "node scripts/verify-native-artifacts.mjs --require-binaries", + ); + expect(workflow).toContain("pnpm --filter @robinbraemer/llrt run verify:native-artifacts"); + expect(workflow).toContain( + "pnpm --filter @robinbraemer/llrt run verify:native-artifacts:strict", + ); + }); + + it("does not rebuild native binaries in the main package prepublish hook", () => { + expect(packageJson.scripts.prepublishOnly).not.toContain("build:native"); + }); +}); diff --git a/packages/llrt/test/native-smoke.test.ts b/packages/llrt/test/native-smoke.test.ts new file mode 100644 index 0000000..cce2914 --- /dev/null +++ b/packages/llrt/test/native-smoke.test.ts @@ -0,0 +1,10 @@ +import { describe, expect, it } from "vitest"; +import { loadNativeBinding } from "../src/native.js"; + +describe("native LLRT binding", () => { + it("loads the native module and exposes a smoke function", () => { + const binding = loadNativeBinding(); + + expect(binding.nativeSmoke()).toBe("llrt-native-ok"); + }); +}); diff --git a/packages/llrt/test/package-publication.test.ts b/packages/llrt/test/package-publication.test.ts new file mode 100644 index 0000000..dfdac7a --- /dev/null +++ b/packages/llrt/test/package-publication.test.ts @@ -0,0 +1,37 @@ +import { existsSync, readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; +import packageJson from "../package.json" with { type: "json" }; + +const packageRoot = join(dirname(fileURLToPath(import.meta.url)), ".."); +const readme = readFileSync(join(packageRoot, "README.md"), "utf8"); + +describe("LLRT package publication docs", () => { + it("documents install, supported native packages, Node support, and unsupported runtimes", () => { + expect(readme).toContain("npm install @robinbraemer/llrt"); + expect(readme).toContain("Node.js 24"); + expect(readme).toContain("Unsupported runtimes"); + expect(readme).toContain("Bun"); + expect(readme).toContain("Cloudflare Workers"); + expect(readme).toContain("browser"); + + for (const target of packageJson.napi.targets) { + expect(readme).toContain(target); + } + }); + + it("documents the runtime contract and safety boundaries", () => { + expect(readme).toContain("JSON-safe"); + expect(readme).toContain("fresh LLRT VM"); + expect(readme).toContain("Host functions"); + expect(readme).toContain("memoryMB"); + expect(readme).toContain("wallTimeMs"); + expect(readme).toContain("does not expose filesystem, process, require, or fetch"); + }); + + it("ships the license file named in package files", () => { + expect(packageJson.files).toContain("LICENSE"); + expect(existsSync(join(packageRoot, "LICENSE"))).toBe(true); + }); +}); diff --git a/packages/llrt/test/runtime.test.ts b/packages/llrt/test/runtime.test.ts new file mode 100644 index 0000000..17ff778 --- /dev/null +++ b/packages/llrt/test/runtime.test.ts @@ -0,0 +1,120 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { LlrtRuntime } from "../src/index.js"; +import { setNativeBindingForTest, type NativeBinding } from "../src/native.js"; + +const stats = { + wallTimeMs: 3, + cpuTimeMs: null, + memoryUsedBytes: null, + memoryLimitBytes: null, + maxStackBytes: null, +}; + +afterEach(() => { + setNativeBindingForTest(undefined); +}); + +describe("LlrtRuntime", () => { + it("passes JSON input to the native binding and parses JSON output", async () => { + const calls: unknown[] = []; + const binding: NativeBinding = { + nativeSmoke() { + return "llrt-native-ok"; + }, + callJson(source, inputJson, options) { + calls.push({ source, inputJson, options }); + return Promise.resolve({ + ok: true, + valueJson: JSON.stringify({ title: "Petstore" }), + stats, + }); + }, + dispose() {}, + }; + setNativeBindingForTest(binding); + + const runtime = new LlrtRuntime({ wallTimeMs: 1000, memoryMB: 64 }); + + const result = await runtime.callJson< + { spec: { info: { title: string } } }, + { title: string } + >( + `async ({ input }) => ({ title: input.spec.info.title })`, + { spec: { info: { title: "Petstore" } } }, + { wallTimeMs: 50 }, + ); + + expect(result).toEqual({ + ok: true, + value: { title: "Petstore" }, + stats, + }); + expect(calls).toEqual([ + { + source: `async ({ input }) => ({ title: input.spec.info.title })`, + inputJson: JSON.stringify({ spec: { info: { title: "Petstore" } } }), + options: { + memoryMb: 64, + wallTimeMs: 50, + cpuTimeMs: undefined, + maxStackBytes: undefined, + }, + }, + ]); + }); + + it("returns a typed serialization failure when input cannot be JSON stringified", async () => { + const runtime = new LlrtRuntime(); + const circular: Record = {}; + circular.self = circular; + + const result = await runtime.callJson(`async () => null`, circular); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error.code).toBe("SERIALIZATION_ERROR"); + expect(result.error.message).toContain("Converting circular structure"); + } + }); + + it("returns a typed native load failure when no native binding can be loaded", async () => { + setNativeBindingForTest(null); + const runtime = new LlrtRuntime(); + + const result = await runtime.callJson(`async () => 1`, {}); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error.code).toBe("NATIVE_LOAD_ERROR"); + } + }); + + it("returns a typed disposed failure without calling native", async () => { + const calls: unknown[] = []; + const binding: NativeBinding = { + nativeSmoke() { + return "llrt-native-ok"; + }, + callJson() { + calls.push("called"); + return Promise.resolve({ + ok: true, + valueJson: "null", + stats, + }); + }, + dispose() {}, + }; + setNativeBindingForTest(binding); + const runtime = new LlrtRuntime(); + + runtime.dispose(); + const result = await runtime.callJson(`async () => 1`, {}); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error.code).toBe("RUNTIME_DISPOSED"); + } + expect(calls).toEqual([]); + }); +}); diff --git a/packages/llrt/test/stress.test.ts b/packages/llrt/test/stress.test.ts new file mode 100644 index 0000000..20728b4 --- /dev/null +++ b/packages/llrt/test/stress.test.ts @@ -0,0 +1,101 @@ +import { describe, expect, it } from "vitest"; +import { LlrtRuntime } from "../src/index.js"; + +describe("LlrtRuntime stress behavior", () => { + it("applies per-call memory limits independently of the runtime default", async () => { + const runtime = new LlrtRuntime({ wallTimeMs: 1000, memoryMB: 32 }); + + const result = await runtime.callJson( + `async () => { + const chunks = []; + for (let index = 0; index < 100; index++) { + chunks.push("x".repeat(1024 * 1024)); + } + return chunks.length; + }`, + {}, + { memoryMB: 1 }, + ); + + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error.code).toBe("MEMORY_LIMIT"); + expect(result.stats.memoryLimitBytes).toBe(1024 * 1024); + } + }); + + it("keeps repeated executions isolated on one runtime instance", async () => { + const runtime = new LlrtRuntime({ wallTimeMs: 1000, memoryMB: 8 }); + + for (let index = 0; index < 25; index++) { + // oxlint-disable-next-line no-await-in-loop -- this test intentionally exercises repeated sequential calls on one runtime instance. + const result = await runtime.callJson<{ index: number }, { index: number; previous: string }>( + `async ({ input }) => { + const previous = globalThis.__stressProbe ?? "clean"; + globalThis.__stressProbe = input.index; + return { index: input.index, previous }; + }`, + { index }, + ); + + expect(result).toMatchObject({ + ok: true, + value: { index, previous: "clean" }, + }); + } + }); + + it("handles concurrent executions with independent host callback traffic", async () => { + const runtime = new LlrtRuntime({ wallTimeMs: 2000, memoryMB: 16 }); + + const results = await Promise.all( + Array.from({ length: 16 }, async (_, index) => + runtime.callJson<{ index: number }, { index: number; echoed: number }>( + `async ({ input, host }) => ({ + index: input.index, + echoed: await host.echo(input.index), + })`, + { index }, + { + functions: { + echo: async (value: number) => value, + }, + }, + ), + ), + ); + + expect(results).toHaveLength(16); + for (let index = 0; index < results.length; index++) { + expect(results[index]).toMatchObject({ + ok: true, + value: { index, echoed: index }, + }); + } + }); + + it("returns timeout failures for concurrent stalled host callbacks", async () => { + const runtime = new LlrtRuntime({ wallTimeMs: 50, memoryMB: 8 }); + + const results = await Promise.all( + Array.from({ length: 4 }, () => + runtime.callJson( + `async ({ host }) => await host.never()`, + {}, + { + functions: { + never: () => new Promise(() => {}), + }, + }, + ), + ), + ); + + for (const result of results) { + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error.code).toBe("TIMEOUT"); + } + } + }); +}); diff --git a/packages/llrt/tsconfig.json b/packages/llrt/tsconfig.json new file mode 100644 index 0000000..acb7568 --- /dev/null +++ b/packages/llrt/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "lib": ["ES2023"], + "types": ["node"], + "outDir": "dist", + "rootDir": "src", + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src"], + "exclude": ["node_modules", "dist", "test", "native"] +} diff --git a/packages/llrt/tsup.config.ts b/packages/llrt/tsup.config.ts new file mode 100644 index 0000000..4bab704 --- /dev/null +++ b/packages/llrt/tsup.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: { + index: "src/index.ts", + }, + format: ["esm"], + dts: true, + sourcemap: true, + clean: true, +}); diff --git a/packages/llrt/vitest.config.ts b/packages/llrt/vitest.config.ts new file mode 100644 index 0000000..8b5840a --- /dev/null +++ b/packages/llrt/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["test/**/*.test.ts"], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fb4eede..2b1f935 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,12 @@ importers: '@modelcontextprotocol/sdk': specifier: ^1.12.1 version: 1.27.1(zod@4.3.6) + '@robinbraemer/llrt': + specifier: workspace:* + version: link:../llrt + '@types/node': + specifier: ^24.13.1 + version: 24.13.1 hono: specifier: ^4.7.6 version: 4.12.5 @@ -40,13 +46,40 @@ importers: version: 6.0.3 vitest: specifier: ^4.0.0 - version: 4.1.7(vite@7.3.1(tsx@4.21.0)) + version: 4.1.7(@types/node@24.13.1)(vite@7.3.1(@types/node@24.13.1)(tsx@4.21.0)) zod: specifier: ^4.0.0 version: 4.3.6 + packages/llrt: + devDependencies: + '@napi-rs/cli': + specifier: ^3.0.0 + version: 3.7.1(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)(@types/node@24.13.1) + '@types/node': + specifier: ^24.13.1 + version: 24.13.1 + tsup: + specifier: ^8.4.0 + version: 8.5.1(postcss@8.5.6)(tsx@4.21.0)(typescript@6.0.3) + typescript: + specifier: ^6.0.0 + version: 6.0.3 + vitest: + specifier: ^4.0.0 + version: 4.1.7(@types/node@24.13.1)(vite@7.3.1(@types/node@24.13.1)(tsx@4.21.0)) + packages: + '@emnapi/core@1.11.0': + resolution: {integrity: sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==} + + '@emnapi/runtime@1.11.0': + resolution: {integrity: sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==} + + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + '@esbuild/aix-ppc64@0.27.3': resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} engines: {node: '>=18'} @@ -209,6 +242,140 @@ packages: peerDependencies: hono: ^4 + '@inquirer/ansi@2.0.7': + resolution: {integrity: sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/checkbox@5.2.1': + resolution: {integrity: sha512-b6xmA/VlTe0ZgDQHDui+Nav470u7u49nRd8/iuhOcQPO9Ch7lGuogydhi2VOmNlZ+zXcM8IcPuNSwQcdJaF/kw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@6.1.1': + resolution: {integrity: sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@11.2.1': + resolution: {integrity: sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@5.2.2': + resolution: {integrity: sha512-ZRVd/oD+sYsUd5zVm0NflqEzlqfYCyHNsqkHl2oWXEUHs12tCbcSFi+wVFEvD8+LGRaMUsVrE7qeo6lSG/S1Vg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@5.1.1': + resolution: {integrity: sha512-YmQpenjbFSHAK3sOd44puHh3V1KXXr+JiNpUztoSQ4drLh2rTVzTap/YtlAVu/5xavifIlBfNEzJ/neZJ1a/1g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/external-editor@3.0.3': + resolution: {integrity: sha512-6thf5I8q7lZwzGLAxPaaGEREEkZ3nyePPDQ1oyobblxmEE8mqTLguScP7pDjUTAibiyb4hfXl+qjUEJ+di/aNA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@2.0.7': + resolution: {integrity: sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + + '@inquirer/input@5.1.2': + resolution: {integrity: sha512-9K/DDBSQpOyZSkt6sOVP9Vo0TR7atX2kuILsUu0x3wVcVbe97lJwIJKMLdMw25tDYuXl/qp6erT0Xs1rfmcfZg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@4.1.1': + resolution: {integrity: sha512-XF4IXAbPnGPgw0wsbC/i2tPcyfdZgDpUlhsqU0SfT4IRIGWha6Xm9VRgN5yYxJq+jnyXlfXI/nQ3ulfk0iEICA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@5.1.1': + resolution: {integrity: sha512-3XBfF7DAsp5qeDsvN5Rd1HmbNokVvEQoUM0QLrRcybC9nX96w3Pbmu7qUsb3IT3J3jBvs2+mTXaKHOUsgHMLzg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@8.5.2': + resolution: {integrity: sha512-IYR/3C/paEVVQYQvdDlFZVjRCJVYHHON0XXMH91KO9GSxs0TdKYWlUdvfQl2EfAHDxUaN3IBffkE/BDTh5nJ6g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@5.3.1': + resolution: {integrity: sha512-QqdTqQddL3qPX/PPrjobpsO25NZ4dWXgTLenrR445L2ptLEYE6Z+PD5c5CNDJNx4ugRgELAIpSIJxZaO2jJ2Og==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@4.2.1': + resolution: {integrity: sha512-xJj8QWKRSrfKoBIITLZK61dD3zwo0Rz11fgDImku30/Oe81zMdIdGgrLY2h6RkJ+KZ/GhNYIRMKnH/62qBTA5g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@5.2.1': + resolution: {integrity: sha512-FlDndEUww8m7BfukO2nJa25vhD+H5jxxCv4oGioKqzyWz3nPHhhw4LKdYRSlXuAx7DsdWia7iyaBPKKS95Evfw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@4.0.7': + resolution: {integrity: sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@jitl/quickjs-ffi-types@0.32.0': resolution: {integrity: sha512-v9T+GQpmk43VDJ7d72sf0Nexhk+ArvtUihW27dy7lqAl0zBObFKtSBBIm5RBjwIhE8VwsPPm9PNuvPvNqLWUEg==} @@ -247,6 +414,394 @@ packages: '@cfworker/json-schema': optional: true + '@napi-rs/cli@3.7.1': + resolution: {integrity: sha512-7FkcxJ70SqpsYnyjaYwLVpkP3Xoyd8YkT5g7DevuEvSY7mzpLSva8oSQNENHiQ1T0GMLnae1X46KmiNIu5OXDw==} + engines: {node: '>= 16'} + hasBin: true + peerDependencies: + '@emnapi/runtime': ^1.7.1 + peerDependenciesMeta: + '@emnapi/runtime': + optional: true + + '@napi-rs/cross-toolchain@1.0.3': + resolution: {integrity: sha512-ENPfLe4937bsKVTDA6zdABx4pq9w0tHqRrJHyaGxgaPq03a2Bd1unD5XSKjXJjebsABJ+MjAv1A2OvCgK9yehg==} + peerDependencies: + '@napi-rs/cross-toolchain-arm64-target-aarch64': ^1.0.3 + '@napi-rs/cross-toolchain-arm64-target-armv7': ^1.0.3 + '@napi-rs/cross-toolchain-arm64-target-ppc64le': ^1.0.3 + '@napi-rs/cross-toolchain-arm64-target-s390x': ^1.0.3 + '@napi-rs/cross-toolchain-arm64-target-x86_64': ^1.0.3 + '@napi-rs/cross-toolchain-x64-target-aarch64': ^1.0.3 + '@napi-rs/cross-toolchain-x64-target-armv7': ^1.0.3 + '@napi-rs/cross-toolchain-x64-target-ppc64le': ^1.0.3 + '@napi-rs/cross-toolchain-x64-target-s390x': ^1.0.3 + '@napi-rs/cross-toolchain-x64-target-x86_64': ^1.0.3 + peerDependenciesMeta: + '@napi-rs/cross-toolchain-arm64-target-aarch64': + optional: true + '@napi-rs/cross-toolchain-arm64-target-armv7': + optional: true + '@napi-rs/cross-toolchain-arm64-target-ppc64le': + optional: true + '@napi-rs/cross-toolchain-arm64-target-s390x': + optional: true + '@napi-rs/cross-toolchain-arm64-target-x86_64': + optional: true + '@napi-rs/cross-toolchain-x64-target-aarch64': + optional: true + '@napi-rs/cross-toolchain-x64-target-armv7': + optional: true + '@napi-rs/cross-toolchain-x64-target-ppc64le': + optional: true + '@napi-rs/cross-toolchain-x64-target-s390x': + optional: true + '@napi-rs/cross-toolchain-x64-target-x86_64': + optional: true + + '@napi-rs/lzma-android-arm-eabi@1.4.5': + resolution: {integrity: sha512-Up4gpyw2SacmyKWWEib06GhiDdF+H+CCU0LAV8pnM4aJIDqKKd5LHSlBht83Jut6frkB0vwEPmAkv4NjQ5u//Q==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@napi-rs/lzma-android-arm64@1.4.5': + resolution: {integrity: sha512-uwa8sLlWEzkAM0MWyoZJg0JTD3BkPknvejAFG2acUA1raXM8jLrqujWCdOStisXhqQjZ2nDMp3FV6cs//zjfuQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/lzma-darwin-arm64@1.4.5': + resolution: {integrity: sha512-0Y0TQLQ2xAjVabrMDem1NhIssOZzF/y/dqetc6OT8mD3xMTDtF8u5BqZoX3MyPc9FzpsZw4ksol+w7DsxHrpMA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/lzma-darwin-x64@1.4.5': + resolution: {integrity: sha512-vR2IUyJY3En+V1wJkwmbGWcYiT8pHloTAWdW4pG24+51GIq+intst6Uf6D/r46citObGZrlX0QvMarOkQeHWpw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/lzma-freebsd-x64@1.4.5': + resolution: {integrity: sha512-XpnYQC5SVovO35tF0xGkbHYjsS6kqyNCjuaLQ2dbEblFRr5cAZVvsJ/9h7zj/5FluJPJRDojVNxGyRhTp4z2lw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@napi-rs/lzma-linux-arm-gnueabihf@1.4.5': + resolution: {integrity: sha512-ic1ZZMoRfRMwtSwxkyw4zIlbDZGC6davC9r+2oX6x9QiF247BRqqT94qGeL5ZP4Vtz0Hyy7TEViWhx5j6Bpzvw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/lzma-linux-arm64-gnu@1.4.5': + resolution: {integrity: sha512-asEp7FPd7C1Yi6DQb45a3KPHKOFBSfGuJWXcAd4/bL2Fjetb2n/KK2z14yfW8YC/Fv6x3rBM0VAZKmJuz4tysg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/lzma-linux-arm64-musl@1.4.5': + resolution: {integrity: sha512-yWjcPDgJ2nIL3KNvi4536dlT/CcCWO0DUyEOlBs/SacG7BeD6IjGh6yYzd3/X1Y3JItCbZoDoLUH8iB1lTXo3w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/lzma-linux-ppc64-gnu@1.4.5': + resolution: {integrity: sha512-0XRhKuIU/9ZjT4WDIG/qnX7Xz7mSQHYZo9Gb3MP2gcvBgr6BA4zywQ9k3gmQaPn9ECE+CZg2V7DV7kT+x2pUMQ==} + engines: {node: '>= 10'} + cpu: [ppc64] + os: [linux] + + '@napi-rs/lzma-linux-riscv64-gnu@1.4.5': + resolution: {integrity: sha512-QrqDIPEUUB23GCpyQj/QFyMlr8SGxxyExeZz9OWFnHfb70kXdTLWrHS/hEI1Ru+lSbQ/6xRqeoGyQ4Aqdg+/RA==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + + '@napi-rs/lzma-linux-s390x-gnu@1.4.5': + resolution: {integrity: sha512-k8RVM5aMhW86E9H0QXdquwojew4H3SwPxbRVbl49/COJQWCUjGi79X6mYruMnMPEznZinUiT1jgKbFo2A00NdA==} + engines: {node: '>= 10'} + cpu: [s390x] + os: [linux] + + '@napi-rs/lzma-linux-x64-gnu@1.4.5': + resolution: {integrity: sha512-6rMtBgnIq2Wcl1rQdZsnM+rtCcVCbws1nF8S2NzaUsVaZv8bjrPiAa0lwg4Eqnn1d9lgwqT+cZgm5m+//K08Kw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/lzma-linux-x64-musl@1.4.5': + resolution: {integrity: sha512-eiadGBKi7Vd0bCArBUOO/qqRYPHt/VQVvGyYvDFt6C2ZSIjlD+HuOl+2oS1sjf4CFjK4eDIog6EdXnL0NE6iyQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/lzma-wasm32-wasi@1.4.5': + resolution: {integrity: sha512-+VyHHlr68dvey6fXc2hehw9gHVFIW3TtGF1XkcbAu65qVXsA9D/T+uuoRVqhE+JCyFHFrO0ixRbZDRK1XJt1sA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@napi-rs/lzma-win32-arm64-msvc@1.4.5': + resolution: {integrity: sha512-eewnqvIyyhHi3KaZtBOJXohLvwwN27gfS2G/YDWdfHlbz1jrmfeHAmzMsP5qv8vGB+T80TMHNkro4kYjeh6Deg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/lzma-win32-ia32-msvc@1.4.5': + resolution: {integrity: sha512-OeacFVRCJOKNU/a0ephUfYZ2Yt+NvaHze/4TgOwJ0J0P4P7X1mHzN+ig9Iyd74aQDXYqc7kaCXA2dpAOcH87Cg==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@napi-rs/lzma-win32-x64-msvc@1.4.5': + resolution: {integrity: sha512-T4I1SamdSmtyZgDXGAGP+y5LEK5vxHUFwe8mz6D4R7Sa5/WCxTcCIgPJ9BD7RkpO17lzhlaM2vmVvMy96Lvk9Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/lzma@1.4.5': + resolution: {integrity: sha512-zS5LuN1OBPAyZpda2ZZgYOEDC+xecUdAGnrvbYzjnLXkrq/OBC3B9qcRvlxbDR3k5H/gVfvef1/jyUqPknqjbg==} + engines: {node: '>= 10'} + + '@napi-rs/tar-android-arm-eabi@1.1.0': + resolution: {integrity: sha512-h2Ryndraj/YiKgMV/r5by1cDusluYIRT0CaE0/PekQ4u+Wpy2iUVqvzVU98ZPnhXaNeYxEvVJHNGafpOfaD0TA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@napi-rs/tar-android-arm64@1.1.0': + resolution: {integrity: sha512-DJFyQHr1ZxNZorm/gzc1qBNLF/FcKzcH0V0Vwan5P+o0aE2keQIGEjJ09FudkF9v6uOuJjHCVDdK6S6uHtShAw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/tar-darwin-arm64@1.1.0': + resolution: {integrity: sha512-Zz2sXRzjIX4e532zD6xm2SjXEym6MkvfCvL2RMpG2+UwNVDVscHNcz3d47Pf3sysP2e2af7fBB3TIoK2f6trPw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/tar-darwin-x64@1.1.0': + resolution: {integrity: sha512-EI+CptIMNweT0ms9S3mkP/q+J6FNZ1Q6pvpJOEcWglRfyfQpLqjlC0O+dptruTPE8VamKYuqdjxfqD8hifZDOA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/tar-freebsd-x64@1.1.0': + resolution: {integrity: sha512-J0PIqX+pl6lBIAckL/c87gpodLbjZB1OtIK+RDscKC9NLdpVv6VGOxzUV/fYev/hctcE8EfkLbgFOfpmVQPg2g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@napi-rs/tar-linux-arm-gnueabihf@1.1.0': + resolution: {integrity: sha512-SLgIQo3f3EjkZ82ZwvrEgFvMdDAhsxCYjyoSuWfHCz0U16qx3SuGCp8+FYOPYCECHN3ZlGjXnoAIt9ERd0dEUg==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/tar-linux-arm64-gnu@1.1.0': + resolution: {integrity: sha512-d014cdle52EGaH6GpYTQOP9Py7glMO1zz/+ynJPjjzYFSxvdYx0byrjumZk2UQdIyGZiJO2MEFpCkEEKFSgPYA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/tar-linux-arm64-musl@1.1.0': + resolution: {integrity: sha512-L/y1/26q9L/uBqiW/JdOb/Dc94egFvNALUZV2WCGKQXc6UByPBMgdiEyW2dtoYxYYYYc+AKD+jr+wQPcvX2vrQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/tar-linux-ppc64-gnu@1.1.0': + resolution: {integrity: sha512-EPE1K/80RQvPbLRJDJs1QmCIcH+7WRi0F73+oTe1582y9RtfGRuzAkzeBuAGRXAQEjRQw/RjtNqr6UTJ+8UuWQ==} + engines: {node: '>= 10'} + cpu: [ppc64] + os: [linux] + + '@napi-rs/tar-linux-s390x-gnu@1.1.0': + resolution: {integrity: sha512-B2jhWiB1ffw1nQBqLUP1h4+J1ovAxBOoe5N2IqDMOc63fsPZKNqF1PvO/dIem8z7LL4U4bsfmhy3gBfu547oNQ==} + engines: {node: '>= 10'} + cpu: [s390x] + os: [linux] + + '@napi-rs/tar-linux-x64-gnu@1.1.0': + resolution: {integrity: sha512-tbZDHnb9617lTnsDMGo/eAMZxnsQFnaRe+MszRqHguKfMwkisc9CCJnks/r1o84u5fECI+J/HOrKXgczq/3Oww==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/tar-linux-x64-musl@1.1.0': + resolution: {integrity: sha512-dV6cODlzbO8u6Anmv2N/ilQHq/AWz0xyltuXoLU3yUyXbZcnWYZuB2rL8OBGPmqNcD+x9NdScBNXh7vWN0naSQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/tar-wasm32-wasi@1.1.0': + resolution: {integrity: sha512-jIa9nb2HzOrfH0F8QQ9g3WE4aMH5vSI5/1NYVNm9ysCmNjCCtMXCAhlI3WKCdm/DwHf0zLqdrrtDFXODcNaqMw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@napi-rs/tar-win32-arm64-msvc@1.1.0': + resolution: {integrity: sha512-vfpG71OB0ijtjemp3WTdmBKJm9R70KM8vsSExMsIQtV0lVzP07oM1CW6JbNRPXNLhRoue9ofYLiUDk8bE0Hckg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/tar-win32-ia32-msvc@1.1.0': + resolution: {integrity: sha512-hGPyPW60YSpOSgzfy68DLBHgi6HxkAM+L59ZZZPMQ0TOXjQg+p2EW87+TjZfJOkSpbYiEkULwa/f4a2hcVjsqQ==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@napi-rs/tar-win32-x64-msvc@1.1.0': + resolution: {integrity: sha512-L6Ed1DxXK9YSCMyvpR8MiNAyKNkQLjsHsHK9E0qnHa8NzLFqzDKhvs5LfnWxM2kJ+F7m/e5n9zPm24kHb3LsVw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/tar@1.1.0': + resolution: {integrity: sha512-7cmzIu+Vbupriudo7UudoMRH2OA3cTw67vva8MxeoAe5S7vPFI7z0vp0pMXiA25S8IUJefImQ90FeJjl8fjEaQ==} + engines: {node: '>= 10'} + + '@napi-rs/wasm-runtime@1.1.5': + resolution: {integrity: sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@napi-rs/wasm-tools-android-arm-eabi@1.0.1': + resolution: {integrity: sha512-lr07E/l571Gft5v4aA1dI8koJEmF1F0UigBbsqg9OWNzg80H3lDPO+auv85y3T/NHE3GirDk7x/D3sLO57vayw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@napi-rs/wasm-tools-android-arm64@1.0.1': + resolution: {integrity: sha512-WDR7S+aRLV6LtBJAg5fmjKkTZIdrEnnQxgdsb7Cf8pYiMWBHLU+LC49OUVppQ2YSPY0+GeYm9yuZWW3kLjJ7Bg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/wasm-tools-darwin-arm64@1.0.1': + resolution: {integrity: sha512-qWTI+EEkiN0oIn/N2gQo7+TVYil+AJ20jjuzD2vATS6uIjVz+Updeqmszi7zq7rdFTLp6Ea3/z4kDKIfZwmR9g==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/wasm-tools-darwin-x64@1.0.1': + resolution: {integrity: sha512-bA6hubqtHROR5UI3tToAF/c6TDmaAgF0SWgo4rADHtQ4wdn0JeogvOk50gs2TYVhKPE2ZD2+qqt7oBKB+sxW3A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/wasm-tools-freebsd-x64@1.0.1': + resolution: {integrity: sha512-90+KLBkD9hZEjPQW1MDfwSt5J1L46EUKacpCZWyRuL6iIEO5CgWU0V/JnEgFsDOGyyYtiTvHc5bUdUTWd4I9Vg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@napi-rs/wasm-tools-linux-arm64-gnu@1.0.1': + resolution: {integrity: sha512-rG0QlS65x9K/u3HrKafDf8cFKj5wV2JHGfl8abWgKew0GVPyp6vfsDweOwHbWAjcHtp2LHi6JHoW80/MTHm52Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/wasm-tools-linux-arm64-musl@1.0.1': + resolution: {integrity: sha512-jAasbIvjZXCgX0TCuEFQr+4D6Lla/3AAVx2LmDuMjgG4xoIXzjKWl7c4chuaD+TI+prWT0X6LJcdzFT+ROKGHQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/wasm-tools-linux-x64-gnu@1.0.1': + resolution: {integrity: sha512-Plgk5rPqqK2nocBGajkMVbGm010Z7dnUgq0wtnYRZbzWWxwWcXfZMPa8EYxrK4eE8SzpI7VlZP1tdVsdjgGwMw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/wasm-tools-linux-x64-musl@1.0.1': + resolution: {integrity: sha512-GW7AzGuWxtQkyHknHWYFdR0CHmW6is8rG2Rf4V6GNmMpmwtXt/ItWYWtBe4zqJWycMNazpfZKSw/BpT7/MVCXQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/wasm-tools-wasm32-wasi@1.0.1': + resolution: {integrity: sha512-/nQVSTrqSsn7YdAc2R7Ips/tnw5SPUcl3D7QrXCNGPqjbatIspnaexvaOYNyKMU6xPu+pc0BTnKVmqhlJJCPLA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@napi-rs/wasm-tools-win32-arm64-msvc@1.0.1': + resolution: {integrity: sha512-PFi7oJIBu5w7Qzh3dwFea3sHRO3pojMsaEnUIy22QvsW+UJfNQwJCryVrpoUt8m4QyZXI+saEq/0r4GwdoHYFQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/wasm-tools-win32-ia32-msvc@1.0.1': + resolution: {integrity: sha512-gXkuYzxQsgkj05Zaq+KQTkHIN83dFAwMcTKa2aQcpYPRImFm2AQzEyLtpXmyCWzJ0F9ZYAOmbSyrNew8/us6bw==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@napi-rs/wasm-tools-win32-x64-msvc@1.0.1': + resolution: {integrity: sha512-rEAf05nol3e3eei2sRButmgXP+6ATgm0/38MKhz9Isne82T4rPIMYsCIFj0kOisaGeVwoi2fnm7O9oWp5YVnYQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/wasm-tools@1.0.1': + resolution: {integrity: sha512-enkZYyuCdo+9jneCPE/0fjIta4wWnvVN9hBo2HuiMpRF0q3lzv1J6b/cl7i0mxZUKhBrV3aCKDBQnCOhwKbPmQ==} + engines: {node: '>= 10'} + + '@octokit/auth-token@6.0.0': + resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==} + engines: {node: '>= 20'} + + '@octokit/core@7.0.6': + resolution: {integrity: sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==} + engines: {node: '>= 20'} + + '@octokit/endpoint@11.0.3': + resolution: {integrity: sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==} + engines: {node: '>= 20'} + + '@octokit/graphql@9.0.3': + resolution: {integrity: sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==} + engines: {node: '>= 20'} + + '@octokit/openapi-types@27.0.0': + resolution: {integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==} + + '@octokit/plugin-paginate-rest@14.0.0': + resolution: {integrity: sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-request-log@6.0.0': + resolution: {integrity: sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-rest-endpoint-methods@17.0.0': + resolution: {integrity: sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/request-error@7.1.0': + resolution: {integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==} + engines: {node: '>= 20'} + + '@octokit/request@10.0.10': + resolution: {integrity: sha512-KxNC2pTqqhszMNrf12ZRd4PonRgyJdsM4F/jySiddQK+DsRcfBtUvqn8t7UsyZhnRJHvX46OohDt5N3VqIWC2w==} + engines: {node: '>= 20'} + + '@octokit/rest@22.0.1': + resolution: {integrity: sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw==} + engines: {node: '>= 20'} + + '@octokit/types@16.0.0': + resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==} + '@oxlint/binding-android-arm-eabi@1.51.0': resolution: {integrity: sha512-jJYIqbx4sX+suIxWstc4P7SzhEwb4ArWA2KVrmEuu9vH2i0qM6QIHz/ehmbGE4/2fZbpuMuBzTl7UkfNoqiSgw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -489,6 +1044,9 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -498,6 +1056,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/node@24.13.1': + resolution: {integrity: sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg==} + '@vitest/expect@4.1.7': resolution: {integrity: sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==} @@ -550,6 +1111,9 @@ packages: any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -557,6 +1121,9 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + before-after-hook@4.0.0: + resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -593,6 +1160,9 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -600,6 +1170,18 @@ packages: chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + clipanion@4.0.0-rc.4: + resolution: {integrity: sha512-CXkMQxU6s9GklO/1f714dkKBMu1lopS1WFF0B8o4AxPykR1hpozxSiUZ5ZUeBjfPgCWqbcNOtZVFhB8Lkfp1+Q==} + peerDependencies: + typanion: '*' + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -619,6 +1201,10 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -670,6 +1256,14 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + emnapi@1.11.0: + resolution: {integrity: sha512-3+AyVLAzk2jr9FPnCT9fgU4/gGoHrB3MpYxqNG6JdmkWmFPpKAhArQgfK/WEiT9kExX2ecXVA6DoPceDShR47g==} + peerDependencies: + node-addon-api: '>= 6.1.0' + peerDependenciesMeta: + node-addon-api: + optional: true + encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} @@ -692,6 +1286,9 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} + es-toolkit@1.47.0: + resolution: {integrity: sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw==} + esbuild@0.27.3: resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} engines: {node: '>=18'} @@ -736,9 +1333,18 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -846,12 +1452,19 @@ packages: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} + js-yaml@4.2.0: + resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} + hasBin: true + json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} json-schema-typed@8.0.2: resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-with-bigint@3.5.8: + resolution: {integrity: sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==} + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -902,6 +1515,10 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -932,6 +1549,10 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + obug@2.1.2: + resolution: {integrity: sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==} + engines: {node: '>=12.20.0'} + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -1078,6 +1699,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.4: + resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==} + engines: {node: '>=10'} + hasBin: true + send@1.2.1: resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} engines: {node: '>= 18'} @@ -1116,6 +1742,10 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + simple-concat@1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} @@ -1195,6 +1825,9 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsup@8.5.1: resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} engines: {node: '>=18'} @@ -1222,6 +1855,9 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + typanion@3.14.0: + resolution: {integrity: sha512-ZW/lVMRabETuYCd9O9ZvMhAh8GslSqaUjxmK/JLPCh6l73CvLBiuXswj/+7LdnWOgYsQ130FqLzFz5aGT4I3Ug==} + type-is@2.0.1: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} @@ -1239,6 +1875,12 @@ packages: ufo@1.6.3: resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + + universal-user-agent@7.0.3: + resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} + unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -1354,6 +1996,22 @@ packages: snapshots: + '@emnapi/core@1.11.0': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild/aix-ppc64@0.27.3': optional: true @@ -1436,6 +2094,125 @@ snapshots: dependencies: hono: 4.12.5 + '@inquirer/ansi@2.0.7': {} + + '@inquirer/checkbox@5.2.1(@types/node@24.13.1)': + dependencies: + '@inquirer/ansi': 2.0.7 + '@inquirer/core': 11.2.1(@types/node@24.13.1) + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@24.13.1) + optionalDependencies: + '@types/node': 24.13.1 + + '@inquirer/confirm@6.1.1(@types/node@24.13.1)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@24.13.1) + '@inquirer/type': 4.0.7(@types/node@24.13.1) + optionalDependencies: + '@types/node': 24.13.1 + + '@inquirer/core@11.2.1(@types/node@24.13.1)': + dependencies: + '@inquirer/ansi': 2.0.7 + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@24.13.1) + cli-width: 4.1.0 + fast-wrap-ansi: 0.2.2 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + optionalDependencies: + '@types/node': 24.13.1 + + '@inquirer/editor@5.2.2(@types/node@24.13.1)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@24.13.1) + '@inquirer/external-editor': 3.0.3(@types/node@24.13.1) + '@inquirer/type': 4.0.7(@types/node@24.13.1) + optionalDependencies: + '@types/node': 24.13.1 + + '@inquirer/expand@5.1.1(@types/node@24.13.1)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@24.13.1) + '@inquirer/type': 4.0.7(@types/node@24.13.1) + optionalDependencies: + '@types/node': 24.13.1 + + '@inquirer/external-editor@3.0.3(@types/node@24.13.1)': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.2 + optionalDependencies: + '@types/node': 24.13.1 + + '@inquirer/figures@2.0.7': {} + + '@inquirer/input@5.1.2(@types/node@24.13.1)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@24.13.1) + '@inquirer/type': 4.0.7(@types/node@24.13.1) + optionalDependencies: + '@types/node': 24.13.1 + + '@inquirer/number@4.1.1(@types/node@24.13.1)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@24.13.1) + '@inquirer/type': 4.0.7(@types/node@24.13.1) + optionalDependencies: + '@types/node': 24.13.1 + + '@inquirer/password@5.1.1(@types/node@24.13.1)': + dependencies: + '@inquirer/ansi': 2.0.7 + '@inquirer/core': 11.2.1(@types/node@24.13.1) + '@inquirer/type': 4.0.7(@types/node@24.13.1) + optionalDependencies: + '@types/node': 24.13.1 + + '@inquirer/prompts@8.5.2(@types/node@24.13.1)': + dependencies: + '@inquirer/checkbox': 5.2.1(@types/node@24.13.1) + '@inquirer/confirm': 6.1.1(@types/node@24.13.1) + '@inquirer/editor': 5.2.2(@types/node@24.13.1) + '@inquirer/expand': 5.1.1(@types/node@24.13.1) + '@inquirer/input': 5.1.2(@types/node@24.13.1) + '@inquirer/number': 4.1.1(@types/node@24.13.1) + '@inquirer/password': 5.1.1(@types/node@24.13.1) + '@inquirer/rawlist': 5.3.1(@types/node@24.13.1) + '@inquirer/search': 4.2.1(@types/node@24.13.1) + '@inquirer/select': 5.2.1(@types/node@24.13.1) + optionalDependencies: + '@types/node': 24.13.1 + + '@inquirer/rawlist@5.3.1(@types/node@24.13.1)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@24.13.1) + '@inquirer/type': 4.0.7(@types/node@24.13.1) + optionalDependencies: + '@types/node': 24.13.1 + + '@inquirer/search@4.2.1(@types/node@24.13.1)': + dependencies: + '@inquirer/core': 11.2.1(@types/node@24.13.1) + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@24.13.1) + optionalDependencies: + '@types/node': 24.13.1 + + '@inquirer/select@5.2.1(@types/node@24.13.1)': + dependencies: + '@inquirer/ansi': 2.0.7 + '@inquirer/core': 11.2.1(@types/node@24.13.1) + '@inquirer/figures': 2.0.7 + '@inquirer/type': 4.0.7(@types/node@24.13.1) + optionalDependencies: + '@types/node': 24.13.1 + + '@inquirer/type@4.0.7(@types/node@24.13.1)': + optionalDependencies: + '@types/node': 24.13.1 + '@jitl/quickjs-ffi-types@0.32.0': {} '@jitl/quickjs-wasmfile-debug-asyncify@0.32.0': @@ -1490,6 +2267,335 @@ snapshots: transitivePeerDependencies: - supports-color + '@napi-rs/cli@3.7.1(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)(@types/node@24.13.1)': + dependencies: + '@inquirer/prompts': 8.5.2(@types/node@24.13.1) + '@napi-rs/cross-toolchain': 1.0.3(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0) + '@napi-rs/wasm-tools': 1.0.1(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0) + '@octokit/rest': 22.0.1 + clipanion: 4.0.0-rc.4(typanion@3.14.0) + colorette: 2.0.20 + emnapi: 1.11.0 + es-toolkit: 1.47.0 + js-yaml: 4.2.0 + obug: 2.1.2 + semver: 7.8.4 + typanion: 3.14.0 + optionalDependencies: + '@emnapi/runtime': 1.11.0 + transitivePeerDependencies: + - '@emnapi/core' + - '@napi-rs/cross-toolchain-arm64-target-aarch64' + - '@napi-rs/cross-toolchain-arm64-target-armv7' + - '@napi-rs/cross-toolchain-arm64-target-ppc64le' + - '@napi-rs/cross-toolchain-arm64-target-s390x' + - '@napi-rs/cross-toolchain-arm64-target-x86_64' + - '@napi-rs/cross-toolchain-x64-target-aarch64' + - '@napi-rs/cross-toolchain-x64-target-armv7' + - '@napi-rs/cross-toolchain-x64-target-ppc64le' + - '@napi-rs/cross-toolchain-x64-target-s390x' + - '@napi-rs/cross-toolchain-x64-target-x86_64' + - '@types/node' + - node-addon-api + - supports-color + + '@napi-rs/cross-toolchain@1.0.3(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': + dependencies: + '@napi-rs/lzma': 1.4.5(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0) + '@napi-rs/tar': 1.1.0(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0) + debug: 4.4.3 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + - supports-color + + '@napi-rs/lzma-android-arm-eabi@1.4.5': + optional: true + + '@napi-rs/lzma-android-arm64@1.4.5': + optional: true + + '@napi-rs/lzma-darwin-arm64@1.4.5': + optional: true + + '@napi-rs/lzma-darwin-x64@1.4.5': + optional: true + + '@napi-rs/lzma-freebsd-x64@1.4.5': + optional: true + + '@napi-rs/lzma-linux-arm-gnueabihf@1.4.5': + optional: true + + '@napi-rs/lzma-linux-arm64-gnu@1.4.5': + optional: true + + '@napi-rs/lzma-linux-arm64-musl@1.4.5': + optional: true + + '@napi-rs/lzma-linux-ppc64-gnu@1.4.5': + optional: true + + '@napi-rs/lzma-linux-riscv64-gnu@1.4.5': + optional: true + + '@napi-rs/lzma-linux-s390x-gnu@1.4.5': + optional: true + + '@napi-rs/lzma-linux-x64-gnu@1.4.5': + optional: true + + '@napi-rs/lzma-linux-x64-musl@1.4.5': + optional: true + + '@napi-rs/lzma-wasm32-wasi@1.4.5(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': + dependencies: + '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + optional: true + + '@napi-rs/lzma-win32-arm64-msvc@1.4.5': + optional: true + + '@napi-rs/lzma-win32-ia32-msvc@1.4.5': + optional: true + + '@napi-rs/lzma-win32-x64-msvc@1.4.5': + optional: true + + '@napi-rs/lzma@1.4.5(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': + optionalDependencies: + '@napi-rs/lzma-android-arm-eabi': 1.4.5 + '@napi-rs/lzma-android-arm64': 1.4.5 + '@napi-rs/lzma-darwin-arm64': 1.4.5 + '@napi-rs/lzma-darwin-x64': 1.4.5 + '@napi-rs/lzma-freebsd-x64': 1.4.5 + '@napi-rs/lzma-linux-arm-gnueabihf': 1.4.5 + '@napi-rs/lzma-linux-arm64-gnu': 1.4.5 + '@napi-rs/lzma-linux-arm64-musl': 1.4.5 + '@napi-rs/lzma-linux-ppc64-gnu': 1.4.5 + '@napi-rs/lzma-linux-riscv64-gnu': 1.4.5 + '@napi-rs/lzma-linux-s390x-gnu': 1.4.5 + '@napi-rs/lzma-linux-x64-gnu': 1.4.5 + '@napi-rs/lzma-linux-x64-musl': 1.4.5 + '@napi-rs/lzma-wasm32-wasi': 1.4.5(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0) + '@napi-rs/lzma-win32-arm64-msvc': 1.4.5 + '@napi-rs/lzma-win32-ia32-msvc': 1.4.5 + '@napi-rs/lzma-win32-x64-msvc': 1.4.5 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + + '@napi-rs/tar-android-arm-eabi@1.1.0': + optional: true + + '@napi-rs/tar-android-arm64@1.1.0': + optional: true + + '@napi-rs/tar-darwin-arm64@1.1.0': + optional: true + + '@napi-rs/tar-darwin-x64@1.1.0': + optional: true + + '@napi-rs/tar-freebsd-x64@1.1.0': + optional: true + + '@napi-rs/tar-linux-arm-gnueabihf@1.1.0': + optional: true + + '@napi-rs/tar-linux-arm64-gnu@1.1.0': + optional: true + + '@napi-rs/tar-linux-arm64-musl@1.1.0': + optional: true + + '@napi-rs/tar-linux-ppc64-gnu@1.1.0': + optional: true + + '@napi-rs/tar-linux-s390x-gnu@1.1.0': + optional: true + + '@napi-rs/tar-linux-x64-gnu@1.1.0': + optional: true + + '@napi-rs/tar-linux-x64-musl@1.1.0': + optional: true + + '@napi-rs/tar-wasm32-wasi@1.1.0(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': + dependencies: + '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + optional: true + + '@napi-rs/tar-win32-arm64-msvc@1.1.0': + optional: true + + '@napi-rs/tar-win32-ia32-msvc@1.1.0': + optional: true + + '@napi-rs/tar-win32-x64-msvc@1.1.0': + optional: true + + '@napi-rs/tar@1.1.0(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': + optionalDependencies: + '@napi-rs/tar-android-arm-eabi': 1.1.0 + '@napi-rs/tar-android-arm64': 1.1.0 + '@napi-rs/tar-darwin-arm64': 1.1.0 + '@napi-rs/tar-darwin-x64': 1.1.0 + '@napi-rs/tar-freebsd-x64': 1.1.0 + '@napi-rs/tar-linux-arm-gnueabihf': 1.1.0 + '@napi-rs/tar-linux-arm64-gnu': 1.1.0 + '@napi-rs/tar-linux-arm64-musl': 1.1.0 + '@napi-rs/tar-linux-ppc64-gnu': 1.1.0 + '@napi-rs/tar-linux-s390x-gnu': 1.1.0 + '@napi-rs/tar-linux-x64-gnu': 1.1.0 + '@napi-rs/tar-linux-x64-musl': 1.1.0 + '@napi-rs/tar-wasm32-wasi': 1.1.0(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0) + '@napi-rs/tar-win32-arm64-msvc': 1.1.0 + '@napi-rs/tar-win32-ia32-msvc': 1.1.0 + '@napi-rs/tar-win32-x64-msvc': 1.1.0 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + + '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': + dependencies: + '@emnapi/core': 1.11.0 + '@emnapi/runtime': 1.11.0 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@napi-rs/wasm-tools-android-arm-eabi@1.0.1': + optional: true + + '@napi-rs/wasm-tools-android-arm64@1.0.1': + optional: true + + '@napi-rs/wasm-tools-darwin-arm64@1.0.1': + optional: true + + '@napi-rs/wasm-tools-darwin-x64@1.0.1': + optional: true + + '@napi-rs/wasm-tools-freebsd-x64@1.0.1': + optional: true + + '@napi-rs/wasm-tools-linux-arm64-gnu@1.0.1': + optional: true + + '@napi-rs/wasm-tools-linux-arm64-musl@1.0.1': + optional: true + + '@napi-rs/wasm-tools-linux-x64-gnu@1.0.1': + optional: true + + '@napi-rs/wasm-tools-linux-x64-musl@1.0.1': + optional: true + + '@napi-rs/wasm-tools-wasm32-wasi@1.0.1(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': + dependencies: + '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + optional: true + + '@napi-rs/wasm-tools-win32-arm64-msvc@1.0.1': + optional: true + + '@napi-rs/wasm-tools-win32-ia32-msvc@1.0.1': + optional: true + + '@napi-rs/wasm-tools-win32-x64-msvc@1.0.1': + optional: true + + '@napi-rs/wasm-tools@1.0.1(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': + optionalDependencies: + '@napi-rs/wasm-tools-android-arm-eabi': 1.0.1 + '@napi-rs/wasm-tools-android-arm64': 1.0.1 + '@napi-rs/wasm-tools-darwin-arm64': 1.0.1 + '@napi-rs/wasm-tools-darwin-x64': 1.0.1 + '@napi-rs/wasm-tools-freebsd-x64': 1.0.1 + '@napi-rs/wasm-tools-linux-arm64-gnu': 1.0.1 + '@napi-rs/wasm-tools-linux-arm64-musl': 1.0.1 + '@napi-rs/wasm-tools-linux-x64-gnu': 1.0.1 + '@napi-rs/wasm-tools-linux-x64-musl': 1.0.1 + '@napi-rs/wasm-tools-wasm32-wasi': 1.0.1(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0) + '@napi-rs/wasm-tools-win32-arm64-msvc': 1.0.1 + '@napi-rs/wasm-tools-win32-ia32-msvc': 1.0.1 + '@napi-rs/wasm-tools-win32-x64-msvc': 1.0.1 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' + + '@octokit/auth-token@6.0.0': {} + + '@octokit/core@7.0.6': + dependencies: + '@octokit/auth-token': 6.0.0 + '@octokit/graphql': 9.0.3 + '@octokit/request': 10.0.10 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + before-after-hook: 4.0.0 + universal-user-agent: 7.0.3 + + '@octokit/endpoint@11.0.3': + dependencies: + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + + '@octokit/graphql@9.0.3': + dependencies: + '@octokit/request': 10.0.10 + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + + '@octokit/openapi-types@27.0.0': {} + + '@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/types': 16.0.0 + + '@octokit/plugin-request-log@6.0.0(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + + '@octokit/plugin-rest-endpoint-methods@17.0.0(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/types': 16.0.0 + + '@octokit/request-error@7.1.0': + dependencies: + '@octokit/types': 16.0.0 + + '@octokit/request@10.0.10': + dependencies: + '@octokit/endpoint': 11.0.3 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + content-type: 2.0.0 + json-with-bigint: 3.5.8 + universal-user-agent: 7.0.3 + + '@octokit/rest@22.0.1': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.6) + '@octokit/plugin-request-log': 6.0.0(@octokit/core@7.0.6) + '@octokit/plugin-rest-endpoint-methods': 17.0.0(@octokit/core@7.0.6) + + '@octokit/types@16.0.0': + dependencies: + '@octokit/openapi-types': 27.0.0 + '@oxlint/binding-android-arm-eabi@1.51.0': optional: true @@ -1624,6 +2730,11 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.8.1 + optional: true + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -1633,6 +2744,10 @@ snapshots: '@types/estree@1.0.8': {} + '@types/node@24.13.1': + dependencies: + undici-types: 7.18.2 + '@vitest/expect@4.1.7': dependencies: '@standard-schema/spec': 1.1.0 @@ -1642,13 +2757,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.7(vite@7.3.1(tsx@4.21.0))': + '@vitest/mocker@4.1.7(vite@7.3.1(@types/node@24.13.1)(tsx@4.21.0))': dependencies: '@vitest/spy': 4.1.7 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1(tsx@4.21.0) + vite: 7.3.1(@types/node@24.13.1)(tsx@4.21.0) '@vitest/pretty-format@4.1.7': dependencies: @@ -1694,10 +2809,14 @@ snapshots: any-promise@1.3.0: {} + argparse@2.0.1: {} + assertion-error@2.0.1: {} base64-js@1.5.1: {} + before-after-hook@4.0.0: {} + bl@4.1.0: dependencies: buffer: 5.7.1 @@ -1744,12 +2863,22 @@ snapshots: chai@6.2.2: {} + chardet@2.1.1: {} + chokidar@4.0.3: dependencies: readdirp: 4.1.2 chownr@1.1.4: {} + cli-width@4.1.0: {} + + clipanion@4.0.0-rc.4(typanion@3.14.0): + dependencies: + typanion: 3.14.0 + + colorette@2.0.20: {} + commander@4.1.1: {} confbox@0.1.8: {} @@ -1760,6 +2889,8 @@ snapshots: content-type@1.0.5: {} + content-type@2.0.0: {} + convert-source-map@2.0.0: {} cookie-signature@1.2.2: {} @@ -1799,6 +2930,8 @@ snapshots: ee-first@1.1.1: {} + emnapi@1.11.0: {} + encodeurl@2.0.0: {} end-of-stream@1.4.5: @@ -1815,6 +2948,8 @@ snapshots: dependencies: es-errors: 1.3.0 + es-toolkit@1.47.0: {} + esbuild@0.27.3: optionalDependencies: '@esbuild/aix-ppc64': 0.27.3 @@ -1902,8 +3037,18 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + fast-uri@3.1.0: {} + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -2004,10 +3149,16 @@ snapshots: joycon@3.1.1: {} + js-yaml@4.2.0: + dependencies: + argparse: 2.0.1 + json-schema-traverse@1.0.0: {} json-schema-typed@8.0.2: {} + json-with-bigint@3.5.8: {} + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -2045,6 +3196,8 @@ snapshots: ms@2.1.3: {} + mute-stream@3.0.0: {} + mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -2067,6 +3220,8 @@ snapshots: obug@2.1.1: {} + obug@2.1.2: {} + on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -2250,6 +3405,8 @@ snapshots: semver@7.7.4: {} + semver@7.8.4: {} + send@1.2.1: dependencies: debug: 4.4.3 @@ -2313,6 +3470,8 @@ snapshots: siginfo@2.0.0: {} + signal-exit@4.1.0: {} + simple-concat@1.0.1: {} simple-get@4.0.1: @@ -2389,6 +3548,9 @@ snapshots: ts-interface-checker@0.1.13: {} + tslib@2.8.1: + optional: true + tsup@8.5.1(postcss@8.5.6)(tsx@4.21.0)(typescript@6.0.3): dependencies: bundle-require: 5.1.0(esbuild@0.27.3) @@ -2428,6 +3590,8 @@ snapshots: dependencies: safe-buffer: 5.2.1 + typanion@3.14.0: {} + type-is@2.0.1: dependencies: content-type: 1.0.5 @@ -2440,13 +3604,17 @@ snapshots: ufo@1.6.3: {} + undici-types@7.18.2: {} + + universal-user-agent@7.0.3: {} + unpipe@1.0.0: {} util-deprecate@1.0.2: {} vary@1.1.2: {} - vite@7.3.1(tsx@4.21.0): + vite@7.3.1(@types/node@24.13.1)(tsx@4.21.0): dependencies: esbuild: 0.27.3 fdir: 6.5.0(picomatch@4.0.3) @@ -2455,13 +3623,14 @@ snapshots: rollup: 4.59.0 tinyglobby: 0.2.15 optionalDependencies: + '@types/node': 24.13.1 fsevents: 2.3.3 tsx: 4.21.0 - vitest@4.1.7(vite@7.3.1(tsx@4.21.0)): + vitest@4.1.7(@types/node@24.13.1)(vite@7.3.1(@types/node@24.13.1)(tsx@4.21.0)): dependencies: '@vitest/expect': 4.1.7 - '@vitest/mocker': 4.1.7(vite@7.3.1(tsx@4.21.0)) + '@vitest/mocker': 4.1.7(vite@7.3.1(@types/node@24.13.1)(tsx@4.21.0)) '@vitest/pretty-format': 4.1.7 '@vitest/runner': 4.1.7 '@vitest/snapshot': 4.1.7 @@ -2478,8 +3647,10 @@ snapshots: tinyexec: 1.1.2 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vite: 7.3.1(tsx@4.21.0) + vite: 7.3.1(@types/node@24.13.1)(tsx@4.21.0) why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.13.1 transitivePeerDependencies: - msw diff --git a/tsconfig.json b/tsconfig.json index c42b599..ff82ecd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "files": [], "references": [ - { "path": "packages/codemode" } + { "path": "packages/codemode" }, + { "path": "packages/llrt" } ] }