moonctl

A spec-driven code generator for MoonBit — parse a .api spec and emit compilable moonapi scaffolding, the way goctl does for Go. Pure logic, no runtime dependencies, verified to build against real moonapi.

CItestsGitHublicense
$moon add moonbitstack/moonctl

The contract at a glance

let spec = @moonctl.parse(source)     // .api source -> Spec { service, routes }
let code = @moonctl.generate(spec)     // -> compilable moonapi scaffold (String)

// the generated build_app + handler stubs build against real moonapi + moonasgi.

§Codegen

The .api spec parser and the moonapi scaffold generator - parse turns source into a Spec; generate emits compilable build_app + handler stubs.

item
suberror 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.

struct
struct 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.

struct
struct 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").

struct
struct 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.

struct
struct 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.

struct
struct 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.

fn
fn parse(source : String) -> Spec raise SpecError

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.

fn
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.

fn
fn generate(spec : Spec, template? : String) -> String raise TemplateError

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.

§Multi-file specs

import resolution - deps names the files a spec pulls in, resolved against the file that wrote them, and parse_all merges an entry spec and everything it imports into one Spec (imported types, routes and @server groups first).

fn
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.

fn
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.

fn
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.

§Naming style

goctl's --style template - Style::parse reads <before>GO<through>ZERO<after> and Style::format spells a name through it, so gozero, goZero, go_zero and Go#zero each name the same thing their own way.

item
suberror 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).

struct
struct Style

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

fn
fn Style::gozero() -> Style

goctl's default style, gozero: every word lower-cased and run together.

fn
fn Style::parse(s : String) -> Style raise StyleError

Read a --style template. It must contain GO and then ZERO — in any case, with anything before, between and after them: `` Style::parse("gozero").format("welcome_to_go_zero") // welcometogozero Style::parse("goZero").format("welcome_to_go_zero") // welcomeToGoZero Style::parse("go_zero").format("welcome_to_go_zero") // welcome_to_go_zero Style::parse("Go#zero").format("welcome_to_go_zero") // Welcome#to#go#zero ` The GO marker's own casing (go, GO or Go) says how the first word is spelled, ZERO's says how every later word is, and what stands between the two markers is what stands between the words. A template missing a marker (go, zero), holding them in the wrong order, or casing one of them any other way (gOZero, goZEro) raises StyleError`.

fn
fn Style::format(self : Style, name : String) -> String

Spell name in this style. name is split into words on _ and before each capital, the first word takes the GO marker's case and the rest take ZERO's, and they are joined with whatever stood between the markers.

§Project tree

The layered output - generate_tree emits the etc/ + internal/config + svc + types + handler + logic + middleware tree goctl writes, each file marked with whether a regeneration may overwrite it, and tree_plan applies that so a second run refreshes the routes and types and keeps every handler you wrote.

enum
enum 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.

struct
struct 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.

fn
fn generate_tree( spec : Spec, style? : Style, dir? : String = "") -> Array[TreeFile]

Generate the layered project tree goctl writes, in MoonBit: the service entry point and its moon.mod.json, 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.json 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.

fn
fn tree_plan( tree : Array[TreeFile], exists : (String) -> Bool) -> Array[GenFile]

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.

§Template engine

A runtime template engine (goctl's text/template equivalent): Value data, {{.Field}} interpolation, if/range/with, $variables, | pipelines and a function library. generate_with drives codegen from a custom template.

enum
enum 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.

item
suberror TemplateError

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

type
type FuncFn = (Array[Value]) -> Value raise TemplateError

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.

struct
struct 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.

fn
fn Template::new() -> Template

A fresh template preloaded with the default function library. Rendering before parse yields the empty string.

fn
fn Template::func(self : Template, name : String, f : FuncFn) -> Template

Register (or override) a pipeline function, returning self for chaining — the analogue of Go's Template.Funcs. Register before parse.

fn
fn Template::parse( self : Template, source : String) -> Template raise TemplateError

Parse source into this template's node tree, returning self for chaining. Raises ParseError on a syntax error (unclosed action, dangling {{end}}, …).

fn
fn Template::render( self : Template, data : Value) -> String raise TemplateError

Render the template against data, producing the output string. Raises ExecError on an execution fault (undefined variable/function, bad range target, …).

fn
fn render(source : String, data : Value) -> String raise TemplateError

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

fn
fn spec_to_value(spec : Spec) -> 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.

fn
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)).

§gRPC codegen

A minimal .proto (proto3) parser and the moonrpc service-stub generator - parse_proto turns source into a Proto; generate_grpc emits message structs, @moonrpc.Method descriptors and a <Service>Server handler skeleton that compiles against moonbitstack/moonrpc.

struct
struct ProtoField

One field of a protobuf message: its name, the (already MoonBit-mapped) type, and its wire number (the = N). repeated becomes Array[T] and map<K, V> becomes Map[K, V].

struct
struct 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.

struct
struct 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.

struct
struct ProtoEnumValue

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

struct
struct 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).

struct
struct 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).

struct
struct ProtoService

A protobuf service declaration and its RPC methods.

struct
struct Proto

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

fn
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.

fn
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; } ``

fn
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.

§ORM model codegen

The moonorm model generator - generate_model turns a Spec's .api type blocks into a model struct, a @moonorm.Model with declared columns plus from_row / to_columns closures, a @moonorm.Table descriptor and an up/down migration pair, all compiling against moonbitstack/moonorm + moondb.

fn
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.

§DDL to CRUD codegen

The SQL DDL front end - parse_ddl reads CREATE TABLE statements (types, primary keys, NOT NULL, defaults) and generate_crud emits a moonorm model plus typed CRUD (find_by_id / insert / update / delete_by_id / all) with parameterised SQL, compiling against moonbitstack/moonorm + moondb.

struct
struct 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).

struct
struct DdlTable

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

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

Parse a SQL DDL script into its CREATE TABLE definitions. Recognises CREATE TABLE [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.

fn
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.

fn
fn generate_crud_from_ddl(source : String) -> String

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

§Struct tags

What a field's back-tick tag says - Field::bind reads whether the value comes from the body, the query string, a URL segment or a header, Field::json_name the wire name it arrives under, Field::mbt_name the MoonBit spelling, and optional / default_ / options / range the constraints that follow the name.

enum
enum 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:).

struct
struct 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 (( )).

fn
fn Field::bind(self : Field) -> Bind

Where this field's value is bound from: form: off the query string, path: out of a URL segment, header: off a request header, and everything else — an untagged field included — out of the request body.

fn
fn Field::json_name(self : Field) -> String

The name this field is carried under on the wire: the one its binding tag gives (json:"user_name"user_name, form:"page"page, path:"region"region), with the options after it stripped, falling back to the field's own name when it carries no tag.

fn
fn Field::mbt_name(self : Field) -> String

The MoonBit spelling of this field: snake_case, because a MoonBit struct field has to start lower-case — UserName is not one. Every generator that emits MoonBit names the field with this, so the struct, the row decoder and the column projection all agree; json_name is the wire side of the same field.

fn
fn Field::optional(self : Field) -> Bool

Whether the tag marked this field optional (or Go's omitempty): it may be absent, which is what keeps it out of a schema's required list.

fn
fn Field::default_(self : Field) -> String?

The default= this field's tag declared, written the way the spec wrote it, or None when it declared none.

fn
fn Field::options(self : Field) -> Array[String]

The values an options=a|b|c tag allows this field to take, empty when the tag declared no such list.

fn
fn Field::range(self : Field) -> Range?

The range=[lo:hi] bounds this field's tag declared, or None when it declared none. Either bound may be empty, which leaves that end open.

§Tag constraints

What those constraints turn into - render_checks emits with_defaults, which fills in every default= the spec declared, and check, which refuses a required field left empty, a value outside its range= and one the options= list does not allow.

§Plugin protocol

The external-plugin contract, spoken the way goctl speaks it - plugin_argv splits the invocation, plugin_request writes {Api, ApiFilePath, Style, Dir} to the plugin's stdin, and parse_gen_files reads the {path, content} files a plugin returns on stdout; plugin_from_json / spec_from_json and gen_files_to_json are the matching reader/writer a MoonBit plugin uses.

struct
struct GenFile

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

struct
struct 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.

fn
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.

fn
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.

fn
fn plugin_request( spec : Spec, api_file_path? : String = "", style? : String = "gozero", 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.

fn
fn plugin_from_json(j : Json) -> Plugin

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

fn
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.

fn
fn parse_gen_files(j : Json) -> Array[GenFile]

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.

fn
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.

§OpenAPI codegen

The OpenAPI / Swagger generator - generate_doc emits a Swagger 2.0, OpenAPI 3.0 or 3.1 document from a Spec (paths from the routes, component schemas from the type blocks), and swagger_ui_stub renders it.

enum
enum 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.

fn
fn openapi_document( spec : Spec, version? : DocVersion = OpenApi31, title? : String = "", api_version? : String = "0.1.0") -> 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.

fn
fn generate_doc( spec : Spec, version? : DocVersion = OpenApi31, title? : String = "", api_version? : String = "0.1.0") -> 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).

fn
fn swagger_ui_stub( spec_url? : String = "/openapi.json", title? : String = "API Docs") -> 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.

§Live datasource reflection

The model datasource front end - parse_dsn reads a sqlite: / postgres:// DSN, and tables_from_reflection / generate_crud_from_reflection fold a live schema's ReflectedColumns into the same moonorm model + CRUD the .sql path produces. The schema reading itself lives in the native reflect sub-package.

struct
struct PgTarget

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

enum
enum DataSource

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

item
suberror DataSourceError

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

fn
fn parse_dsn(dsn : String) -> DataSource raise DataSourceError

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.

struct
struct 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.

fn
fn tables_from_reflection(cols : Array[ReflectedColumn]) -> Array[DdlTable]

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.

fn
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.

§Project scaffolds

The nested project generators - scaffold_api / scaffold_rpc / scaffold_model emit a whole runnable project tree (moon.mod.json, a sample spec, generated source, README), and scaffold_docker / scaffold_kube emit a Dockerfile and a Kubernetes deployment, as goctl's api new / rpc new / docker / kube do.

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

Scaffold a runnable moonapi service project under <name>/: a moon.mod.json (depending on the published moonapi + moonasgi), a sample <name>.api spec, its generated routes+handlers in src/app.mbt (through the same generate the gen api command uses, so it compiles), and a README. This is goctl's goctl api new.

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

Scaffold a moonrpc service project under <name>/: a moon.mod.json (depending on the published moonrpc), a sample <name>.proto, its generated service stub in src/service.mbt (through the same generate_grpc as gen proto), and a README. goctl's goctl rpc new.

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

Scaffold a moonorm data-layer project under <name>/: a moon.mod.json (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.

fn
fn scaffold_docker(name : String, port? : Int = 8080) -> 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.

fn
fn scaffold_kube( name : String, port? : Int = 8080, replicas? : Int = 2) -> 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.

§GraphQL generator

A moongql schema and its resolver skeletons from the same .api spec the REST routes come from, so one description feeds both surfaces.

fn
fn generate_gql(spec : Spec) -> String

Generate a moongql code-first schema and resolver stubs from spec's type blocks. Each type becomes an object type with its fields mapped to GraphQL types; a Query root gains a <type>(id: ID!): <Type> lookup per type; and every field gets a resolver stub. The output compiles against moonbitstack/moongql; fill in the stub bodies to make it run.

§Agent scaffolding

Generating a moonkoog tool registry from the spec's types, so an .api description becomes tools an agent can call.

fn
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.

§Reflection helper

The small runtime the generated code leans on where MoonBit has no reflection: turning a declared descriptor into the JSON shape a caller sends.

fn
fn reflect_sqlite( db : @sqlite.SqliteDriver) -> Array[@moonctl.ReflectedColumn] raise @moondb.DbError

Read every user table's columns from a live SQLite database and return them as @moonctl.ReflectedColumns, ready for @moonctl.generate_crud_from_reflection. Tables are listed from sqlite_master (skipping SQLite's own sqlite_* tables), and each table's columns come from PRAGMA table_info — its name, declared type, notnull flag, and pk index. This is the SQLite half of goctl's model … datasource. The table name is interpolated into the PRAGMA (which cannot bind parameters); it comes from sqlite_master, i.e. the database's own schema, not from user input.

item
async fn reflect_postgres( conn : @pg.PgConn) -> Array[@moonctl.ReflectedColumn] raise @moondb.DbError

Read every table in the public schema of a live PostgreSQL database and return its columns as @moonctl.ReflectedColumns. The introspection joins information_schema.columns against the primary-key columns from table_constraints / key_column_usage, ordered by table and column position, so column order and primary keys survive. This is the PostgreSQL half of goctl's model pg datasource; it runs over @pg.PgConn's async wire protocol, so call it inside an event loop.