Skip to content

Architecture

This document provides an overview of Forge’s architecture, explaining how the runtime works and how components interact.

Overview

Forge is a desktop application framework that combines:

  • Rust - Host runtime, native integrations, window management
  • Deno - JavaScript/TypeScript runtime for app logic
  • WebView - System web renderer (wry/tao) for UI
┌─────────────────────────────────────────────────────────────────┐
│ Forge Application │
├─────────────────────────────────────────────────────────────────┤
│ ┌───────────────────┐ ┌─────────────────────────┐ │
│ │ Deno Runtime │ IPC │ WebView (Renderer) │ │
│ │ (src/main.ts) │ ◄─────► │ (web/index.html) │ │
│ └─────────┬─────────┘ └─────────────────────────┘ │
│ │ │
│ │ host:* modules │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Forge Host Runtime (Rust) │ │
│ │ ┌────────┬────────┬────────┬────────┬────────┬────────┐ │ │
│ │ │ext_win │ext_ipc │ ext_ui │ ext_fs │ ext_net│ext_wasm│ │ │
│ │ └────────┴────────┴────────┴────────┴────────┴────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────┐
│ Operating │
│ System │
└─────────────────┘

Runtime Components

1. Forge Host (forge-host)

The main Rust binary that orchestrates all runtime components:

  • Embeds Deno runtime (JsRuntime) with all extension crates registered
  • Creates native windows via tao/wry, handling the platform event loop
  • Bridges async operations - Extensions define ops and channel-based commands; forge-host handles the main thread event loop integration
  • Initializes extension state - Sets up IPC channels, capability checkers, and resource limits for each extension
  • Routes IPC messages - Forwards commands from Deno ops to platform APIs via tokio channels and UserEvent enums
  • Enforces capability permissions - Parses manifest.app.toml and creates capability adapters for each extension

Key architectural pattern: Extensions do NOT reimplement logic in forge-host. Extensions define the ops and data structures; forge-host initializes their state and provides the event loop bridge. For example:

  • ext_window defines WindowCmd enum and ops
  • forge-host receives WindowCmd via channel and translates to UserEvent for the tao event loop

Location: crates/forge-host/

2. Deno Runtime

JavaScript/TypeScript execution environment that:

  • Runs app’s src/main.ts
  • Provides host:* module imports
  • Handles business logic
  • Communicates with renderers via IPC

3. WebView Renderers

System-native web views (WebKit/WebView2/WebKitGTK) that:

  • Render HTML/CSS/JS UI
  • Load content via app:// protocol
  • Communicate with Deno via window.host

Host Module System

Apps access native capabilities through host:* module specifiers:

import { createWindow, dialog, menu, tray } from "host:window";
import { sendToWindow, onChannel } from "host:ipc";
import { openWindow } from "host:ui";
import { readTextFile } from "host:fs";
import { fetch } from "host:net";
import { compileFile, instantiate } from "host:wasm";

Module Resolution

  1. Deno encounters import from "host:*"
  2. Custom module loader intercepts
  3. Returns ESM shim from extension’s ts/init.ts
  4. Shim calls Deno.core.ops.*
  5. Op calls Rust extension function
TypeScript ESM Shim Rust Op
────────────────────────────────────────────────────────────
readTextFile() ──► op_fs_read_text() ──► ext_fs::read_text()

Extensions

Each host:* module has a Rust extension with structured error codes:

ModuleExtensionError RangeDescription
host:fsext_fs1000-1999File system operations
host:ipcext_ipc2000-2999Inter-process communication
host:netext_net3000-3999Networking
host:processext_process4000-4999Process spawning
host:wasmext_wasm5000-5999WebAssembly compilation and execution
host:windowext_window6000-6999Window management, dialogs, menus, tray
host:sysext_sys7000-7999System info, clipboard, notifications
host:uiext_ui(legacy)Basic window operations

IPC Communication

IPC enables bidirectional messaging between Deno and WebView renderers.

Renderer → Deno

  1. Renderer calls window.host.send("channel", data)
  2. WebView posts message to Rust handler
  3. Rust pushes to Deno’s message queue
  4. Deno receives via windowEvents() generator
Renderer Rust Deno
─────────────────────────────────────────────────────────────────
window.host.send() ──► WebView IPC ──► mpsc channel ──► windowEvents()

Deno → Renderer

  1. Deno calls sendToWindow(windowId, channel, payload)
  2. Rust serializes and routes to WebView
  3. WebView executes window.__host_dispatch()
  4. Preload script calls registered callbacks
Deno Rust Renderer
──────────────────────────────────────────────────────────────────────
sendToWindow() ──► evaluate_script() ──► __host_dispatch() ──► callbacks

Channel Allowlists

Channels can be restricted per-window:

[capabilities.channels]
allowed = ["user:*", "app:state"]

Empty allowlist = deny all (security default)


Capability Model

Forge uses capability-based security to restrict app permissions.

Capability Flow

┌────────────────────────────────────────────────────────────┐
│ manifest.app.toml │
│ [capabilities.fs] │
│ read = ["~/.myapp/*"] │
└────────────────────────────────────────────────────────────┘
▼ parsed at startup
┌────────────────────────────────────────────────────────────┐
│ Capabilities Struct │
│ FsCapabilities { read_patterns: [...], write_patterns } │
└────────────────────────────────────────────────────────────┘
▼ checked on each op call
┌────────────────────────────────────────────────────────────┐
│ op_fs_read_text(path) │
│ 1. Resolve path (expand ~, normalize) │
│ 2. Check against read_patterns │
│ 3. Return error if denied │
│ 4. Perform operation if allowed │
└────────────────────────────────────────────────────────────┘

Pattern Matching

Capabilities use glob patterns:

  • * matches any non-path-separator characters
  • ** matches any characters including /
  • ~ expands to home directory

Example checks:

// Capability: read = ["~/.myapp/*"]
check_read("~/.myapp/config.json") // ✓ Allowed
check_read("~/.myapp/data/file.txt") // ✗ Denied (no **)
check_read("~/.other/file.txt") // ✗ Denied

Asset Loading

Development Mode

Request: app://index.html
┌─────────────────────┐
│ Custom Protocol │
│ Handler (Rust) │
└──────────┬──────────┘
┌─────────────────────┐
│ Read from disk: │
│ {app_dir}/web/... │
└─────────────────────┘

Production Mode

FORGE_EMBED_DIR=./web cargo build
┌─────────────────────┐
│ build.rs embeds │
│ files into binary │
└─────────────────────┘
Request: app://index.html
┌─────────────────────┐
│ Serve from │
│ embedded assets │
└─────────────────────┘

Window Management

Window Lifecycle

createWindow(opts)
├── Create tao::Window
│ │
│ └── Configure: size, title, decorations
├── Create wry::WebView
│ │
│ ├── Load app:// URL
│ ├── Inject preload script
│ └── Set up IPC handlers
└── Return Window handle
├── Methods: close(), minimize(), maximize()
├── Position: getPosition(), setPosition()
├── Size: getSize(), setSize()
└── State: isFullscreen(), isFocused()

Event Loop

The main event loop handles:

  1. Window events (close, resize, focus)
  2. Menu events (app menu, context menu, tray)
  3. IPC messages (renderer → Deno)
  4. File watcher events (HMR in dev mode)
event_loop.run(move |event, target, control_flow| {
match event {
Event::WindowEvent { .. } => handle_window_event(),
Event::MenuEvent { .. } => handle_menu_event(),
Event::UserEvent(msg) => handle_ipc_message(),
_ => {}
}
});

Build System

forge-weld

The forge-weld crate provides code generation and binding utilities for Forge extensions. It generates TypeScript type definitions, init modules, and Rust extension macros.

// In your extension's build.rs
use forge_weld::ExtensionBuilder;
fn main() {
ExtensionBuilder::new("host_fs", "host:fs")
.ts_path("ts/init.ts")
.ops(&["op_fs_read_text", "op_fs_write_text"])
.generate_sdk_types("sdk")
.dts_generator(generate_types)
.build()
.expect("Failed to build extension");
}

Extension Build Output

Each extension’s build process generates:

  1. TypeScript types (sdk/generated/host.*.d.ts) - Type declarations for the module
  2. Init module (embedded in binary) - The transpiled TypeScript shim
  3. Rust bindings - Extension registration and op definitions
Extension Build
├── ts/init.ts (TypeScript source)
│ │
│ ▼
├── Transpile to JavaScript (esbuild)
│ │
│ ▼
├── Embed in binary (build.rs)
└── Generate .d.ts (sdk/generated/)

Development

Terminal window
forge dev my-app
├── Start forge-host with --dev flag
├── Load manifest.app.toml
├── Initialize Deno runtime
├── Execute src/main.ts
├── Start HMR WebSocket server (port 35729)
└── Watch for file changes

Production Build

Terminal window
forge build my-app
├── Bundle Deno code (esbuild)
├── Copy web assets
└── Output to dist/
forge bundle my-app
├── Build forge-host with FORGE_EMBED_DIR
├── Create platform package:
├── macOS: .app bundle DMG
├── Windows: MSIX package
└── Linux: AppImage
└── Output to dist/{platform}/

Security Model

Sandboxing Layers

  1. Capability System - Explicit permission grants
  2. Channel Allowlists - IPC message filtering
  3. CSP Headers - Content Security Policy
  4. Process Isolation - WebView in separate process

CSP Configuration

Development (relaxed for HMR):

default-src 'self' app:;
script-src 'self' 'unsafe-inline' 'unsafe-eval' app:;
connect-src 'self' ws://localhost:35729;

Production (strict):

default-src 'self' app:;
script-src 'self' app:;
style-src 'self' 'unsafe-inline' app:;

Crate Dependencies

forge-host
├── deno_core # Deno runtime
├── deno_ast # TypeScript transpilation
├── tao # Window management (cross-platform)
├── wry # WebView
├── muda # Menus
├── tray-icon # System tray
├── rfd # File dialogs
├── notify # File watching (HMR)
├── tokio-tungstenite # WebSocket for HMR
├── tracing # Logging
└── ext_* # Host modules (registered as Deno extensions)
forge-weld
├── deno_ast # TypeScript transpilation via deno_ast
├── linkme # Compile-time symbol collection (distributed slices)
├── serde # Type serialization
└── thiserror # Error types
forge-weld-macro
├── syn # Rust syntax parsing
├── quote # Token generation
└── proc-macro2 # Proc macro utilities

Crate Structure

Forge consists of 12 crates:

Core Crates

CratePurpose
forgeCLI tool (forge dev/build/bundle)
forge-hostMain runtime binary
forge-weldCode generation and binding utilities
forge-weld-macroProcedural macros for forge-weld

Extension Crates

CrateModulePurpose
ext_windowhost:windowWindow management, dialogs, menus, tray
ext_ipchost:ipcInter-process communication
ext_uihost:uiBasic window operations
ext_fshost:fsFile system operations
ext_nethost:netNetworking
ext_syshost:sysSystem info, clipboard, notifications
ext_processhost:processProcess spawning
ext_wasmhost:wasmWebAssembly compilation and execution

File Structure

forge/
├── crates/
│ ├── forge-host/ # Main runtime binary
│ │ ├── src/
│ │ │ ├── main.rs # Entry point, event loop
│ │ │ └── capabilities.rs
│ │ └── build.rs # Asset embedding
│ │
│ ├── forge/ # CLI tool
│ │ └── src/
│ │ └── main.rs # CLI commands
│ │
│ ├── forge-weld/ # Code generation
│ │ └── src/
│ │ ├── lib.rs # Main entry
│ │ ├── ir.rs # Intermediate representation
│ │ ├── codegen.rs # Code generators
│ │ └── build.rs # ExtensionBuilder
│ │
│ ├── forge-weld-macro/ # Procedural macros
│ │
│ ├── ext_window/ # host:window extension
│ │ ├── src/lib.rs # Window ops
│ │ ├── ts/init.ts # TypeScript shim
│ │ └── build.rs # Type generation
│ │
│ ├── ext_ipc/ # host:ipc extension
│ │ ├── src/lib.rs # IPC ops
│ │ ├── ts/init.ts # TypeScript shim
│ │ └── build.rs # Type generation
│ │
│ ├── ext_ui/ # host:ui extension
│ ├── ext_fs/ # host:fs extension
│ ├── ext_net/ # host:net extension
│ ├── ext_sys/ # host:sys extension
│ ├── ext_process/ # host:process extension
│ └── ext_wasm/ # host:wasm extension
├── sdk/ # TypeScript SDK
│ ├── generated/ # Auto-generated types
│ │ ├── host.window.d.ts
│ │ ├── host.ipc.d.ts
│ │ ├── host.ui.d.ts
│ │ ├── host.fs.d.ts
│ │ └── ...
│ └── preload.ts # Renderer bridge
├── examples/ # Example apps
│ └── example-deno-app/
└── site/ # Documentation site
└── src/content/docs/