moonzero

    moonzero — a service framework for MoonBit (← go-zero): config-driven assembly of a moonapi application with middleware, producing a runnable moonasgi AsgiApp. v0.6.2 runs real unary, server/client-streaming, and bidirectional zRPC calls over moonrpc's h2c transport (an in-process RpcChannel drives HPACK HEADERS + length-prefixed DATA + grpc-status trailers through the H2Server engine), graceful shutdown draining in-flight calls, a persisted, watchable etcd v3-shaped registry (watch, events_since catch-up, snapshot/restore) with round-robin/pick-first balancers and a load-balanced client, real file-backed registry I/O over moonbitlang/async's fs with a live filesystem watcher (native discov driver), request metrics (counter + latency histogram), and W3C traceparent propagation.

    go-zero
    microservice
    server
    middleware
    moonapi
    moonbit
    Download zip
    Version
    0.7.3
    License
    Apache-2.0
    Last updated
    2 hours ago
    Downloads
    3

    #moonzero

    A service framework for MoonBit — ← go-zero.

    Check and Test License mooncakes

    Moved on mooncakes from Lfan-ke/moonzero to moonbitstack/moonzero.

    moonzero is the integration layer of the moon* suite — the role go-zero plays for Go. It assembles a moonapi application from config, wraps it in middleware, and produces a runnable moonasgi AsgiApp that a server (mooncat) runs. It depends only on moonapi + moonasgi, so it stays backend-agnostic.

    flowchart LR conf["ServiceConf"] --> srv["**moonzero** Server"] api["moonapi App"] --> srv mw["middleware<br/>(logging, ...)"] --> srv srv -->|"to_asgi()"| asgi(["moonasgi AsgiApp"]) asgi --> cat["mooncat serves it"]

    #Quickstart

    let app = @moonapi.App::new()
    let api = @moonzero.Group::new(app, "/api/v1") // prefix a set of routes
    api.get("/ping", _ctx => @moonapi.text(200, "pong"))

    let conf = @moonzero.ServiceConf::new(
    name="greet", host="127.0.0.1", port=8888, timeout_ms=3000, log_level=Info,
    )
    let server = @moonzero.Server::new(conf, app)
    .use_(@moonzero.cors(@moonzero.CorsConf::new())) // Access-Control-* headers
    .use_(@moonzero.request_id()) // x-request-id per request
    .use_(@moonzero.recovery) // 500 instead of a panic
    .use_(@moonzero.logging)

    server.describe() // "greet listening on 127.0.0.1:8888"
    @mooncat.serve(server.to_asgi(), host=conf.host, port=conf.port) // run it (native)

    Verified across all backends (wasm, wasm-gc, js, native) in CI, 0 warnings under --deny-warn.

    #Resilience middleware

    Beyond the base onion (logging, recovery, CORS, request-id), moonzero ships go-zero's resilience set, each modelled as a pure decision core over an injected Clock so its timing is exactly testable:

    let clock = @moonzero.Clock::new(() => now_ms()) // real time at the async edge
    let server = @moonzero.Server::new(conf, app)
    .use_(@moonzero.maxbytes(1 << 20)) // 413 over 1 MiB
    .use_(@moonzero.rate_limit(@moonzero.TokenBucket::new(100.0), clock))// 429 when empty
    .use_(@moonzero.breaker(@moonzero.Breaker::new(clock))) // 503 while shedding
    .use_(@moonzero.timeout(3000L, clock)) // deadline
    .use_(@moonzero.structured_logging(clock)) // one JSON line/req

    • rate_limit / period_limit — a TokenBucket that admits a burst and refills continuously, and a PeriodLimit that counts a fixed window per key; both answer 429 once they run out. Both are process-local, so N replicas admit N quotas — for a fleet use redis_rate_limit / redis_period_limit over RedisTokenLimit / RedisPeriodLimit, which run go-zero's tokenscript.lua and periodscript.lua in redis so every replica draws on one bucket or one window. An unreachable redis leaves the token limiter running on its local bucket (go-zero's rescueLimiter) rather than opening the gate.
    • breaker — a Breaker: go-zero's googleBreaker, Google SRE's client-side throttle. It keeps a Window of the last 10s in 40 buckets and sheds a fraction of calls, (total - 5 - max(w, 1.1) * accepts) / (total + 1) scaled by the run of clean buckets, answering 503 for the ones it sheds — a struggling backend keeps whatever load it can still serve instead of being cut off wholesale. The clock and the shed roll are both injected, so the decisions are exactly reproducible.
    • timeout — a Deadline enforced on the response path. Preemptively aborting a hung handler needs racing it against a timer (@async.any) under the native runtime; that race is wired at the async server edge, the deadline core is portable and tested.
    • maxbytes — rejects a request whose declared Content-Length exceeds the limit with 413.
    • structured_logging — a RequestLog rendered as one JSON line per request (method, path, status, duration, request-id, client-ip, user-agent).

    #Auth, YAML config, and zRPC over the h2c transport

    // JWT HS256 — self-built SHA-256/HMAC (verified against NIST/RFC vectors)
    let token = @moonzero.jwt_sign(
    Map([("sub", Json::string("alice")), ("exp", Json::number(1893456000.0))]),
    "topsecret",
    )
    let server = @moonzero.Server::new(conf, app)
    .use_(@moonzero.auth("topsecret", clock)) // 401 unless a valid Bearer JWT
    .use_(@moonzero.tracing()) // W3C traceparent in/out
    .use_(@moonzero.metrics(m, clock)) // request counter + latency histogram

    // The etc/*.yaml goctl writes, loaded and assembled the way go-zero's engine does
    let conf = @moonzero.RestConf::from_yaml(etc_yaml) // PascalCase keys, MOONZERO_* env overrides
    let server = @moonzero.RestEngine::new(conf).build(app) // the chain Middlewares asks for

    // A real zRPC call over moonrpc's h2c transport — unary and streaming
    let rpc = @moonzero.RpcServer::new(@moonzero.RpcServerConf::new(name="greeter", port=9090))
    let g = rpc.group("hello.Greeter")
    g.register("SayHello", req => handle(req)) // unary
    g.register_server_streaming("Tail", req => chunks(req)) // one in, many out
    g.register_client_streaming("Upload", msgs => summarize(msgs)) // many in, one out
    g.register_bidi_streaming("Chat", () => @moonzero.BidiStreamHandler::{
    on_message: m => [echo(m)], // a reply the instant each message arrives
    on_end: () => [b"bye"], // a farewell after the client half-closes
    })
    let ch = @moonzero.RpcChannel::connect(rpc)
    ch.call("/hello.Greeter/SayHello", request) // Ok(reply) | Err(status)
    ch.call_server_streaming("/hello.Greeter/Tail", request) // Ok([msg, ...]) | Err(status)
    ch.call_client_streaming("/hello.Greeter/Upload", [a, b]) // Ok(reply) | Err(status)
    let call = ch.open_bidi("/hello.Greeter/Chat") // stream both ways
    call.send(a) // -> the replies produced right then (interleaved)
    call.close_send() // -> Ok([on_end replies...]) | Err(status)

    • jwt_sign / jwt_verify — compact HS256 tokens on a self-built SHA-256 + HMAC-SHA256, signatures compared in constant time, exp/nbf enforced, and the alg:none downgrade refused. Interop-verified against the canonical jwt.io token.
    • auth — the middleware that requires Authorization: Bearer <jwt> and answers 401 for an absent, malformed, tampered, or expired token.
    • ServiceConf::from_yaml — a minimal-subset YAML parser (block maps, nesting, sequences, typed scalars, comments) feeding the same field reader as the JSON loader, so both formats agree field-for-field.
    • Conf — the conf.Load port: keys match canonically (lowercase, _ and - ignored), so a goctl-written Name/MaxBytes/Log.Level loads as readily as name/max_bytes/log_level; dotted paths reach nested blocks; ,env= lets a variable override the file; and default= / options= / range=[a:b) behave as go-zero's tags do — a missing required field or a value outside its constraint is an error, never a quiet default.
    • RestConf / RestEngineconfig-driven assembly (← rest.RestConf and newEngine): host, port, TLS files, MaxConns, MaxBytes, Timeout, CpuThreshold, Signature, TraceIgnorePaths and the eleven Middlewares flags load from one etc/*.yaml, and the engine installs exactly the layers those flags ask for, in go-zero's order, over shared connection permits / breaker window / metric set. Metrics and Gunzip load but install nothing — see AGENTS.md.
    • logx — a leveled structured logger: entries below the configured level are dropped unrendered, everything else is one JSON object per line with @timestamp, level, content, an optional WithDuration, and typed fields. RestEngine::new points it at the config's Log.Level, which is what finally makes that setting mean something.
    • RpcServer / RpcGroupconfig-driven zRPC groups that register moonrpc Method handlers by gRPC path.
    • RpcChannel — a client over the h2c transport: to_h2 turns the registered handlers into a moonrpc H2Server, and the call family runs real exchanges through it — HPACK-coded HEADERS, length-prefixed DATA frames, and the grpc-status trailer read back off the reply. Unary (call), server-streaming (call_server_streaming, one request then every framed reply in order), client-streaming (call_client_streaming, each request as its own DATA frame then one reply after half-close), and bidirectional streaming all round-trip through the same engine. A call to an unregistered path comes back UNIMPLEMENTED, the trailers-only response a gRPC server sends for an unknown method.
    • Bidi streamingopen_bidi opens a stream that stays open both ways: each BidiCall::send writes one request message and returns the replies the server produced right then (an echo handler answers each message as it arrives), and close_send half-closes, runs the server's on_end, and reports the final grpc-status. call_bidi_streaming drives a whole exchange in one shot, returning the interleaved replies followed by the on_end messages. The channel's HPACK decoder is advanced across every reply block, so its dynamic table stays in lockstep with the engine's encoder for the life of the call.
    • ShutdownCoordinatorgraceful shutdown for a serving zRPC server: dispatch_graceful counts each call as in-flight for its duration, initiate_shutdown makes new calls come back Unavailable (a stopped listener) while in-flight ones run to completion, and is_drained reports when the last one has finished so the process may exit.

    #Discovery, metrics, tracing

    // Persisted, watchable registry (etcd v3-shaped): register, watch, snapshot
    let reg = @moonzero.PersistentRegistry::new()
    reg.watch(e => log(e)) // Put/Delete events in revision order
    reg.register("greeter", @moonzero.Endpoint::new("10.0.0.1", 9090))
    reg.register("greeter", @moonzero.Endpoint::new("10.0.0.2", 9090))
    let saved = reg.snapshot() // persist to a file/etcd; restore reloads it

    // Real registry I/O over a file (native): a publisher persists, a reader watches
    @discov.persist_registry(path, reg) // write the snapshot to a real file
    let reader = @discov.FileRegistry::load(path) // load it back
    reader.reload() // re-read -> the Put/Delete diff since last load
    reader.watch(dir, e => log(e)) // reload on every filesystem change

    // Load-balanced client: resolve an instance, then call it over h2c
    let ch = @moonzero.LoadBalancedChannel::new(reg.resolver(), cluster, "greeter")
    ch.call("/hello.Greeter/SayHello", request) // dials 10.0.0.1, then .2, cycling

    // Metrics read out for a /metrics scrape after serving
    let m = @moonzero.ServerMetrics::new()
    m.requests().value("GET /ping 200") // request count for that label
    m.latency().mean() // mean request latency, ms

    • InMemoryRegistry — a service registry shaped like go-zero's etcd discov store: a service -> instance -> endpoint map with a monotonic store revision a watcher can compare against. RoundRobin, pick_first and WeightedRoundRobin balancers select an endpoint from a resolved set.
    • WeightedRoundRobin — a weighted balancer over Endpoint::weight, using smooth weighted round-robin: over one cycle each instance is served exactly its share, and a weight-5 instance is spread through the cycle rather than handed five requests in a row. An instance weighted zero or less is never picked.
    • PersistentRegistry — the persisted, watchable registry: adds a live watch (Put/Delete events in revision order), an events_since catch-up from any revision, and snapshot/restore that round-trip the whole keyspace through an etcd v3 RangeResponse-shaped JSON document without losing a revision — the bytes a file- or etcd-backed deployment persists and reloads.
    • LoadBalancedChannel — a load-balanced zRPC client over a Resolve interface: it resolves a service, picks a live instance with the balancer, dials it through an RpcCluster, and makes the call, so instances registering or leaving between calls take effect on the next one. Any store exposing resolver() backs it; an etcd- or consul-backed store drops in unchanged. Unary, server-streaming, and bidi calls all go through the resolve-then-balance path.
    • FileRegistry — real registry I/O in the native discov sub-package (← go-zero's discov publisher/subscriber, over the filesystem instead of etcd's network): persist_registry writes the snapshot to a real file through moonbitlang/async's fs, FileRegistry::load reads it back and exposes a resolver(), reload re-reads and returns the Put/Delete diff since the last load, and watch/watch_once block on a real filesystem watcher and reload on every change. Reader and publisher share only the file, exactly as an etcd subscriber and publisher share only the keyspace, so the balancer and load-balanced channel above drive it unchanged.
    • ServerMetrics — a CounterVec of per-method/route/status request tallies and a cumulative latency Histogram (Prometheus le buckets, sum, count), driven by the metrics middleware that times each request on the clock.
    • tracingtrace-id propagation: continue an inbound W3C traceparent or start a new trace, mint a child span, and stamp traceparent + x-trace-id onto the response.

    #Roadmap (transliterating go-zero)

    A conf.Load-shaped loader (canonical keys, env overrides, default=/options=/range=) + RestConf and the engine that builds a chain from its Middlewares flags + leveled structured logging + service assembly + the base middleware onion (logging, recovery, CORS, request-id) + the resilience set (timeout, rate-limit, breaker, maxbytes, structured logging) + route groups + JWT auth + zRPC groups with real unary, server/client-streaming, and bidirectional h2c round-trips + graceful shutdown draining in-flight calls + a persisted, watchable registry with round-robin/weighted/pick-first balancers and a load-balanced client + real file-backed registry I/O with a live filesystem watcher + request metrics + trace-id propagation are here. Next: an etcd/consul network client behind the same Resolve interface, and moonctl-driven scaffolding of a full moonzero service from a spec.

    #License

    Apache-2.0.

    BidiStreamFactory

    type BidiStreamFactory = () -> BidiStreamHandler

    A factory that mints one BidiStreamHandler per call, so each stream gets its own handler state (← the fresh ServerStream gRPC hands every bidi invocation).

    ClientStreamHandler

    type ClientStreamHandler = (Array[Bytes]) -> Bytes

    A client-streaming handler: every request message the client sends is collected, and after the client half-closes the handler returns one reply.

    Middleware

    An AsgiApp transformer — one layer of the middleware onion.

    Resolve

    type Resolve = (String) -> Array[Endpoint]

    The resolve half of go-zero's discovery (← discov.Discovery): a function from a service name to its live endpoints. Any store — the in-memory InMemoryRegistry, the persisted PersistentRegistry, or a future etcd/consul client — exposes one via resolver(), so a balancer and the load-balanced channel are written once against the interface and the backing store swaps by swapping the closure.

    RpcHandler

    type RpcHandler = (Bytes) -> Bytes

    A unary RPC handler: it maps a request message's wire bytes to a response message's wire bytes (the application/grpc+proto payload, sans the length-prefix framing that @moonrpc.encode_message adds). Streaming handlers arrive with the h2 transport; this is the unary shape zRPC registers today.

    ServerStreamHandler

    type ServerStreamHandler = (Bytes) -> Array[Bytes]

    A server-streaming handler: one request message in, an ordered sequence of response messages out (each framed as its own length-prefixed gRPC message). Mirrors go-zero's pb.XxxServer server-streaming method, which writes to a grpc.ServerStream instead of returning one reply.

    ConsulHttp

    pub trait ConsulHttp {
    fn request(Self, String, String, Bytes) -> ConsulResponse raise
    }

    A transport to a consul agent: it performs one HTTP request (method and path, with body for writes) and returns the response. Implementations own the medium — a real HTTP socket, or an in-memory fake.

    RedisConn

    pub(open) trait RedisConn {
    async fn execute(Self, Array[Bytes]) -> RespValue
    }

    A live redis connection: it sends an encoded command (an array of argument byte strings) and returns the decoded reply. Implementations own the transport — a real socket speaking RESP, or an in-memory fake. It is open and async so that a socket can be one: real redis I/O suspends, and a sealed synchronous trait left every implementation a test double.

    ConfigError

    pub suberror ConfigError {
    ConfigError(String)
    }

    A config-loading failure (← go-zero's conf.Load errors): malformed JSON or YAML, a non-mapping root, a field of the wrong type, a required field with no value, or a value outside its options=/range= constraint, with a human-readable reason.

    ConsulError

    pub suberror ConsulError {
    ConsulError(String)
    }

    A consul API call that failed — a transport error, or a non-2xx agent response.

    EtcdError

    pub suberror EtcdError {
    EtcdError(String)
    }

    An etcd gRPC call that returned a non-OK grpc-status.

    Http1Error

    pub suberror Http1Error {
    Http1Error(String)
    }

    A malformed HTTP response.

    JwtError

    pub suberror JwtError {
    MalformedToken(String)
    UnsupportedAlg(String)
    BadSignature
    Expired
    NotYetValid
    }

    A JWT verification failure (← go-zero's handler.Authorize rejection cases). Every path a bad token can fail on is reported distinctly so callers (and the auth middleware) can log or branch precisely.

    RedisError

    pub suberror RedisError {
    RedisError(String)
    }

    A redis command that failed — a connection-level error, or a redis error reply.

    RespError

    pub suberror RespError {
    RespError(String)
    }

    A malformed or truncated RESP frame.

    Unavailable

    pub suberror Unavailable

    Raised by Breaker::run for a call the throttle shed (← go-zero's ErrServiceUnavailable).

    Balancer

    pub(all) enum Balancer {
    RoundRobinBalancer(RoundRobin)
    PickFirst
    WeightedBalancer(WeightedRoundRobin)
    }

    The choice of balancer for a load-balanced channel: round-robin cycles through the resolved instances (spreading load evenly), WeightedBalancer shares them out in proportion to Endpoint::weight, PickFirst pins the head instance (← gRPC's pick_first).

    Balancer::pick

    fn Balancer::pick(self : Balancer, endpoints : Array[Endpoint]) -> Endpoint?

    Pick one endpoint from a resolved set, or None if it is empty.

    Balancer::round_robin

    fn Balancer::round_robin() -> Balancer

    A round-robin balancer, cursor at the first instance.

    Balancer::weighted

    fn Balancer::weighted() -> Balancer

    A weighted balancer, with no credit accrued yet.

    BidiCall

    pub struct BidiCall {
    channel : RpcChannel
    sid : Int
    pending : Bytes
    status_code : Int
    ended : Bool
    }

    A live client-side bidirectional call over the h2c channel (← gRPC's ClientStream): the request stream stays open while messages flow both ways. send writes one request message and returns whatever replies the server produced right then (bidi interleaving — an echo handler answers each message as it arrives); close_send half-closes the request stream, runs the server's on_end, and reports the final grpc-status. The channel's HPACK decoder is advanced across every reply block, so its dynamic table stays in lockstep with the engine's encoder for the life of the call. pending holds DATA octets not yet split into a whole length-prefixed message (a message may straddle two DATA frames under flow control).

    BidiCall::close_send

    fn BidiCall::close_send(self : BidiCall) -> Result[Array[Bytes],
    Status
    ] raise

    Half-close the request stream: run the server's on_end, return its final reply messages, and map the grpc-status trailer to Ok/Err. Calling it a second time is an error (Cancelled).

    BidiCall::send

    fn BidiCall::send(self : BidiCall, msg : Bytes) -> Array[Bytes] raise

    Send one request message on the open stream and return the replies the server emitted in response to it (possibly empty). A no-op once the stream is half-closed.

    BidiStreamHandler

    pub(all) struct BidiStreamHandler {
    on_message : (Bytes) -> Array[Bytes]
    on_end : () -> Array[Bytes]
    }

    A live bidirectional call (← go-zero's pb.XxxServer bidi method, which reads from and writes to the same grpc.ServerStream): on_message fires once per request message and returns the replies to send right then, so responses interleave with requests; on_end runs after the client half-closes and returns the final replies before the grpc-status trailer. The moonzero-local mirror of @moonrpc.BidiHandler, so callers register bidi methods without naming the transport package.

    Breaker

    pub struct Breaker {
    win : Window
    clock : Clock
    roll : () -> Double
    last_pass : Int64?
    }

    Google SRE's client-side throttling breaker (← go-zero's googleBreaker, the algorithm behind every breaker it hands out). There is no open/closed state machine and no failure streak: the breaker keeps a rolling Window of call outcomes and sheds a fraction of new calls, so a struggling backend keeps receiving as much load as it can still serve rather than being cut off wholesale and then flooded again on recovery.

    With accepts successes out of total calls in the window, failing the run of all-failure buckets at the head of the window and working the run of all-success ones:

    w = k - (k - min_k) * failing / buckets drop = (total - protection - max(w, min_k) * accepts) / (total + 1) drop *= (buckets - working) / buckets

    A non-positive drop admits everything, which is the whole healthy case. A sustained failure run decays w towards min_k, so past successes count for less and the throttle bites harder; a run of clean buckets scales drop back down as the backend recovers.

    Both sources of nondeterminism are injected — clock for time, rand for the shed roll — so every decision is reproducible in a test.

    Breaker::allow

    fn Breaker::allow(self : Breaker) -> Promise?

    Ask to make one call. Some promise means it may proceed and the caller settles that promise once the call finishes; None means the throttle shed it. A shed is recorded too, so a breaker that is dropping never reads back as idle and throttle itself off.

    Breaker::drop_ratio

    fn Breaker::drop_ratio(self : Breaker) -> Double

    The fraction of new calls the breaker is currently shedding — 0.0 while the window is healthy. The force-pass probe is deliberately not folded in: this is the standing throttle, which is what a dashboard or an alert wants.

    Breaker::new

    fn Breaker::new(clock : Clock, rand? : () -> Double, buckets? : Int, bucket_ms? : Int64) -> Breaker

    A breaker over a buckets × bucket_ms window (go-zero's ten seconds in forty slices by default) reading time from clock. rand supplies the shed roll as a draw from [0, 1); omit it and the breaker draws from a system-seeded generator, pass one to fix the decisions.

    Breaker::run

    fn[T] Breaker::run(self : Breaker, req : () -> T raise) -> T raise

    Run req under the breaker (← go-zero's Breaker.Do). A shed call raises Unavailable and req never runs; otherwise req returning settles the promise as a success, and req raising settles it as a failure and propagates.

    Bucket

    pub struct Bucket {
    sum : Int64
    succ : Int64
    fail : Int64
    drop : Int64
    }

    One time slice of a Window. sum counts every call that landed in the slice including the shed ones, so a slice being throttled still reads as busy rather than idle.

    Clock

    pub struct Clock {
    now_ms : () -> Int64
    }

    A monotonic time source in milliseconds, injected into the resilience middlewares (rate-limit, breaker, timeout) so their timing logic is a pure function of an explicit clock rather than a hidden wall-clock read. go-zero reads timex.Now() directly; because that is neither portable across MoonBit's backends nor testable, moonzero threads the clock as a value — the same pattern Go's clockwork/x/time/rate accept for a Clock.

    Clock::new

    fn Clock::new(now_ms : () -> Int64) -> Clock

    Wrap a now-in-milliseconds thunk as a Clock.

    Clock::now

    fn Clock::now(self : Clock) -> Int64

    The current time in milliseconds, as reported by the wrapped source.

    Clock::system

    fn Clock::system() -> Clock

    The platform clock, in milliseconds since the Unix epoch (← go-zero's timex.Now()). Every backend can read it, so a service that has no reason to inject its own time source can take this one.

    Conf

    pub struct Conf {
    root : Map[String, Json]
    }

    A loaded config document (← go-zero's conf.Load): a parsed mapping read through canonical keys and dotted paths, with the ,env=NAME override and the default= / options= / range= constraints go-zero spells as struct tags.

    A field that is absent and has no default is an error, and so is a value that violates its options= or range= constraint — a bad value never degrades into the default.

    Conf::at

    fn Conf::at(self : Conf, path : String) -> Json?

    The value at a dotted path, or None if any segment is missing or a non-mapping is walked into.

    Conf::bool

    fn Conf::bool(self : Conf, path : String, default? : Bool, env? : String, also? : Array[String]) -> Bool raise ConfigError

    Read a Bool field, honouring env= and default=. true/false spelled as a string decode too, which is how an env override arrives.

    Conf::int

    fn Conf::int(self : Conf, path : String, default? : Int, range? : String, env? : String, also? : Array[String]) -> Int raise ConfigError

    Read an Int field, honouring env=, default= and range=. A fractional value is truncated toward zero after the range check.

    Conf::int64

    fn Conf::int64(self : Conf, path : String, default? : Int64, range? : String, env? : String, also? : Array[String]) -> Int64 raise ConfigError

    Read an Int64 field, honouring env=, default= and range=.

    Conf::list

    fn Conf::list(self : Conf, path : String) -> Array[Conf] raise ConfigError

    The list of sub-documents at path — the shape a []Struct config field takes, each element read back through the same constraint-checked accessors. An absent path is an empty list; a non-list, or a list holding anything but mappings, is an error.

    Conf::of_json

    fn Conf::of_json(src : String) -> Conf raise ConfigError

    Load a document from JSON. Raises ConfigError on malformed JSON or a non-object root.

    Conf::of_yaml

    fn Conf::of_yaml(src : String) -> Conf raise ConfigError

    Load a document from the YAML go-zero ships as etc/*.yaml. Raises ConfigError on malformed YAML or a non-mapping root.

    Conf::string

    fn Conf::string(self : Conf, path : String, default? : String, options? : Array[String], env? : String, also? : Array[String]) -> String raise ConfigError

    Read a string field, honouring env=, default= and options=.

    Conf::strings

    fn Conf::strings(self : Conf, path : String, default? : Array[String], env? : String, also? : Array[String]) -> Array[String] raise ConfigError

    Read a string-list field, honouring env= and default=. An env override is comma-separated, since a shell variable carries one string.

    ConsulClient

    pub struct ConsulClient {
    http : &ConsulHttp
    }

    A consul client over a ConsulHttp, exposing the agent operations discovery uses.

    ConsulClient::agent_services

    fn ConsulClient::agent_services(self : ConsulClient) -> Array[(String, String)] raise ConsulError

    GET /v1/agent/services: every service instance registered on this agent, as (instance-id, service-name) pairs — the list a by-name deregister filters to find the ids to drop.

    ConsulClient::check_pass

    fn ConsulClient::check_pass(self : ConsulClient, service_id : String) -> Unit raise ConsulError

    PUT /v1/agent/check/pass/service:<id>: mark an instance's TTL check passing, the keep-alive that renews its lease.

    ConsulClient::deregister_service

    fn ConsulClient::deregister_service(self : ConsulClient, id : String) -> Unit raise ConsulError

    PUT /v1/agent/service/deregister/<id>: deregister one instance.

    ConsulClient::health_service

    fn ConsulClient::health_service(self : ConsulClient, name : String) -> Array[Endpoint] raise ConsulError

    GET /v1/health/service/<name>?passing=true: the healthy instances of name, each as its Service.Address:Service.Port endpoint (falling back to Node.Address when the service advertises no address of its own, as consul's clients do).

    ConsulClient::new

    A client over http.

    ConsulClient::register_service

    fn ConsulClient::register_service(self : ConsulClient, id : String, name : String, address : String, port : Int, ttl_secs : Int) -> Unit raise ConsulError

    PUT /v1/agent/service/register: register an instance under name at address:port with an id, held alive by a TTL check that consul deregisters ttl*3 seconds after it stops passing. Pass the check with check_pass before each TTL lapses to stay healthy.

    ConsulDiscovery

    pub struct ConsulDiscovery {
    client : ConsulClient
    }

    A consul-backed service registry / resolver over a ConsulClient. Each instance is a consul service named service, uniquely identified per agent by its address.

    ConsulDiscovery::deregister

    fn ConsulDiscovery::deregister(self : ConsulDiscovery, service : String) -> Unit raise

    Deregister every instance of service registered on this agent.

    ConsulDiscovery::deregister_instance

    fn ConsulDiscovery::deregister_instance(self : ConsulDiscovery, service : String, endpoint : Endpoint) -> Unit raise

    Deregister one instance of service.

    ConsulDiscovery::keepalive

    fn ConsulDiscovery::keepalive(self : ConsulDiscovery, service : String, endpoint : Endpoint) -> Unit raise

    Refresh an instance's lease by passing its TTL check.

    ConsulDiscovery::new

    A consul discovery over client.

    ConsulDiscovery::register

    fn ConsulDiscovery::register(self : ConsulDiscovery, service : String, endpoint : Endpoint, ttl? : Int) -> String raise

    Register endpoint for service with a ttl-second TTL check and immediately pass the check so the instance is healthy at once (a fresh TTL check starts critical). Returns the instance id; renew it with keepalive before the TTL lapses.

    ConsulDiscovery::resolve

    fn ConsulDiscovery::resolve(self : ConsulDiscovery, service : String) -> Array[Endpoint] raise

    Resolve service to its healthy endpoints.

    ConsulDiscovery::resolver

    fn ConsulDiscovery::resolver(self : ConsulDiscovery) -> ((String) -> Array[Endpoint])

    This consul discovery as a Resolve interface value, so the balancer and the load-balanced channel run against consul unchanged. A resolve error surfaces as an empty endpoint set, matching the other drivers.

    ConsulResponse

    pub struct ConsulResponse {
    status : Int
    body : Bytes
    }

    A consul agent's HTTP response: the status code and the raw body bytes.

    ConsulResponse::body

    fn ConsulResponse::body(self : ConsulResponse) -> Bytes

    The raw response body bytes.

    ConsulResponse::new

    fn ConsulResponse::new(status : Int, body : Bytes) -> ConsulResponse

    A consul response.

    ConsulResponse::status

    fn ConsulResponse::status(self : ConsulResponse) -> Int

    The HTTP status code.

    CorsConf

    pub(all) struct CorsConf {
    allow_origin : String
    allow_methods : String
    allow_headers : String
    allow_credentials : Bool
    max_age : Int
    }

    CORS configuration (← go-zero's cors.Middleware options): the values echoed back in the Access-Control-* preflight/response headers.

    CorsConf::new

    fn CorsConf::new(allow_origin? : String, allow_methods? : String, allow_headers? : String, allow_credentials? : Bool, max_age? : Int) -> CorsConf

    Build a permissive CORS config: any origin, the full method set, and a one-day preflight cache. Credentials are off by default, matching go-zero.

    CounterVec

    pub struct CounterVec {
    counts : Map[String, Int64]
    }

    A monotonic counter (← go-zero's metric.CounterVec) partitioned by a label string. Each inc/add accrues against one label (e.g. "GET /ping 200"), so a single vector holds the per-method/route/status request tallies Prometheus scrapes. Counters only ever go up.

    CounterVec::add

    fn CounterVec::add(self : CounterVec, label : String, delta : Int64) -> Unit

    Add delta to label's count (creating the series on first sight).

    CounterVec::inc

    fn CounterVec::inc(self : CounterVec, label : String) -> Unit

    Increment label's count by one.

    CounterVec::labels

    fn CounterVec::labels(self : CounterVec) -> Array[String]

    The set of labels that have been observed.

    CounterVec::new

    fn CounterVec::new() -> CounterVec

    A fresh counter vector with no labels seen yet.

    CounterVec::to_exposition

    fn CounterVec::to_exposition(self : CounterVec, name~ : String, help~ : String, label? : String) -> String

    Render this counter vector as Prometheus text exposition: a # HELP line, a # TYPE <name> counter line, then one <name>{<label>="<value>"} <count> series per observed label (sorted for a stable scrape). label names the single dimension the vector partitions on.

    CounterVec::total

    fn CounterVec::total(self : CounterVec) -> Int64

    The sum of every label's count — the total number of observations.

    CounterVec::value

    fn CounterVec::value(self : CounterVec, label : String) -> Int64

    The current count for label, 0 if never touched.

    Deadline

    pub struct Deadline {
    budget_ms : Int64
    started_ms : Int64
    }

    A request deadline (← go-zero's timeout middleware's context.WithTimeout): a budget in milliseconds measured from a start instant on the shared clock. A budget_ms <= 0 means "no deadline" and never expires — go-zero's convention for a disabled timeout.

    Deadline::expired

    fn Deadline::expired(self : Deadline, now : Int64) -> Bool

    Whether the deadline has passed at time now. A non-positive budget never expires.

    Deadline::remaining

    fn Deadline::remaining(self : Deadline, now : Int64) -> Int64

    Milliseconds left before the deadline at time now (never negative); -1 for a disabled (non-positive-budget) deadline, which has no finite remaining.

    Deadline::start

    fn Deadline::start(budget_ms : Int64, now : Int64) -> Deadline

    Start a deadline of budget_ms milliseconds at time now.

    Endpoint

    pub(all) struct Endpoint {
    host : String
    port : Int
    weight : Int
    } derive(Eq,
    Debug
    )

    A service endpoint (← go-zero's discov target): the host and port an instance listens on, plus a routing weight (default 1). The weight is carried through registration and snapshots and is what WeightedRoundRobin shares traffic by; round-robin and pick-first ignore it.

    Endpoint::address

    fn Endpoint::address(self : Endpoint) -> String

    The host:port dial string.

    Endpoint::new

    fn Endpoint::new(host : String, port : Int, weight? : Int) -> Endpoint

    Build an endpoint; weight defaults to 1, matching an unweighted instance.

    EtcdClient

    pub struct EtcdClient {
    channel : RpcChannel
    }

    An etcd v3 client bound to a gRPC channel (real etcd, or a mock server in tests).

    EtcdClient::delete_range

    fn EtcdClient::delete_range(self : EtcdClient, req : EtcdDeleteRangeRequest) -> EtcdDeleteRangeResponse raise

    KV.DeleteRange: delete the key or range in req (deregister).

    EtcdClient::lease_grant

    fn EtcdClient::lease_grant(self : EtcdClient, req : EtcdLeaseGrantRequest) -> EtcdLeaseGrantResponse raise

    Lease.LeaseGrant: obtain a lease with the requested TTL.

    EtcdClient::new

    fn EtcdClient::new(channel : RpcChannel) -> EtcdClient

    An etcd client over channel.

    EtcdClient::put

    fn EtcdClient::put(self : EtcdClient, req : EtcdPutRequest) -> EtcdPutResponse raise

    KV.Put: store the key/value (optionally under a lease) in req.

    EtcdClient::range

    fn EtcdClient::range(self : EtcdClient, req : EtcdRangeRequest) -> EtcdRangeResponse raise

    KV.Range: read the key or prefix range in req.

    EtcdClient::watch

    fn EtcdClient::watch(self : EtcdClient, req : EtcdWatchRequest) -> Array[EtcdWatchResponse] raise

    Watch.Watch: open a watch with req and read the stream of responses the server produces (etcd's Watch is a bidi stream; this drives the common open-then-observe direction over the server-streaming path). Each WatchResponse carries the batch of change events since the last one.

    EtcdDeleteRangeRequest

    pub(all) struct EtcdDeleteRangeRequest {
    key : Bytes
    range_end : Bytes
    prev_kv : Bool
    } derive(Eq)

    A DeleteRangeRequest: delete the key at key, or the half-open range [key, range_end); prev_kv asks the server to return the deleted key/values. A discovery client sends this to deregister an instance.

    EtcdDeleteRangeRequest::decode

    Decode a DeleteRangeRequest.

    EtcdDeleteRangeRequest::encode

    Encode a DeleteRangeRequest (key=1, range_end=2, prev_kv=3).

    EtcdDeleteRangeResponse

    pub(all) struct EtcdDeleteRangeResponse {
    deleted : Int64
    prev_kvs : Array[EtcdKeyValue]
    } derive(Eq)

    A DeleteRangeResponse: the number of keys deleted and, when prev_kv was requested, the prev_kvs that were removed.

    EtcdDeleteRangeResponse::decode

    Decode a DeleteRangeResponse.

    EtcdDeleteRangeResponse::encode

    Encode a DeleteRangeResponse (deleted=2, prev_kvs=3 repeated).

    EtcdDiscovery

    pub struct EtcdDiscovery {
    client : EtcdClient
    prefix : String
    }

    An etcd-backed service registry / resolver. Instances of one service live under <prefix><service>/, so a Range over that prefix returns them all.

    EtcdDiscovery::deregister

    fn EtcdDiscovery::deregister(self : EtcdDiscovery, service : String) -> Unit raise

    Remove every instance of service (deregister the whole service prefix).

    EtcdDiscovery::new

    fn EtcdDiscovery::new(client : EtcdClient, prefix? : String) -> EtcdDiscovery

    An etcd discovery bound to client; keys live under prefix (default "moonzero/", mirroring go-zero's configurable discovery key root).

    EtcdDiscovery::register

    fn EtcdDiscovery::register(self : EtcdDiscovery, service : String, endpoint : Endpoint, ttl? : Int64) -> Int64 raise

    Register endpoint for service under a fresh lease living ttl seconds, and return the granted lease id (renew it with the client's keep-alive to stay registered). The instance key is <prefix><service>/<lease-id> and its value is the host:port dial string, so a resolver reads the endpoints straight back.

    EtcdDiscovery::resolve

    fn EtcdDiscovery::resolve(self : EtcdDiscovery, service : String) -> Array[Endpoint] raise

    Resolve service to its live endpoints: Range the service prefix and parse each value as a host:port endpoint. Malformed values are skipped.

    EtcdDiscovery::resolver

    fn EtcdDiscovery::resolver(self : EtcdDiscovery) -> ((String) -> Array[Endpoint])

    This etcd discovery as a Resolve interface value, so the balancer and the load-balanced channel written against Resolve run against real etcd unchanged. A resolve error surfaces as an empty endpoint set (the balancer's no-instance case), matching how the in-memory resolver behaves for an unknown service.

    EtcdEvent

    pub(all) struct EtcdEvent {
    event_type : EtcdEventType
    kv : EtcdKeyValue
    } derive(Eq)

    A watch Event (mvccpb.Event): a change to one key, carrying the resulting KeyValue (for a delete, the key with cleared metadata). This is what a discovery watcher folds into add/remove of a service instance.

    EtcdEvent::decode

    fn EtcdEvent::decode(data : Bytes) -> EtcdEvent raise
    PbError

    Decode an Event.

    EtcdEvent::encode

    fn EtcdEvent::encode(self : EtcdEvent) -> Bytes

    Encode an Event (type=1, kv=2). PUT (0) is the proto3 default and omitted.

    EtcdEventType

    pub(all) enum EtcdEventType {
    Put
    Delete
    } derive(Eq)

    The kind of change a watch Event reports (mvccpb.Event.EventType): a key was Put (created or updated) or Deleted.

    EtcdEventType::from_int

    fn EtcdEventType::from_int(n : Int) -> EtcdEventType

    The event type for a protobuf enum number; unknown numbers read as Put.

    EtcdEventType::to_int

    fn EtcdEventType::to_int(self : EtcdEventType) -> Int

    The protobuf enum number of an event type (PUT = 0, DELETE = 1).

    EtcdKeyValue

    pub(all) struct EtcdKeyValue {
    key : Bytes
    create_revision : Int64
    mod_revision : Int64
    version : Int64
    value : Bytes
    lease : Int64
    } derive(Eq)

    An etcd KeyValue (mvccpb.KeyValue): a key, its value, the create/mod revisions and version that track its history, and the lease it is attached to.

    EtcdKeyValue::decode

    Decode a KeyValue from protobuf wire bytes; unknown fields are skipped.

    EtcdKeyValue::empty

    fn EtcdKeyValue::empty() -> EtcdKeyValue

    The empty key/value with all-zero metadata.

    EtcdKeyValue::encode

    fn EtcdKeyValue::encode(self : EtcdKeyValue) -> Bytes

    Encode a KeyValue to its protobuf wire bytes (field numbers per etcd mvccpb.proto: key=1, create_revision=2, mod_revision=3, version=4, value=5, lease=6). Proto3 default (empty / zero) fields are omitted.

    EtcdLeaseGrantRequest

    pub(all) struct EtcdLeaseGrantRequest {
    ttl : Int64
    id : Int64
    } derive(Eq)

    A LeaseGrantRequest: ask etcd for a lease living ttl seconds (id 0 lets the server assign one). A registered service key attaches to the lease and vanishes when the lease expires — go-zero's instance liveness mechanism.

    EtcdLeaseGrantRequest::decode

    Decode a LeaseGrantRequest.

    EtcdLeaseGrantRequest::encode

    fn EtcdLeaseGrantRequest::encode(self : EtcdLeaseGrantRequest) -> Bytes

    Encode a LeaseGrantRequest (TTL=1, ID=2).

    EtcdLeaseGrantResponse

    pub(all) struct EtcdLeaseGrantResponse {
    id : Int64
    ttl : Int64
    error : String
    } derive(Eq)

    A LeaseGrantResponse: the granted lease id, its actual ttl, and an error string when the grant failed.

    EtcdLeaseGrantResponse::decode

    Decode a LeaseGrantResponse.

    EtcdLeaseGrantResponse::encode

    Encode a LeaseGrantResponse (ID=2, TTL=3, error=4).

    EtcdLeaseKeepAliveRequest

    pub(all) struct EtcdLeaseKeepAliveRequest {
    id : Int64
    } derive(Eq)

    A LeaseKeepAliveRequest: renew lease id before it expires. A discovery client streams these to keep its instance registered.

    EtcdLeaseKeepAliveRequest::decode

    Decode a LeaseKeepAliveRequest.

    EtcdLeaseKeepAliveRequest::encode

    Encode a LeaseKeepAliveRequest (ID=1).

    EtcdLeaseKeepAliveResponse

    pub(all) struct EtcdLeaseKeepAliveResponse {
    id : Int64
    ttl : Int64
    } derive(Eq)

    A LeaseKeepAliveResponse: the renewed lease id and its remaining ttl (0 = the lease has expired).

    EtcdLeaseKeepAliveResponse::decode

    Decode a LeaseKeepAliveResponse.

    EtcdLeaseKeepAliveResponse::encode

    Encode a LeaseKeepAliveResponse (ID=2, TTL=3).

    EtcdPutRequest

    pub(all) struct EtcdPutRequest {
    key : Bytes
    value : Bytes
    lease : Int64
    } derive(Eq)

    A PutRequest: store value at key, optionally under lease.

    EtcdPutRequest::decode

    Decode a PutRequest.

    EtcdPutRequest::encode

    fn EtcdPutRequest::encode(self : EtcdPutRequest) -> Bytes

    Encode a PutRequest (key=1, value=2, lease=3).

    EtcdPutResponse

    pub(all) struct EtcdPutResponse {
    prev_kv : EtcdKeyValue
    } derive(Eq)

    A PutResponse: when prev_kv was requested on the PutRequest, the key/value that the put replaced (its key is empty when there was none).

    EtcdPutResponse::decode

    Decode a PutResponse.

    EtcdPutResponse::encode

    fn EtcdPutResponse::encode(self : EtcdPutResponse) -> Bytes

    Encode a PutResponse (prev_kv=2); an absent previous value (empty key) is omitted.

    EtcdRangeRequest

    pub(all) struct EtcdRangeRequest {
    key : Bytes
    range_end : Bytes
    limit : Int64
    } derive(Eq)

    A RangeRequest (etcdserverpb): read the key at key, or the half-open range [key, range_end) when range_end is set, up to limit results (0 = no limit).

    EtcdRangeRequest::decode

    Decode a RangeRequest.

    EtcdRangeRequest::encode

    fn EtcdRangeRequest::encode(self : EtcdRangeRequest) -> Bytes

    Encode a RangeRequest (key=1, range_end=2, limit=3).

    EtcdRangeResponse

    pub(all) struct EtcdRangeResponse {
    kvs : Array[EtcdKeyValue]
    count : Int64
    } derive(Eq)

    A RangeResponse: the matched key/values and the total count in the range (which may exceed the returned kvs when a limit capped them).

    EtcdRangeResponse::decode

    Decode a RangeResponse; each kvs entry is a nested KeyValue message.

    EtcdRangeResponse::encode

    fn EtcdRangeResponse::encode(self : EtcdRangeResponse) -> Bytes

    Encode a RangeResponse (kvs=2 repeated, count=4).

    EtcdWatchCancelRequest

    pub(all) struct EtcdWatchCancelRequest {
    watch_id : Int64
    } derive(Eq)

    A WatchCancelRequest: stop the watch stream identified by watch_id.

    EtcdWatchCancelRequest::decode

    Decode a WatchCancelRequest.

    EtcdWatchCancelRequest::encode

    Encode a WatchCancelRequest (watch_id=1).

    EtcdWatchCreateRequest

    pub(all) struct EtcdWatchCreateRequest {
    key : Bytes
    range_end : Bytes
    start_revision : Int64
    } derive(Eq)

    A WatchCreateRequest: subscribe to changes on key, or on the half-open range [key, range_end), from start_revision (0 = current). A discovery watcher opens one over the service's key prefix.

    EtcdWatchCreateRequest::decode

    Decode a WatchCreateRequest.

    EtcdWatchCreateRequest::encode

    Encode a WatchCreateRequest (key=1, range_end=2, start_revision=3).

    EtcdWatchRequest

    pub(all) enum EtcdWatchRequest {
    Create(EtcdWatchCreateRequest)
    Cancel(EtcdWatchCancelRequest)
    } derive(Eq)

    A WatchRequest, the request_union oneof of the bidi Watch stream: either a Create to open a watch or a Cancel to close one.

    EtcdWatchRequest::decode

    Decode a WatchRequest; the last-set oneof arm wins, defaulting to an empty Create.

    EtcdWatchRequest::encode

    fn EtcdWatchRequest::encode(self : EtcdWatchRequest) -> Bytes

    Encode a WatchRequest (create_request=1, cancel_request=2).

    EtcdWatchResponse

    pub(all) struct EtcdWatchResponse {
    watch_id : Int64
    created : Bool
    canceled : Bool
    events : Array[EtcdEvent]
    } derive(Eq)

    A WatchResponse: the server-assigned watch_id, the created / canceled lifecycle flags, and the batch of change events since the last response. A discovery watcher folds each event into add/remove of a service instance.

    EtcdWatchResponse::decode

    Decode a WatchResponse.

    EtcdWatchResponse::encode

    fn EtcdWatchResponse::encode(self : EtcdWatchResponse) -> Bytes

    Encode a WatchResponse (watch_id=2, created=3, canceled=4, events=11 repeated).

    Group

    pub struct Group {
    app :
    App

    prefix : String
    }

    A route group (← go-zero's RouteGroup): registers a set of routes on an underlying moonapi.App under a shared path prefix, so related endpoints (e.g. everything under /api/v1) are declared without repeating the prefix.

    Group::delete

    fn Group::delete(self : Group, path : String, handler : (
    Context
    ) ->
    Response
    raise, summary? : String) -> Unit

    Register a DELETE route under the group's prefix.

    Group::get

    fn Group::get(self : Group, path : String, handler : (
    Context
    ) ->
    Response
    raise, summary? : String) -> Unit

    Register a GET route under the group's prefix.

    Group::new

    fn Group::new(app :
    App
    , prefix : String) -> Group

    Open a group that prefixes every route it registers with prefix on app.

    Group::patch

    fn Group::patch(self : Group, path : String, handler : (
    Context
    ) ->
    Response
    raise, summary? : String) -> Unit

    Register a PATCH route under the group's prefix.

    Group::post

    fn Group::post(self : Group, path : String, handler : (
    Context
    ) ->
    Response
    raise, summary? : String) -> Unit

    Register a POST route under the group's prefix.

    Group::prefix

    fn Group::prefix(self : Group) -> String

    The prefix this group joins onto each registered route.

    Group::put

    fn Group::put(self : Group, path : String, handler : (
    Context
    ) ->
    Response
    raise, summary? : String) -> Unit

    Register a PUT route under the group's prefix.

    Group::route

    fn Group::route(self : Group, verb :
    Method
    , path : String, handler : (
    Context
    ) ->
    Response
    raise, summary? : String) -> Unit

    Register a route for an explicit method under the group's prefix.

    Histogram

    pub struct Histogram {
    bounds : Array[Double]
    bucket : Array[Int64]
    sum : Double
    count : Int64
    }

    A cumulative histogram (← go-zero's metric.HistogramVec, Prometheus semantics): a sorted list of le (less-than-or-equal) upper bounds and, for each, the count of observations that fell at or below it, plus the running sum and total count. An observation above every bound still lands in the implicit +Inf bucket that count represents.

    Histogram::bounds

    fn Histogram::bounds(self : Histogram) -> Array[Double]

    The upper bounds this histogram partitions on.

    Histogram::bucket_count

    fn Histogram::bucket_count(self : Histogram, i : Int) -> Int64

    The cumulative count in the bucket bounded by bounds[i] — how many observations were <= that bound.

    Histogram::mean

    fn Histogram::mean(self : Histogram) -> Double

    The mean of the observations, or 0 when none have been recorded.

    Histogram::new

    fn Histogram::new(bounds? : Array[Double]) -> Histogram

    A histogram over bounds (defaulting to default_latency_buckets). The bounds are taken as given; supply them in ascending order, as Prometheus requires.

    Histogram::observe

    fn Histogram::observe(self : Histogram, value : Double) -> Unit

    Record one observation: it lands in every bucket whose le bound it does not exceed (cumulative), and updates the sum and count.

    Histogram::sum_value

    fn Histogram::sum_value(self : Histogram) -> Double

    The sum of all observed values (Prometheus _sum).

    Histogram::to_exposition

    fn Histogram::to_exposition(self : Histogram, name~ : String, help~ : String) -> String

    Render this histogram as Prometheus text exposition: a # HELP line, a # TYPE <name> histogram line, the cumulative <name>_bucket{le="<bound>"} series capped by the le="+Inf" bucket (every observation, including those above the last bound), then <name>_sum and <name>_count.

    Histogram::total

    fn Histogram::total(self : Histogram) -> Int64

    The total number of observations (the +Inf bucket count).

    Http1Response

    pub struct Http1Response {
    status : Int
    headers : Array[(String, String)]
    body : Bytes
    }

    A parsed HTTP response: the status code, the headers as (name, value) pairs with names lower-cased, and the raw body bytes.

    Http1Response::body

    fn Http1Response::body(self : Http1Response) -> Bytes

    The raw body bytes.

    Http1Response::header

    fn Http1Response::header(self : Http1Response, name : String) -> String?

    The first value of header name (matched case-insensitively), or None.

    Http1Response::status

    fn Http1Response::status(self : Http1Response) -> Int

    The status code.

    InMemoryRegistry

    pub struct InMemoryRegistry {
    instances : Map[String, Map[String, Endpoint]]
    seq : Int
    revision : Int64
    }

    An in-memory service registry (← go-zero's etcd discov store, minus the network): a two-level map of service -> instance-id -> endpoint and a monotonic revision bumped on every mutation, mirroring etcd's store revision so a watcher could detect change. Instance ids are <service>/<n>, the leaf of the etcd key an instance would lease.

    InMemoryRegistry::deregister

    fn InMemoryRegistry::deregister(self : InMemoryRegistry, service : String, key : String) -> Bool

    Remove the instance at key from service. Returns true if it existed (and bumps the revision), false if the service or key was unknown.

    InMemoryRegistry::new

    A fresh, empty registry at revision 0.

    InMemoryRegistry::register

    fn InMemoryRegistry::register(self : InMemoryRegistry, service : String, endpoint : Endpoint) -> String

    Register endpoint under service and return its instance key. Each call mints a distinct key, so two instances of one service coexist, and bumps the revision.

    InMemoryRegistry::resolve

    fn InMemoryRegistry::resolve(self : InMemoryRegistry, service : String) -> Array[Endpoint]

    The endpoints registered for service, in registration order.

    InMemoryRegistry::resolver

    fn InMemoryRegistry::resolver(self : InMemoryRegistry) -> ((String) -> Array[Endpoint])

    This registry as a Resolve interface value.

    InMemoryRegistry::revision

    fn InMemoryRegistry::revision(self : InMemoryRegistry) -> Int64

    The store revision, incremented on each register/deregister — etcd's mod-revision, the value a watcher compares against to see new state.

    InMemoryRegistry::services

    fn InMemoryRegistry::services(self : InMemoryRegistry) -> Array[String]

    Every service name with at least one live instance.

    Layer

    pub(all) struct Layer {
    name : String
    middleware : (async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit) -> (async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit)
    }

    One layer of the assembled chain: go-zero's name for the handler, and the middleware that stands in for it.

    LoadBalancedChannel

    pub struct LoadBalancedChannel {
    resolve : (String) -> Array[Endpoint]
    cluster : RpcCluster
    balancer : Balancer
    service : String
    }

    A load-balanced zRPC client (← go-zero's zrpc.Client over a discovery target): it resolves a service through the Resolver, picks a live instance with the Balancer, dials it on the RpcCluster, and makes the call. The whole resolve→balance→dial→call path runs per call, so instances registering or deregistering between calls take effect on the next one.

    LoadBalancedChannel::call

    fn LoadBalancedChannel::call(self : LoadBalancedChannel, path : String, request : Bytes) -> Result[Bytes,
    Status
    ] raise

    Make a unary call to path, resolving and balancing to a live instance first.

    LoadBalancedChannel::call_bidi_streaming

    fn LoadBalancedChannel::call_bidi_streaming(self : LoadBalancedChannel, path : String, requests : Array[Bytes]) -> Result[Array[Bytes],
    Status
    ] raise

    Make a bidirectional-streaming call to path, resolving and balancing to a live instance first, then driving the whole requests exchange to completion.

    LoadBalancedChannel::call_server_streaming

    fn LoadBalancedChannel::call_server_streaming(self : LoadBalancedChannel, path : String, request : Bytes) -> Result[Array[Bytes],
    Status
    ] raise

    Make a server-streaming call to path, resolving and balancing to a live instance first.

    LoadBalancedChannel::new

    fn LoadBalancedChannel::new(resolve : (String) -> Array[Endpoint], cluster : RpcCluster, service : String, balancer? : Balancer) -> LoadBalancedChannel

    Build a load-balanced channel for service over a Resolve interface, dialing through cluster with balancer (round-robin by default).

    LogField

    pub(all) struct LogField {
    key : String
    value : Json
    }

    One extra key/value on a log entry (← logx.LogField). Fields are rendered alongside the message inside the same JSON object, so a collector can index them without parsing the message text.

    LogField::new

    fn LogField::new(key : String, value : Json) -> LogField

    Build a log field.

    LogField::num

    fn LogField::num(key : String, value : Double) -> LogField

    Build a number-valued log field.

    LogField::str

    fn LogField::str(key : String, value : String) -> LogField

    Build a string-valued log field.

    LogLevel

    pub(all) enum LogLevel {
    Debug
    Info
    Error
    Severe
    } derive(Compare, Eq,
    Debug
    )

    Log verbosity (← go-zero's LogConf.Level), ordered from most to least verbose. Compare follows that order so thresholds can be tested directly.

    LogLevel::parse

    fn LogLevel::parse(s : String) -> LogLevel

    Parse a level name, falling back to Info for anything unrecognised — the same lenient default go-zero applies to a missing/empty level.

    LogLevel::to_string

    fn LogLevel::to_string(self : LogLevel) -> String

    The canonical lowercase name go-zero uses on the wire.

    LogState

    type LogState

    The bits of a logger that are shared by every view derived from it, so set_level on the process logger is seen by a with_duration view taken before it (← logx's package-level level and writer).

    Logger

    pub struct Logger {
    state : LogState
    clock : Clock
    duration_ms : Int64?
    }

    A leveled, structured logger (← go-zero's logx): entries below the configured level are dropped without being rendered, and everything else is written as one JSON object per line carrying @timestamp, level, content, an optional duration, and any extra fields.

    The timestamp is milliseconds from the injected Clock, not a formatted calendar time: MoonBit has no portable date formatter, and the clock is already how every other timed layer here reads the time.

    Logger::debug

    fn Logger::debug(self : Logger, content : String, fields? : Array[LogField]) -> Unit

    Write a debug entry.

    Logger::enabled

    fn Logger::enabled(self : Logger, level : LogLevel) -> Bool

    Whether an entry at level would be written.

    Logger::error

    fn Logger::error(self : Logger, content : String, fields? : Array[LogField]) -> Unit

    Write an error entry.

    Logger::info

    fn Logger::info(self : Logger, content : String, fields? : Array[LogField]) -> Unit

    Write an info entry.

    Logger::level

    fn Logger::level(self : Logger) -> LogLevel

    The level below which entries are dropped.

    Logger::log

    fn Logger::log(self : Logger, level : LogLevel, content : String, fields? : Array[LogField]) -> Unit

    Render and write one entry, unless level is below the threshold.

    Logger::new

    fn Logger::new(clock : Clock, level? : LogLevel, writer? : (String) -> Unit) -> Logger

    A logger writing to writer (println by default) at level and above.

    Logger::set_level

    fn Logger::set_level(self : Logger, level : LogLevel) -> Unit

    Raise or lower the threshold (← logx.SetLevel), for this logger and every view derived from it.

    Logger::set_writer

    fn Logger::set_writer(self : Logger, writer : (String) -> Unit) -> Unit

    Send entries somewhere other than stdout (← logx.SetWriter) — a file sink, a buffer under test, a collector client.

    Logger::severe

    fn Logger::severe(self : Logger, content : String, fields? : Array[LogField]) -> Unit

    Write a severe entry.

    Logger::with_duration

    fn Logger::with_duration(self : Logger, ms : Int64) -> Logger

    A view of this logger that stamps duration on every entry (← logx.WithDuration), sharing the level and writer with the original.

    ManualClock

    pub struct ManualClock {
    ms : Int64
    }

    A deterministic, hand-advanced clock for tests and for driving the rate-limit / breaker cores without a real time source. Wall time is replaced by an explicit advance, so a token bucket's refill or a breaker's open window can be exercised exactly.

    ManualClock::advance

    fn ManualClock::advance(self : ManualClock, delta : Int64) -> Unit

    Move the manual clock forward by delta milliseconds.

    ManualClock::as_clock

    fn ManualClock::as_clock(self : ManualClock) -> Clock

    A Clock view over this manual clock: reading it reflects every advance.

    ManualClock::new

    fn ManualClock::new(start? : Int64) -> ManualClock

    A manual clock starting at start milliseconds (default 0).

    MaxConns

    pub struct MaxConns {
    max : Int
    in_flight : Int
    }

    A permit pool of max concurrent slots (← go-zero's syncx.Limit).

    MaxConns::in_flight

    fn MaxConns::in_flight(self : MaxConns) -> Int

    The number of requests currently holding a permit.

    MaxConns::new

    fn MaxConns::new(max : Int) -> MaxConns

    A pool admitting at most max requests at once.

    MaxConns::release

    fn MaxConns::release(self : MaxConns) -> Unit

    Return a permit taken by try_acquire (Return).

    MaxConns::try_acquire

    fn MaxConns::try_acquire(self : MaxConns) -> Bool

    Take a permit if one is free (TryBorrow), returning whether it was taken.

    MiddlewaresConf

    pub(all) struct MiddlewaresConf {
    trace : Bool
    log : Bool
    prometheus : Bool
    max_conns : Bool
    breaker : Bool
    shedding : Bool
    timeout : Bool
    recover : Bool
    metrics : Bool
    max_bytes : Bool
    gunzip : Bool
    } derive(Eq,
    Debug
    )

    Which of go-zero's built-in layers the engine installs (← MiddlewaresConf). Every flag defaults to true, so an etc/*.yaml that says nothing about middleware still gets the whole chain.

    MiddlewaresConf::new

    fn MiddlewaresConf::new(trace? : Bool, log? : Bool, prometheus? : Bool, max_conns? : Bool, breaker? : Bool, shedding? : Bool, timeout? : Bool, recover? : Bool, metrics? : Bool, max_bytes? : Bool, gunzip? : Bool) -> MiddlewaresConf

    The full chain, which is what go-zero defaults to.

    Outcome

    pub(all) enum Outcome {
    Succ
    Fail
    Drop
    } derive(Eq,
    Debug
    )

    How a call recorded in a Window ended (← the success/fail/drop markers go-zero's breaker writes into its window). A Drop is a call that was shed before it ran: traffic, but neither a success nor a failure of whatever is downstream.

    PeriodLimit

    pub struct PeriodLimit {
    period_ms : Int64
    quota : Int
    windows : Map[String, (Int64, Int)]
    }

    A fixed-window rate limiter kept in this process: each key may make up to quota requests per period; a key's window opens on its first request and resets once period has elapsed. It is the counting complement of the continuous TokenBucket, and because every decision is a function of (state, now) it is exactly testable without a real clock.

    The window is local, so N replicas admit N quotas. RedisPeriodLimit is the same limiter with its window in redis and is what a fleet should run; this one is for a single process, and for the fallback a caller wants when redis is unreachable.

    PeriodLimit::new

    fn PeriodLimit::new(period_secs~ : Int, quota~ : Int) -> PeriodLimit

    A limiter admitting quota requests per period_secs seconds, per key.

    PeriodLimit::take

    fn PeriodLimit::take(self : PeriodLimit, key : String, now : Int64) -> PeriodResult

    Account for one request under key at now and report its outcome. Opens a fresh window if the key's current one has elapsed; PeriodAllowed below quota, PeriodHitQuota at exactly the quota (the last admitted request), and PeriodOverQuota beyond it.

    PeriodResult

    pub(all) enum PeriodResult {
    PeriodAllowed
    PeriodHitQuota
    PeriodOverQuota
    } derive(Eq,
    Debug
    )

    The outcome of a PeriodLimit.take (← go-zero's period-limit result codes): a request within quota, the one that reaches it exactly (still admitted), or one beyond it (rejected).

    PersistentRegistry

    pub struct PersistentRegistry {
    instances : Map[String, Map[String, Endpoint]]
    key_rev : Map[String, Int64]
    seq : Int
    revision : Int64
    events : Array[RegistryEvent]
    watchers : Array[(RegistryEvent) -> Unit]
    }

    A persisted, watchable service registry (← go-zero's etcd discov publisher): the same two-level service -> instance-id -> endpoint store as InMemoryRegistry, plus a per-key mod-revision, an append-only event log for catch-up watchers, live watcher callbacks fired on every mutation, and snapshot/restore that round-trip the whole keyspace through an etcd v3 RangeResponse-shaped JSON document — the bytes a file- or etcd-backed deployment persists and reloads without losing a revision.

    PersistentRegistry::deregister

    fn PersistentRegistry::deregister(self : PersistentRegistry, service : String, key : String) -> Bool

    Remove the instance at key from service. On success bumps the revision and emits a Delete; an unknown service or key is a no-op returning false.

    PersistentRegistry::events_since

    fn PersistentRegistry::events_since(self : PersistentRegistry, revision : Int64) -> Array[RegistryEvent]

    Every event with a revision greater than revision (← etcd's watch start_revision): the catch-up a client replays to reach the current state before switching to live watch callbacks.

    PersistentRegistry::keyed_entries

    fn PersistentRegistry::keyed_entries(self : PersistentRegistry) -> Array[(String, Endpoint, Int64)]

    Every registered instance as (instance-key, endpoint, mod-revision), across all services — the flat keyspace a file- or etcd-backed reader diffs one load against the next to compute the Put/Delete events a change produced.

    PersistentRegistry::new

    A fresh, empty persisted registry at revision 0.

    PersistentRegistry::register

    fn PersistentRegistry::register(self : PersistentRegistry, service : String, endpoint : Endpoint) -> String

    Register endpoint under service, mint a fresh instance key, bump the revision, and emit a Put. Returns the instance key (<service>/<n>).

    PersistentRegistry::resolve

    fn PersistentRegistry::resolve(self : PersistentRegistry, service : String) -> Array[Endpoint]

    The endpoints registered for service, in registration order.

    PersistentRegistry::resolver

    fn PersistentRegistry::resolver(self : PersistentRegistry) -> ((String) -> Array[Endpoint])

    This registry as a Resolve interface value.

    PersistentRegistry::restore

    fn PersistentRegistry::restore(src : String) -> PersistentRegistry raise ConfigError

    Rebuild a registry from a snapshot document, preserving instance keys, their endpoints and mod-revisions, the id counter, and the store revision — so a reloaded registry mints the next key exactly where the persisted one left off and a watcher's events_since(old_revision) still lines up.

    PersistentRegistry::revision

    fn PersistentRegistry::revision(self : PersistentRegistry) -> Int64

    The store revision, bumped on each register/deregister.

    PersistentRegistry::services

    fn PersistentRegistry::services(self : PersistentRegistry) -> Array[String]

    Every service name with at least one live instance.

    PersistentRegistry::snapshot

    fn PersistentRegistry::snapshot(self : PersistentRegistry) -> String

    Serialize the whole keyspace as an etcd v3 RangeResponse-shaped JSON document: a header carrying the store revision and the id counter, and one key/value entry per instance carrying its endpoint and mod-revision. This is the exact payload a file- or etcd-backed deployment persists; restore rebuilds an identical registry from it, revisions intact.

    PersistentRegistry::watch

    fn PersistentRegistry::watch(self : PersistentRegistry, on_event : (RegistryEvent) -> Unit) -> Unit

    Register a live watcher fired on every subsequent mutation, in revision order (← etcd's Watch with no start revision). To also see changes already applied, replay events_since first.

    PrivateKeyConf

    pub(all) struct PrivateKeyConf {
    fingerprint : String
    key_file : String
    } derive(Eq,
    Debug
    )

    One signing key the request-signature check would verify against (← rest.PrivateKeyConf).

    Promise

    pub struct Promise {
    b : Breaker
    }

    The receipt for an admitted call (← go-zero's breaker.Promise). Exactly one of accept / reject settles it; until then the window holds no record of how the call went.

    Promise::accept

    fn Promise::accept(self : Promise) -> Unit

    Settle the call as a success.

    Promise::reject

    fn Promise::reject(self : Promise) -> Unit

    Settle the call as a failure.

    RedisClient

    pub struct RedisClient {
    conn : &RedisConn
    }

    A redis client over a RedisConn, exposing the typed commands the discovery driver uses. Every command maps a redis error reply to RedisError.

    RedisClient::command

    async fn RedisClient::command(self : RedisClient, args : Array[Bytes]) -> RespValue raise RedisError

    Send args as a command and return the reply, turning a connection failure or a redis -ERR reply into RedisError.

    RedisClient::del

    async fn RedisClient::del(self : RedisClient, keys : Array[Bytes]) -> Int64 raise RedisError

    DEL key...: delete the given keys, returning how many existed (deregistration).

    RedisClient::eval

    async fn RedisClient::eval(self : RedisClient, source : String, keys : Array[Bytes], args : Array[Bytes]) -> RespValue raise RedisError

    EVAL source numkeys key... arg...: run a Lua script server-side, where the whole script travels with the call.

    RedisClient::evalsha

    async fn RedisClient::evalsha(self : RedisClient, sha : String, keys : Array[Bytes], args : Array[Bytes]) -> RespValue raise RedisError

    EVALSHA sha numkeys key... arg...: run a script the server already holds. A server that has forgotten it answers NOSCRIPT, which surfaces as a RedisError.

    RedisClient::expire

    async fn RedisClient::expire(self : RedisClient, key : Bytes, ttl_secs : Int) -> Bool raise RedisError

    EXPIRE key ttl: refresh a key's expiry (the discovery keep-alive). true if the key existed and its TTL was set.

    RedisClient::get

    async fn RedisClient::get(self : RedisClient, key : Bytes) -> Bytes? raise RedisError

    GET key: the value at key, or None if the key is absent or expired.

    RedisClient::new

    fn RedisClient::new(conn : &RedisConn) -> RedisClient

    A client over conn.

    RedisClient::ping

    async fn RedisClient::ping(self : RedisClient) -> String raise RedisError

    PING: the server's PONG liveness reply.

    RedisClient::scan_match

    async fn RedisClient::scan_match(self : RedisClient, pattern : Bytes, count : Int) -> Array[Bytes] raise RedisError

    SCAN-iterate every key matching pattern (a glob like prefix/*), following the cursor to completion so the whole keyspace is covered without ever blocking the server on a KEYS scan. count is the per-step hint passed to redis.

    RedisClient::script_load

    async fn RedisClient::script_load(self : RedisClient, source : String) -> String raise RedisError

    SCRIPT LOAD source: put source in the server's script cache and return the SHA1 it answers to from then on.

    RedisClient::set_ex

    async fn RedisClient::set_ex(self : RedisClient, key : Bytes, value : Bytes, ttl_secs : Int) -> Unit raise RedisError

    SET key value EX ttl: store value at key with a ttl-second expiry, the lease a registered instance is held alive by.

    RedisDiscovery

    pub struct RedisDiscovery {
    client : RedisClient
    prefix : String
    seen : Map[String, Array[Endpoint]]
    }

    A redis-backed service registry / resolver. Instances of one service live under <prefix><service>/, one key per instance keyed by its dial address, so a SCAN of that prefix returns them all.

    RedisDiscovery::deregister

    async fn RedisDiscovery::deregister(self : RedisDiscovery, service : String) -> Unit

    Deregister every instance of service (delete the whole service prefix).

    RedisDiscovery::deregister_instance

    async fn RedisDiscovery::deregister_instance(self : RedisDiscovery, service : String, endpoint : Endpoint) -> Unit

    Deregister one instance of service by deleting its key.

    RedisDiscovery::keepalive

    async fn RedisDiscovery::keepalive(self : RedisDiscovery, service : String, endpoint : Endpoint, ttl? : Int) -> Bool

    Refresh an instance's lease, extending its key's expiry by ttl seconds. false if the key had already lapsed (the instance must re-register).

    RedisDiscovery::last

    fn RedisDiscovery::last(self : RedisDiscovery, service : String) -> Array[Endpoint]

    The endpoints the last resolve of service found, without going to redis.

    RedisDiscovery::new

    fn RedisDiscovery::new(client : RedisClient, prefix? : String) -> RedisDiscovery

    A redis discovery over client; keys live under prefix (default "moonzero/").

    RedisDiscovery::register

    async fn RedisDiscovery::register(self : RedisDiscovery, service : String, endpoint : Endpoint, ttl? : Int) -> String

    Register endpoint for service with a ttl-second lease and return its instance key. Renew it with keepalive before the TTL lapses to stay registered; let it lapse and redis drops the key, deregistering the instance automatically.

    RedisDiscovery::resolve

    async fn RedisDiscovery::resolve(self : RedisDiscovery, service : String) -> Array[Endpoint]

    Resolve service to its live endpoints: SCAN the service prefix and read each instance's value as its host:port endpoint. Keys that lapse mid-scan and malformed values are skipped. The answer is also kept as the service's last known set, which is what resolver hands a balancer.

    RedisDiscovery::resolver

    fn RedisDiscovery::resolver(self : RedisDiscovery) -> ((String) -> Array[Endpoint])

    This redis discovery as a Resolve interface value, so the balancer and the load-balanced channel run against redis unchanged. Resolve is synchronous and a SCAN over a socket is not, so the closure reads the set the last resolve of that service found — the same arrangement discov's file registry uses, where the async reload and the synchronous resolve are separate steps. A service not resolved yet balances over nothing.

    RedisPeriodLimit

    pub struct RedisPeriodLimit {
    client : RedisClient
    script : RedisScript
    period_secs : Int
    quota : Int
    prefix : String
    }

    A fixed-window rate limiter whose window lives in redis (← go-zero's limit.PeriodLimit). One INCRBY-ed counter per key, given a period-second expiry the first time it appears, so every replica pointed at the same redis and key draws on one quota instead of each getting its own.

    RedisPeriodLimit::new

    fn RedisPeriodLimit::new(client : RedisClient, period_secs~ : Int, quota~ : Int, prefix? : String) -> RedisPeriodLimit

    A limiter admitting quota requests per period_secs seconds per key, counting in client's redis under prefix-prefixed keys.

    RedisPeriodLimit::take

    async fn RedisPeriodLimit::take(self : RedisPeriodLimit, key : String) -> PeriodResult raise RedisError

    Account for one request under key and report its outcome, in the same three states the local limiter reports. A code the script cannot have returned, or a reply that is not an integer at all, raises — go-zero answers Unknown with ErrUnknownCode there, and a caller has to decide what an undecided limiter means.

    RedisScript

    pub struct RedisScript {
    source : String
    sha : String?
    }

    A Lua script bound to its SHA1 (← go-redis's Script, the shape go-zero's ScriptRun drives). The first run SCRIPT LOADs the source and keeps the digest, every later run is an EVALSHA that ships a 40-byte hash instead of the whole program, and a server that has forgotten it — a restart, a SCRIPT FLUSH — answers NOSCRIPT, which loads it again and retries once.

    RedisScript::new

    fn RedisScript::new(source : String) -> RedisScript

    A script over source, not yet loaded anywhere.

    RedisScript::run

    async fn RedisScript::run(self : RedisScript, client : RedisClient, keys : Array[Bytes], args : Array[Bytes]) -> RespValue raise RedisError

    Run the script on client over keys and args, loading it first if its digest is not known yet or the server has dropped it.

    RedisScript::source

    fn RedisScript::source(self : RedisScript) -> String

    The Lua this script runs.

    RedisTokenLimit

    pub struct RedisTokenLimit {
    client : RedisClient
    script : RedisScript
    rate : Int
    burst : Int
    token_key : Bytes
    ts_key : Bytes
    rescue : TokenBucket
    }

    A token-bucket rate limiter whose bucket lives in redis (← go-zero's limit.TokenLimiter). The token count and the second it was last refilled sit under {key}.tokens and {key}.ts, so every replica pointed at the same redis and key spends from one bucket. When redis cannot be reached the limiter keeps limiting from rescue, its process-local TokenBucket, the way go-zero drops to its in-process rescueLimiter rather than letting an outage open the gate.

    RedisTokenLimit::allow

    async fn RedisTokenLimit::allow(self : RedisTokenLimit, now_ms : Int64) -> Bool

    Try to admit one request at now_ms.

    RedisTokenLimit::allow_n

    async fn RedisTokenLimit::allow_n(self : RedisTokenLimit, n : Int, now_ms : Int64) -> Bool

    Try to admit a request costing n tokens at now_ms. The script is handed whole seconds because go-zero hands it now.Unix(): the shared bucket refills at one-second granularity however finely the clock is read. Lua's false arrives as a null reply and its true as 1; anything else, and any redis failure, is served by the local bucket instead.

    RedisTokenLimit::new

    fn RedisTokenLimit::new(client : RedisClient, rate~ : Int, burst~ : Int, key~ : String) -> RedisTokenLimit

    A limiter admitting rate requests per second with room for a burst of that many back-to-back, over the bucket key names in client's redis.

    RedisTokenLimit::rescue

    The process-local bucket this limiter falls back to, so a caller can inspect it.

    RegistryEvent

    pub(all) enum RegistryEvent {
    Put(key~ : String, endpoint~ : Endpoint, revision~ : Int64)
    Delete(key~ : String, revision~ : Int64)
    } derive(Eq,
    Debug
    )

    A change to the registry keyspace, in etcd v3's watch shape: a Put carries the instance key and its endpoint, a Delete carries the key that went away, and both carry the store revision the change produced. A watcher receives these in revision order, so a client can rebuild the live set incrementally instead of re-resolving the whole service.

    RegistryEvent::revision

    fn RegistryEvent::revision(self : RegistryEvent) -> Int64

    The store revision a registry event was produced at.

    RequestLog

    pub(all) struct RequestLog {
    http_method : String
    path : String
    status : Int
    duration_ms : Int64
    request_id : String
    client_ip : String
    user_agent : String
    }

    A structured access-log record (← go-zero's logx HTTP access fields). Rather than a free-form line, each request is captured as typed fields and rendered as one JSON object per line — the format go-zero emits under logx and the shape log collectors (ELK, Loki) expect.

    RequestLog::fields

    fn RequestLog::fields(self : RequestLog) -> Array[LogField]

    The record as logx fields, for logging it through a Logger — the method and path make the entry's message, so they are not repeated here.

    RequestLog::render

    fn RequestLog::render(self : RequestLog) -> String

    The record rendered as a single-line JSON string, ready to write to a log sink.

    RequestLog::to_json

    fn RequestLog::to_json(self : RequestLog) -> Json

    The record as a JSON value with a stable field order, duration_ms rendered as a number of milliseconds.

    RespReader

    pub struct RespReader {
    data : Bytes
    pos : Int
    }

    A cursor over a RESP byte stream, decoding one value at a time. A socket client feeds it a buffered reply; read advances past exactly one value, so several pipelined replies decode in sequence.

    RespReader::at_end

    fn RespReader::at_end(self : RespReader) -> Bool

    Whether every byte has been consumed.

    RespReader::new

    fn RespReader::new(data : Bytes) -> RespReader

    A reader positioned at the start of data.

    RespReader::read

    fn RespReader::read(self : RespReader) -> RespValue raise RespError

    Decode the next RESP value, advancing the cursor past it.

    RespValue

    pub(all) enum RespValue {
    SimpleString(String)
    Error(String)
    Integer(Int64)
    BulkString(Bytes)
    Null
    Array(Array[RespValue])
    Boolean(Bool)
    Double(Double)
    BigNumber(String)
    BulkError(String)
    VerbatimString(String, Bytes)
    RespMap(Array[(RespValue, RespValue)])
    RespSet(Array[RespValue])
    Push(Array[RespValue])
    } derive(Eq,
    Debug
    )

    A decoded RESP value. RESP2's null bulk string ($-1) and null array (*-1) both decode to Null, unifying with RESP3's explicit null (_). BulkString carries raw bytes (redis values are binary-safe); the text-line types carry the decoded string.

    RespValue::decode

    fn RespValue::decode(data : Bytes) -> RespValue raise RespError

    Decode exactly one RESP value from data, requiring it to consume the whole input. Trailing bytes after a complete value are a framing error.

    RestConf

    pub(all) struct RestConf {
    service : ServiceConf
    cert_file : String
    key_file : String
    verbose : Bool
    max_conns : Int
    max_bytes : Int
    cpu_threshold : Int64
    signature : SignatureConf
    middlewares : MiddlewaresConf
    trace_ignore_paths : Array[String]
    }

    A REST service's configuration (← go-zero's rest.RestConf), embedding ServiceConf the way go-zero's does. The name, bind address and request timeout live on that embedded config — host(), port() and timeout_ms() read them — and everything else here is RestConf's own.

    max_bytes is an Int where go-zero uses int64: it is compared against a request's Content-Length, and its own range= tag caps it at 32 MiB.

    RestConf::from_json

    fn RestConf::from_json(src : String) -> RestConf raise ConfigError

    Load a RestConf from a JSON config string, with the same semantics as from_yaml.

    RestConf::from_yaml

    fn RestConf::from_yaml(src : String) -> RestConf raise ConfigError

    Load a RestConf from the YAML go-zero ships as etc/*.yaml. Keys are matched canonically, MOONZERO_* env variables override the file, and a value outside its options=/range= constraint is an error.

    Name and Port carry no default, exactly as in go-zero: a rest service that does not say who it is or where to listen fails to load.

    RestConf::host

    fn RestConf::host(self : RestConf) -> String

    The address the service binds.

    RestConf::new

    fn RestConf::new(service? : ServiceConf, cert_file? : String, key_file? : String, verbose? : Bool, max_conns? : Int, max_bytes? : Int, cpu_threshold? : Int64, signature? : SignatureConf, middlewares? : MiddlewaresConf, trace_ignore_paths? : Array[String]) -> RestConf

    A REST config with go-zero's defaults: no TLS, not verbose, 10000 connections, a 1 MiB body cap, a 90% CPU shed threshold, and the full middleware chain.

    RestConf::port

    fn RestConf::port(self : RestConf) -> Int

    The port the service binds.

    RestConf::timeout_ms

    fn RestConf::timeout_ms(self : RestConf) -> Int

    The per-request timeout budget in milliseconds; 0 disables it.

    RestConf::tls

    fn RestConf::tls(self : RestConf) -> Bool

    Whether TLS is configured — go-zero serves HTTPS once both files are named.

    RestEngine

    pub struct RestEngine {
    conf : RestConf
    clock : Clock
    logger : Logger
    metrics : ServerMetrics
    conns : MaxConns
    circuit : Breaker
    shedder : Shedder
    }

    The engine that turns a RestConf into a runnable service (← go-zero's rest.engine). It owns the state the built-in layers need — the connection permits, the breaker window, the metric set, the shedder — so every request shares one of each, and it installs exactly the layers Middlewares asks for.

    RestEngine::build

    Assemble app under the configured chain. go-zero's chain.New names the outermost handler first while Server::use_ makes the most recent layer outermost, so the list goes on back to front.

    RestEngine::conf

    fn RestEngine::conf(self : RestEngine) -> RestConf

    The config the engine was built from.

    RestEngine::layers

    fn RestEngine::layers(self : RestEngine) -> Array[Layer]

    The chain the flags ask for, outermost first, in go-zero's buildChainWithNativeMiddlewares order.

    A layer whose configured value disables it is left out even when its flag is on, as in go-zero: no shedder without a CpuThreshold, no timeout without a budget, no body cap without a MaxBytes.

    Two of go-zero's eleven flags install nothing here. Metrics is go-zero's internal stat.Metrics sink, which moonzero has no counterpart for — its one metric set is the Prometheus one the Prometheus flag installs. Gunzip needs a DEFLATE decoder, which neither moonzero nor any dependency carries. Both flags still load, so a go-zero config round-trips through RestConf.

    RestEngine::metrics

    fn RestEngine::metrics(self : RestEngine) -> ServerMetrics

    The metric set the prometheus layer records into — the same one mount_metrics publishes.

    RestEngine::names

    fn RestEngine::names(self : RestEngine) -> Array[String]

    The names of the layers layers would install, outermost first.

    RestEngine::new

    fn RestEngine::new(conf : RestConf, clock? : Clock, logger? : Logger, metrics? : ServerMetrics, usage? : () -> Int64) -> RestEngine raise ConfigError

    Build the engine for conf, pointing the logger at the configured level (← ServiceConf.SetUp's logx.SetUp).

    usage is the CPU meter the shedder reads, per mille. Raises ConfigError for a strict signature config with no keys, which is go-zero's ErrSignatureConfig.

    RoundRobin

    pub struct RoundRobin {
    cursor : Int
    }

    A round-robin balancer (← go-zero's roundRobinBalancer) over a resolved endpoint set: successive picks cycle through the instances, spreading load evenly. Holds only a cursor, so it is cheap to keep per client.

    RoundRobin::new

    fn RoundRobin::new() -> RoundRobin

    A round-robin balancer starting at the first instance.

    RoundRobin::pick

    fn RoundRobin::pick(self : RoundRobin, endpoints : Array[Endpoint]) -> Endpoint?

    Pick the next endpoint in rotation, or None if the set is empty. The cursor advances modulo the set size, so it stays valid as instances come and go.

    RpcChannel

    pub struct RpcChannel {
    engine :
    H2Server

    encoder :
    HpackEncoder

    decoder :
    HpackDecoder

    authority : String
    next_stream_id : Int
    }

    An in-process gRPC channel bound to a server engine — the client half of the h2c transport. A call is carried as the real HTTP/2 frames a socket-backed client would send: an HPACK-coded HEADERS block with the gRPC pseudo-headers, a length-prefixed DATA frame closing the stream, and the grpc-status trailer read back off the engine's reply. The channel's HPACK encoder pairs with the engine's decoder and vice versa, so the dynamic-table state stays in lockstep across every call on the channel.

    RpcChannel::call

    fn RpcChannel::call(self : RpcChannel, path : String, request : Bytes) -> Result[Bytes,
    Status
    ] raise

    Invoke a unary method at path with request as its message payload, driving the call through the h2c engine and returning the reply payload on grpc-status: 0, or the mapped @moonrpc.Status otherwise. request and the returned reply are bare message bytes; the length-prefix framing is applied and stripped by the transport.

    RpcChannel::call_bidi_streaming

    fn RpcChannel::call_bidi_streaming(self : RpcChannel, path : String, requests : Array[Bytes]) -> Result[Array[Bytes],
    Status
    ] raise

    Drive a whole bidirectional call at path in one shot: send every message in requests (collecting the interleaved replies in order), then half-close and append the on_end replies. The result is every reply message the server produced, in emission order, or the non-zero grpc-status the stream closed with.

    RpcChannel::call_client_streaming

    fn RpcChannel::call_client_streaming(self : RpcChannel, path : String, requests : Array[Bytes]) -> Result[Bytes,
    Status
    ] raise

    Invoke a client-streaming method at path: send every message in requests as its own DATA frame, half-close the stream, and read back the single reply. An empty requests still opens and half-closes the stream, so the handler runs with no messages.

    RpcChannel::call_server_streaming

    fn RpcChannel::call_server_streaming(self : RpcChannel, path : String, request : Bytes) -> Result[Array[Bytes],
    Status
    ] raise

    Invoke a server-streaming method at path: send the single request message and read back the ordered sequence of reply messages the server produced, or the mapped error @moonrpc.Status if the stream closed with a non-zero grpc-status. On Ok the array holds every message in emission order (possibly empty).

    RpcChannel::connect

    fn RpcChannel::connect(server : RpcServer, authority? : String) -> RpcChannel

    Open a channel to server over an in-process h2c transport, exchanging the opening SETTINGS the way a real connection does. Client-initiated streams use odd identifiers (RFC 7540 §5.1.1), starting at 1.

    RpcChannel::open_bidi

    fn RpcChannel::open_bidi(self : RpcChannel, path : String) -> BidiCall raise

    Open a bidirectional stream to path, sending the request HEADERS without half-closing so the stream stays open for interleaved sends. An unregistered path answers trailers-only UNIMPLEMENTED during this HEADERS feed, which the returned call captures as its status.

    RpcCluster

    pub struct RpcCluster {
    servers : Map[String, RpcServer]
    }

    The in-process dial table: an endpoint address maps to the RpcServer listening there. It stands in for DNS resolution plus a socket dial in the in-process h2c transport — a real deployment opens a connection to the address instead of looking the server up here, but the resolve→balance→call path above it is the same.

    RpcCluster::add

    fn RpcCluster::add(self : RpcCluster, endpoint : Endpoint, server : RpcServer) -> Unit

    Bind the server reachable at endpoint's address.

    RpcCluster::dial

    fn RpcCluster::dial(self : RpcCluster, endpoint : Endpoint) -> RpcChannel?

    Open a channel to the server bound at endpoint, or None if nothing is reachable there (a stale registry entry pointing at a gone instance).

    RpcCluster::new

    fn RpcCluster::new() -> RpcCluster

    An empty cluster.

    RpcGroup

    pub struct RpcGroup {
    server : RpcServer
    service : String
    }

    A per-service registration handle (← go-zero's service registrar closure): binds a set of methods to one package.Service on a shared RpcServer.

    RpcGroup::register

    fn RpcGroup::register(self : RpcGroup, name : String, handler : (Bytes) -> Bytes) -> Unit

    Register a method name on this group's service, building the @moonrpc.Method descriptor and installing handler under its gRPC path. (register, not method — the latter is a reserved word.)

    RpcGroup::register_bidi_streaming

    fn RpcGroup::register_bidi_streaming(self : RpcGroup, name : String, factory : () -> BidiStreamHandler) -> Unit

    Register a bidirectional-streaming method name on this group's service.

    RpcGroup::register_client_streaming

    fn RpcGroup::register_client_streaming(self : RpcGroup, name : String, handler : (Array[Bytes]) -> Bytes) -> Unit

    Register a client-streaming method name on this group's service.

    RpcGroup::register_server_streaming

    fn RpcGroup::register_server_streaming(self : RpcGroup, name : String, handler : (Bytes) -> Array[Bytes]) -> Unit

    Register a server-streaming method name on this group's service.

    RpcGroup::service

    fn RpcGroup::service(self : RpcGroup) -> String

    The fully-qualified package.Service this group registers under.

    RpcServer

    pub struct RpcServer {
    conf : RpcServerConf
    handlers : Map[String, (Bytes) -> Bytes]
    server_streaming : Map[String, (Bytes) -> Array[Bytes]]
    client_streaming : Map[String, (Array[Bytes]) -> Bytes]
    bidi_streaming : Map[String, () -> BidiStreamHandler]
    }

    A zRPC server (← go-zero's zrpc.Server): config plus a registry mapping each method's gRPC :path (/package.Service/Method) to its handler. Handlers are registered via @moonrpc.Method descriptors — directly or through a RpcGroup — and dispatched by path, mirroring how go-zero registers service implementations on the underlying gRPC server. Unary, server-streaming, and client-streaming methods live in separate registries so one path resolves to exactly one cardinality.

    RpcServer::conf

    fn RpcServer::conf(self : RpcServer) -> RpcServerConf

    The server's configuration.

    RpcServer::dispatch

    fn RpcServer::dispatch(self : RpcServer, path : String, request : Bytes) -> Result[Bytes,
    Status
    ]

    Dispatch a unary call to the handler registered for path, returning the response bytes. An unregistered path yields Err(Unimplemented) — exactly the grpc-status a real gRPC server returns for an unknown method — so a transport can translate the result straight onto the wire.

    RpcServer::dispatch_graceful

    fn RpcServer::dispatch_graceful(self : RpcServer, coord : ShutdownCoordinator, path : String, request : Bytes) -> Result[Bytes,
    Status
    ]

    Dispatch a unary call through the shutdown gate: refuse with Unavailable when the server is shutting down, otherwise run the handler and count it as in-flight for the duration so a concurrent shutdown drains behind it. The gate wraps RpcServer::dispatch, so an unregistered path still yields Unimplemented.

    RpcServer::group

    fn RpcServer::group(self : RpcServer, service : String) -> RpcGroup

    Open a RpcGroup that registers methods under the fully-qualified package.Service name — go-zero's per-service registration, without repeating the service name on each method.

    RpcServer::has_method

    fn RpcServer::has_method(self : RpcServer, path : String) -> Bool

    Whether a handler is registered for path.

    RpcServer::lookup

    fn RpcServer::lookup(self : RpcServer, path : String) -> (Bytes) -> Bytes?

    Look up the handler registered for a gRPC :path, or None if unregistered.

    RpcServer::methods

    fn RpcServer::methods(self : RpcServer) -> Array[String]

    The gRPC paths of every registered method.

    RpcServer::new

    fn RpcServer::new(conf : RpcServerConf) -> RpcServer

    Build an empty RPC server from its config.

    RpcServer::register

    fn RpcServer::register(self : RpcServer, desc :
    Method
    , handler : (Bytes) -> Bytes) -> Unit

    Register handler for method, keyed by its gRPC path. A later registration for the same path replaces the earlier one.

    RpcServer::register_bidi_streaming

    fn RpcServer::register_bidi_streaming(self : RpcServer, desc :
    Method
    , factory : () -> BidiStreamHandler) -> Unit

    Register a bidirectional-streaming handler for method, keyed by its gRPC path. factory runs once per call so each stream gets fresh handler state.

    RpcServer::register_client_streaming

    fn RpcServer::register_client_streaming(self : RpcServer, desc :
    Method
    , handler : (Array[Bytes]) -> Bytes) -> Unit

    Register a client-streaming handler for method, keyed by its gRPC path.

    RpcServer::register_server_streaming

    fn RpcServer::register_server_streaming(self : RpcServer, desc :
    Method
    , handler : (Bytes) -> Array[Bytes]) -> Unit

    Register a server-streaming handler for method, keyed by its gRPC path.

    RpcServer::to_h2

    Build a @moonrpc.H2Server protocol engine from this zRPC server's registered handlers — the transport-facing view of the same registry dispatch reads. Each handler is bound to its gRPC path, so a request arriving over the h2c transport is dispatched to exactly the handler the group registered.

    RpcServerConf

    pub(all) struct RpcServerConf {
    name : String
    host : String
    port : Int
    timeout_ms : Int
    } derive(Eq,
    Debug
    ,
    FromJson
    )

    zRPC server configuration (← go-zero's zrpc.RpcServerConf): the service name, the address it listens on, and a per-call timeout in milliseconds (0 disables it). The registry/etcd fields of go-zero's conf are modelled by the separate discovery layer; this is the transport-facing core.

    RpcServerConf::from_json

    fn RpcServerConf::from_json(src : String) -> RpcServerConf raise ConfigError

    Load an RpcServerConf from a JSON config string, filling omitted fields from the new() defaults — the lenient loader matching go-zero's ,optional/,default= config tags. Keys are matched canonically, so go-zero's Name/Host/Port/Timeout load as readily as moonzero's own timeout_ms.

    RpcServerConf::from_yaml

    fn RpcServerConf::from_yaml(src : String) -> RpcServerConf raise ConfigError

    Load an RpcServerConf from a YAML config string (self-built yaml_parse), with the same default-filling semantics as from_json.

    RpcServerConf::new

    fn RpcServerConf::new(name? : String, host? : String, port? : Int, timeout_ms? : Int) -> RpcServerConf

    Build an RPC server config with go-zero-style defaults (0.0.0.0:8080, 2s timeout).

    Server

    pub struct Server {
    conf : ServiceConf
    handler : async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit
    }

    A moonzero service: its config plus the assembled application (a moonapi App with any middleware already wrapped around it).

    Server::describe

    fn Server::describe(self : Server) -> String

    A human-readable description of what this service binds to.

    Server::new

    Assemble a service from config and a moonapi application.

    Server::to_asgi

    fn Server::to_asgi(self : Server) -> (async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit)

    The assembled AsgiApp, ready for a server (mooncat) to run.

    Server::use_

    Wrap the current application in another middleware layer (outermost last).

    ServerMetrics

    pub struct ServerMetrics {
    requests : CounterVec
    latency : Histogram
    }

    The request metrics an HTTP service exposes (← go-zero's server metrics): a request counter partitioned by method/route/status and a latency histogram. Held by the caller so it can be read out for a /metrics scrape after serving.

    ServerMetrics::latency

    fn ServerMetrics::latency(self : ServerMetrics) -> Histogram

    The request-latency histogram, in milliseconds.

    ServerMetrics::new

    Fresh server metrics: an empty counter and a default-bucket latency histogram.

    ServerMetrics::requests

    fn ServerMetrics::requests(self : ServerMetrics) -> CounterVec

    The request counter, labelled "<METHOD> <path> <status>".

    ServerMetrics::to_exposition

    fn ServerMetrics::to_exposition(self : ServerMetrics) -> String

    Render the whole server metric set as one exposition document a Prometheus server can scrape: the latency histogram followed by the request counter, under go-zero's canonical metric names. go-zero splits the counter into path/method/code labels; moonzero keeps the request signature as one request label.

    ServiceConf

    pub(all) struct ServiceConf {
    name : String
    host : String
    port : Int
    timeout_ms : Int
    log_level : LogLevel
    } derive(Eq,
    FromJson
    )

    Service configuration (← go-zero's ServiceConf): the service name, its bind address, a request timeout, and the log level. timeout_ms is the per-request budget in milliseconds; 0 disables the deadline.

    ServiceConf::from_json

    fn ServiceConf::from_json(src : String) -> ServiceConf raise ConfigError

    Load a ServiceConf from a JSON config string, applying go-zero-style defaults for every omitted field (an empty {} yields exactly ServiceConf::new()). This is the lenient loader mirroring go-zero's conf.Load with ,optional/,default= struct tags: unlike the strict derived FromJson — reachable via @json.from_json and requiring every field present — a partial config is filled from the same defaults new() uses.

    Keys are matched canonically, so go-zero's Name/Host/Port/Timeout and moonzero's own timeout_ms/log_level spellings all load.

    Raises ConfigError on malformed JSON, a non-object root, a field of the wrong type, or a log level outside debug|info|error|severe.

    ServiceConf::from_yaml

    fn ServiceConf::from_yaml(src : String) -> ServiceConf raise ConfigError

    Load a ServiceConf from a YAML config string — the format go-zero actually ships (etc/*.yaml) — with the same lenient, default-filling semantics as from_json: an empty document yields exactly ServiceConf::new(), and each omitted field falls back to its new() default. The YAML is parsed by the self-built yaml_parse (block mappings, nesting, sequences, scalars, comments) into a Json object, then decoded by the shared field reader — so JSON and YAML configs agree field-for-field.

    Raises ConfigError on malformed YAML, a non-mapping root, a field of the wrong type, or a log level outside debug|info|error|severe.

    ServiceConf::new

    fn ServiceConf::new(name? : String, host? : String, port? : Int, timeout_ms? : Int, log_level? : LogLevel) -> ServiceConf

    Build a config with sensible defaults (0.0.0.0:8888, 3s timeout, info).

    Shedder

    pub struct Shedder {
    threshold : Int64
    usage : () -> Int64
    }

    Load shedding under CPU pressure (← go-zero's load.AdaptiveShedder, wired from RestConf.CpuThreshold): admit while measured CPU sits at or below the threshold, shed above it. Both numbers are per-mille, so go-zero's default 900 means 90%.

    go-zero reads stat.CpuUsage() and additionally sheds on in-flight count against a moving pass/latency estimate. No MoonBit backend can read a CPU meter, so the usage source is injected the way Clock is, and a shedder built without one reports 0 and never sheds — the in-flight half is not modelled.

    Shedder::allow

    fn Shedder::allow(self : Shedder) -> Bool

    Whether a request is admitted at the current usage.

    Shedder::new

    fn Shedder::new(threshold : Int64, usage? : () -> Int64) -> Shedder

    A shedder that sheds once usage exceeds threshold per-mille.

    Shedder::usage

    fn Shedder::usage(self : Shedder) -> Int64

    The CPU usage the shedder is reading, per mille.

    ShutdownCoordinator

    pub struct ShutdownCoordinator {
    shutting_down : Bool
    in_flight : Int
    }

    A graceful-shutdown coordinator for a zRPC server (← go-zero's proc.AddShutdownListener + gRPC's GracefulStop): once shutdown is initiated the server stops admitting new calls, but calls already in flight are allowed to run to completion. A call brackets its work between begin_call and end_call; begin_call returns false when the server is shutting down, which the transport surfaces as Unavailable — exactly the status a client sees once a server has stopped listening. The server is fully drained once shutdown has been initiated and no calls remain in flight.

    The counter is plain mutable state, which is safe under moonbitlang/async's cooperative single-threaded scheduling: begin_call/end_call never yield, so the count is only observed at await points between them.

    ShutdownCoordinator::begin_call

    fn ShutdownCoordinator::begin_call(self : ShutdownCoordinator) -> Bool

    Admit a new call: register it as in-flight and return true, unless shutdown has been initiated, in which case the call is refused (false) and the count is left untouched.

    ShutdownCoordinator::end_call

    fn ShutdownCoordinator::end_call(self : ShutdownCoordinator) -> Unit

    Mark an admitted call finished, dropping it from the in-flight count. Only call it for a call that begin_call admitted; the count never goes below zero.

    ShutdownCoordinator::in_flight

    fn ShutdownCoordinator::in_flight(self : ShutdownCoordinator) -> Int

    The number of calls currently in flight.

    ShutdownCoordinator::initiate_shutdown

    fn ShutdownCoordinator::initiate_shutdown(self : ShutdownCoordinator) -> Unit

    Begin the graceful shutdown: from now on begin_call refuses new calls while in-flight ones keep running. Idempotent.

    ShutdownCoordinator::is_drained

    fn ShutdownCoordinator::is_drained(self : ShutdownCoordinator) -> Bool

    Whether the server is fully drained: shutdown initiated and no call in flight. A supervisor loops on this (yielding between checks) to know the last in-flight RPC has finished and the process may exit.

    ShutdownCoordinator::is_shutting_down

    fn ShutdownCoordinator::is_shutting_down(self : ShutdownCoordinator) -> Bool

    Whether shutdown has been initiated.

    ShutdownCoordinator::new

    A coordinator that is serving normally with no calls in flight.

    SignatureConf

    pub(all) struct SignatureConf {
    strict : Bool
    expiry_ms : Int64
    private_keys : Array[PrivateKeyConf]
    } derive(Eq,
    Debug
    )

    Request-signature settings (← rest.SignatureConf): whether an unsigned or badly-signed request is refused outright, how long a signature stays valid, and the keys it is checked against.

    moonzero loads and validates this config — a strict service with no keys is refused at assembly, as go-zero's ErrSignatureConfig does — but ships no content-signature layer to consume it.

    SignatureConf::new

    fn SignatureConf::new(strict? : Bool, expiry_ms? : Int64, private_keys? : Array[PrivateKeyConf]) -> SignatureConf

    Signature settings that verify nothing: not strict, go-zero's one-hour expiry, no keys.

    TokenBucket

    pub struct TokenBucket {
    capacity : Double
    refill_per_ms : Double
    tokens : Double
    last_ms : Int64
    }

    A token-bucket rate limiter kept in this process: the bucket holds up to capacity tokens and refills continuously at refill_per_ms tokens per millisecond, and each admitted request spends one. Because every decision is a function of (state, now), the limiter is exactly testable without a real clock.

    The bucket is local, so N replicas admit N times the rate. RedisTokenLimit is the same limiter with its bucket in redis and is what a fleet should run; this one serves a single process, and is what RedisTokenLimit itself falls back to when redis is unreachable (← go-zero's rescueLimiter).

    TokenBucket::allow

    fn TokenBucket::allow(self : TokenBucket, now : Int64) -> Bool

    Try to admit one request at time now: refill, then spend a token if one is available. Returns true when admitted, false when the bucket is empty.

    TokenBucket::allow_n

    fn TokenBucket::allow_n(self : TokenBucket, n : Double, now : Int64) -> Bool

    Try to admit a request costing n tokens at time now. Returns false (spending nothing) when fewer than n tokens are available.

    TokenBucket::available

    fn TokenBucket::available(self : TokenBucket, now : Int64) -> Double

    The (fractional) number of tokens currently available, after refilling to now. Useful for metrics and tests.

    TokenBucket::new

    fn TokenBucket::new(rate : Double, burst? : Double, now? : Int64) -> TokenBucket

    Build a bucket admitting rate requests per second on average with room for a burst of that many back-to-back (default burst = rate). It starts full at time now. A non-positive rate/burst is clamped to a minimum so the bucket always has a defined capacity.

    TraceContext

    pub(all) struct TraceContext {
    trace_id : String
    span_id : String
    flags : Int
    }

    A W3C Trace Context (← go-zero's OpenTelemetry propagation): the 128-bit trace id shared across a request's whole call tree, the 64-bit span id of the current hop, and the 8-bit sampling flags.

    TraceContext::span_id

    fn TraceContext::span_id(self : TraceContext) -> String

    The span id of this hop.

    TraceContext::to_traceparent

    fn TraceContext::to_traceparent(self : TraceContext) -> String

    Format as a W3C traceparent header value: 00-<32 hex trace-id>-<16 hex span-id>-<2 hex flags>.

    TraceContext::trace_id

    fn TraceContext::trace_id(self : TraceContext) -> String

    The trace id (the value propagated unchanged down the call tree).

    WeightedRoundRobin

    pub struct WeightedRoundRobin {
    scores : Map[String, Int]
    }

    A weighted balancer over Endpoint::weight, using smooth weighted round-robin: every pick credits each instance with its own weight, serves the highest-credited one, then charges it the total weight of the set. Across one full cycle each instance is served exactly its share of the traffic, and the picks interleave instead of arriving in runs — a weight-5 instance is not handed five requests back to back.

    Credit is keyed by address(), so an instance that leaves and returns resumes where it was rather than jumping the queue, and two instances sharing an address are treated as one. An instance whose weight is zero or negative is never picked; a set where every weight is non-positive yields None.

    WeightedRoundRobin::new

    A weighted balancer with no credit accrued yet.

    WeightedRoundRobin::pick

    fn WeightedRoundRobin::pick(self : WeightedRoundRobin, endpoints : Array[Endpoint]) -> Endpoint?

    Pick the next endpoint in weight order, or None if nothing is eligible.

    Window

    pub struct Window {
    size : Int
    bucket_ms : Int64
    buckets : Array[Bucket]
    offset : Int
    bucket_start : Int64
    }

    A rolling window of size buckets covering bucket_ms milliseconds each (← go-zero's collection.RollingWindow), which is how the breaker reads a backend's recent health: forty 250ms slices, so the last ten seconds and nothing older.

    Time never moves on its own — every call carries the now it happens at, as the other resilience cores here do. Writing at a later now clears the buckets the gap swept past before recording; reading at a later now skips them without clearing, so a window nobody writes to still ages out, and two reads at the same now agree.

    Window::add

    fn Window::add(self : Window, o : Outcome, now : Int64) -> Unit

    Record one call ending as o at time now.

    Window::each

    fn Window::each(self : Window, now : Int64, f : (Bucket) -> Unit) -> Unit

    Visit the buckets still inside the window at now, oldest first (← go-zero's Reduce). Stale buckets are skipped, not cleared: reading leaves the window exactly as it was.

    Window::new

    fn Window::new(size? : Int, bucket_ms? : Int64, now? : Int64) -> Window

    An empty window of size buckets of bucket_ms each, its first bucket starting at now. The defaults are go-zero's ten seconds in forty slices. A size or duration below one is raised to one, the way the other cores here clamp their bounds rather than rejecting them.

    auth

    fn auth(secret : String, clock : Clock) -> ((async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit) -> (async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit))

    JWT auth middleware (← go-zero's handler.Authorize): require every HTTP request to carry a valid Authorization: Bearer <jwt> header. The token is verified against secret under HS256 at the current time read from clock (milliseconds, converted to the JWT seconds epoch); an absent, malformed, tampered, expired, or not-yet-valid token is rejected with 401 Unauthorized before the wrapped app runs. Non-HTTP scopes (lifespan, websocket) pass through untouched.

    base64url_decode

    fn base64url_decode(s : String) -> Bytes

    Decode a base64url string (padding optional) back to bytes, remapping -/_ to +// before decoding. Lenient about missing padding, the way JWT segments are written.

    base64url_encode

    fn base64url_encode(data : Bytes) -> String

    base64url encoding (RFC 4648 §5, no padding): standard base64 with +// remapped to -/_ and trailing = dropped — the alphabet JWT uses for its header, payload, and signature segments.

    breaker

    Circuit-breaker middleware (← go-zero's breaker interceptor): gate each HTTP request through a shared Breaker. An admitted request's outbound HttpResponseStart status is observed — a 5xx settles its promise as a failure, anything else as a success, and that is what feeds the window. A shed request is answered 503 Service Unavailable without the wrapped app running. Non-HTTP scopes pass through untouched.

    The promise starts out a failure and only the observed status makes it a success, on a defer — the same shape go-zero's doReq uses, so a handler that raises or is cancelled before answering counts against the window instead of quietly not counting at all.

    canonical_key

    fn canonical_key(key : String) -> String

    go-zero's canonical form for a config key (← the WithCanonicalKeyFunc that conf.Load installs): lowercased, with _ and - dropped. Every lookup compares on this form, so MaxBytes, maxBytes, max_bytes and max-bytes all name one field — which is what lets a genuine go-zero etc/*.yaml, whose keys are all PascalCase, load into a config instead of silently yielding defaults.

    constant_time_eq

    fn constant_time_eq(a : Bytes, b : Bytes) -> Bool

    A constant-time byte-string equality: it inspects every byte of both inputs regardless of where they first differ, so an attacker cannot recover a valid signature byte-by-byte from response timing. Unequal lengths return false immediately (length is not secret). Used to compare JWT signatures.

    consul_check_id

    fn consul_check_id(service_id : String) -> String

    The check id consul assigns an inline service TTL check: service:<service-id>.

    consul_parse_health

    fn consul_parse_health(body : Bytes) -> Array[Endpoint] raise ConsulError

    Parse a consul health/service JSON body into endpoints — exposed so the native HTTP-socket path decodes a real agent's response exactly as the client does.

    consul_register_body

    fn consul_register_body(id : String, name : String, address : String, port : Int, ttl_secs : Int) -> Bytes

    The JSON body of a service/register request: the instance's id, name, address, and port, plus a TTL check that consul deregisters ttl*3 seconds after it stops passing. Exposed so the native HTTP-socket path builds the exact same body.

    cors

    CORS middleware (← go-zero's cors.Middleware): wrap the outbound Send so the configured Access-Control-* headers are injected onto every HttpResponseStart, leaving the body and other events untouched.

    default_latency_buckets

    let default_latency_buckets : Array[Double]

    The default latency buckets go-zero ships (milliseconds): a request spends most of its time under a second, so the bounds cluster there.

    etcd_prefix_end

    fn etcd_prefix_end(prefix : Bytes) -> Bytes

    The etcd range-end for a prefix scan: the prefix with its last byte incremented, which is the smallest key greater than every key sharing the prefix (etcd's getPrefix). An all-0xff tail scans to the end of the keyspace (\x00).

    etcd_service_prefix

    fn etcd_service_prefix(prefix : String, service : String) -> String

    The key prefix a service's instances live under: <prefix><service>/. Exposed so the native gRPC-socket path builds the exact same keys as the in-process driver.

    exposition_content_type

    let exposition_content_type : String

    The content type a /metrics scrape carries so a Prometheus server parses the body as the text exposition format (version=0.0.4).

    generate_span_id

    fn generate_span_id(seed : Int64) -> String

    A 16-hex-char (64-bit) span id from seed.

    generate_trace_id

    fn generate_trace_id(seed : Int64) -> String

    A 32-hex-char (128-bit) trace id from seed, mixing two independent words.

    hmac_sha256

    fn hmac_sha256(key : Bytes, msg : Bytes) -> Bytes

    HMAC-SHA256 (RFC 2104): a keyed message-authentication code over sha256. A key longer than the 64-byte block is hashed first; a shorter key is zero-padded. The message is authenticated as H((K ⊕ opad) ∥ H((K ⊕ ipad) ∥ msg)). Verified against RFC 4231 test case 2. This is the signature function behind JWT HS256.

    http1_parse_response

    fn http1_parse_response(data : Bytes) -> Http1Response raise Http1Error

    Parse a whole HTTP response (status line, headers, body). The body is everything after the header terminator, trimmed to Content-Length when the server sent one.

    http1_request

    fn http1_request(verb : String, path : String, host : String, body : Bytes, content_type? : String) -> Bytes

    Build an HTTP/1.0 request: request line, Host, an optional Content-Type, a Content-Length for the body, and Connection: close, then the body. Sending 1.0 with close makes the response close-delimited, so the client reads to EOF.

    jwt_authorized

    fn jwt_authorized(token : String?, secret : String, now_secs : Int64) -> Bool

    Whether a request bearing token is authorised at now_secs: the token verifies against secret under HS256 and is neither expired nor not-yet-valid. A missing token is unauthorised. Exposed as a pure decision so the middleware's accept/reject is testable without driving the transport.

    jwt_sign

    fn jwt_sign(claims : Map[String, Json], secret : String) -> String

    Sign a claims set as a compact JWT using HS256 (← go-zero's jwt.NewWithClaims(SigningMethodHS256, ...)). The header is fixed to {"alg":"HS256","typ":"JWT"}; claims is serialised as the JSON payload (include exp/iat/nbf/sub/… as ordinary entries); secret is the shared HS256 key. Returns header.payload.signature, each segment base64url-encoded.

    jwt_verify

    fn jwt_verify(token : String, secret : String, now_secs : Int64) -> Map[String, Json] raise JwtError

    Verify a compact HS256 JWT and return its claims (← go-zero's handler.Authorize). Checks, in order: three segments; header alg is HS256; the HMAC-SHA256 signature matches (compared in constant time); exp (if present) is strictly after now_secs; nbf (if present) is at or before now_secs. now_secs is the verification time as a Unix timestamp in seconds (JWT NumericDate). Raises the matching JwtError on any failure; a tampered payload or signature fails at BadSignature.

    logging

    A request-logging middleware: writes METHOD path through the process logx at info for each HTTP request, then delegates to the wrapped application. It logs on the way in and knows nothing of the response; structured_logging is the layer that times the request and records its status.

    logx

    let logx : Logger

    The process-wide logger (← logx's package-level logger): what the middlewares write to when no logger is handed to them, on the system clock at info. RestEngine::new points its level at the loaded Log.Level.

    max_conns

    Max-connections middleware (← go-zero's MaxConns): hold a permit for the wrapped app's duration, or answer 503 Service Unavailable when every permit is taken. Non-HTTP scopes (lifespan, websocket) pass through untouched.

    maxbytes

    fn maxbytes(limit : Int) -> ((async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit) -> (async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit))

    Max-bytes middleware (← go-zero's MaxBytesHandler): reject any HTTP request whose declared Content-Length exceeds limit bytes with 413 Payload TooLarge, before the wrapped app runs. A limit <= 0 disables the check. Non- HTTP scopes pass through untouched.

    metrics

    Metrics middleware (← go-zero's prometheus interceptor): time each HTTP request on the shared clock and, when the response starts, count it under "<METHOD> <path> <status>" and record its latency in milliseconds. The record is taken once per request even if a downstream (under a recovery race) emits a second start. Non-HTTP scopes pass through unmeasured.

    metrics_handler

    A handler that serves the current metric set in the text exposition format, with the Prometheus content type — the scrape target go-zero's prometheus.StartAgent publishes at /metrics.

    mount_metrics

    fn mount_metrics(app :
    App
    , m : ServerMetrics) -> Unit

    Register the GET /metrics scrape endpoint on app (← go-zero's prometheus.StartAgent), so a Prometheus server can pull the exposition.

    next_trace_context

    fn next_trace_context(inbound : String?, seed : Int64) -> TraceContext

    Derive the outgoing trace context for a request: reuse the inbound traceparent's trace id if the client sent a valid one (continuing the distributed trace), else start a new trace, and always mint a fresh child span id from seed. This is the propagation decision, pulled out as a pure function so it is testable without the transport.

    parse_traceparent

    fn parse_traceparent(value : String) -> TraceContext?

    Parse a W3C traceparent value, or None if it is malformed. Only the four canonical fields with correct lengths are accepted; the flags default to 0 if unparseable.

    period_limit

    fn period_limit(limiter : PeriodLimit, clock : Clock, key? : String) -> ((async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit) -> (async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit))

    Fixed-window rate-limit middleware over the process-local limiter: account for each HTTP request under key and answer 429 Too Many Requests once the key is over quota for the current window, otherwise delegating to the wrapped app. The limiter is captured once per assembly, so its windows are shared across every request this layer serves — but only within this process; use redis_period_limit to share them across replicas. Non-HTTP scopes pass through untouched.

    period_script

    let period_script : String

    go-zero's core/limit/periodscript.lua, verbatim: INCRBY the key, hang the window on it the first time it appears, and answer 1 below quota, 2 exactly at it, 0 past it. Public so a caller can pre-load it, and so a redis double can evaluate the very text the limiter ships.

    pick_first

    fn pick_first(endpoints : Array[Endpoint]) -> Endpoint?

    Pick the first endpoint (← gRPC's pick_first), or None if the set is empty. A stable choice that only moves when the head instance goes away.

    rate_limit

    fn rate_limit(bucket : TokenBucket, clock : Clock) -> ((async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit) -> (async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit))

    Rate-limit middleware over the process-local bucket: admit each HTTP request against a shared TokenBucket read at clock.now(), answering 429 Too ManyRequests when the bucket is empty and otherwise delegating to the wrapped app. The bucket is captured once per assembly, so its state is shared across every request this layer serves — but only within this process; use redis_rate_limit to share it across replicas. Non-HTTP scopes (lifespan, websocket) pass through untouched.

    recovery

    Recovery middleware (← go-zero's RecoverHandler): run the wrapped application inside a try, and if it raises, emit a 500 Internal ServerError instead of letting the failure escape to the server. A downstream that has already streamed its response start before raising will produce a second start event; recovery is a last-resort guard, so it always answers rather than trying to detect that race.

    redis_instance_key

    fn redis_instance_key(prefix : String, service : String, endpoint : Endpoint) -> String

    The key one instance of service lives at: <prefix><service>/<address>.

    redis_period_limit

    fn redis_period_limit(limiter : RedisPeriodLimit, key? : String) -> ((async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit) -> (async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit))

    Fixed-window rate-limit middleware over a shared redis: period_limit with the window in redis, so replicas behind one redis spend one quota between them. A redis that cannot be reached admits the request — the limiter is a guard on the service, not a dependency that should be able to close it.

    redis_rate_limit

    fn redis_rate_limit(limiter : RedisTokenLimit, clock : Clock) -> ((async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit) -> (async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit))

    Rate-limit middleware over a shared redis: rate_limit with the bucket in redis, so replicas behind one redis spend from one bucket. A redis that cannot be reached leaves the limiter running on its local bucket, so the layer keeps limiting either way. Non-HTTP scopes pass through untouched.

    redis_service_pattern

    fn redis_service_pattern(prefix : String, service : String) -> String

    The SCAN MATCH glob for every instance of service: <prefix><service>/*.

    redis_service_prefix

    fn redis_service_prefix(prefix : String, service : String) -> String

    The key prefix a service's instances live under: <prefix><service>/. Exposed so the native RESP-socket path builds the exact same keys as the in-process driver.

    request_id

    fn request_id(header? : String) -> ((async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit) -> (async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit))

    Request-ID middleware (← go-zero's trace/x-request-id handling): reuse an inbound x-request-id if the client sent one, otherwise mint a fresh monotonic id, and stamp it onto every response's HttpResponseStart. The counter is captured once per assembly, so ids stay unique across the requests this layer serves.

    resolve_one

    fn resolve_one(registry : InMemoryRegistry, service : String, balancer : RoundRobin) -> Endpoint?

    Resolve service on the registry and pick one endpoint with balancer — the resolve-then-balance step a zRPC client runs before each call. An etcd- or consul-backed registry with the same resolve shape drops in unchanged.

    resp_command

    fn resp_command(args : Array[String]) -> Bytes

    Encode a command given string arguments (the common case — command names and keys are text), UTF-8 encoding each.

    resp_encode_command

    fn resp_encode_command(args : Array[Bytes]) -> Bytes

    Encode a command as the client frame redis expects: an array of bulk strings, one per argument (*<n>\r\n$<len>\r\n<arg>\r\n...). Arguments are raw bytes, so binary keys and values round-trip unchanged.

    resp_kind

    fn resp_kind(value : RespValue) -> String

    A short name for a value's RESP type, for error messages ("unexpected <kind> reply") — RespValue derives Eq/Debug but not a renderable form.

    set_log_level

    fn set_log_level(level : LogLevel) -> Unit

    Set the process logger's level (← logx.SetLevel).

    set_log_writer

    fn set_log_writer(writer : (String) -> Unit) -> Unit

    Send the process logger's entries to writer (← logx.SetWriter).

    sha256

    fn sha256(msg : Bytes) -> Bytes

    SHA-256 (FIPS 180-4): hash an arbitrary byte string to a 32-byte digest. A self-built primitive — MoonBit's core ships no crypto — implementing the full message schedule and 64-round compression over 512-bit blocks with the standard length-padding. Verified against the NIST vectors ("", "abc"). The building block for hmac_sha256, and through it for JWT HS256 signing.

    shedding

    Shedding middleware (← go-zero's SheddingHandler): answer 503 ServiceUnavailable without running the app while the shedder is refusing, and pass everything else through. Non-HTTP scopes are never shed.

    status_of_code

    fn status_of_code(code : Int) ->
    Status

    Map a numeric grpc-status code back to a @moonrpc.Status. Anything outside the canonical 0–16 range is reported as Unknown, matching how a gRPC client treats an unrecognised code.

    structured_logging

    fn structured_logging(clock : Clock, logger? : Logger) -> ((async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit) -> (async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit))

    Structured-logging middleware (← go-zero's LogHandler): time each HTTP request on the shared clock, capture its method/path/client-ip/user-agent and the response status observed on HttpResponseStart, and write one access entry through logger (the process logx by default) when the response starts. Access logs are info, so a service configured at error or above emits none of them. Non-HTTP scopes pass through without logging.

    timeout

    fn timeout(budget_ms : Int64, clock : Clock) -> ((async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit) -> (async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit))

    Timeout middleware (← go-zero's TimeoutHandler): establish a per-request Deadline of budget_ms at clock.now() for the wrapped app.

    Async boundary (faithful model). Preemptively aborting an in-flight handler the instant its deadline fires requires racing the handler against a timer and cancelling the loser — in MoonBit that is @async.any([handler,timer]) with structured cancellation, which only runs under the native async runtime and cannot be driven synchronously. What this middleware does portably: it installs the deadline and enforces it on the response path — if the handler blows its budget before emitting its first event, the client receives a 503 timeout (from timeout_events) and the late response is suppressed. The remaining gap (a handler that hangs and never emits) is closed by the race/cancel wired at the async server edge. A budget_ms <= 0 disables the timeout, passing straight through.

    token_script

    let token_script : String

    go-zero's core/limit/tokenscript.lua, verbatim: read the bucket and the second it was last touched, refill by the elapsed seconds capped at capacity, spend requested if that many are there, and write both back under a TTL of two fill times. Public so a caller can pre-load it, and so a redis double can evaluate the very text the limiter ships.

    tracing

    fn tracing(header? : String, ignore_paths? : Array[String]) -> ((async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit) -> (async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit))

    Trace-id propagation middleware (← go-zero's trace handler): continue the inbound traceparent trace or start a new one, mint a child span, and stamp both traceparent and a convenience x-trace-id onto the response so the id flows to the client and downstream calls. The per-assembly seed counter keeps span ids distinct across the requests this layer serves. Requests whose path is listed in ignore_paths are left untraced (← WithTraceIgnorePaths, the blacklist that keeps health checks out of the trace store), as are non-HTTP scopes.

    yaml_parse

    fn yaml_parse(src : String) -> Json raise ConfigError

    Parse a minimal YAML subset into a Json value: block mappings (key: value), arbitrary indentation-based nesting, block sequences (-item, including - key: value maps in a list), scalars (quoted/plain strings, integers, floats, true/false, ~/null), and # line comments. Enough of YAML 1.1 to load go-zero-style service config. Flow style ({a: 1}, [1, 2]), anchors/aliases, multi-document streams, and block scalars (|/>) are not supported — use JSON for those. Raises ConfigError on a line that is neither a mapping entry nor a sequence item.