Skip to content

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.

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;
}

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 (on close) 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 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 sourceresolveWorkletConfig() (Bare.argv)mode=dev CLI token
Portephemeral (0)fixed 8080
Authon (cookie/token)off (no token)
Origin checkonon (see allowedOrigins)
handoff.jsonwritten to storagenot 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 hosts start the worklet with three positional arguments, in this exact order, each an existing directory:

PositionArgContents
0webAssetsthe built web app dir, served at / by the loopback server
1storagedurable app-sandbox: handoff.json + the p2p corestore
2cacheephemeral 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.

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.

TokenMaps toNotes
webassets=webAssetsbuilt web app dir served at / same-origin
storage=storagedurable app-sandbox (persists across runs)
cache=cacheephemeral app-sandbox
mode=deviceModedev selects the dev backend; default device
port=portdev default 8080; invalid values warn + ignore
host=hostbind address, default 127.0.0.1
auth=authon/off/true/false/1/0; default on (off in dev); off is ignored without mode=dev
origin=allowedOriginsrepeatable; an extra allowed page origin
Terminal window
bare worklet/app.core.js mode=dev origin=http://localhost:5173 \
webassets=dist storage=./.store cache=./.cache

Unknown labels (e.g. bootstrap=127.0.0.1:49737) are ignored and stay consumer-owned — the framework only parses the runtime options above.

Bundle with esbuild, externalizing packages, node platform, ESM output:

Terminal window
esbuild worklet/index.ts --bundle --packages=external \
--platform=node --format=esm --outfile=worklet/app.core.js

The host runs this bundle inside Bare. core/main.core.ts is the reference; your entry is “just register plugins + wire p2p + 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() before start()); 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.