MusePi

Natives Architecture

@musepi/pi-natives is a two-layer package around an ESM loader:

  1. ESM loader/package entrypoint resolves and loads the correct .node addon with createRequire, validates the release sentinel outside workspace-dev loads, and re-exports generated classes/functions plus enum runtime objects as explicit named ESM exports.
  2. Rust N-API module layer implements the exported functions/classes and emits the generated TypeScript declarations.

This document is the foundation for deeper module-level docs.

Implementation files

Package entrypoint and public surface

packages/natives/package.json points at generated native artifacts:

There is no current packages/natives/src TypeScript wrapper layer. Consumers import functions/classes/enums directly from @musepi/pi-natives; the type contract is the generated native/index.d.ts plus the explicit named exports generated into native/index.js by scripts/gen-enums.ts.

Current capability groups in the generated API include:

Loader layer

packages/natives/native/index.js is the package entrypoint; it calls loadNative() from loader-state.js, which owns runtime addon selection and optional embedded extraction.

Candidate resolution model

Filename strategy:

Platform-specific variant detection

For x64, variant selection uses:

PI_NATIVE_VARIANT can force modern or baseline; invalid values are ignored.

Binary distribution and extraction model

The published @musepi/pi-natives package ships only the loader layer in native/: the ESM loader (index.js), generated declarations (index.d.ts), the loader-state.js/.d.ts helpers, and the embedded-addon manifest stub (embedded-addon.js). It carries no .node binaries.

Each platform’s prebuilt .node is published as a separate optional-dependency leaf package — @musepi/pi-natives-<platform>-<arch>, one per supported tag — which the core lists in optionalDependencies at the lockstep version during publish. npm/bun install only the leaf whose os/cpu match the host. The working-tree package keeps built .node files under native/ for local dev; the release-publish rewrite (prepareNativeCorePackage in scripts/ci-release-publish.ts) strips them from the core tarball, and the leaves are generated by packages/natives/scripts/gen-npm-packages.ts (LEAF_TARGETS). Adding a build target therefore requires a matching LEAF_TARGETS entry, or the binary never reaches npm users.

For compiled binaries, loader behavior is:

  1. Check versioned user cache path: <getNativesDir()>/<packageVersion>/....
  2. Check legacy compiled-binary location:
    • Windows: %USERPROFILE%\.musepi
    • non-Windows: ~/.local/bin
  3. Fall back to packaged native/ and executable directory candidates.

getNativesDir() uses $XDG_DATA_HOME/musepi/natives when $XDG_DATA_HOME/musepi exists; otherwise it uses ~/.musepi/natives.

If a populated embedded addon manifest is present, it is also treated as a compiled-binary signal. Current embedded manifests point at a gzip-compressed tar archive (embedded-addons.<tag>.tar.gz) that contains one or more matching .node files. The loader extracts the archive into the versioned cache directory, validates the selected file by size, and prepends that cache path before normal candidate probing.

For npm/bun installs (non-compiled), loader-state.js resolves the platform leaf directory via require.resolve("@musepi/pi-natives-<tag>/package.json") and probes its .node before the core package’s native/ directory and the executable directory. The optional-dependency binary is therefore preferred over any .node left in the core (e.g. a stale local-dev build). On Windows node_modules installs, the loader first stages the selected leaf/core addon into <getNativesDir()>/<packageVersion>/... and prepends that staged path so running processes do not lock the node_modules copy during global updates.

Failure modes

Loader failures are explicit:

Rust N-API module layer

crates/pi-natives/src/lib.rs declares exported module ownership:

N-API exports are generated from Rust #[napi] functions/classes/objects/enums. Snake_case Rust names are exposed as camelCase JavaScript names unless explicitly configured by napi-rs.

Ownership boundaries

For the contributor-facing crate map covering pi-natives, pi-shell, pi-ast, pi-iso, pi-walker, pi_uu_grep, pi-uutils-ctx, and the vendored brush-* crates, see native-crates.md. The root-docs inclusion policy that keeps internal Rust crates under native architecture docs unless promoted as user-facing also lives in user-facing-packages.md.

Runtime flow (high level)

  1. Consumer imports from @musepi/pi-natives.
  2. native/index.js computes platform/arch/variant and candidate paths.
  3. Optional embedded archive extraction or Windows node_modules staging can prepend a versioned-cache candidate.
  4. Each candidate is require(...)d; install/compiled loads must expose the package-version sentinel.
  5. The loaded addon object is bound to explicit named ESM exports, including generated enum objects.
  6. Caller invokes generated N-API functions/classes directly.

Glossary