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_windowdefinesWindowCmdenum and opsforge-hostreceivesWindowCmdvia channel and translates toUserEventfor 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
- Deno encounters
import from "host:*" - Custom module loader intercepts
- Returns ESM shim from extension’s
ts/init.ts - Shim calls
Deno.core.ops.* - 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:
| Module | Extension | Error Range | Description |
|---|---|---|---|
host:fs | ext_fs | 1000-1999 | File system operations |
host:ipc | ext_ipc | 2000-2999 | Inter-process communication |
host:net | ext_net | 3000-3999 | Networking |
host:process | ext_process | 4000-4999 | Process spawning |
host:wasm | ext_wasm | 5000-5999 | WebAssembly compilation and execution |
host:window | ext_window | 6000-6999 | Window management, dialogs, menus, tray |
host:sys | ext_sys | 7000-7999 | System info, clipboard, notifications |
host:ui | ext_ui | (legacy) | Basic window operations |
IPC Communication
IPC enables bidirectional messaging between Deno and WebView renderers.
Renderer → Deno
- Renderer calls
window.host.send("channel", data) - WebView posts message to Rust handler
- Rust pushes to Deno’s message queue
- Deno receives via
windowEvents()generator
Renderer Rust Deno─────────────────────────────────────────────────────────────────window.host.send() ──► WebView IPC ──► mpsc channel ──► windowEvents()Deno → Renderer
- Deno calls
sendToWindow(windowId, channel, payload) - Rust serializes and routes to WebView
- WebView executes
window.__host_dispatch() - Preload script calls registered callbacks
Deno Rust Renderer──────────────────────────────────────────────────────────────────────sendToWindow() ──► evaluate_script() ──► __host_dispatch() ──► callbacksChannel 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") // ✓ Allowedcheck_read("~/.myapp/data/file.txt") // ✗ Denied (no **)check_read("~/.other/file.txt") // ✗ DeniedAsset 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:
- Window events (close, resize, focus)
- Menu events (app menu, context menu, tray)
- IPC messages (renderer → Deno)
- 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.rsuse 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:
- TypeScript types (
sdk/generated/host.*.d.ts) - Type declarations for the module - Init module (embedded in binary) - The transpiled TypeScript shim
- 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
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 changesProduction Build
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
- Capability System - Explicit permission grants
- Channel Allowlists - IPC message filtering
- CSP Headers - Content Security Policy
- 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 utilitiesCrate Structure
Forge consists of 12 crates:
Core Crates
| Crate | Purpose |
|---|---|
forge | CLI tool (forge dev/build/bundle) |
forge-host | Main runtime binary |
forge-weld | Code generation and binding utilities |
forge-weld-macro | Procedural macros for forge-weld |
Extension Crates
| Crate | Module | Purpose |
|---|---|---|
ext_window | host:window | Window management, dialogs, menus, tray |
ext_ipc | host:ipc | Inter-process communication |
ext_ui | host:ui | Basic window operations |
ext_fs | host:fs | File system operations |
ext_net | host:net | Networking |
ext_sys | host:sys | System info, clipboard, notifications |
ext_process | host:process | Process spawning |
ext_wasm | host:wasm | WebAssembly 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/