Bare Docs

Bare runtime API

Showing every release, with badges on the differences.

Reference for the Bare global namespace and its C embedding API: process properties, lifecycle methods and events, native addons, threads, IPC, and bare.h.

stable

The core JavaScript API of Bare is exposed through the global Bare namespace. This page documents that surface. For the command-line runner, see the bare CLI. Bare provides no standard library beyond this namespace; the collection of external bare-* modules built for it is in Bare modules.

Process properties

Bare.platform

The operating system Bare was compiled for. One of android, darwin, ios, linux, win32.

Bare.arch

The processor architecture Bare was compiled for. One of arm, arm64, ia32, mips, mipsel, riscv64, x64.

Bare.argv

The command-line arguments passed to the process when launched.

Bare.pid

The ID of the current process.

Bare.exitCode

The code returned once the process exits. If the process exits via Bare.exit() without a code, Bare.exitCode is used. Assigned values are truncated to eight bits, so 256 becomes 0.

Bare.version

The Bare version string, with a leading v, such as v1.34.1.

Bare.versions

An object of version strings for Bare and its dependencies.

Lifecycle methods

Bare.exit([code])

Immediately terminate the process or current thread with exit status code, defaulting to Bare.exitCode. Like Bare.exitCode, code is truncated to eight bits.

Bare.suspend([linger])

Suspend the process and all threads, emitting a suspend event so work can stop. When no work remains and the process would otherwise exit, an idle event is emitted and the loop blocks instead of exiting.

Bare.wakeup([deadline])

Wake the process and all threads during suspension, emitting a wakeup event. Work may run until deadline, after which the process suspends again.

Bare.idle()

Immediately suspend the event loop and trigger the idle event.

Bare.resume()

Resume the process and all threads after suspension, emitting a resume event. May be called from a suspend, wakeup, or idle listener to cancel suspension, which is possible from the moment the suspend event is emitted until all idle listeners have run.

Events

Listeners attach with Bare.on(event, listener).

uncaughtException— (err)

A JavaScript exception was thrown without being caught. By default uncaught exceptions are printed to stderr and the process is aborted; adding a listener overrides that default.

unhandledRejection— (reason, promise)

A promise was rejected without being handled. Default behaviour (print to stderr, abort) is overridden by adding a listener.

beforeExit— (code)

Emitted when the loop runs out of work, before the process or thread exits. Scheduling more work here keeps the process alive and re-arms the event. Not emitted on explicit Bare.exit() or an uncaught exception.

exit— (code)

Emitted when the process or thread exits.

Additional work must not be scheduled from an exit listener. If the process is forcefully terminated from within an exit listener, the remaining listeners will not run.

suspend— (linger), wakeup— (deadline), idle— (), resume— ()

The lifecycle events described under Lifecycle methods. Each is emitted for the process or for the current thread.

  • suspend — the process or thread was suspended. Defer, cancel, or pause any in-progress or outstanding work, such as network activity or file system access, and schedule no additional work.
  • wakeup — the process or thread woke up during suspension. Once it becomes idle, or if it is not idle by the time deadline has passed, it suspends itself again and an idle event is emitted.
  • idle — the process or thread became idle after suspension. Once all listeners have run, the event loop blocks and no further work is performed until the process is resumed.
  • resume — the process or thread resumed after suspension. Continue deferred and paused work; new work may be scheduled again.

A suspend, wakeup, or idle listener may call Bare.resume() to cancel the suspension before it completes.

Lifecycle

Bare exposes process suspension so it can behave on platforms with strict application-lifecycle rules—mobile in particular. A suspended loop stays alive but idle rather than exiting, and can be woken for bounded bursts of work. The same transitions are available from C (bare_suspend(), bare_wakeup(), bare_resume()) and from JavaScript.

Suspending emits a suspend event. Once the loop has no work left and would otherwise exit, it emits idle and blocks, which keeps the process from exiting. Resuming emits resume and unblocks the loop, which can then exit once no work is left. While the process is suspended, the loop can be woken for limited periods to perform work, which emits wakeup. Each wakeup has a deadline, after which the loop is stopped and the process suspends again, emitting another idle event.

Bare.suspend() Bare.exit() Bare.resume() Bare.wakeup() Bare.idle() Bare.exit() Bare.resume() Bare.idle() Bare.exit() Bare.resume() Bare.exit() Bare.wakeup() Active Suspending Terminated Exiting Awake Suspended Idle

For the rationale, see Inside Bare; for driving it from a native host, see One core, many platforms.

Bare.Addon

Loads native addons—typically C/C++ shared libraries.

This is an advanced API most users never touch directly; native addons are normally loaded for you by the modules that need them.

  • new Addon(url) New in 1.31.0 — load a static or dynamic addon from a url whose protocol is builtin:, linked:, or file:. If url is not a static addon, Bare looks for a matching dynamic object library instead. Throws for any other protocol, or if loading fails. Returns an addon whose addon.url is the WHATWG URL identifier of the addon and whose addon.exports are its exports.
  • Addon.host— the target triplet identifying the current addon host.
  • Addon.seal() New in 1.32.0 — freeze addon loading. Once sealed, no dynamic addon can be loaded by the current thread or any other thread of the process, now or in the future; loading one throws. Statically linked addons are unaffected. This is one-way for the lifetime of the process, and applies to this process alone—intended for embedders that load a fixed set of trusted addons up front and then want to block any further native code, for example when building a sandbox. Embedders running several Bare processes within the same operating system process can seal each of them independently, and sealing one has no effect on the addons the others can load. Addons are owned by the process that loaded them and are unloaded when it is torn down. From Bare 1.33.0, sealing also freezes the embedder context registry, after which no further handles can be published. See the threat model for what sealing does and doesn't guarantee.
  • Addon.sealed New in 1.32.0 — whether Addon.seal() has been called.
  • Deprecated in 1.31.0 Addon.cache, Addon.load(url), Addon.resolve(specifier, parentURL[, options])— superseded by new Addon(url). Still work, but new code should use the constructor.

Bare.Thread

Lightweight threads with synchronous joins—similar to Node.js workers but with a minimal surface.

This is an advanced API most users never touch directly.

  • Thread.isMainThread— true on the main thread.
  • Thread.self— the current thread as a ThreadProxy, or null on the main thread. Thread.self.data is the data passed on creation, or null if none was passed.
  • new Thread([filename][, options][, callback])— start a thread running filename. If callback is given, its body runs on the new thread with Thread.self.data as its argument. Options: data (default null), transfer (a transfer list, default []), encoding (the encoding of source when it is a string, default 'utf8'), stackSize (in bytes, 0 for the default), and Deprecated in 1.32.0 source.
  • new Thread(callback), new Thread(filename, source[, options]) New in 1.32.0 — a plain callback can be passed with no filename, and source (a string or Buffer) can be passed positionally right after filename instead of via options.source.
  • Deprecated in 1.31.0 Thread.create(...)— equivalent to new Thread(...). Still works, but new code should use the constructor.
  • A thread is loaded through a protocol that reaches nothing, so it runs only the source it was given. filename names that source rather than locating it, and a thread does not inherit the module protocol of the thread that spawned it. Anything else the thread needs, including the modules it imports, must travel with it as source or data. Reading a module graph off disk and handing it over is the spawner's job, so that a thread never reaches further than the code that started it. To run a module graph on a thread, bundle it with bare-bundle and pass the bundle as source, which is what bare-thread does.
  • thread.joined— whether the thread has been joined.
  • thread.join()— block until the thread exits.
  • thread.suspend([linger]), thread.wakeup([deadline]), thread.resume(), thread.terminate()— drive the thread's lifecycle, equivalent to calling the matching Bare.* method inside it.

Higher-level worker threads are available in bare-worker.

Bare.IPC

Optional streaming communication between an embedder and JavaScript. Defaults to null, meaning no streaming channel is set. When an embedder provides one, Bare.IPC is a bare-stream Duplex. The higher-level bare-kit IPC abstraction is built on this.

This is an advanced API most users never touch directly.

Embedding

Bare can be embedded with the C API in include/bare.h:

#include <bare.h>
#include <uv.h>

bare_t *bare;
bare_setup(uv_default_loop(), platform, &env /* Optional */, argc, argv, options, &bare);
bare_load(bare, filename, source, &module /* Optional */);
bare_run(bare, UV_RUN_DEFAULT);

int exit_code;
bare_teardown(bare, UV_RUN_DEFAULT, &exit_code);

If source is NULL, the contents of filename are read at runtime. For platform shells that wrap this API, see bare-ios and bare-android, or use bare-kit, which packages embedding behind a worklet API.

From Bare 1.34.0, an embedder whose thread belongs to a host loop, such as the run loop of a user interface, drives Bare with bare_poll() instead of bare_run(). It runs the loop without blocking and reports how long the host may sleep before calling again:

int timeout;
bare_poll(bare, &timeout);

timeout is in milliseconds, and may be NULL if the host has no use for it. A timeout of -1 means the host may sleep until the loop's backend descriptor, as given by uv_backend_fd(), becomes readable. With a timeout of 0, poll again without sleeping. bare_poll() returns zero once the process has exited, and sets timeout to 0 when it does, so check the return value rather than the timeout. Tear the process down at that point rather than polling it again.

Do not sleep on the timeout alone. Work can arrive from another thread while the host sleeps, and a host that waits only for the timeout to elapse will not notice it until then. Wait on the descriptor from uv_backend_fd() as well, and wake on whichever comes first.

To hand addons a native handle that only the embedder can produce, such as the Android JavaVM *, publish it with bare_context_set() before the first bare_load(). See the embedder context API.

Bare is designed to be embedded alongside code the embedder may not fully trust. Bare.Addon.seal() is the mechanism for freezing the set of native code a process can load. The Bare threat model sets out what sealing does and does not promise, and embedders should read it before running untrusted JavaScript.

To build libbare for linking into an embedder, see Building Bare.

bare_poll()

int
bare_poll(bare_t *bare, int *timeout);

Runs the event loop without blocking, in place of bare_run(), for an embedder whose thread belongs to a host loop, such as the run loop of a user interface. Requires Bare >=1.34.0.

ParameterTypeDescription
barebare_t *The process to poll.
timeoutint *Receives how long the host may sleep before polling again, in milliseconds.

Returns zero once the process has exited, at which point it should be torn down rather than polled again.

int timeout;
bare_poll(bare, &timeout);

A timeout of -1 means that the host may sleep until the backend descriptor of the loop, as given by uv_backend_fd(), becomes readable. A host that sleeps on the timeout alone rather than on the descriptor will miss work that arrives from another thread.

bare_attach() and bare_detach()

int
bare_attach(bare_t *bare, bare_t **previous);

int
bare_detach(bare_t *bare, bare_t *previous);

bare_attach() attaches the process to the calling thread and stores the process that was attached before in previous. The attached process is the one that bare_context_get() resolves against and that an addon loaded while attached belongs to. bare_detach() detaches the process and attaches previous in its place; a NULL previous leaves the thread attached to no process. Both return 0 on success. Requires Bare >=1.34.0.

bare_run() attaches the process while it runs, and so does loading an addon, so native code reached through either is already attached. A call the embedder makes itself is not, so attach the process around it:

bare_t *previous;
bare_attach(bare, &previous);

js_call_function(env, receiver, fn, argc, argv, &result);

bare_detach(bare, previous);

bare_run(bare, UV_RUN_NOWAIT);

Attachments nest, so detach on the same thread and in the reverse order of attaching. bare_detach() returns -1 if the process is not the one the thread is attached to, which is what detaching out of order looks like, and restores nothing. bare_attach() returns -1 if the process has terminated or exited.

Attaching does not run the loop. The call may leave work behind that only the loop will run, which is why the example runs it once detached.

C API reference

Everything below is declared in include/bare.h, except where a different header is named. Unless a function says otherwise, it returns 0 on success and a negative value on failure.

Setup and teardown

int
bare_version(int *major, int *minor, int *patch);

Stores the version of the Bare that is linked in. Any of the pointers may be NULL. Use it when Bare is linked dynamically, where the version of the header can differ from the version of the library. For the version of the header, see BARE_VERSION.

int
bare_setup(uv_loop_t *loop, js_platform_t *platform, js_env_t **env, int argc, const char *argv[], const bare_options_t *options, bare_t **result);

Sets up a Bare process on loop and stores it in result. platform is the JavaScript platform to create the environment with. If env is given, it receives the JavaScript environment of the process, for use with the js_* functions. argc and argv become Bare.argv, and are copied. options may be NULL for the defaults; see bare_options_t. Returns a negative value on failure, in which case nothing is allocated and result is untouched.

int
bare_load(bare_t *bare, const char *filename, const uv_buf_t *source, js_value_t **result);

Loads the module identified by filename, which may be any of the formats the module system supports; see bare-module. If source is NULL the contents of filename are read from disk. Otherwise source must remain alive until bare_teardown() has returned. If result is given, it receives the exports of the module. The process is attached to the calling thread while the module loads.

int
bare_run(bare_t *bare, uv_run_mode mode);

Runs the I/O event loop in mode, emitting beforeExit and exit as the loop runs out of work. It returns zero once the process has exited. With UV_RUN_ONCE or UV_RUN_NOWAIT it can return a non-zero value, meaning work remains and it should be called again. If the process is suspended, it does not return until another thread resumes it. To drive the loop from a host loop that owns the thread, use bare_poll() instead.

bare_poll(), bare_attach() and bare_detach(), which require Bare >=1.34.0, are documented above under bare_poll() and bare_attach() and bare_detach().

int
bare_teardown(bare_t *bare, uv_run_mode mode, int *exit_code);

Tears the process down and frees it, storing the exit code in exit_code if given. Do not use the JavaScript environment, or bare, once it has returned zero. It runs the loop in mode first. If handles are still open afterwards it returns a positive value and leaves bare valid, in which case call it again.

int
bare_exit(bare_t *bare, int exit_code);

Immediately terminates the process with an exit status of exit_code. It works by calling Bare.exit() in the JavaScript environment, so call it from the thread that owns the environment. To terminate from any other thread, use bare_terminate().

int
bare_terminate(bare_t *bare);

Terminates the process as soon as possible. Safe to call from any thread.

Suspension

These are the C counterparts of Bare.suspend(), Bare.wakeup() and Bare.resume(), and are safe to call from any thread. Each returns the result of waking the loop, so it returns a negative value only if that fails.

int
bare_suspend(bare_t *bare, int linger);

Suspends the process as soon as possible. Once it has suspended, bare_run() does not return until another thread resumes the process.

int
bare_wakeup(bare_t *bare, int deadline);

Wakes the process up if it is suspended, and gives it time until deadline to do background work.

int
bare_resume(bare_t *bare);

Resumes the process as soon as possible. If the process is not yet idle after being suspended, the suspension is cancelled.

Sealing, context and attaching

int
bare_seal(bare_t *bare);

Seals addon loading for the process, equivalent to Bare.Addon.seal(). Once sealed, the process cannot load a further dynamic addon on any of its threads, now or in the future, and statically linked addons stay available. The seal applies to the process alone and is lifted when it is torn down. It also freezes the context registry, so nothing can be published to it afterwards. Requires Bare >=1.32.0.

bare_context_set() and bare_context_get(), which require Bare >=1.33.0, and bare_attach() and bare_detach(), which require Bare >=1.34.0, are documented in the embedder context API.

bare_options_t

struct bare_options_s {
  int version;
  size_t memory_limit;
};

Options for bare_setup(). Set version to 0, the only version there is, and any field you leave unset takes its default. A NULL options takes every default.

FieldSinceDescription
version0The version of the struct. Fields newer than this version are ignored.
memory_limit0The memory limit of each JavaScript heap in bytes. The limit applies to each thread individually, including the main thread. By default it is inferred from the amount of physical memory of the device. The default can be changed at build time with BARE_MEMORY_LIMIT.

Listeners

Each of these attaches a native listener that is the C counterpart of a JavaScript event on Bare. data is passed back to the listener as its last argument. A listener is a single slot, so a second call replaces the first, and NULL clears it. The event listeners run after the JavaScript listeners for the same event, and only for the main thread, not for the other threads of the process.

FunctionListener typeEquivalent to
bare_on_before_exit(bare, cb, data)void (*)(bare_t *, void *data)Bare.on('beforeExit', cb)
bare_on_exit(bare, cb, data)void (*)(bare_t *, void *data)Bare.on('exit', cb)
bare_on_suspend(bare, cb, data)void (*)(bare_t *, int linger, void *data)Bare.on('suspend', cb)
bare_on_wakeup(bare, cb, data)void (*)(bare_t *, int deadline, void *data)Bare.on('wakeup', cb)
bare_on_idle(bare, cb, data)void (*)(bare_t *, void *data)Bare.on('idle', cb)
bare_on_resume(bare, cb, data)void (*)(bare_t *, void *data)Bare.on('resume', cb)
bare_on_thread(bare, cb, data)void (*)(bare_t *, js_env_t *, void *data)none

The listener types are bare_before_exit_cb, bare_exit_cb, bare_suspend_cb, bare_wakeup_cb, bare_idle_cb, bare_resume_cb and bare_thread_cb. Unlike the others, a bare_on_thread() listener is called with the JavaScript environment of each thread created with the Thread constructor, before that thread runs any JavaScript, so it is the place to modify the environment of the thread. The beforeExit and exit listeners are not passed the exit code.

Headers and macros

include/bare.h includes three more headers, available to embedders and native addons alike.

HeaderProvides
bare/version.hBARE_VERSION_MAJOR, BARE_VERSION_MINOR, BARE_VERSION_PATCH, and BARE_VERSION, the version of the header as a string such as "1.34.1".
bare/target.hBARE_PLATFORM ("ios", "darwin", "android", "linux" or "win32"), BARE_ARCH ("arm64", "arm", "x64", "ia32", "riscv64", "mips" or "mipsel"), BARE_LIBC ("gnu", "musl" or "bionic", on Linux and Android only), BARE_SIMULATOR, and the combined BARE_TARGET, equal to BARE_HOST. Each platform and architecture also has a matching flag, such as BARE_PLATFORM_LINUX or BARE_ARCH_X64.
bare/module.hThe native addon API below.

bare/module.h is how a native addon registers itself, which is the C side of Bare.Addon:

BARE_MODULE(id, fn)

Registers the addon, where fn is js_value_t *fn(js_env_t *env, js_value_t *exports) and returns the exports. id is an identifier that is unique within the library, used to name the generated registration code. Define BARE_MODULE_NAME before including the header to name the addon, which a statically linked addon requires. A library registers one module. By default the addon registers through exported symbols that Bare looks up once the library is loaded, which suits a dynamic addon. Define BARE_MODULE_REGISTER_CONSTRUCTOR to register from a library constructor instead, which suits both dynamic and statically linked addons.

uv_lib_t *
bare_module_find(const char *query);

Finds a loaded addon by name, optionally suffixed with .bare. Addons are named <name>@<version>, and the version may be cut off at a component boundary, so foo@1 matches foo@1.2.3 but not foo@10.0.0. A query with no version matches only an addon registered without one, and if several addons match, the most recently loaded is returned. Only the addons loaded by the process running on the calling thread are matched, along with the statically linked addons. Returns NULL if nothing matches or no process is running on the thread. The library belongs to Bare and must not be closed. It stays loaded for as long as the process that loaded the addon, so a caller that keeps it past the call must take a reference of its own.

void
bare_module_register(bare_module_t *module);

Registers a module with Bare. BARE_MODULE() calls it for you, so you do not normally call it directly. A module registered while the thread is loading a dynamic addon becomes that addon, owned by the process doing the load. A module registered at any other time is a statically linked addon, owned by no process, available to every process including a sealed one, and registered for the life of the operating system process. module->version must be BARE_MODULE_VERSION. The module is copied, so it need not outlive the call.

Building Bare

Bare is compiled with bare-make. From a checkout of holepunchto/bare, install the tool globally, install the build and runtime dependencies, generate the build system, and build:

npm i -g bare-make
npm i
bare-make generate
bare-make build

bare-make generate only has to be run once per repository checkout. After updating bare-make or your compiler toolchain, regenerate the build system with bare-make generate --no-cache, which disregards the existing build system cache.

When the build completes, the bare(.exe) binary is in the build/bin directory. The libbare.(a|lib) and (lib)bare.(dylib|dll|lib) libraries are in the root of the build directory.

Linking

When linking against the static libbare.(a|lib) library, use whole archive linking. Bare relies on constructor functions to register native addons, and without whole archive linking the linker removes them because nothing references them.

Compile options

Compile options customize aspects of the runtime. Set one by passing --define option=value to bare-make generate.

The compile options are not covered by semantic versioning and are subject to change without warning.

OptionDefaultDescription
BARE_ENGINEgithub:holepunchto/libjsThe JavaScript engine to use.
BARE_PREBUILDSONEnable prebuilds for supported third-party dependencies.
BARE_MEMORY_LIMIT0The default memory limit of each JavaScript heap. See memory_limit in bare_options_t.

Sanitizers

Compile Bare with a sanitizer by passing --sanitize to bare-make generate:

bare-make generate --sanitize address

This instruments Bare but not the JavaScript engine. To instrument the engine too, build a sanitized V8 prebuild with chromium-prebuilds and add --define BARE_PREBUILDS=OFF --define GN_DIR=<src> --define GN_OUT_DIR=<out>.

Supported platforms

Bare sorts the platforms it targets into two tiers. Tier 1 targets have prebuilds, as defined by the project's prebuild.yml workflow, and a compilation or test failure on one fails the project's CI. Tier 2 targets are known to work, but are not compiled or tested automatically, so a regression can slip into a release and counts as a bug. A target can move between tiers in a minor release, and that is not a breaking change.

PlatformArchitecturesMinimumTierNotes
Linux (glibc)arm64, x64Linux 5.15, GNU C Library 2.351Ubuntu 22.04, Debian 12, OpenWrt 23.05
Linux (glibc)riscv64Linux 6.8, GNU C Library 2.392Ubuntu 24.04, Debian 13
Linux (musl)arm, arm64, ia32, x64Linux 5.10, musl 1.22Alpine 3.13, OpenWrt 22.03
Linux (musl)mips, mipselLinux 5.10, musl 1.22OpenWrt 22.03
Linux (musl)riscv64Linux 6.6, musl 1.22Alpine 3.20
Androidarm, arm64, ia32, x64Android 101
macOSarm64, x64macOS 13.01
iOSarm64, x64iOS 15.01x64 is simulator only
Windowsx64Windows 101
Windowsarm64Windows 111

Bare is developed primarily on Apple hardware, with Linux and Windows systems running as virtual machines.

The macOS and iOS minimums rose in Bare 1.34.1, from 12.0 and 14.0. A project that ships to macOS 12 or iOS 14 stays on Bare 1.34.0 or earlier. The table follows Bare's README, which is the authority if the two ever differ.

See also

Last updated on

Was this helpful?

On this page