bext-plugin-api 0.2.0

Plugin trait definitions and shared types for bext — the public ABI for plugin authors
Documentation
//! Shared types for the plugin API: [`PluginManifest`], [`PluginCapability`],
//! priority constants, and fuel budget defaults for WASM execution.

/// Manifest returned by a plugin during initialization.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PluginManifest {
    pub name: String,
    pub version: String,
    #[serde(default)]
    pub description: String,
    #[serde(default)]
    pub capabilities: Vec<PluginCapability>,
    // TODO: coordinate with bext-registry R3 schema bump
    #[serde(default)]
    pub requires_capabilities: Vec<PluginCapability>,
    #[serde(default)]
    pub provides_capabilities: Vec<PluginCapability>,
}

/// What a plugin can do.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PluginCapability {
    Transform,
    Middleware,
    CacheBackend,
    Lifecycle,
    Auth,
    Session,
    Mailer,
    Tracer,
    Scheduled,
    Webhook,
    FeatureFlag,
    Locking,
    I18n,
    StorageClient,
    SearchClient,
    AuthzPolicy,
}

/// Permissions for WASM plugins (sandbox limits).
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct WasmPluginPermissions {
    /// Allowed URL patterns for http_fetch (glob matching).
    #[serde(default)]
    pub allowed_urls: Vec<String>,
    /// Storage quota in KB. Default: 1024 (1 MB).
    #[serde(default = "default_storage_quota")]
    pub storage_quota_kb: u64,
    /// Max HTTP fetches per minute. Default: 30.
    #[serde(default = "default_max_fetch")]
    pub max_fetch_per_minute: u32,
    /// Allowed KV namespaces. Empty = no KV access.
    #[serde(default)]
    pub kv_namespaces: Vec<String>,
    /// Allowed queue names. Empty = no queue access.
    #[serde(default)]
    pub queue_names: Vec<String>,
}

fn default_storage_quota() -> u64 {
    1024
}

fn default_max_fetch() -> u32 {
    30
}

impl Default for WasmPluginPermissions {
    fn default() -> Self {
        Self {
            allowed_urls: Vec::new(),
            storage_quota_kb: default_storage_quota(),
            max_fetch_per_minute: default_max_fetch(),
            kv_namespaces: Vec::new(),
            queue_names: Vec::new(),
        }
    }
}

// ---------------------------------------------------------------------------
// Priority constants — document the default ordering for built-in components
// so plugin authors know where to insert.
// ---------------------------------------------------------------------------

/// Built-in middleware priorities.
pub mod priority {
    /// CORS middleware (outermost).
    pub const CORS: u32 = 100;
    /// Rate limiter.
    pub const RATE_LIMIT: u32 = 200;
    /// JWT / session auth.
    pub const AUTH: u32 = 300;
    /// Tenant resolver.
    pub const TENANT: u32 = 400;
    /// Request tracing (innermost built-in).
    pub const TRACING: u32 = 500;
    /// Default priority for plugin middleware.
    pub const PLUGIN_DEFAULT: u32 = 600;
}

/// Built-in transform priorities.
pub mod transform_priority {
    pub const FLOW_EXTRACT: u32 = 100;
    pub const BARREL_OPTIMIZE: u32 = 200;
    pub const FONT_OPTIMIZE: u32 = 250;
    pub const IMPORT_STRIP: u32 = 300;
    pub const SHIM_INJECT: u32 = 350;
    pub const CSS_MODULE: u32 = 400;
    pub const ENV_INLINE: u32 = 500;
    pub const SERVER_BOUNDARY: u32 = 600;
    pub const CACHE_DIRECTIVE: u32 = 700;
    /// Default priority for plugin transforms (after all built-ins).
    pub const PLUGIN_DEFAULT: u32 = 1000;
}

/// Sandbox runtime type for user-supplied plugins.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SandboxType {
    /// Compiled WASM module executed via wasmtime. Best for Rust/Go/C plugins.
    /// Sub-millisecond startup, 1-5MB per instance, fuel-budgeted execution.
    Wasm,
    /// JavaScript executed via QuickJS (embedded interpreter). Best for tenant
    /// scripting — pricing rules, transforms, webhook handlers.
    /// Sub-millisecond startup, <1MB per instance, no filesystem/network by default.
    QuickJs,
    /// Arbitrary executable run inside an nsjail process sandbox. Best when
    /// WASM/QuickJS are too restrictive — full Linux userspace with namespace,
    /// cgroup, and seccomp isolation.
    /// ~10ms startup, near-zero memory overhead, requires nsjail binary.
    Nsjail,
}

/// Permissions shared across all sandbox types.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SandboxPermissions {
    /// Allowed URL patterns for outbound HTTP (glob matching).
    #[serde(default)]
    pub allowed_urls: Vec<String>,
    /// Storage quota in KB. Default: 1024 (1 MB).
    #[serde(default = "default_storage_quota")]
    pub storage_quota_kb: u64,
    /// Max HTTP fetches per minute. Default: 30.
    #[serde(default = "default_max_fetch")]
    pub max_fetch_per_minute: u32,
    /// Max memory in MB (QuickJS/nsjail). Default: 64.
    #[serde(default = "default_max_memory_mb")]
    pub max_memory_mb: u64,
    /// Max wall-clock execution time in seconds per call. Default: 10.
    #[serde(default = "default_max_time_secs")]
    pub max_time_secs: u64,
}

fn default_max_memory_mb() -> u64 {
    64
}
fn default_max_time_secs() -> u64 {
    10
}

impl Default for SandboxPermissions {
    fn default() -> Self {
        Self {
            allowed_urls: Vec::new(),
            storage_quota_kb: default_storage_quota(),
            max_fetch_per_minute: default_max_fetch(),
            max_memory_mb: default_max_memory_mb(),
            max_time_secs: default_max_time_secs(),
        }
    }
}

/// Fuel budgets for WASM plugin calls.
pub mod fuel {
    pub const ON_REQUEST_COMPLETE: u64 = 100_000_000;
    pub const ON_CACHE_WRITE: u64 = 50_000_000;
    pub const ON_CACHE_INVALIDATE: u64 = 50_000_000;
    pub const ON_RELOAD: u64 = 200_000_000;
    pub const ON_SERVER_START: u64 = 500_000_000;
    pub const CLEANUP: u64 = 100_000_000;
}