Worklet Entry
The only worklet entry in the framework repo is core/main.core.ts. It exists
to keep the bundle a single import of createWorkletRuntime. As a consumer you
write your own — typically a thin file plus a p2p wiring module.
Anatomy of an entry
Section titled “Anatomy of an entry”import { createWorkletRuntime, resolveWorkletConfig, type WorkletRuntime,} from '@ekrooh/bare/runtime';import type { PluginManifest } from '@ekrooh/bare/core';
declare const photosPlugin: PluginManifest; // your app.photos manifest
export function createAppRuntime(): WorkletRuntime { const runtime = createWorkletRuntime({ ...resolveWorkletConfig(), // 1. Register your plugins (defaults are registered automatically). plugins: [photosPlugin], });
// 2. Wire your own modules that need the loopback server, storage, or the // protocol socket. See "Backend → Web Push" for the socket. return runtime;}What createWorkletRuntime gives you
Section titled “What createWorkletRuntime gives you”WorkletRuntime exposes:
server— the loopback HTTP+WS server (mount,mountDir,onConnection,credentials, …).pluginRegistry/pluginRouter— the plugin surface; register your own manifests and route envelopes through them.hostBridge— the host IPC bridge (null in dev, where there is no host).config—{ webAssets, storage, cache, deviceMode }.start()/close()— bind, mount the web app, attach the protocol socket, write the handoff file, and (onclose) shut down.start()is idempotent and rejects if the handoff file cannot be written;close()is idempotent and fails pending host calls.
createWorkletRuntime is synchronous. Passing auth: false without
deviceMode: false throws.
Device vs dev mode
Section titled “Device vs dev mode”Device mode is the default. Dev mode is only ever chosen explicitly, with
deviceMode: false or the mode=dev CLI token, so a missing or broken host
configuration can never leave the loopback server unauthenticated:
| Device (default) | Dev (deviceMode: false / mode=dev) | |
|---|---|---|
| Config source | resolveWorkletConfig() (Bare.argv) | mode=dev CLI token |
| Port | ephemeral (0) | fixed 8080 |
| Auth | on (cookie/token) | off (no token) |
| Origin check | on | on (see allowedOrigins) |
handoff.json | written to storage | not written |
The origin check holds in both modes: a request must target the bound server
(127.0.0.1, localhost or [::1] on the bound port), and a page Origin
must be that server or one listed in allowedOrigins (CLI: origin=). Browser
dev from the Vite server therefore passes its origin, e.g.
origin=http://localhost:5173.
See Dev Mode & Storage for running a stateful consumer backend under the CLI.
On-device argv contract
Section titled “On-device argv contract”On-device hosts start the worklet with three positional arguments, in this exact order, each an existing directory:
| Position | Arg | Contents |
|---|---|---|
| 0 | webAssets | the built web app dir, served at / by the loopback server |
| 1 | storage | durable app-sandbox: handoff.json + the p2p corestore |
| 2 | cache | ephemeral app-sandbox: asset-cache, photo spool, temp |
The arguments land at Bare.argv[0..2]. The Android reference host
(ReferenceHost.kt: storage in noBackupFilesDir/bare, cache in
cacheDir/bare-cache) and iOS host (BareRuntime.swift, via
BareStorage.prepare(): Application Support/bare and Caches/bare-cache)
both pass all three:
worklet.start("/main.core.bundle", bundleStream, arrayOf(webappDir.absolutePath, storageDir.absolutePath, bareCacheDir.absolutePath))Keep durable storage (the p2p identity, keys) out of any cache directory the OS or the user may clear, and out of backups.
If cache is missing or not a directory, the runtime falls back to storage
and logs a warning ([bare] cache dir missing or not a directory...). The
fallback is graceful, but pass a real cache dir so boots are quiet and
ephemeral data (asset cache, photo spool) doesn’t pollute durable storage.
CLI config tokens (dev / e2e)
Section titled “CLI config tokens (dev / e2e)”resolveCliConfig() parses the bare CLI argv as key=value tokens. Without
mode=dev it keeps the device defaults (auth on, ephemeral port); mode=dev
selects the dev backend (auth off, fixed port 8080, deviceMode: false).
resolveWorkletConfig() tries the on-device positional shape first, then falls
back to these tokens — one entry serves both.
| Token | Maps to | Notes |
|---|---|---|
webassets= | webAssets | built web app dir served at / same-origin |
storage= | storage | durable app-sandbox (persists across runs) |
cache= | cache | ephemeral app-sandbox |
mode= | deviceMode | dev selects the dev backend; default device |
port= | port | dev default 8080; invalid values warn + ignore |
host= | host | bind address, default 127.0.0.1 |
auth= | auth | on/off/true/false/1/0; default on (off in dev); off is ignored without mode=dev |
origin= | allowedOrigins | repeatable; an extra allowed page origin |
bare worklet/app.core.js mode=dev origin=http://localhost:5173 \ webassets=dist storage=./.store cache=./.cacheUnknown labels (e.g. bootstrap=127.0.0.1:49737) are ignored and stay
consumer-owned — the framework only parses the runtime options above.
Bundling
Section titled “Bundling”Bundle with esbuild, externalizing packages, node platform, ESM output:
esbuild worklet/index.ts --bundle --packages=external \ --platform=node --format=esm --outfile=worklet/app.core.jsThe host runs this bundle inside Bare. core/main.core.ts is the reference;
your entry is “just register plugins + wire p2p + start”.
Don’t block before start()
Section titled “Don’t block before start()”start() binds the loopback server and the WebSocket protocol socket — the
web layer’s lifeline. Anything that runs before it must not block: a
store.ready() that awaits network-bound work (e.g. a hyperswarm
join().flushed() against an unreachable DHT) delays the WS bind
indefinitely, and the app shows repeated “WebSocket connection lost” +
“invoke timeout” errors with no obvious cause.
Rules of thumb:
- Nothing blocking runs before
start()— register plugins and wire modules synchronously, then start. - Network bootstrap is fire-and-forget. Swarm/DHT joins run in the
background (never
await ...flushed()beforestart()); the p2p layer self-heals once the DHT is reachable. - Boot degraded. The app must render and serve even with the swarm offline — gallery works, sync is “pending” until peers connect.