moonquotakit

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

rate-limit
quota
token-bucket
gcra
scheduler
moon add vivid-oi/moonquotakit@0.3.0
Download zip
Author
Version
0.3.0
License
Apache-2.0
Last updated
last month
Downloads
14
README

#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

Powered by MoonBit

Site sourceReport issuePackagesBuild queueSkillsStatistics

© 2026 mooncakes.io