One JavaScript core, two runtimes: testing on Node.js and Bare
Havenfold team ·
Bare is Holepunch's small JavaScript runtime built to be embedded and to run on phones as well as laptops; Node.js is the familiar runtime for servers and desktops. If the same JavaScript core has to run inside a phone app and on a home server, the honest way to know it works is to run its tests on both runtimes, and to treat the differences you find as real findings.
This post explains what Bare is, why a single core matters, how Havenfold tests its core packages on Node.js and Bare, and a concrete difference the two-runtime testing surfaced. Havenfold is pre-alpha: do not use it for real data.
What Bare is
Bare's own README describes it as a "small and modular JavaScript runtime for desktop and mobile". Like Node.js, it has an asynchronous, event-driven architecture. Unlike Node.js, it makes embedding and cross-device support core use cases, "aiming to run just as well on your phone as on your laptop". It's built on libjs, which provides bindings to V8, and libuv for the event loop, and adds only a few pieces on top.
That makes it a natural fit for peer-to-peer apps where a phone is a full participant rather than a thin client.
Why one core instead of two
A common pattern for apps that sync between a phone and a server is two implementations: one in the mobile language, one on the server. Every protocol change then has to be made twice, and the two can drift apart in subtle ways that only show up in production.
Havenfold's design principles reject that explicitly. Principle H2 is "One protocol, one core": phones, boxes, workers and hardware run the same core, with no second implementation. On phones, the core runs inside a Bare worklet; on the home box and workers it runs as a node service.
How the two-runtime testing works
The README's developer section describes it: the core packages are tested on both Node.js and Bare, and Bare comes in as a development dependency, so there's nothing extra to install. Running npm test runs the same check script CI runs:
- every package's tests on Node.js;
- the core packages and the SDK again on Bare;
- installer and service tests;
- independent regeneration of the protocol test vectors;
- licence header and dependency licence checks, derivation-label checks and coverage thresholds for critical modules;
- the phone package's own checks and a short fuzzing run.
The same tests running on two runtimes catch a class of bugs neither would catch alone: code that quietly depends on a Node.js-only module, behaviour that differs between the runtimes' standard libraries, or timing that holds on one event loop and not the other.
Small compatibility layers, written down
Sometimes a dependency expects something only Node.js provides. The repository's answer is a small, named layer rather than a fork. For example, core/vault/bare-crypto is listed in the README as "a small crypto shim so that a vault dependency also runs on Bare". Keeping such layers small and visible makes them easy to review and easy to delete when the dependency no longer needs them.
A real difference the testing found
Havenfold's audit package lists known limits openly, and one of them comes straight from running on Bare. According to the limits document (in Turkish), Bare releases cancelled timers only when the event loop turns; in a fuzzing run that fed a very large number of inputs without letting the loop turn, this showed up as apparent memory growth. The fuzzing harness now lets the loop turn regularly. The document is careful about scope: the effect on the product's transport and ceremony timers has not been measured.
That's the kind of finding two-runtime testing is for. It's not a crash or a headline; it's a difference in behaviour that would be easy to miss if the core were tested on only one runtime.
Where it honestly stands
- Node.js today on the box and workers. The node software's cores run on Node.js today; running them on Bare is planned. On phones, the core runs on Bare.
- Node.js version. The README asks for Node.js 22; CI tests only 22, and some steps fail on 24 today.
- Pre-alpha. Tests on two runtimes are a strong habit, not a proof. Havenfold has not run in a real home and has had no independent security audit.
Summary
- Bare is Holepunch's small, embeddable JavaScript runtime for phones and desktops; Node.js is the familiar server runtime.
- Havenfold's principle "one protocol, one core" means phones, boxes and workers run the same core, with no second implementation.
- Every package's tests run on Node.js, and the core packages and SDK run again on Bare, in the same check CI uses.
- Small, named compatibility layers bridge the gaps, and two-runtime testing surfaced a real timer behaviour difference that is now documented as a known limit.
- Havenfold is pre-alpha and not for real data.
Havenfold is pre-alpha: do not use it for real data. It has not yet run in a real home, and there is no independent security audit yet.
Sources