Architecture

Understanding how NANO works under the hood

High-Level Architecture

NANO consists of a Rust-based runtime that embeds V8 to execute JavaScript in isolated environments.

  [Client Request]
         |
         v
  [HTTP Router]  -->  [App Registry]
         |                    |
         v                    v
  [Worker Pool]  <--  [Isolate Cache]
         |
         v
  [V8 Isolate]  -->  [JavaScript Execution]

V8 Isolates

Each application runs in its own V8 isolate, providing complete isolation from other applications while sharing the same OS process.

Isolate Properties

  • Memory limit: Configurable heap size per isolate
  • Context reset: ~5ms between requests (not full recreate)
  • Thread-local: Isolates never move between threads

Sliver Bundles

A sliver is a portable tar bundle of an app's files plus optional V8 bytecode compiled at pack time. It carries no heap snapshot; the app runs from its VFS source and uses the bytecode to skip parse + compile when the V8 version matches.

nano sliver create --from-dir <dir>
1. Collect the app's files into vfs/
2. Compile the entrypoint JS to V8 bytecode
3. Record the V8 cache-version tag in meta.json
4. Write meta.json + vfs/ + bytecode.v8bc into a tar

At serve time: if the bytecode's version tag matches the
running V8, the first request skips JavaScript parse+compile.

The serving path runs from the sliver's VFS source plus the optional precompiled bytecode; it does not restore an isolate from a baked V8 heap snapshot.

Worker Pool

The worker pool manages concurrent request handling with thread-local isolates.

Request Flow

  1. HTTP request arrives at router
  2. Router determines target app from hostname/path
  3. Request is queued for worker pool
  4. Available worker picks up request
  5. Worker resets isolate context (~5ms)
  6. JavaScript handler executes
  7. Response is returned to client

WebSocket Upgrade Path v2.0a

WebSocket connections use a dedicated worker thread per connection. The isolate lifetime is bound to the connection — when the connection closes, the isolate is recycled.

HTTP GET + Upgrade: websocket
        |
        v
  detect_ws_upgrade() — router.rs
        |  axum 101 handshake
        v
  tokio relay task — WsChannels (mpsc)
        |  WsInbound / WsOutbound
        v
  TenantPool::dispatch_ws()
        |  lazy-spawn WS worker thread
        v
  JS fetch() handler (registers addEventListener)
        |
        v
  'ws_messages loop
        |  recv_timeout(idle_timeout_ms) per frame
        |  CpuTimeoutGuard + OOM check per message
        v
  Close/Disconnect → break 'requests → isolate recycled

Key Properties

  • One isolate per connection: Worker thread dedicated to single WS connection
  • Isolate recycled on close: Fresh isolate for next connection — no state leakage
  • Per-frame enforcement: CPU timeout guard and OOM check on every message
  • Idle timeout: Connection closed after ws_idle_timeout_ms with no frames (default 60s)
  • Max message size: 32 MiB default (ws_max_message_bytes)