Murasaki

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 routes

If 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():

  • activation is cold-start, second-instance, or os-event.
  • transport is argv, open-url, or open-file.
  • targets contains { 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 update

beforeQuit() 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:

  1. the current shutdown({ reason: 'dev-reload' }),
  2. Vite invalidation of that module,
  3. construction of a fresh lifecycle,
  4. 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:

PathIntended data
dataDatabases, user documents owned by the app, durable state
cacheRe-creatable caches
logsApplication logs
tempEphemeral 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.

Next

On this page