Project and code generator for MoonBit services.
Moved on mooncakes from Lfan-ke/moonctl to moonbitstack/moonctl.
service greet {
get /ping ping "health check"
get /users/:id get_user
post /users create_user "create a user"
}// 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@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)
}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")
}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"`
}pub fn ListReq::with_defaults(self : ListReq) -> ListReq { … } // fills in every default=
pub fn ListReq::check(self : ListReq) -> String? { … } // the first field that fails$ 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.mbtlet 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| template | welcome_to_go_zero becomes |
|---|---|
| gozero (default) | welcometogozero |
| goZero | welcomeToGoZero |
| go_zero | welcome_to_go_zero |
| Go#zero | Welcome#to#go#zero |
import "user.api"
import (
"shared/base.api"
"../common.api"
)let spec = @moonctl.parse_all(source, files, from="spec/greet.api")@moonctl.parse("service greet {\n gett /ping ping\n}")
// SpecError — line 2: unknown verb "gett"| Package | What | goctl |
|---|---|---|
| api | the .api language: types, routes, tags, imports | api/parser, api/spec |
| gen | the moonapi service a spec describes, and the validation its tags ask for | api/gogen |
| template | the template engine, Go text/template syntax and semantics | text/template |
| style | --style naming templates | config |
| proto | moonrpc messages, codecs and handler tables from .proto | rpc |
| doc | OpenAPI and Swagger documents | api/docgen |
| gql | a moongql schema | — |
| model | moonorm models from a spec, from DDL, or from a live database | model |
| agent | a moonkoog agent | — |
| plugin | the plugin protocol | plugin |
| scaffold | project skeletons and the layered trees | api new, rpc new, gql new, mcp new, docker, kube |
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$ 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 environmentlet stub = @moonctl.generate_grpc(@moonctl.parse_proto(proto_src)) // -> moonrpc stub
let model = @moonctl.generate_model(@moonctl.parse(api_src)) // -> moonorm model + migrationlet code = @moonctl.generate_crud_from_ddl(sql_src) // -> moonorm model + CRUD (String)$ mctl model datasource sqlite:shop.db
$ mctl model datasource postgres://user:pass@localhost:5432/shop$ 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| Path | api | rpc | gql |
|---|---|---|---|
| <name>.mbt | serve the app on mooncat | serve the handler tables on GrpcServer | serve graphql_app on mooncat |
| etc/, internal/config, internal/svc | ✓ | ✓ | ✓ |
| generated | internal/types, internal/handler/routes.mbt | pb/, internal/server, client/<name>client.mbt | internal/schema |
| the author's | internal/handler/*, internal/logic/* | internal/logic/* | internal/logic/resolvers.mbt |
| test | the routes through TestClient | Ping over a real socket | a { ping } query |
let files = @moonctl.scaffold_rpc("echo") // -> Array[GenFile] to write
let tree = @moonctl.generate_rpc_tree(proto) // -> Array[TreeFile], each marked Always or Oncelet (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$ 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)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)| Language | File | Ports the caller fills in |
|---|---|---|
| TypeScript | greet_client.ts | Fetch (defaults to the global fetch), token |
| Dart | greet_client.dart | Send, token |
| Kotlin | greet_client.kt | Transport + Codec |
| Java | GreetClient.java | Transport + Codec, nested in the class |
$ 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.javalet sql = @moonctl.generate_migrations(spec)pub(all) struct Collection {
name : String
run : (MongoOp, Json, Json) -> Json raise MongoError // op, filter, document
}# greet.routes
post /v1/users /shop.UserService/Create
get /v1/stock /shop.StockService/Count| Command | What it does |
|---|---|
| mctl help | the 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|migrate | the key = value file mctl reads before it starts |
| mctl env | what 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 |
$ mctl config set home ~/templates
$ mctl config set branch release
$ mctl config get branch
releaselet 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")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_withfn cache_file_name(service : String) -> Stringfn completion(shell : String) -> Stringfn format(source : String) -> Stringfn gateway_file_name(proto_path : String) -> Stringfn generate_crud_from_ddl(source : String) -> Stringfn generate_doc(spec : Spec, version? : DocVersion, title? : String, api_version? : String) -> Stringfn markdown_file_name(service : String) -> Stringfn migrate_file_name(service : String) -> Stringfn mongo_file_name(service : String) -> Stringfn openapi_document(spec : Spec, version? : DocVersion, title? : String, api_version? : String) -> Jsonsyntax = "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"
}syntax = "proto3";
package greet;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest { string name = 1; }
message HelloReply { string message = 1; }fn pin_module(mod : String, name : String, version : String) -> (String, String?)fn pinned_version(mod : String, name : String) -> String?let port : Intfn resolve_import(from : String, path : String) -> Stringfn safe_path(path : String) -> Boolfn swagger_ui_stub(spec_url? : String, title? : String) -> Stringfn template_table() -> Stringfn usage_text(version : String) -> Stringfn validate_files(source : String, files : Map[String, String], from? : String) -> Array[Diagnostic]Install
Download zipProject and code generator for MoonBit services.