Process model
Startup, development reloads, graceful shutdown, and app-owned resources.
Murasaki separates the native event loop from Node's event loop. In a packaged application the Rust launcher starts and supervises the bundled Node child; the Node child starts your Main lifecycle and local HTTP server; only then does the host load the renderer.
launch
→ Rust host acquires the per-user app lock
→ host chooses the app-local origin and creates a runtime token
→ Node child loads src/main.ts
→ main.ready(context) finishes
→ local server begins listening
→ registered cold-start URLs/files are delivered to main.openRequested()
→ declared WebViews load their renderer routesIf ready() rejects, startup fails instead of showing a renderer attached to a
half-initialized backend. Use it for resources that must exist before the UI is
usable: opening a database, running migrations, or starting an app-owned
service.
In packaged macOS and Windows apps, a second launch does not start another
backend. Its argv / working directory are delivered to
main.secondInstance(), and the host focuses the primary window. Development
mode and Linux do not currently provide this lock.
Open requests
Packaged macOS and Windows apps can register custom URL schemes and document
extensions with protocols and fileAssociations in
murasaki.config.ts. After ready() has completed, matching activations are
normalized and delivered to main.openRequested():
activationiscold-start,second-instance, oros-event.transportisargv,open-url, oropen-file.targetscontains{ kind: 'url', url, scheme }or{ kind: 'file', path }values.
On a second launch, secondInstance() still receives the raw process
arguments while openRequested() receives only registered URLs and files.
Use openRequested() for cross-platform open behavior and
secondInstance() for other secondary-launch arguments.
URL and file activations are untrusted operating-system input. Registration selects which inputs reach the hook; it does not authenticate the sender or make the URL contents or file contents safe.
Quit sequence
Normal window-close and app-quit requests use the lifecycle below:
quit request
→ beforeQuit(context) may return false ┐
→ context.signal aborts
→ shutdown(context) ┘ bounded together by shutdownTimeoutMs
→ Node child exits
→ native host exits or applies an updatebeforeQuit() may cancel a normal close—for example while a document has
unsaved changes. Forced shutdown paths, including process signals and dev Main
reloads, ignore cancellation. The combined hooks default to a 10-second limit;
shutdown() should stop accepting work first, then flush and close resources.
The OS close control and appWindow.close() hide a secondary, so it can be
shown again with windows.open(). Closing the primary main window is an
application quit request and follows the sequence above. Cancellation is
application-wide; Murasaki does not expose per-window close lifecycle events.
Explicit windows.close(label) destroys a secondary target, which cannot
currently be recreated in the same process.
Development reloads
During murasaki dev, a change to the configured Main entry triggers:
- the current
shutdown({ reason: 'dev-reload' }), - Vite invalidation of that module,
- construction of a fresh lifecycle,
- a new
ready()call.
Changes imported by Main still follow Vite's module graph, but only the exact
configured entry currently triggers the lifecycle restart. Avoid module-level
timers or sockets: create them inside ready() and close them inside
shutdown() so reloads do not leak work.
Application paths
MainContext.paths gives stable, per-appId locations:
| Path | Intended data |
|---|---|
data | Databases, user documents owned by the app, durable state |
cache | Re-creatable caches |
logs | Application logs |
temp | Ephemeral staging files |
Use these instead of writing beside the executable. Packaged resources may be read-only, app locations vary by OS, and updates may replace the application bundle.
Process failures
The host owns the Node child lifetime. On macOS/Linux the bundled server exits if it becomes orphaned; on Windows it is assigned to a Job Object that is closed with the launcher. Murasaki does not yet expose a public crash-restart policy, health-check hook, or multiple supervised worker processes. If you spawn workers or child processes from Main, you own their shutdown behavior.