Skip to content

Repository files navigation

libbun

Rust facade for hosting JavaScript and TypeScript providers through a non-CLI Bun embedding boundary.

This repository owns the stable facade, conformance tests, and a vendored Bun source snapshot. It does not call Bun CLI main, Cli::start, or process-global command dispatch.

Current Bun source target:

9ecb985ad0f06fa12cbd8eede2404589992527d5

Status

The initial crate defines the embedding ABI, settled provider-call receipts, structured provider execution failures, structural value carriers, prepared source bundle artifacts, output capture, deterministic shutdown, and Rust-substrate rejection. Ordinary provider hosts call once and receive one terminal success or failure; raw event-loop pumping and parked async handles are kept on the explicitly named LowLevelBunHost diagnostic interface.

The native adapter binds this facade to Bun/JSC internals and has a real linked integration flow for source module load, prepared source bundle load, synchronous and asynchronous settled provider calls, structured provider errors, host environment overlays, dedicated internal log capture, and shutdown. Downstream hosts consume the native implementation only through the replaceable dynamic plugin described by ADR-2038; they should not statically link libbun-native.

Downstream Use

Downstream Rust applications depend on the facade crate and load the native Bun implementation through a replaceable plugin. Product hosts should bundle that plugin relative to their own binary. The download/cache helpers are development and packaging conveniences, not the runtime contract for shipped hosts.

Bundled Product Integration

Use this mode for applications that ship a native binary.

Depend on the facade without download-plugin:

cargo add libbun --features dynamic-loading

Bundle the verified native plugin beside the host binary, or in a deterministic directory relative to it:

bin/
  ss
  liblibbun_plugin_native.dylib      # macOS
  liblibbun_plugin_native.so         # Linux

Then load by exact path or binary-relative resolution:

use libbun::dynamic::DynamicBunRuntime;

let runtime = DynamicBunRuntime::initialize_with_bundled_plugin(config, host_binary_path)?;

LIBBUN_PLUGIN_PATH remains a user/admin replacement override. Product hosts must not rely on ~/.cache/libbun, LIBBUN_HOME, or build-output release caches at runtime.

Automatic Cargo Build Download

Use this mode for local development and experiments whose Cargo builds are allowed to download verified release artifacts. It is not the product shipping topology for native host binaries.

Add libbun with dynamic-loading and download-plugin:

cargo add libbun --features dynamic-loading,download-plugin

With download-plugin, libbun's build script selects the Cargo TARGET, downloads the matching native plugin release asset for the crate version, verifies its committed checksum, and extracts it under Cargo's OUT_DIR.

download-plugin is intentionally opt-in because it makes Cargo builds depend on network access unless an override is provided. Use these overrides when the artifact is pre-fetched by CI, a package manager, or an app release process:

LIBBUN_PLUGIN_PATH=/absolute/path/to/liblibbun_plugin_native.dylib
LIBBUN_PLUGIN_BUNDLE_DIR=/absolute/path/to/extracted/libbun/bundle
LIBBUN_PLUGIN_ARCHIVE=/absolute/path/to/libbun-plugin-native-vX.Y.Z-<target>.tar.zst
LIBBUN_DOWNLOAD_PLUGIN=0

LIBBUN_PLUGIN_PATH is also the user replacement path and always wins at runtime.

No-Download Packaging

Package managers, hermetic CI systems, and app release processes can fetch the GitHub Release assets directly and place the extracted plugin into the host bundle. The important rules are that the plugin remains dynamically loaded, user-replaceable, and binary-relative for product hosts.

Download the plugin asset that matches the host platform from the native plugin release tag selected by the libbun facade crate. Facade patch releases may reuse an existing native plugin release when the native bytes do not change. The selected tag is exposed by libbun::release::RELEASE_TAG and in missing plugin errors. The supported native plugin release targets are:

libbun-plugin-native-vX.Y.Z-aarch64-apple-darwin.tar.zst
libbun-plugin-native-vX.Y.Z-x86_64-unknown-linux-gnu.tar.zst
libbun-plugin-native-vX.Y.Z-aarch64-unknown-linux-gnu.tar.zst

The consumer contract is the same on every platform:

consumer app -> bundled plugin path or LIBBUN_PLUGIN_PATH -> native plugin

The implementation behind that plugin is recorded in libbun-native-bundle.json:

macOS:
consumer app -> dynamically loaded .dylib -> in-process Bun/JSC/WebKit

Linux in-process releases:
consumer app -> dynamically loaded .so -> in-process Bun/JSC/WebKit

Older Linux helper-backed releases:
consumer app -> dynamically loaded .so -> helper process -> Bun/JSC/WebKit

Linux in-process tarballs contain liblibbun_plugin_native.so plus libbun-native-bundle.json; helper-backed tarballs also contain libbun-runtime-native. Hosts always point LIBBUN_PLUGIN_PATH at the .so. For older helper-backed bundles, set LIBBUN_RUNTIME_NATIVE_PATH only when testing or replacing a modified helper build. The helper process is an implementation detail of those releases, not a downstream API commitment.

Hosts should prefer DynamicBunRuntime::load(...) with an exact bundled path, DynamicBunRuntime::initialize_with_bundled_plugin(...), or DynamicBunRuntime::initialize_with_plugin_dir(...). These APIs honor LIBBUN_PLUGIN_PATH as the replacement override and do not inspect runtime plugin caches.

Manual macOS bundling example when download-plugin is not used:

native_version=v0.2.3
target=aarch64-apple-darwin
curl -LO "https://github.com/enki/libbun/releases/download/${native_version}/libbun-plugin-native-${native_version}-${target}.tar.zst"
mkdir -p dist/bin
tar --zstd -xf "libbun-plugin-native-${native_version}-${target}.tar.zst" -C dist/bin

Linux setup is the same except for the target name and .so filename:

native_version=v0.2.3
target=aarch64-unknown-linux-gnu
curl -LO "https://github.com/enki/libbun/releases/download/${native_version}/libbun-plugin-native-${native_version}-${target}.tar.zst"
mkdir -p dist/bin
tar --zstd -xf "libbun-plugin-native-${native_version}-${target}.tar.zst" -C dist/bin

Minimal dynamic-loading example:

use libbun::dynamic::DynamicBunRuntime;
use libbun::{
    BunHost, BunModuleSpec, BunRuntimeConfig, ProviderCallResult,
    ProviderContractIdentity, ProviderDeadline, ProviderDomainClass,
    ProviderRequest, ProviderSettleOptions, SettledProviderReceipt,
    StructuralValue,
};
use serde_json::json;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let host_binary_path = std::env::current_exe()?;
    let config = BunRuntimeConfig::new("example-host", std::env::current_dir()?);
    let runtime =
        DynamicBunRuntime::initialize_with_bundled_plugin(config.clone(), host_binary_path)?;
    let mut host = BunHost::from_runtime(config, runtime);

    let receipt = host.call_provider_until_settled(
        ProviderRequest {
            contract: ProviderContractIdentity {
                package: "@example/provider".to_string(),
                capability: "example/run".to_string(),
                contract_fingerprint: "example".to_string(),
            },
            domain: ProviderDomainClass::JavaScriptExternalTransport,
            module: BunModuleSpec::Source {
                module_id: "provider".to_string(),
                source: r#"
                    export async function run(input) {
                        await Promise.resolve();
                        return { ok: true, input };
                    }
                "#
                .to_string(),
            },
            export: "run".to_string(),
            input: StructuralValue(json!({ "value": 7 })),
        },
        ProviderSettleOptions::new(ProviderDeadline::from_millis(5_000)),
    )?;

    let SettledProviderReceipt::Ready { result, .. } = receipt else {
        panic!("provider failed: {receipt:?}");
    };
    assert_eq!(
        result,
        ProviderCallResult::Ok(StructuralValue(json!({
            "ok": true,
            "input": { "value": 7 }
        })))
    );

    host.shutdown()?;
    Ok(())
}

If LIBBUN_PLUGIN_PATH is unset and no bundled plugin exists next to the host binary or in the configured plugin directory, initialization fails with an error naming the expected plugin filename and directory. If the plugin ABI does not match the facade ABI, initialization fails before a runtime is created.

If you redistribute the native plugin binary, pass through the matching SOURCE.txt, NOTICE.txt, licenses.json, source archive, and checksum file from the same GitHub Release. Keep the plugin replaceable by user-controlled path or configuration.

Vendored Bun

Bun source is tracked at vendor/bun. The snapshot is created from upstream Git history with git archive, so it excludes nested .git metadata and local build artifacts. Bun build-time source dependencies needed by the Rust crates, including lolhtml, are vendored under vendor/bun/vendor.

Update to a new upstream ref:

scripts/update-vendored-bun.sh <ref>

Verify the vendored snapshot:

scripts/verify-vendored-bun.sh

Prepare Bun's generated Rust inputs and check the reusable Rust runtime crates:

scripts/check-vendored-bun-rust.sh

That script runs Bun configure/codegen inside vendor/bun, rewrites generated artifact identity to the pinned BUN_SOURCE_COMMIT, checks bun_jsc plus bun_runtime, and type-checks the native/ adapter with Bun's pinned nightly toolchain.

Native Adapter

native/ contains the nightly-only adapter that implements BunEmbeddingRuntime over vendored Bun/JSC crates. It is kept out of the default crate so downstream users can depend on the stable facade without pulling Bun's build toolchain into their normal Rust build. It is an internal implementation crate for the dynamic plugin, not a downstream dependency surface.

Run native adapter integration tests against Bun's C++/JSC objects:

scripts/prepare-native-bun-link.sh
LIBBUN_NATIVE_LINK_BUN=1 cargo +nightly-2026-05-06 test --manifest-path native/Cargo.toml --features internal-adapter

The native link manifest is prepared from Bun's release profile only so local integration tests do not depend on a developer debug build directory at runtime. Debug-profile manifests, bun-debug, build/debug, and debug WebKit/JSC inputs are rejected by the preparation script and Cargo build scripts.

Static native Bun/JSC/WebKit link inputs are test-only. Release packaging and release-asset verification call scripts/assert-no-static-link-assets.sh and refuse archives containing object files, static libraries, Rust rlibs, or bitcode objects. Those assets must not appear in published plugin assets, checksum tables, or crates.io-facing release artifacts.

Dynamic Plugin

plugin/ builds libbun-plugin-native as a cdylib. This is the only supported way for downstream applications to use the native Bun/JSC implementation.

The commands below are local test builds. They may use static native link inputs to exercise the embedding boundary, but their outputs are not distributable release assets.

Build the macOS in-process plugin for local testing after preparing the native link manifest:

scripts/prepare-native-bun-link.sh
LIBBUN_NATIVE_LINK_BUN=1 cargo +nightly-2026-05-06 build --release --manifest-path plugin/Cargo.toml

Build the Linux in-process plugin for local testing with PIC WebKit inputs:

scripts/prepare-native-bun-link.sh
scripts/fetch-webkit-pic-artifact.sh --target x86_64-unknown-linux-gnu \
  --manifest vendor/bun/build/release/libbun_native_link_manifest.txt \
  --out vendor/bun/build/release/libbun_native_link_manifest.pic.txt
LIBBUN_NATIVE_LINK_MANIFEST=vendor/bun/build/release/libbun_native_link_manifest.pic.txt \
  LIBBUN_NATIVE_LINK_BUN=1 \
  RUSTFLAGS="-C link-arg=-fuse-ld=lld" \
  cargo +nightly-2026-05-06 build --release --manifest-path plugin/Cargo.toml --features linux-in-process

The older helper-backed Linux transport is quarantined for legacy diagnostics. It is not a production release path. Building it requires an explicit legacy feature and opt-in environment variable:

scripts/prepare-native-bun-link.sh
LIBBUN_ENABLE_LEGACY_LINUX_HELPER=1 \
  cargo +nightly-2026-05-06 build --release --manifest-path plugin/Cargo.toml \
    --features legacy-linux-helper-process
LIBBUN_NATIVE_LINK_BUN=1 cargo +nightly-2026-05-06 build --release --manifest-path runtime/Cargo.toml

Use LIBBUN_NATIVE_BUN_BUILD_DIR=vendor/bun/build/native-$(uname -m)-$(uname -s) to keep platform-specific Bun native build products outside the default vendor/bun/build/release directory. The native plugin link path is release profile only; do not use debug Bun profiles for libbun plugin artifacts.

Rust hosts can enable the facade's dynamic-loading feature and load the plugin at runtime with libbun::dynamic::DynamicBunRuntime. BunHost initialization through the trait reads LIBBUN_PLUGIN_PATH; hosts that want explicit path control can call DynamicBunRuntime::load(path, config) directly.

Native Plugin Releases

Official native plugin binaries are produced by GitHub Actions and published as GitHub Release assets with matching source, notice, license inventory, source instructions, and checksum files.

Release packaging is fail-closed for static-linkable files. Any object file, static library, Rust rlib, or bitcode object in a release archive is rejected before assets are uploaded, verified, or recorded in checksum tables.

Before creating a release tag, run the local preflight:

scripts/preflight-native-plugin-release.sh v0.2.3

On Linux, preflight defaults to the PIC single-plugin in-process release path. The older helper-backed path is available only with LIBBUN_NATIVE_RUNTIME_MODE=helper-process and LIBBUN_ENABLE_LEGACY_LINUX_HELPER=1 for diagnostics.

After the preflight passes, commit the release changes and push the annotated release tag:

git add .
git commit -m "Prepare native plugin release"
scripts/create-native-plugin-release.sh v0.2.3

Pushing the tag triggers .github/workflows/release-native-plugin.yml. Inspect the completed workflow and GitHub Release assets before announcing the release.

About

Bun embedding facade for native provider hosting

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages