moonctl

    Project and code generator for MoonBit services.

    goctl
    codegen
    scaffold
    api
    moonapi
    moonbit
    cli
    Download zip
    Version
    0.12.4
    License
    Apache-2.0
    Last updated
    17 hours ago
    Downloads
    128

    #moonctl (mctl)

    A spec-driven code generator for MoonBit — ← goctl.

    Check and Test License mooncakes

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

    moonctl parses a .api service spec and emits compilable moonapi scaffolding — the role goctl plays for Go. It's pure logic (parse → generate text), so it has no runtime dependencies and runs on every backend.

    #From spec to code

    Given this .api spec:

    service greet { get /ping ping "health check" get /users/:id get_user post /users create_user "create a user" }

    generate(parse(spec)) emits:

    // Code generated by moonctl. DO NOT EDIT.

    ///|
    /// Build the greet application with its routes wired to handlers.
    pub fn build_app() -> @moonapi.App {
    let app = @moonapi.App::new()
    app.get("/ping", ctx => ping(ctx), summary="health check")
    app.get("/users/:id", ctx => get_user(ctx))
    app.post("/users", ctx => create_user(ctx), summary="create a user")
    app
    }

    ///|
    /// `get /ping`.
    pub fn ping(_ctx : @moonapi.Context) -> @moonasgi.Response {
    @moonapi.text(200, "TODO: ping")
    }
    // ... one stub per handler

    Each route registers its handler through an arrow closure. moonapi's ApiHandler is a raising fn-type, and under moonc 0.10.5 a pure named function no longer coerces to one; a closure infers the raise effect, so both a stub that never raises and a filled-in handler that raises an HttpException register unchanged.

    The generated output is verified to compile against real moonapi + moonasgi — a code generator whose output doesn't build isn't a code generator.

    #Route groups

    An @server( … ) block annotates the service block under it, the way goctl groups handlers:

    @server ( group: user prefix: /api/v1 jwt: Auth middleware: Log,Trace ) service greet { @doc "log a user in" @handler login post /login (LoginReq) returns (LoginResp) }

    Every route in the block carries the group. Its path is emitted with the prefix applied, the OpenAPI document tags the operation user, and the scaffold marks the block with what the spec asked for — a group's jwt and middleware have no moonapi call to emit them into, so they are stated rather than dropped:

    pub fn build_app() -> @moonapi.App {
    let app = @moonapi.App::new()
    // @server group: user, prefix: /api/v1, jwt: Auth, middleware: Log,Trace
    app.post("/api/v1/login", ctx => login(ctx), summary="log a user in")
    app
    }

    ///|
    /// `post /api/v1/login` of group user.
    pub fn login(_ctx : @moonapi.Context) -> @moonasgi.Response {
    @moonapi.text(200, "TODO: login")
    }

    group, prefix, jwt, middleware, maxBytes, timeout and signature are read into a Group; any other annotation the block carried is kept in Group::extra, for a template or plugin to use.

    #Struct tags

    A field's back-tick tag says where its value comes from and what it has to look like, the way goctl borrowed it from Go:

    type ListReq { Region string `path:"region"` Page int `form:"page,default=1,range=[1:100]"` Size int `form:"size,optional"` Trace string `header:"X-Trace"` Sort string `json:"sort,options=asc|desc"` }

    json: reads out of the request body, form: off the query string, path: out of a URL segment and header: off a request header — so the OpenAPI document gives page as a query parameter, region as a path one under the name its tag chose, and keeps only the body-bound fields as schema properties. The options after the wire name are read too: optional (and Go's omitempty) keeps a field out of required, and default=, options= and range= become a default, an enum and minimum/maximum in the document — plus the code that enforces them, emitted beside the struct:

    pub fn ListReq::with_defaults(self : ListReq) -> ListReq { … } // fills in every default= pub fn ListReq::check(self : ListReq) -> String? { … } // the first field that fails

    A block can inline another by naming it on a line of its own (Base), and a nested Inner { … } becomes a type of its own named OuterInner — MoonBit has neither embedding nor anonymous structs, so an inlined block's fields are written out where they belong. A field is spelled snake_case in every generated MoonBit name, because UserName is not a MoonBit struct field; json_name is the wire side of the same field.

    #The project tree

    mctl gen api writes what goctl writes — a layered project, not a file:

    $ mctl gen api greet.api --dir out --style go_zero out/moon.mod out/internal/types/types.mbt out/moon.pkg out/internal/handler/routes.mbt out/greet.mbt out/internal/handler/user/login_handler.mbt out/etc/greet.yaml out/internal/logic/user/login_logic.mbt out/internal/config/config.mbt out/internal/middleware/log_middleware.mbt out/internal/svc/service_context.mbt

    A grouped route's handler and logic go under the group's own directory, an ungrouped one's sit beside the routes, and every directory gets the moon.pkg that makes it a MoonBit package. --flat writes the single <file>.mbt earlier versions did.

    Run it again and your code survives. Only the two files moonctl owns — internal/types/types.mbt and internal/handler/routes.mbt, both of which must follow the spec — are rewritten. Everything else is written once and then left alone, because that is where the handler and logic bodies you filled in live. A route added to the spec since the last run still gets its stubs; the ones already on disk are not touched.

    The same rule is available to a library caller, which is where it is decided:

    let tree = @moonctl.generate_tree(spec, style~, dir~) // -> [TreeFile { path, content, regen }]
    let write = @moonctl.tree_plan(tree, p => on_disk(p)) // -> the files to actually write

    #Naming style

    --style is goctl's naming template, not a list of cases: <before>GO<through>ZERO<after>, where the casing of the GO marker spells the first word, ZERO's spells every later one, and what stands between the markers stands between the words.

    templatewelcome_to_go_zero becomes
    gozero (default)welcometogozero
    goZerowelcomeToGoZero
    go_zerowelcome_to_go_zero
    Go#zeroWelcome#to#go#zero

    Words split on _ and before each capital. A template missing a marker (go, zero) or casing one of them any other way (gOZero, goZEro) is refused with a StyleError rather than silently taken for something else. The style names every file of the tree and the handler, logic and middleware entry points inside them; the names the spec itself chose — its type blocks and their fields — keep their spelling, since a MoonBit type name has to keep its capital.

    #Multi-file specs

    A spec can import others, singly or in a group, and each path is read relative to the file that wrote it:

    import "user.api" import ( "shared/base.api" "../common.api" )

    The imported types, routes and @server groups are merged in ahead of the importing file's own. A file is read once however many times it is imported, so a cycle terminates, and an import naming a file that is not there is reported against the file that asked for it rather than silently generating half a program.

    Reading files is the caller's job — the library stays pure and all-backend — so it comes in two halves: deps says which files a source needs, and parse_all merges the ones you collected.

    let spec = @moonctl.parse_all(source, files, from="spec/greet.api")

    #When a spec is wrong

    parse raises a SpecError naming the line it could not read — a misspelt verb or annotation, a block left open, a field with no type, a route no @handler names:

    @moonctl.parse("service greet {\n gett /ping ping\n}")
    // SpecError — line 2: unknown verb "gett"

    Without that, a one-character typo generates a program that is quietly missing a route. parse_lenient skips those lines instead and returns whatever the spec does describe, for a spec that is still being written.

    #Packages

    The layout follows goctl's: the spec language on its own, then one package per thing generated from it. The root re-exports the lot, so @moonctl.parse works as well as @api.parse.

    PackageWhatgoctl
    apithe .api language: types, routes, tags, importsapi/parser, api/spec
    genthe moonapi service a spec describes, and the validation its tags ask forapi/gogen
    templatethe template engine, Go text/template syntax and semanticstext/template
    style--style naming templatesconfig
    protomoonrpc messages, codecs and handler tables from .protorpc
    docOpenAPI and Swagger documentsapi/docgen
    gqla moongql schema—
    modelmoonorm models from a spec, from DDL, or from a live databasemodel
    agenta moonkoog agent—
    pluginthe plugin protocolplugin
    scaffoldproject skeletons and the layered treesapi new, rpc new, gql new, mcp new, docker, kube

    #Usage

    As a library:

    let spec = @moonctl.parse(source) // -> Spec { service, routes, types, info, groups, imports }
    let code = @moonctl.generate(spec) // -> compilable moonapi scaffold (String)
    let tree = @moonctl.generate_tree(spec) // -> the layered project, file by file

    As the mctl command-line tool (native binary):

    $ mctl gen api greet.api # -> a moonapi project tree (etc/, internal/…) $ mctl gen api greet.api --style go_zero --dir out $ mctl gen api greet.api --flat # -> greet.mbt (one file, as before) $ mctl gen rpc greet.proto # -> a moonrpc project tree (pb/, internal/…, client/) $ mctl gen rpc greet.proto --flat # -> greet_grpc.mbt (messages, codecs, handler tables) $ mctl gen gql greet.api # -> a moongql project tree (internal/schema, …) $ mctl gen model user.api # -> user_model.mbt (moonorm model + migration) $ mctl gen crud schema.sql # -> schema_model.mbt (moonorm model + typed CRUD) $ mctl gen doc greet.api # -> greet_openapi.json + greet_swagger.html $ mctl model datasource sqlite:shop.db # -> datasource_model.mbt (read a live DB's schema) $ mctl api new blog # scaffold a moonapi service project under blog/ $ mctl rpc new echo # scaffold a moonrpc service project under echo/ $ mctl gql new books # scaffold a moongql service project under books/ $ mctl model new account # scaffold a moonorm data-layer project under account/ $ mctl docker greet # -> Dockerfile + .dockerignore $ mctl kube greet # -> deploy/deployment.yaml + deploy/service.yaml $ mctl plugin ./my-plugin greet.api # run an external plugin over the parsed spec $ mctl api format greet.api # canonical spelling on stdout (--write for in place) $ mctl api validate greet.api # findings, then "N error(s), M warning(s)" $ mctl api doc greet.api # -> greet_api.md (Markdown reference) $ mctl api ts greet.api # -> greet_client.ts (typed client) $ mctl api dart greet.api # -> greet_client.dart $ mctl api kt greet.api # -> greet_client.kt $ mctl api java greet.api # -> GreetClient.java $ mctl migrate greet.api # -> greet_migrate.sql (one table per type) $ mctl model mysql datasource root@tcp(db)/shop # -> mysql_model.mbt $ mctl model pg datasource postgres://u:p@db/shop # -> pg_model.mbt $ mctl model mongo greet.api # -> greet_mongo.mbt (documents + repositories) $ mctl model cache greet.api # -> greet_cache.mbt (cache-aside CRUD) $ mctl gateway greet.proto # -> greet_gateway.mbt (HTTP front door) $ mctl quickstart blog # scaffold an api project, then say what to run $ mctl template list # the bundled template library $ mctl upgrade 0.13.0 # move the moonbitstack pins in moon.mod $ mctl config set branch release # edit ~/.mctl/config $ mctl env # what mctl resolved, and from where $ mctl completion bash # a shell completion script $ mctl bug "api format eats my comment" # a report pre-filled with the environment

    generate works as a synonym for gen, and rpc for proto.

    #Cross-repo codegen

    Like goctl, mctl is the code generator for the whole stack, not just the web tier. Two more front-ends feed the sibling libraries:

    • .proto → moonrpc: parse_proto reads a proto3 file (service / rpc / message, including stream, repeated, and map<K, V>); generate_grpc emits a struct per message, a @moonrpc.Method descriptor per RPC (its gRPC :path is /package.Service/Method), a <Service>Server handler-registration struct (one synchronous-core handler per RPC, returning its reply or a gRPC @moonrpc.Status), and a <service>_methods() listing.

    • .api type → moonorm: generate_model turns each storable type block into a model struct, a <table>_model : @moonorm.Model[T] (declared columns — an id column is the primary key — plus the from_row decoder and to_columns projection that MoonBit can't synthesise for want of reflection), a <table>_table : @moonorm.Table descriptor, and a <table>_up / <table>_down migration pair (create the table idempotently, drop it — the explicit stand-in for an Alembic revision). A block with a non-storable field (a slice, map, or nested message) still gets its struct and a plain Table descriptor. The output compiles against moonorm + moondb, so a consumer imports both.

    Both outputs are verified to compile against the real moonrpc / moonorm in a scratch consumer project (moon check → rc 0), the same guarantee the moonapi scaffold carries.

    let stub = @moonctl.generate_grpc(@moonctl.parse_proto(proto_src)) // -> moonrpc stub
    let model = @moonctl.generate_model(@moonctl.parse(api_src)) // -> moonorm model + migration

    #SQL DDL → CRUD

    goctl model mysql ddl reads a .sql schema and generates a model with typed CRUD; parse_ddl + generate_crud are the MoonBit version. parse_ddl reads CREATE TABLE statements — column types, column- and table-level primary keys, NOT NULL, and DEFAULT clauses — and generate_crud emits, per table, the record struct, a @moonorm.Model, an up/down migration pair, and typed CRUD: <table>_insert and <table>_all always, plus <table>_find_by_id, <table>_update, and <table>_delete_by_id when the table has a single-column primary key. SQL types map onto the @moondb.Value set — INTEGER/BIGINT → Int/Int64, TEXT → String, REAL → Double, BLOB → Bytes, BOOLEAN → Bool. Every generated statement is parameterised, so a CRUD call binds its id and columns rather than splicing them.

    let code = @moonctl.generate_crud_from_ddl(sql_src) // -> moonorm model + CRUD (String)

    The output is verified to compile against the real moonorm + moondb in a scratch consumer (moon check → rc 0).

    #Live datasource reflection

    goctl model … datasource connects to a running database, reads its schema, and generates models. mctl model datasource <dsn> is the MoonBit version. A SQLite DSN reads sqlite_master + PRAGMA table_info through the FFI driver; a PostgreSQL DSN reads information_schema over the async wire protocol. Both produce the same neutral ReflectedColumn list, which folds into the same generate_crud pipeline the .sql front end uses, so a live schema and a .sql file yield an identical model.

    $ mctl model datasource sqlite:shop.db $ mctl model datasource postgres://user:pass@localhost:5432/shop

    DSN parsing (parse_dsn) and the reflected-column → model fold (tables_from_reflection / generate_crud_from_reflection) are pure, all-backend core in the library; the schema reading lives in the native reflect sub-package, so the library itself stays dependency-free. The pipeline is verified end to end against a real in-memory SQLite database (create tables → reflect → generate → the generated models compile against moonorm + moondb).

    #Project scaffolds

    api new, rpc new, gql new and mcp new write a service that runs as it stands: moon test --target native passes, moon run --target native . serves it on port 12000.

    $ mctl api new blog # moonapi on mooncat $ mctl rpc new echo # moonrpc, with a typed client $ mctl gql new books # moongql on mooncat $ mctl mcp new notes # moonmcp over HTTP, or stdio with -- --stdio $ mctl model new account # a moonorm data layer $ mctl docker greet # Dockerfile (two-stage native build) + .dockerignore $ mctl kube greet # deploy/deployment.yaml + deploy/service.yaml

    The four services share goctl's layout. Files marked DO NOT EDIT belong to mctl and are rewritten by the matching gen command; everything else is seeded once and then belongs to the author.

    Pathapirpcgql
    <name>.mbtserve the app on mooncatserve the handler tables on GrpcServerserve graphql_app on mooncat
    etc/, internal/config, internal/svc✓✓✓
    generatedinternal/types, internal/handler/routes.mbtpb/, internal/server, client/<name>client.mbtinternal/schema
    the author'sinternal/handler/*, internal/logic/*internal/logic/*internal/logic/resolvers.mbt
    testthe routes through TestClientPing over a real socketa { ping } query

    For every message, pb/ has encode and decode in protobuf wire form, checked against Google's runtime in both directions. Every service gets a record with one function per RPC and handlers(), its RPCs keyed by :path for GrpcServer. A request that does not decode ends the call with INVALID_ARGUMENT.

    Every generated moon.mod imports the libraries at the versions in pins. On every push, CI scaffolds each kind of project, runs its tests against those versions, and regenerates over an rpc scaffold to check that the author's files survive. A project that wants a later release edits its own moon.mod.

    let files = @moonctl.scaffold_rpc("echo") // -> Array[GenFile] to write
    let tree = @moonctl.generate_rpc_tree(proto) // -> Array[TreeFile], each marked Always or Once

    #Plugins

    Like goctl api plugin, mctl plugin <exe> <file.api> drives an out-of-tree generator off the same parsed spec the built-in generators use, and speaks goctl's protocol: mctl runs <exe> with whatever arguments the invocation carried and writes it one JSON object on stdin — {Api, ApiFilePath, Style, Dir}, the parsed spec plus the file it came from, the --style template and the output directory, which is what tells a plugin where to write and how to spell what it writes. The plugin prints the files it wants written — a JSON {"files": [{"path", "content"},…]} reply — on its stdout, which mctl writes to disk. The wire shapes are plugin_request (request) and parse_gen_files (reply); a plugin written in MoonBit reads its input with plugin_from_json (or spec_from_json, which unwraps the envelope for a plugin that wants nothing but the spec) and writes its output with gen_files_to_json. cmd/demo-plugin is a working example that emits a route listing.

    let (bin, argv) = @moonctl.plugin_argv("my-plugin -flag value") // what to run
    let req = @moonctl.plugin_request(spec, api_file_path~, style~, dir~) // its stdin
    let files = @moonctl.parse_gen_files(@json.parse(out)) // the plugin's stdout -> files to write

    #Checking a spec: api format, api validate

    A spec that parses can still be wrong: a route pointing at a type nobody declared, two handlers under one name, a :id segment with no field to fill it. validate is the second pair of eyes, and every finding names the rule that caught it, so a toolchain can turn one class of finding off without turning the rest off.

    $ mctl api validate greet.api error [unknown-type] POST /users: request type "CreateReq" is not declared warning [path-param] GET /users/:id: `:id` has no field named "id" in GetReq, so nothing fills it mctl: greet.api: 1 error(s), 1 warning(s)

    format is the canonical spelling of the same file — one route and one type per line, tags where they were, comments kept — so a spec reviews as a diff instead of as a re-indent. It is idempotent, which is what makes it safe to run over a whole repository:

    let findings = @moonctl.validate_source(source) // Array[Diagnostic]
    let ok = @moonctl.has_errors(findings) == false
    let canonical = @moonctl.format(source) // format(format(x)) == format(x)

    #Clients: TypeScript, Dart, Kotlin, Java

    mctl api ts|dart|kt|java reads the same spec and writes a typed client, one file per language. The request type of a route becomes a model, its path: / form: / header: fields become parameters (the body and the path segments leading, the optional ones last), its response type becomes the return type, and a non-2xx answer raises the client's own error. The generator lives in src/client and shares one reading of the spec across four emitters, so a route is spelled the same way in all four.

    LanguageFilePorts the caller fills in
    TypeScriptgreet_client.tsFetch (defaults to the global fetch), token
    Dartgreet_client.dartSend, token
    Kotlingreet_client.ktTransport + Codec
    JavaGreetClient.javaTransport + Codec, nested in the class

    Transport is a port on purpose: a generated client compiles with no third-party dependency, and runs over fetch, dio, Ktor or a test double without editing the generated file. TypeScript and Dart serialize with the JSON their standard library already has; Kotlin and Java take a Codec (encode(value) -> String, decode(text) -> Map), so the project's existing kotlinx.serialization, Gson or Jackson does the JSON and the client forces no fourth library into the build.

    Each generated client was compiled once with its own compiler as the acceptance check:

    $ tsc --strict --noEmit --target es2022 --lib es2022,dom greet_client.ts $ dart analyze greet_client.dart $ kotlinc greet_client.kt -d greet.jar $ javac -Xlint:all -d out GreetClient.java

    mctl api doc writes the human-facing counterpart to the OpenAPI document: the route table, the groups, then one table per type saying where each field is bound and what the tag constrains it to.

    #Migrations from a spec

    mctl migrate reads the type blocks as tables — a column per field, the id field as the primary key, optional as NULL, and a list or a nested type as JSON — and writes <service>_migrate.sql. It is a starting point, and says so in its header: a spec knows the shape of a value, not its indexes, its uniqueness or its foreign keys.

    let sql = @moonctl.generate_migrations(spec)

    #Document models: model mongo

    A document store is not a table with a different name, and a Mongo driver is not a dependency generated code should have to carry. So mctl model mongo writes each type block as a document — a struct with derive(FromJson, ToJson), so a value round-trips without a hand-written field walker — and a typed repository over a three-call port:

    pub(all) struct Collection {
    name : String
    run : (MongoOp, Json, Json) -> Json raise MongoError // op, filter, document
    }

    A driver binds run to insertOne / findOne / find / replaceOne / deleteOne, and every repository is typed from there: insert, all, and — when the spec has a field to look a record up by — find, replace and delete. A nested type nests that type's document, so owner: User is stored as a UserDoc. The generated file needs moonbitlang/core/json and nothing else.

    #An HTTP front door: gateway

    mctl gateway turns a .proto into an HTTP gateway: a route table, the status codes a client cares about (200, 400 for a body that is not JSON, 404 for a path no route answers, 502 for an upstream failure), and a Caller port that takes a gRPC method's full path and a request document. Routes default to POST /<Service>/<Method> — generated before anyone has decided on URLs — and come from a sibling mapping file when there is one:

    # greet.routes post /v1/users /shop.UserService/Create get /v1/stock /shop.StockService/Count

    A line naming a method the .proto does not declare is reported and skipped, and a streaming RPC is left out of the table with a comment saying which one, because an HTTP response is one body with one end.

    #Cache-aside CRUD: model cache

    mctl model cache writes the layer in front of a table's CRUD: read-through on find, invalidate on write and on evict. The port is generic, so a cache holds records rather than the text they serialise to, and an in-memory implementation is a Map — which is what the generated memory_cache is, and what a test drives the layer with.

    A broken cache never fails a request: a failed read is a miss and a failed invalidation is swallowed, because a cache that can take a service down is worse than no cache at all.

    #Template library, config, and the housekeeping

    The command surface is data, not code: src/cli holds one table that mctl help prints, mctl completion completes from and the README lists, so a new command is completable and documented by being added once.

    CommandWhat it does
    mctl helpthe command table, with the flags every generator shares
    mctl completion <shell>a completion script for bash, zsh, fish or pwsh, generated from the table
    mctl template list / show <name>the bundled template library, and what one entry produces
    mctl config list|get|set|path|migratethe key = value file mctl reads before it starts
    mctl envwhat mctl resolved — home, remote, branch, style — and where from
    mctl bug [title]a bug report pre-filled with the environment
    mctl upgrade [version]move the moonbitstack/* pins in moon.mod, touching nothing else
    mctl quickstart <name>scaffold an api project, then print what to run next
    mctl migrate <file.api>the SQL a spec's types imply
    mctl model mysql|pg datasource <dsn>live-database reflection, written under the engine's name
    mctl model mongo <file.api>documents and typed repositories over a collection port
    mctl model cache <file.api>cache-aside CRUD: read-through, then invalidate on write
    mctl gateway <file.proto>an HTTP gateway over the proto's RPCs, routes from <stem>.routes when present

    The template library is reached three ways, as in goctl: the bundled defaults, a --home <dir> of your own, or --remote <url> --branch <name> for a shared one. config keeps the choice between runs, and --home / --remote / --branch win over it for a single run.

    $ mctl config set home ~/templates $ mctl config set branch release $ mctl config get branch release

    #OpenAPI / Swagger

    generate_doc emits an OpenAPI document straight from a .api spec — routes become paths (a :id segment turns into {id}, with a typed path parameter), and type blocks become component schemas. It speaks every mainstream dialect, the same three moonapi's runtime emitter does, so a generated service and a hand-built app document the same way.

    let spec = @moonctl.parse(api_src)
    let json = @moonctl.generate_doc(spec, version=OpenApi31) // or Swagger20 / OpenApi30
    let html = @moonctl.swagger_ui_stub(spec_url="/openapi.json")

    #Template engine

    moonctl ships a runtime template engine — the faithful equivalent of goctl's text/template. Because MoonBit has no reflection, template data is an explicit tagged Value (Str/Int/Bool/List/Dict/…), the way encoding/json models dynamic data. It supports {{.Field}} interpolation, {{if}}/{{else if}}/{{else}}/{{end}}, {{range}} (with $index, $value binding) and {{with}}, $variable assignment, | pipelines with parenthesised sub-pipelines, {{- -}} whitespace trimming, {{/* comments */}}, and a function library (upper, lower, title, trim, trimPrefix/trimSuffix, hasPrefix/hasSuffix, contains, replace, eq/ne/lt/le/gt/ge, and/or/not, len, index, default, printf/print/println, plus your own via func).

    let out = @moonctl.render(
    "{{range .routes}}{{.verb}} {{.path}} -> {{.handler}}\n{{end}}",
    @moonctl.spec_to_value(spec),
    )
    // or drive codegen from a caller-supplied template:
    let code = @moonctl.generate(spec, template=my_template) // == generate_with

    #Status & roadmap (transliterating goctl)

    Shipped: the .api parser (service blocks, @server( … ) route groups, verb path handler "summary" routes, typed type blocks → request/response schemas, // comments, and a SpecError naming the line a spec goes wrong on), the moonapi generator (verified to compile against real moonapi), the runtime template engine (goctl's text/template equivalent), the cross-repo generators — .proto → moonrpc service stubs, .api type → moonorm models (struct + Model + Table + up/down migration), SQL DDL → moonorm models with typed CRUD, and .api → OpenAPI (Swagger 2.0 / 3.0 / 3.1) + a Swagger UI stub — with the generated model and CRUD verified to compile against the real moonorm + moondb and the generated document verified to parse — the plugin system (an external generator driven off the parsed spec over stdio JSON, with a working demo plugin), live-datasource schema reflection (model datasource — read a running SQLite or PostgreSQL schema and generate models, verified end to end against a real SQLite database), the nested project scaffolds (api/rpc/model new, docker, kube), and the mctl gen api/proto/model/crud/doc + mctl model datasource + mctl plugin CLI binary (native), the layered project tree gen api writes (etc/, internal/config, svc, types, handler, logic, middleware) with goctl's regeneration rule — only the routes and the types are rewritten, so a second run keeps every handler body you wrote — goctl's --style naming templates, and multi-file specs (import, single and grouped, resolved against the importing file). Generated moonapi routing registers each handler through a raising arrow closure, so a scaffold compiles under moonc 0.10.5 (where a pure named handler no longer coerces to moonapi's raising ApiHandler). All verified across every backend (0 warnings under --deny-warn). This round added the spec toolchain (api format, api validate with per-rule diagnostics, api doc as Markdown), the four typed clients (api ts, api dart, api kt, api java — each compiled once with its own compiler, and snapshotted by the tests), migrate (a spec's types as SQL), the model mysql|pg datasource spellings, and the command surface that makes the rest discoverable (help, completion, template, config, env, bug, upgrade, quickstart). The latest round closed the rest of goctl's list: model mongo (a document model and a typed repository over a collection port, compiled and driven through a fake driver in the tests), model cache (cache-aside CRUD over a generic cache port, driven against the generated in-memory cache), gateway (a .proto as an HTTP front door, with a route mapping and the 200/400/404/502 answers exercised end to end), and the plugin fault coverage mctl was missing — a plugin may not write outside the output directory, and a path it claims twice is refused rather than silently overwritten. Each of the three generators' output is compiled under moon check --target all --deny-warn as part of that work.

    Still open: the template library is catalogued and configurable (--home, --remote, --branch), but no template directory is read from disk yet — the bundled catalogue is what generation uses — and mctl template init is advertised in the usage line without an implementation behind it. The gateway matches a route by verb and path exactly, so a :param pattern is emitted but never matched.

    #Documentation

    docs/ is the published documentation — a landing page, a quick start, three guides, a command reference and the library API. Plain HTML and CSS with one small script: open docs/index.html in a browser to read it, there is no build step and nothing to install.

    The API page is the exception to "hand written": scripts/gen_api_reference.py regenerates it from the pkg.generated.mbti interfaces moon info writes, and CI fails if the committed page and the current interfaces disagree. Run it after changing a public signature.

    #License

    Apache-2.0.

    Bind

    Where a field's value is bound from (← goctl's struct tags): the request body (json:, and an untagged field), the query string (form:), a URL segment (path:), or a request header (header:).

    Command

    One command: what it is called, the line that shows how to call it, and one sentence saying what it does.

    Config

    One configuration: the fields mctl understands. version is the file format, not the mctl version, and it goes up when migrate_config learns a new shape.

    DataSource

    A parsed datasource DSN: which backend to read, and how to reach it.

    DataSourceError

    Raised when a DSN string cannot be parsed into a DataSource.

    DdlColumn

    One parsed column of a CREATE TABLE statement: the SQL column name, the MoonBit type_ its SQL type maps to (Int/Int64/String/Double/Bool/ Bytes), whether it is a primary key, whether it accepts NULL, and its literal DEFAULT clause if any (kept as source text — it is emitted into DDL comments, never bound).

    DdlTable

    A parsed CREATE TABLE: the SQL table name and its ordered columns.

    Diagnostic

    One finding of validate: how bad it is, the rule that produced it, the at — the route, type or field it is about — and a sentence saying what is wrong.

    DocVersion

    Target OpenAPI / Swagger document version. mctl emits every mainstream version from one .api spec — the same shape moonapi's runtime emitter takes, so a generated service and a hand-built moonapi app document the same way.

    Field

    One field of a type block: its name, the (already MoonBit-mapped) type, and the raw back-tick struct tag (json:"…"/path/form/header, empty when absent) that carries the wire name and binding location.

    FuncFn

    A pipeline function: it receives the already-evaluated argument list (with the piped-in value, if any, appended last, mirroring Go) and returns a Value.

    GatewayRoute

    One HTTP route and the gRPC method behind it: the verb and path the gateway answers, and the fully-qualified method it forwards to.

    GenFile

    One file a plugin asks mctl to write: a destination path (relative to the output directory) and its full content.

    Group

    The @server( … ) annotations governing a block of routes (← goctl's route group): the name under which its routes are grouped, the path prefix already folded into their paths, the jwt claim type, the middleware chain, the max_bytes request cap (0 when unset), the request timeout, and whether the group's requests are signed. extra keeps every other annotation the block carried, in order — goctl lets a spec invent its own, and dropping them would lose what the author wrote.

    Label

    Whether a field holds one value, a list, or a map with proto key and value types.

    Lang

    The language a client is generated for: the four goctl generates or that the community asks for.

    PgTarget

    A PostgreSQL connection target parsed from a postgres:// DSN.

    Plugin

    What a plugin is handed on stdin (← goctl's plugin.Plugin): the parsed spec under api, the .api file it was read from, the --style naming template, and the directory the generated tree goes under.

    Proto

    A parsed .proto file: its package (empty when none) and the services and messages it declares.

    ProtoEnum

    A protobuf enum declaration, generated into a MoonBit enum plus an integer mapping (proto3 enums are int-backed, and the value numbered 0 is the default).

    ProtoEnumValue

    One value of a protobuf enum: its name and its integer number.

    ProtoField

    One field of a protobuf message: its name, its MoonBit type (repeated is Array[T], map<K, V> is Map[K, V]), its proto element type as written, its label and its wire number.

    ProtoMessage

    A protobuf message declaration, generated into a MoonBit struct. oneof groups are lifted out of fields into oneofs; reserved field numbers and names are recorded so a reused one can be caught.

    ProtoOneof

    A protobuf oneof group: its name and the members (exactly one may be set). Generated into a MoonBit enum — the faithful equivalent of the "exactly one" invariant, which flattening to optional fields would lose.

    ProtoService

    A protobuf service declaration and its RPC methods.

    Range

    A range= constraint, e.g. range=[1:120] or range=(0:]. Each bound is written as the spec wrote it and is empty when that end is open; lo_inc / hi_inc say whether the bracket was inclusive ([ ]) or exclusive (( )).

    ReflectedColumn

    One column of a reflected database schema: the table it belongs to, its name, its raw SQL type text (INTEGER, character varying, VARCHAR(255), …), and whether it is a primary key / accepts NULL. This is the neutral shape a live reader (SQLite PRAGMA table_info, PostgreSQL information_schema.columns) produces and tables_from_reflection folds into DdlTables.

    Regen

    Whether a regeneration may replace a file that is already on disk. Always is for what moonctl owns — the routes and the types, which must follow the spec — and Once for what it only seeds: handlers, logic, configuration, manifests.

    Route

    One route in a service spec: HTTP verb (lower-cased), path pattern (with its group's prefix applied), handler name, an optional summary, and the @server group it was declared under. A modern goctl route (verb /path (Req) returns(Resp)) also carries its request/response type names (its @doc fills summary); both are empty for the legacy inline form (verb /path handler"summary").

    Rpc

    One rpc method of a service: its name, request and response message names, and whether either side is a stream (client/server/bidi streaming).

    Severity

    How bad a finding is: Error means the generated code would not compile or would not match the spec, Warning means it would compile but probably is not what the author meant.

    Spec

    A parsed .api service specification. groups lists the @server( … ) blocks in the order they were declared; each route also points at the one it belongs to. imports holds the import paths exactly as the file wrote them — relative to the file itself, so resolving one needs the path it was read from; deps and parse_all do that.

    SpecError

    A spec the parser could not read: Syntax is the 1-based line and what went wrong there, Missing an import whose path was not among the files handed to parse_all.

    Style

    A --style naming template (← goctl's --style), ready to spell names with. Build one with Style::parse, apply it with Style::format.

    StyleError

    A --style template that is not <before>GO<through>ZERO<after>: either a marker is missing (go, zero), they are the wrong way round, or one is cased neither go/GO/Go nor zero/ZERO/Zero (gOZero, goZEro).

    Template

    A parsed, reusable template — moonctl's text/template equivalent. Build it with Template::new, register extra functions with func, parse a source with parse, then render it against a Value any number of times. The parsed node tree stays private, captured inside renderer.

    TemplateEntry

    One template: the name a user types, the kind of spec it reads, one sentence on what it produces, and the directory under the template home it lives in.

    TemplateError

    A template parse or execution failure, carrying a human-readable message.

    TreeFile

    One file of a generated project tree: where it goes, what is in it, and whether regenerating the tree is allowed to overwrite it.

    TypeDef

    A named message schema declared by a type Name { field: Type … } block, generated into a MoonBit struct (goctl's request/response types). embeds names the blocks it inlined — a bare Base line — whose fields belong to it as if they had been written out; an anonymous nested block instead becomes a TypeDef of its own, named after the two blocks, with a field pointing at it.

    Value

    Dynamic template data. .Field lookups resolve against Dict; range iterates List (index/element) or Dict (sorted key/value). This is the explicit stand-in for the arbitrary Go values goctl reflects over.

    bug_report

    fn bug_report(title : String, version : String, rows : Array[(String, String)]) -> String

    mctl bug: a report with the environment filled in and the questions a maintainer would otherwise have to ask. title may be empty, in which case the report asks for one.

    cache_file_name

    fn cache_file_name(service : String) -> String

    The file a cached layer lands in: greet → greet_cache.mbt, beside the <service>_model.mbt whose records it caches.

    client_file

    fn client_file(spec :
    Spec
    , lang :
    Lang
    ) -> (String, String)

    The file name and the source of the client lang for spec, which is what mctl api ts <file.api> writes.

    client_file_name

    fn client_file_name(lang :
    Lang
    , service : String) -> String

    The file a client for service lands in: greet_client.ts and friends, named after the service so two specs generated into one directory do not collide.

    command_names

    fn command_names() -> Array[String]

    The words a shell should complete at the first position.

    commands

    Every command mctl accepts, in the order help prints them: the generators, the scaffolds, the spec tools, then the housekeeping.

    completion

    fn completion(shell : String) -> String

    A shell completion script for shell, or "" for a shell mctl has none for. Completion is generated from commands and subcommands, so a new command is completable the moment it is in the table.

    config_get

    fn config_get(c :
    Config
    , key : String) -> String?

    The value of one key, or None when mctl has no such key.

    config_keys

    fn config_keys() -> Array[String]

    The keys config get and config set accept.

    config_set

    fn config_set(c :
    Config
    , key : String, value : String) ->
    Config

    c with one key set. template appends — a config may name several — while a scalar key replaces what was there. An unknown key is returned unchanged, so a caller checks the key against config_keys first.

    config_text

    fn config_text(c :
    Config
    ) -> String

    The configuration as the file it is stored in, with a comment saying what the file is for and where the keys come from.

    default_config

    The file mctl writes when there is none yet: the bundled library, the default branch, the gozero spelling.

    default_routes

    Every RPC of proto as the default route: POST /<Service>/<Method> — the gRPC path with its package dropped, because a gateway in front of one package does not need the package repeated in the URL. A POST is the method gRPC itself uses, so the mapping is the one that always works.

    deps

    fn deps(source : String, from? : String) -> Array[String]

    The files source imports, each resolved against from — the path source itself was read from. A caller that can read files walks a multi-file spec with this: read a file, follow its deps, and hand everything it collected to parse_all.

    env_report

    fn env_report(version : String, rows : Array[(String, String)]) -> String

    mctl env: one line per setting, then a hint at what to change. rows pairs a setting with its value and, in parentheses, where it came from.

    find_template

    fn find_template(name : String) ->
    TemplateEntry
    ?

    The template called name, or None.

    format

    fn format(source : String) -> String

    Format an .api source. Nesting is read off braces and off the two blocks that open with a parenthesis — a multi-line @server ( and a type ( group; blank lines collapse to one; the result ends in exactly one newline. Comments and struct tags survive verbatim, and the function is idempotent: formatting an already formatted spec returns it unchanged.

    gateway_file_name

    fn gateway_file_name(proto_path : String) -> String

    The file a gateway lands in: the .proto's own name, without its directory and without its extension — proto/greet.proto → greet_gateway.mbt. A .proto may declare several services, so the file name comes from the file rather than from one of them.

    gen_files_to_json

    fn gen_files_to_json(files : Array[
    GenFile
    ]) -> Json

    Serialise a list of generated files into the plugin-reply JSON — the writer a MoonBit plugin uses on its stdout. Emits the {"files": [...]} envelope.

    generate

    Generate compilable moonapi scaffolding from a spec: a MoonBit struct for every type block, a build_app that wires every route to its handler, plus a stub for each handler. The routing part depends only on moonbitstack/moonapi and moonbitstack/moonasgi; the emitted schemas are dependency-free.

    Pass template to render the spec through the runtime template engine (see generate_with) instead of the built-in generator; omit it for the default scaffold.

    generate_agent

    fn generate_agent(spec :
    Spec
    ) -> String

    Generate a moonkoog agent scaffold from spec. Every route becomes a Tool whose descriptor advertises the operation (a single string input parameter to fill out) and whose execute_raw is a stub, and build_agent assembles them into an AIAgent with the service's name in its system prompt. The output compiles against moonbitstack/moonkoog; fill in each execute_raw to make it run.

    generate_cached

    fn generate_cached(spec :
    Spec
    ) -> String

    Generate the cached CRUD layer for every table of spec: for a type block with a field to look records up by, the key function plus read-through find, invalidating write, and evict. A block with no single-valued field has no key to cache under, so it gets no layer and the file says why.

    The output needs no import and no driver: the record types it names are the ones mctl model generates for the same spec.

    generate_client

    Generate the client of lang for spec. The result is one file: the models the spec declares, then the client class, in that order, so a reader meets the data before the calls that carry it.

    generate_crud

    fn generate_crud(tables : Array[
    DdlTable
    ]) -> String

    Generate a moonorm data-access layer from parsed DDL: for every table, the record struct, its @moonorm.Model (columns + from_row + to_columns), a @moonorm.Table descriptor, an up/down migration pair, and typed CRUD (insert/all, plus find_by_id/update/delete_by_id when the table has a single-column primary key). The output compiles against moonbitstack/moonorm + moonbitstack/moondb; a consuming package imports both. This is the .sql-schema counterpart of goctl's model mysql ddl.

    generate_crud_from_ddl

    fn generate_crud_from_ddl(source : String) -> String

    Parse a SQL DDL script and generate its moonorm data-access layer in one step.

    generate_crud_from_reflection

    fn generate_crud_from_reflection(cols : Array[
    ReflectedColumn
    ]) -> String

    Generate a moonorm data-access layer (models + typed CRUD) straight from a live schema's reflected columns — the in-memory counterpart of generate_crud_from_ddl. The reflect sub-package produces the ReflectedColumns from a real connection.

    generate_doc

    fn generate_doc(spec :
    Spec
    , version? :
    DocVersion
    , title? : String, api_version? : String) -> String

    Generate an OpenAPI / Swagger document from spec, stringified with two-space indentation. version picks the dialect (Swagger 2.0 / OpenAPI 3.0 / 3.1).

    generate_gateway

    Generate the gateway for proto with routes: the route table, the gateway itself, and the two entry points — handle, which takes a JSON body already parsed, and handle_text, which parses the body a request carried.

    Status codes are the four an HTTP client cares about: 200 for a reply, 400 for a body that is not JSON, 404 for a path no route answers, and 502 for a call the upstream failed. A gRPC status does not leak through: an HTTP client reads it where it is meaningful — in the reply body.

    generate_gql

    fn generate_gql(spec :
    Spec
    ) -> String

    Generate a moongql code-first schema from spec's type blocks. Each type becomes an object type with its fields mapped to GraphQL types, and the Query root has ping: String! plus a <type>(id: ID!): <Type> lookup per type.

    Only the schema is generated: the resolvers are the author's code. A root field needs one; an object field without one reads its parent's member of that name.

    generate_gql_tree

    The layered GraphQL tree for spec under dir: internal/schema with build_schema() (rewritten on every run), internal/logic with the resolvers and a test that queries ping, internal/config/internal/svc (seeded once), and the entry point serving graphql_app on 12000.

    Hand the result to tree_plan to keep what the author has filled in.

    generate_grpc

    fn generate_grpc(proto :
    Proto
    ) -> String

    Generate a moonrpc service stub from a parsed .proto: a MoonBit struct per message, a @moonrpc.Method descriptor per RPC (its gRPC :path is /package.Service/Method), a <Service>Server handler-registration struct (one synchronous-core handler field per RPC, each returning its reply or a gRPC @moonrpc.Status), and a <service>_methods() listing. The output compiles against moonbitstack/moonrpc.

    generate_markdown

    fn generate_markdown(spec :
    Spec
    , title? : String) -> String

    The whole reference: the service, a route table, then a table per type. title replaces the service name in the heading for a reader who wants the document to say what the service is for.

    generate_mcp_tree

    fn generate_mcp_tree(module_ : String, style? :
    Style
    , dir? : String) -> Array[
    TreeFile
    ]

    The MCP server tree for module module_ under dir: the shared module, configuration and context, internal/tools with the annotated functions, their generated registration and a test, and the build rule that keeps the registration in step with the functions.

    generate_migrations

    fn generate_migrations(spec :
    Spec
    ) -> String

    The migration file for a spec: one CREATE TABLE per type. service names the file and the header, so two specs migrated into one directory do not look alike.

    generate_model

    fn generate_model(spec :
    Spec
    ) -> String

    Generate a moonorm data layer for every type block in spec. A block whose fields are all storable yields: the model struct; a <table>_model : @moonorm.Model[T] built with @moonorm.Model::new — declared columns (an id column is the primary key), a from_row decoder, and the to_columns projection an INSERT binds; a <table>_table : @moonorm.Table descriptor; and a <table>_up / <table>_down migration pair (create the table idempotently / drop it). This is the explicit MoonBit stand-in for a SQLAlchemy declarative class plus an Alembic revision, which reflection would otherwise synthesise. A block with a non-storable field (a slice, map, or nested message) still gets its struct and a plain @moonorm.Table descriptor.

    The output compiles against moonbitstack/moonorm + moonbitstack/moondb; a consuming package imports both.

    generate_mongo

    fn generate_mongo(spec :
    Spec
    ) -> String

    Generate a Mongo document model for every type block of spec: a Doc struct that is the document as stored, and a Repo over the Collection port with insert, all, and — when the spec has a key field — find, replace and delete.

    The output compiles against MoonBit core: Json and ToJson are builtin, and the file imports moonbitlang/core/json as @json for from_json. A driver supplies the port; without one, nothing here needs a database to typecheck.

    generate_rpc_tree

    The layered gRPC tree goctl writes for a .proto, in MoonBit, under dir: pb/ with the messages, codecs and handler tables (rewritten on every run), internal/logic with one function per RPC and internal/config/internal/svc (seeded once), internal/server wiring the logic into each service, client/ with a typed client per service, and the entry point serving it all on 12000.

    Hand the result to tree_plan to keep what the author has filled in.

    generate_tree

    Generate the layered project tree goctl writes, in MoonBit: the service entry point and its moon.mod, etc/<service>.yaml, internal/config, internal/svc, internal/types, internal/handler (the generated routes plus a stub per handler, under internal/handler/<group>/ for a grouped route), internal/logic, internal/middleware for every middleware an @server block named, and a moon.pkg for each of those packages.

    style (default gozero) names the files and the handler, logic and middleware entry points inside them. Names the spec itself chose — the type blocks and their fields — are left as written, since a MoonBit type name has to keep its capital. dir puts the whole tree under a directory.

    Each file says whether a regeneration may overwrite it; hand the result to tree_plan to apply that.

    generate_with

    fn generate_with(spec :
    Spec
    , template_source : String) -> String raise
    TemplateError

    Generate code from a Spec with a caller-supplied template instead of the built-in generator — the runtime-template path goctl exposes for custom scaffolds. Equivalent to render(template_source, spec_to_value(spec)).

    has_errors

    fn has_errors(diagnostics : Array[
    Diagnostic
    ]) -> Bool

    Whether any finding is an Error — what makes mctl api validate exit non-zero.

    has_method

    fn has_method(proto :
    Proto
    , target : String) -> Bool

    Whether proto declares the gRPC method target (/package.Service/Method), so a hand-written route that names a method the spec does not have is caught here rather than at the first request.

    markdown_file_name

    fn markdown_file_name(service : String) -> String

    The file name a Markdown reference lands in: greet → greet_api.md.

    migrate_config

    fn migrate_config(text : String) -> (String, Array[String])

    A v1 configuration — the uppercase, environment-style keys mctl read before it had a file — rewritten as the current shape. Returns the new text and one line per key that changed, so mctl config migrate can say what it did.

    migrate_file_name

    fn migrate_file_name(service : String) -> String

    The name a migration file takes: greet → greet_migrate.sql.

    mod_pins

    fn mod_pins(mod : String) -> Array[(String, String)]

    The version strings of mod, as (name, version) pairs: every pin the file carries, in order.

    mongo_file_name

    fn mongo_file_name(service : String) -> String

    The file a document model lands in: greet → greet_mongo.mbt, named after the service so two specs generated into one directory do not collide.

    openapi_document

    fn openapi_document(spec :
    Spec
    , version? :
    DocVersion
    , title? : String, api_version? : String) -> Json

    Build the OpenAPI / Swagger document for spec as a Json value. Routes fold into paths → HTTP method → operation (with operationId, any :name path parameters, and a 200 response); every type block becomes a component schema (definitions in 2.0, components/schemas in 3.x). version selects the document dialect; title/api_version fill the info block.

    parse

    Read a goctl-style .api description into a Spec. Grammar (one statement per line):

    syntax = "v1" info ( title: "greet" version: "v2" ) type LoginReq { name: string } @server ( group: user prefix: /api/v1 middleware: Log ) service greet { @doc "health check" @handler ping get /ping @handler login post /login (LoginReq) returns (LoginResp) get /legacy legacy_handler "the inline form" }

    A type block declares a message schema (its fields become a MoonBit struct); field types use goctl's Go spellings (string, int64, []T, map[K]V, …). An @server( … ) block annotates every route of the service block that follows it: the routes carry its prefix in their paths and its Group on their group. Verbs are lower-cased, blank lines and // comments are dropped.

    Any other line raises a SpecError naming it — a misspelt verb or a missing brace would otherwise generate a quietly truncated program. Use parse_lenient for a best-effort read of a spec that is still being written.

    parse_all

    fn parse_all(source : String, files : Map[String, String], from? : String) ->
    Spec
    raise
    SpecError

    Parse source — the spec at from — together with everything it imports, taken from files (a map from resolved path to source, the shape deps resolves to). What the imports describe is merged in first, in the order they were declared, then the importing file's own: types, routes and @server groups all end up in one Spec. A file is scanned once however many times it is imported, so a cycle terminates.

    The service name is the last one declared, so the importing file's own service block wins and a spec that does nothing but import still takes the name from what it imported.

    An import naming a file that is not in files raises Missing; a file that does not parse raises its first complaint the way parse does, with the file it came from named alongside the line.

    parse_config

    fn parse_config(text : String) ->
    Config

    Read a configuration. A key mctl does not know is kept out of the result rather than refused, so a file written by a newer mctl still loads. An empty or unreadable-text file is the default configuration.

    parse_ddl

    fn parse_ddl(source : String) -> Array[
    DdlTable
    ]

    Parse a SQL DDL script into its CREATE TABLE definitions. Recognises CREATETABLE [IF NOT EXISTS] name ( … ) (quoted or bare name), skipping any other statement. Column types, primary keys (column- or table-level), NOT NULL and DEFAULT clauses are captured; everything needed to emit a moonorm model.

    parse_dsn

    Parse a datasource DSN into a DataSource. Recognised forms:

    • SQLite: sqlite:PATH, sqlite://PATH, sqlite3:PATH, file:PATH, the literal :memory:, or a bare path ending in .db / .sqlite / .sqlite3.
    • PostgreSQL: postgres://[user[:password]@]host[:port][/database][?…] (and the postgresql:// spelling). The port defaults to 5432, the user to postgres; the database name is required.

    Raises BadDsn on an unrecognised scheme or a PostgreSQL URL missing its host or database.

    parse_gen_files

    Parse a plugin's stdout into the files to write. Two shapes are accepted: a bare array [{path, content}, …], or an object {"files": [ … ]} (goctl-style envelope). An element missing path or content is skipped. The list preserves the plugin's order.

    parse_lenient

    fn parse_lenient(source : String) ->
    Spec

    parse without the diagnostics: a line the parser cannot read is skipped and whatever the spec does describe is returned. For a caller that generates from a half-written spec on every keystroke; anything that reports to a user should call parse.

    parse_proto

    fn parse_proto(source : String) ->
    Proto

    Parse a minimal .proto (proto3). Recognises the package declaration, service blocks with their rpc methods (including stream), message blocks with scalar/repeated/map fields, and top-level enum declarations. syntax, import, and option are skipped.

    syntax = "proto3"; package greet; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply); } message HelloRequest { string name = 1; } message HelloReply { string message = 1; }

    parse_route_map

    The routes a mapping file declares, in the order it declares them. A line is VERB /path /package.Service/Method; # starts a comment, and a blank line is skipped. A line naming a method the .proto does not declare, or one that streams, is left out — skipped reports it so the caller can say which.

    pin_module

    fn pin_module(mod : String, name : String, version : String) -> (String, String?)

    mod with name pinned at version, or mod unchanged when it does not pin that name. Returns the new text and the change it made, if any.

    pinned_version

    fn pinned_version(mod : String, name : String) -> String?

    The version name is pinned at in mod, or None when the file does not pin it.

    pins

    let pins : Map[String, String]

    The version of each library generated code imports.

    These are the versions this repository's CI scaffolds a project against and builds, so a generated project is promised to compile with them. A project that wants a later release edits its own moon.mod.

    plugin_argv

    fn plugin_argv(invocation : String) -> (String, Array[String])

    Split a plugin invocation into the program to run and the arguments to run it with, the way goctl reads its -plugin value: the first whitespace-separated word is the executable, the rest are its argv. A quoted word keeps its spaces.

    plugin_from_json

    Read the request a plugin was handed on stdin. Missing members degrade to empty rather than raising, so a hand-written request still parses.

    plugin_plan

    What a plugin run will actually write: the files whose path is safe, and a note per file that was refused or superseded. A later file for a path an earlier one already claimed is refused rather than silently overwriting it — a plugin that emits the same file twice has a bug, and the report should say so rather than leave a coin-flip in the output.

    plugin_request

    fn plugin_request(spec :
    Spec
    , api_file_path? : String, style? : String, dir? : String) -> String

    The plugin request as a two-space-indented JSON string, ready to write to the plugin's stdin: goctl's four keys — the spec under Api, plus the file it was read from, the --style template and the output directory, which is everything a plugin needs to decide where its output goes and what to call it.

    port

    let port : Int

    The port a scaffolded service listens on.

    12000, the one every server in this family defaults to: HTTP, WebSocket and h2c share it because one listener tells them apart, so a generated manifest and a generated configuration name the same number without anyone saying which they meant.

    proto_reserved_conflicts

    fn proto_reserved_conflicts(m :
    ProtoMessage
    ) -> Array[String]

    The names of m's fields (oneof members included) that reuse a reserved field number or name — protobuf forbids reusing either, and this surfaces an accidental reuse. Empty when the message is clean.

    render

    Parse and render source against data in one call, with the default function library.

    resolve_import

    fn resolve_import(from : String, path : String) -> String

    An import path resolved against the file that declared it: taken relative to that file's directory, with ./.. folded away. An already-absolute path is left where it points.

    safe_path

    fn safe_path(path : String) -> Bool

    Whether a path a plugin supplies lands inside the directory mctl writes to. A plugin runs as its own process and its reply is arbitrary text, so the one thing mctl must not do is believe it: ../../.ssh/authorized_keys, /etc/passwd and C:/Windows/system32/… are all accepted by a naive writer, and all three write where a code generator has no business writing. Only a relative path with no .. segment is allowed; a path with one is refused and named in the run's report.

    scaffold_api

    fn scaffold_api(name : String) -> Array[
    GenFile
    ]

    Scaffold a moonapi service under <name>/: goctl's api new. The layered tree of generate_tree for a sample spec with GET /ping, a test calling it, and an entry point serving it on mooncat.

    scaffold_docker

    fn scaffold_docker(name : String, port? : Int) -> Array[
    GenFile
    ]

    Scaffold container files for a native mctl-generated service: a Dockerfile (a MoonBit build stage that produces the native binary, then a slim runtime stage that runs it) and a .dockerignore. name is the binary/image name, port the port the service listens on. goctl's goctl docker.

    scaffold_gql

    fn scaffold_gql(name : String) -> Array[
    GenFile
    ]

    Scaffold a moongql service under <name>/: the layered tree of generate_gql_tree for a sample spec, with a ping resolver and a test querying it.

    scaffold_kube

    fn scaffold_kube(name : String, port? : Int, replicas? : Int) -> Array[
    GenFile
    ]

    Scaffold a Kubernetes deployment for the service under deploy/: a Deployment (replicas pods of the <name> image, a container port, and a liveness probe on /ping) and a Service exposing it. goctl's goctl kube deploy.

    The manifests are built as values and written by moonyaml, not joined out of string fragments. A generator that joins fragments has the indentation in its string literals, where a missed space is invisible, and quotes nothing — so the first service name with a colon in it emits a file Kubernetes reads as something else.

    scaffold_mcp

    fn scaffold_mcp(name : String) -> Array[
    GenFile
    ]

    Scaffold an MCP server under <name>/: annotated tools, a resource and a prompt, a test calling the tool, and an entry point serving them over Streamable HTTP or, with --stdio, over stdio.

    scaffold_model

    fn scaffold_model(name : String) -> Array[
    GenFile
    ]

    Scaffold a moonorm data-layer project under <name>/: a moon.mod (depending on the published moonorm + moondb), a sample schema.sql, its generated models + CRUD in src/model.mbt (through the same generate_crud_from_ddl as gen crud), and a README. goctl's goctl model … new.

    scaffold_rpc

    fn scaffold_rpc(name : String) -> Array[
    GenFile
    ]

    Scaffold a moonrpc service under <name>/: goctl's rpc new. The layered tree of generate_rpc_tree for a sample proto with a Ping that answers pong, and a test serving it and calling it through the typed client.

    spec_from_json

    fn spec_from_json(j : Json) ->
    Spec

    Reconstruct a Spec from the request JSON — the reader a plugin written in MoonBit uses on its stdin. The whole request ({Api, ApiFilePath, Style, Dir}) and a bare spec are both accepted, so a plugin that wants nothing but the routes need not unwrap the envelope. Missing or mistyped members degrade to empty rather than raising, so a partial request still parses.

    spec_to_json

    fn spec_to_json(spec :
    Spec
    ) -> Json

    Serialise a Spec into the plugin request JSON. The shape mirrors the Spec itself — service, routes (verb/path/handler/summary, plus the group the route was declared under), types (name plus fields of name/type) and groups — so a plugin in any language parses it with an ordinary JSON reader. type_ is written as type, the name a caller expects.

    spec_to_value

    Expose a parsed .api Spec as template Value data: a Dict with service (string), routes (list of {verb, path, handler, summary,hasSummary, group, hasGroup}), types (list of {name, fields:[{name,type}]}), groups (list of the @server blocks) and hasTypes (bool). This is what the template-driven generator ranges over.

    subcommands

    fn subcommands(name : String) -> Array[String]

    The second word of a command that takes a sub-command, for a completion script that completes two levels deep.

    swagger_ui_stub

    fn swagger_ui_stub(spec_url? : String, title? : String) -> String

    A self-contained Swagger UI page for the document served at spec_url. The same stub moonapi ships, so a generated service and a live app render alike.

    tables_from_reflection

    Fold reflected columns into DdlTables, grouping by table in first-seen order and mapping each raw SQL type onto its MoonBit scalar (the leading type keyword drives the mapping, so character varying and VARCHAR(255) both land on String). The result feeds generate_crud, so a live schema and a .sql file reach the same model generator.

    template_detail

    mctl template show <name>: one entry, and where it sits in the library.

    template_table

    fn template_table() -> String

    mctl template list: a table a reader scans rather than a paragraph they read.

    templates

    The bundled library, in the order it is listed.

    templates_of

    The templates of one kind — the ones a gen of that kind may pick.

    tree_plan

    The files of tree a run should actually write, given a way to ask whether a path is already on disk. A file moonctl owns (Always — the routes and the types) is always written; anything else is written only when it is not there yet, so a second run refreshes what follows the spec and leaves every handler and logic body the author has since filled in exactly as it is.

    upgrade_pins

    fn upgrade_pins(mod : String, modules : Array[String], version : String) -> (String, Array[String])

    mctl upgrade <version>: every name in modules moved to version, plus one line per name that changed. A name the file does not pin is left alone and reported as skipped — pinning it would be a change the user did not ask for.

    usage_text

    fn usage_text(version : String) -> String

    The mctl help page: what mctl is, then one line per command, then the flags every generator shares.

    validate

    Check a parsed spec: the routes, the types and the places one names the other. Findings come in spec order — routes first, then types — so a report reads the way the file does.

    validate_files

    fn validate_files(source : String, files : Map[String, String], from? : String) -> Array[
    Diagnostic
    ]

    Validate one file of a spec together with everything it imports, files being every source the walk has read keyed by the path it was read from. Without this a spec that embeds a type from another file is told that the type does not exist — true of the file on its own, false of the spec it belongs to.

    validate_source

    fn validate_source(source : String) -> Array[
    Diagnostic
    ]

    Validate a spec given as source. A spec the parser cannot read is reported as the one finding it is — syntax, naming the line — rather than raising, so a caller checks a file the same way whether it is broken or merely wrong.

    Source Files

    Powered by MoonBit

    Site sourceReport issueModulesSkillsBuild queueStatistics

    © 2026 mooncakes.io