moonquotakit

    A backend-neutral quota, rate limiting, and fairness scheduling toolkit for MoonBit.

    rate-limit
    quota
    token-bucket
    gcra
    scheduler
    Download zip
    Author
    Version
    0.3.0
    License
    Apache-2.0
    Last updated
    3 months ago
    Downloads
    44

    No dependencies

    #MoonQuotaKit

    MoonQuotaKit is a backend-neutral quota, rate limiting, and fairness scheduling toolkit for MoonBit.

    It is designed as a foundation library, not a web-framework plugin. The core package does not depend on browser, file system, network, or platform-specific APIs, so it can be reused from CLI tools, API gateways, task queues, WebAssembly services, and native MoonBit programs.

    #Scope

    MoonQuotaKit provides:

    • Atomic multi-dimensional quota transactions with all-or-nothing state changes.
    • QuotaKey for stable scope/subject/resource identities.
    • LimitSpec for reusable capacity, refill, window, and burst rules.
    • Token Bucket checks for burst-friendly quotas.
    • Fixed Window checks for explainable request caps.
    • GCRA checks for smooth traffic shaping.
    • Hierarchical quota links for user -> tenant or job -> project accounting.
    • Batch evaluation for replay, import, and simulation workloads.
    • Weighted fair-share helpers for choosing the next subject to serve.
    • Validation, stats, and JSON summaries for audit and debugging.
    • Deterministic snapshots and restore boundaries for database, cache, or distributed-storage adapters without adding I/O to the core.

    #Atomic Multi-Dimensional Quotas

    Real services rarely enforce one limit. An AI request may consume a user request budget, a tenant token budget, and a model-specific budget at the same time. Sequential checks are unsafe: if a later limit rejects the request, earlier buckets may already have been debited.

    check_atomic_token_buckets evaluates every charge on staged state and commits only when all dimensions allow the request:

    let report = engine.check_atomic_token_buckets([
    @moonquotakit.QuotaCharge::new(user, "user-requests", 1),
    @moonquotakit.QuotaCharge::new(tenant, "tenant-tokens", 800),
    @moonquotakit.QuotaCharge::new(model, "model-tokens", 800),
    ], now_ms)

    if report.committed {
    // Dispatch work.
    }

    Repeated charges to the same bucket are evaluated cumulatively on staged state. Missing limits and insufficient balances reject the whole transaction without changing any bucket. Hierarchical quota checks use the same atomic mechanism.

    #Quick Start

    let engine = @moonquotakit.QuotaEngine::new()
    ignore(engine.add_limit(@moonquotakit.LimitSpec::new("tokens", 4, 2, 1000L, 2)))

    let alice = @moonquotakit.QuotaKey::new("tenant-a", "alice", "llm")
    let decision = engine.check_token_bucket(alice, "tokens", 0L, 1)

    if decision.allowed {
    println("allowed")
    } else {
    println("retry after \{decision.retry_after_ms}ms")
    }

    Run the bundled demo:

    moon run cmd/main

    Run tests:

    moon fmt --check moon check --deny-warn --target all moon info && git diff --exit-code -- '*.mbti' moon test --deny-warn --target all

    Run the deterministic workload benchmark:

    moon run cmd/bench

    #Why This Project Exists

    MoonBit already has many useful packages around parsing, data structures, web, and async building blocks. MoonQuotaKit focuses on a narrower infrastructure problem that often appears in real services but is usually scattered inside application code: reusable quota semantics.

    The project intentionally keeps policy-free primitives separate from any HTTP framework. A user can plug the same engine into an API gateway, local task scheduler, model-token budget manager, crawler, or multi-tenant SaaS backend. The atomic charge API is the boundary between admission and dispatch, so denied work cannot leave partial quota consumption behind.

    #Public Development Plan

    The repository uses small, traceable commits. Planned work:

    1. Add pluggable indexed storage while preserving deterministic semantics.
    2. Add reservation and compensation primitives for distributed adapters.
    3. Add adapter examples for a cache-backed gateway and durable task queue.
    4. Keep API names idiomatic and backend-neutral.

    AtomicQuotaReport

    pub(all) struct AtomicQuotaReport {
    allowed : Bool
    committed : Bool
    failed_index : Int
    decisions : Array[Decision]
    } derive(
    Debug
    )

    AtomicQuotaReport::to_json

    fn AtomicQuotaReport::to_json(self : AtomicQuotaReport) -> String

    BatchReport

    pub(all) struct BatchReport {
    total : Int
    allowed : Int
    denied : Int
    decisions : Array[Decision]
    } derive(
    Debug
    )

    BatchReport::to_json

    fn BatchReport::to_json(self : BatchReport) -> String

    BucketState

    pub(all) struct BucketState {
    tokens : Int
    last_refill_ms : Int64
    } derive(Eq,
    Debug
    )

    BucketState::new

    fn BucketState::new(tokens : Int, last_refill_ms : Int64) -> BucketState

    Decision

    pub(all) struct Decision {
    allowed : Bool
    reason : String
    cost : Int
    remaining : Int
    retry_after_ms : Int64
    reset_after_ms : Int64
    trace : Array[String]
    } derive(Eq,
    Debug
    )

    Decision::allow

    fn Decision::allow(reason : String, cost : Int, remaining : Int, reset_after_ms : Int64, trace : Array[String]) -> Decision

    Decision::deny

    fn Decision::deny(reason : String, cost : Int, remaining : Int, retry_after_ms : Int64, reset_after_ms : Int64, trace : Array[String]) -> Decision

    Decision::to_json

    fn Decision::to_json(self : Decision) -> String

    EvaluationInput

    pub(all) struct EvaluationInput {
    key : QuotaKey
    now_ms : Int64
    cost : Int
    } derive(Eq,
    Debug
    )

    EvaluationInput::new

    fn EvaluationInput::new(key : QuotaKey, now_ms : Int64, cost : Int) -> EvaluationInput

    FairShareItem

    pub(all) struct FairShareItem {
    subject : String
    weight : Int
    used : Int
    score : Int
    } derive(Eq,
    Debug
    )

    FairShareItem::to_json

    fn FairShareItem::to_json(self : FairShareItem) -> String

    GcraState

    pub(all) struct GcraState {
    theoretical_arrival_ms : Int64
    } derive(Eq,
    Debug
    )

    LimitSpec

    pub(all) struct LimitSpec {
    name : String
    capacity : Int
    refill : Int
    window_ms : Int64
    burst : Int
    } derive(Eq,
    Debug
    )

    LimitSpec::new

    fn LimitSpec::new(name : String, capacity : Int, refill : Int, window_ms : Int64, burst : Int) -> LimitSpec

    LimitSpec::per_minute

    fn LimitSpec::per_minute(name : String, capacity : Int) -> LimitSpec

    LimitSpec::per_second

    fn LimitSpec::per_second(name : String, capacity : Int) -> LimitSpec

    QuotaCharge

    pub(all) struct QuotaCharge {
    key : QuotaKey
    limit_name : String
    cost : Int
    } derive(Eq,
    Debug
    )

    QuotaCharge::new

    fn QuotaCharge::new(key : QuotaKey, limit_name : String, cost : Int) -> QuotaCharge

    QuotaEngine

    pub(all) struct QuotaEngine {
    limits : Array[LimitSpec]
    buckets : Array[TrackedBucket]
    windows : Array[TrackedWindow]
    gcra_flows : Array[TrackedGcra]
    links : Array[QuotaLink]
    subjects : Array[SubjectBudget]
    } derive(
    Debug
    )

    QuotaEngine::add_limit

    fn QuotaEngine::add_limit(self : QuotaEngine, limit : LimitSpec) -> Bool

    fn QuotaEngine::add_link(self : QuotaEngine, link : QuotaLink) -> Bool

    QuotaEngine::add_subject

    fn QuotaEngine::add_subject(self : QuotaEngine, subject : String, weight : Int) -> Bool

    QuotaEngine::bucket_count

    fn QuotaEngine::bucket_count(self : QuotaEngine) -> Int

    QuotaEngine::check_atomic_token_buckets

    fn QuotaEngine::check_atomic_token_buckets(self : QuotaEngine, charges : Array[QuotaCharge], now_ms : Int64) -> AtomicQuotaReport

    Atomically evaluates token-bucket charges against one logical instant. No bucket is changed unless every charge is allowed.

    QuotaEngine::check_gcra

    fn QuotaEngine::check_gcra(self : QuotaEngine, key : QuotaKey, limit_name : String, now_ms : Int64, cost : Int) -> Decision

    QuotaEngine::check_hierarchy

    fn QuotaEngine::check_hierarchy(self : QuotaEngine, key : QuotaKey, limit_name : String, now_ms : Int64, cost : Int) -> Decision

    QuotaEngine::check_many_token_bucket

    fn QuotaEngine::check_many_token_bucket(self : QuotaEngine, limit_name : String, requests : Array[EvaluationInput]) -> BatchReport

    QuotaEngine::check_token_bucket

    fn QuotaEngine::check_token_bucket(self : QuotaEngine, key : QuotaKey, limit_name : String, now_ms : Int64, cost : Int) -> Decision

    QuotaEngine::check_window

    fn QuotaEngine::check_window(self : QuotaEngine, key : QuotaKey, limit_name : String, now_ms : Int64, cost : Int) -> Decision

    QuotaEngine::fair_snapshot

    fn QuotaEngine::fair_snapshot(self : QuotaEngine) -> Array[FairShareItem]

    QuotaEngine::gcra_count

    fn QuotaEngine::gcra_count(self : QuotaEngine) -> Int

    QuotaEngine::limit_count

    fn QuotaEngine::limit_count(self : QuotaEngine) -> Int

    fn QuotaEngine::link_count(self : QuotaEngine) -> Int

    QuotaEngine::new

    QuotaEngine::next_fair_subject

    fn QuotaEngine::next_fair_subject(self : QuotaEngine) -> String

    QuotaEngine::record_usage

    fn QuotaEngine::record_usage(self : QuotaEngine, subject : String, cost : Int) -> Bool

    QuotaEngine::restore

    fn QuotaEngine::restore(self : QuotaEngine, snapshot : QuotaSnapshot) -> Unit

    Restores a snapshot atomically at the engine boundary. Callers are expected to validate application-level serialization before restoring it.

    QuotaEngine::snapshot

    fn QuotaEngine::snapshot(self : QuotaEngine) -> QuotaSnapshot

    Copies all deterministic state for persistence, hand-off, or replication.

    QuotaEngine::stats

    fn QuotaEngine::stats(self : QuotaEngine) -> QuotaStats

    QuotaEngine::subject_count

    fn QuotaEngine::subject_count(self : QuotaEngine) -> Int

    QuotaEngine::to_json

    fn QuotaEngine::to_json(self : QuotaEngine) -> String

    QuotaEngine::validate

    fn QuotaEngine::validate(self : QuotaEngine) -> Array[ValidationIssue]

    QuotaEngine::window_count

    fn QuotaEngine::window_count(self : QuotaEngine) -> Int

    QuotaKey

    pub(all) struct QuotaKey {
    scope : String
    subject : String
    resource : String
    } derive(Eq,
    Debug
    )

    QuotaKey::key

    fn QuotaKey::key(self : QuotaKey) -> String

    QuotaKey::new

    fn QuotaKey::new(scope : String, subject : String, resource : String) -> QuotaKey

    pub(all) struct QuotaLink {
    child : QuotaKey
    parent : QuotaKey
    limit_name : String
    ratio : Int
    } derive(Eq,
    Debug
    )

    QuotaLink::new

    fn QuotaLink::new(child : QuotaKey, parent : QuotaKey, limit_name : String, ratio : Int) -> QuotaLink

    QuotaSnapshot

    pub(all) struct QuotaSnapshot {
    limits : Array[LimitSpec]
    buckets : Array[TrackedBucket]
    windows : Array[TrackedWindow]
    gcra_flows : Array[TrackedGcra]
    links : Array[QuotaLink]
    subjects : Array[SubjectBudget]
    } derive(
    Debug
    )

    A deterministic in-memory snapshot for application-managed persistence. The library deliberately does not perform I/O; adapters may serialize this value to a database, distributed cache, or file without changing policy.

    QuotaSnapshot::to_json

    fn QuotaSnapshot::to_json(self : QuotaSnapshot) -> String

    Exports a compact, deterministic operational summary for adapter health checks. Full state remains available through snapshot.

    QuotaStats

    pub(all) struct QuotaStats {
    limits : Int
    token_buckets : Int
    windows : Int
    gcra_flows : Int
    subjects : Int
    } derive(Eq,
    Debug
    )

    QuotaStats::to_json

    fn QuotaStats::to_json(self : QuotaStats) -> String

    SubjectBudget

    pub(all) struct SubjectBudget {
    subject : String
    weight : Int
    used : Int
    } derive(Eq,
    Debug
    )

    SubjectBudget::new

    fn SubjectBudget::new(subject : String, weight : Int) -> SubjectBudget

    TrackedBucket

    pub(all) struct TrackedBucket {
    key : QuotaKey
    limit_name : String
    state : BucketState
    } derive(Eq,
    Debug
    )

    TrackedBucket::new

    fn TrackedBucket::new(key : QuotaKey, limit_name : String, state : BucketState) -> TrackedBucket

    TrackedGcra

    pub(all) struct TrackedGcra {
    key : QuotaKey
    limit_name : String
    state : GcraState
    } derive(Eq,
    Debug
    )

    TrackedWindow

    pub(all) struct TrackedWindow {
    key : QuotaKey
    limit_name : String
    state : WindowCounter
    } derive(Eq,
    Debug
    )

    ValidationIssue

    pub(all) struct ValidationIssue {
    code : String
    message : String
    } derive(Eq,
    Debug
    )

    ValidationIssue::to_json

    fn ValidationIssue::to_json(self : ValidationIssue) -> String

    WindowCounter

    pub(all) struct WindowCounter {
    window_start_ms : Int64
    used : Int
    } derive(Eq,
    Debug
    )

    Source Files