Skip to main content

Overview

The flora runtime is a multi-threaded Rust service that bridges Discord events to JavaScript isolates. It uses V8 (via Deno Core) to execute guild scripts in isolated environments with configurable resource limits.

Core Components

BotRuntime

The main runtime coordinator that manages a pool of worker threads. Each worker can host multiple guild isolates. Source: apps/runtime/src/runtime/mod.rs:34
pub struct BotRuntime {
    workers: Vec<Worker>,
    num_workers: usize,
    secrets: Arc<SecretService>,
    guild_routes: Arc<parking_lot::Mutex<HashMap<String, usize>>>,
    migration_queues: Arc<Mutex<HashMap<String, Vec<QueuedGuildEvent>>>>,
}
Key responsibilities:
  • Worker pool initialization
  • Guild-to-worker routing (consistent hashing)
  • Event dispatch coordination
  • Runtime migration between workers

Worker Threads

Each worker runs on a dedicated thread with its own tokio runtime. Workers handle multiple guild isolates and one default runtime. Source: apps/runtime/src/runtime/worker.rs:44
pub struct Worker {
    id: usize,
    sender: mpsc::UnboundedChannel<WorkerCommand>,
    handle: Option<JoinHandle<()>>,
    backlog: Arc<AtomicUsize>,
}
Worker thread loop:
  1. Listen for commands via unbounded channel
  2. Execute cron tick every second
  3. Process commands: deploy, dispatch, migrate, update secrets
  4. Manage guild isolates lifecycle
Source: apps/runtime/src/runtime/worker.rs:84

JavaScript Runtime State

Each guild has an isolated V8 runtime with its own event loop, dispatch function, and secrets.
pub struct JsRuntimeState {
    runtime: JsRuntime,
    dispatch_fn: Option<Global<v8::Function>>,
    secrets: Arc<SecretsRuntimeData>,
}
Isolation guarantees:
  • Separate V8 isolate per guild
  • No shared global state between guilds
  • Independent event loops
  • Per-guild secret scopes

Service Layer

DeploymentService

Manages guild script deployments stored in PostgreSQL and cached in Redis. Operations:
  • Upsert deployment (bundle + source map)
  • Retrieve deployment by guild ID
  • List all deployments for cache restoration
  • Invalidate cache on updates
Source: apps/runtime/src/services/deployments/mod.rs

KvService

Provides per-guild key-value storage backed by Sled (on-disk) and indexed in PostgreSQL. Storage model:
  • Guild-scoped stores (multiple per guild)
  • Sled databases at ./data/kv/{guild_id}/{store_name}
  • Metadata tree for expiration and custom metadata
  • In-memory LRU cache of Sled instances
Source: apps/runtime/src/services/kv/service.rs:24

SecretService

Encrypts and manages guild secrets using ChaCha20-Poly1305. Features:
  • Master key encryption
  • Per-guild secret runtime data
  • Secret scope thread-local storage
  • Placeholder derivation for safe references
Source: apps/runtime/src/services/secrets/mod.rs

AuthService

Handles Discord OAuth flow and session management via Redis. Flow:
  1. User authorizes via Discord OAuth
  2. Exchange code for access token
  3. Store session in Redis with TTL
  4. Sign session cookie with API secret
Source: apps/runtime/src/services/auth/service.rs

TokenService

Manages API tokens stored in PostgreSQL for CLI authentication. Source: apps/runtime/src/services/tokens/service.rs

Discord Integration

DiscordHandler

Serenity event handler that converts Discord gateway events to JavaScript payloads. Source: apps/runtime/src/discord_handler.rs:16 Event processing:
  1. Gateway event arrives from Discord
  2. Convert to typed payload (expose_payload macro)
  3. Serialize to JSON
  4. Route to guild runtime via BotRuntime::dispatch_js_event
  5. Filter non-guild events (flora is guild-only)
Supported events:
  • ready - Bot connected
  • messageCreate - New message
  • messageUpdate - Message edited
  • messageDelete / messageDeleteBulk - Message(s) deleted
  • interactionCreate - Slash command
  • componentInteraction - Button/select menu
  • modalSubmit - Modal form submission
  • reactionAdd / reactionRemove / reactionRemoveAll - Reactions
Source: apps/runtime/src/discord_handler.rs:26

Deno Core Integration

Extensions

Runtime capabilities exposed to JavaScript via Deno Core extensions. Available extensions:
  • flora_ops - Discord operations (messages, interactions, commands)
  • flora_kv - Key-value storage
  • flora_cron - Cron job registration
  • flora_secrets - Secret access
  • deno_web - Web APIs (fetch, URL, etc.)
  • deno_fetch - HTTP client
  • deno_net - Network APIs
  • deno_tls - TLS support
Source: apps/runtime/src/ops/mod.rs

Ops

Operations are async Rust functions exposed to JavaScript. Example op structure:
#[op2(async)]
pub async fn op_send_message(
    state: Rc<RefCell<OpState>>,
    #[serde] input: SendMessageInput,
) -> Result<String, JsErrorBox> {
    let http = state.borrow().borrow::<Arc<Http>>().clone();
    // ... perform Discord API call
    Ok(message_id)
}
Source: apps/runtime/src/ops/message.rs

Cron Scheduler

Per-worker cron scheduler with POSIX cron expression support. Architecture:
  • Shared cron registry (parking_lot::Mutex)
  • Tick interval: 1 second
  • Jobs evaluated using croner crate
  • Dispatched as synthetic events (__cron:<name>)
Job lifecycle:
  1. Register via op_cron from JavaScript
  2. Store in worker registry with next run time
  3. Tick checks for due jobs
  4. Dispatch to guild runtime with timeout
  5. Mark not running after completion
  6. Clear on guild redeploy
Source: apps/runtime/src/runtime/worker.rs:268

HTTP API

Axum-based HTTP server for CLI integration. Endpoints:
  • POST /deployments - Deploy guild script
  • GET /deployments/:guild_id - Get deployment
  • GET /logs/:guild_id - Stream logs (SSE)
  • GET /kv/:guild_id/stores - List KV stores
  • POST /kv/:guild_id/stores/:store/keys/:key - Set KV value
  • POST /auth/login - Discord OAuth
  • POST /tokens - Create API token
Middleware:
  • Timeout layer (10s)
  • Logging middleware
  • Auth middleware (cookie or bearer token)
Source: apps/runtime/src/handlers/mod.rs

Worker Pool Architecture

Guild routing:
  • Consistent hashing (guild_id → worker index)
  • Sticky routing (guilds stay on same worker)
  • Migration support (move guild between workers)
  • Backlog tracking per worker

Startup Sequence

From apps/runtime/src/main.rs:40:
  1. Load configuration and initialize tracing
  2. Connect to PostgreSQL and run migrations
  3. Connect to Redis with exponential backoff
  4. Initialize V8 once per process
  5. Create worker pool
  6. Initialize each worker sequentially (avoid V8 races)
  7. Load SDK bundle into all workers
  8. Restore cached deployments from database
  9. Deploy each cached guild script
  10. Start Discord client (Serenity)
  11. Start HTTP API server (Axum)
  12. Run both concurrently with tokio::try_join

Shutdown Sequence

From apps/runtime/src/runtime/mod.rs:325:
  1. Drop BotRuntime triggers shutdown
  2. Attempt graceful migration of all guilds
  3. Send shutdown command to each worker
  4. Join worker threads
  5. Drop all runtime states
Guild runtimes are migrated between workers during shutdown to balance load.

Thread Safety

Arc usage:
  • Arc<Http> - Shared Discord HTTP client
  • Arc<BotRuntime> - Shared across handlers
  • Arc<SecretsRuntimeData> - Per-guild secret data
Locking strategy:
  • parking_lot::Mutex for cron registry (fast, uncontended)
  • tokio::Mutex for migration queues (async-aware)
  • Arc<RwLock<BoundedCache>> for KV DB cache
V8 isolate safety:
  • V8 isolates are not Send/Sync
  • Worker threads never share isolates
  • Migration transfers ownership via channels

Build docs developers (and LLMs) love