///|
/// The tiny line-delimited JSON protocol spoken between the CLI and the
/// daemon over a loopback TCP socket.
///
/// Using TCP (rather than a Unix socket) keeps the transport portable and
/// available in `moonbitlang/async` without extra FFI. The socket is bound to
/// `127.0.0.1` only and the daemon publishes its ephemeral port in
/// `~/.pm3/daemon.port`.
///|
/// Command names understood by the daemon.
pub let cmd_ping : String = "ping"
///|
/// Answered with the daemon's build; a client older than this command gets
/// "unknown command" back, which also means the daemon is not its build.
pub let cmd_version : String = "version"
///|
pub let cmd_list : String = "list"
///|
pub let cmd_start : String = "start"
///|
/// Start every app declared in a config file. `args[0]` is a JSON array of
/// `App` records, already resolved by the client.
pub let cmd_start_apps : String = "start_apps"
///|
pub let cmd_stop : String = "stop"
///|
pub let cmd_restart : String = "restart"
///|
pub let cmd_delete : String = "delete"
///|
pub let cmd_describe : String = "describe"
///|
pub let cmd_flush : String = "flush"
///|
pub let cmd_reload_logs : String = "reloadLogs"
///|
pub let cmd_save : String = "save"
///|
pub let cmd_resurrect : String = "resurrect"
///|
pub let cmd_kill : String = "kill"
///|
pub let cmd_get_logrotate : String = "get_logrotate"
///|
pub let cmd_get_config = "get_config"
///|
pub let cmd_set_logrotate : String = "set_logrotate"
///|
pub let cmd_reset : String = "reset"
///|
/// A request from the CLI. Kept deliberately generic so new commands do not
/// require protocol version bumps.
pub(all) struct Request {
cmd : String
args : Array[String]
opts : Map[String, String]
flags : Map[String, Bool]
} derive(Eq, Debug, ToJson, FromJson)
///|
/// A reply from the daemon. `data` carries an optional JSON payload.
pub(all) struct Response {
ok : Bool
msg : String
data : Json
} derive(Debug, ToJson, FromJson)
///|
/// A request with a command name and no arguments.
pub fn Request::new(cmd : String) -> Request {
{ cmd, args: [], opts: Map([]), flags: Map([]), }
}
///|
/// A request with positional arguments.
pub fn Request::with_args(cmd : String, args : Array[String]) -> Request {
{ cmd, args, opts: Map([]), flags: Map([]), }
}
///|
/// A successful response with no payload.
pub fn Response::success(msg : String) -> Response {
{ ok: true, msg, data: Json::null(), }
}
///|
/// A successful response carrying a JSON payload.
pub fn Response::success_data(msg : String, data : Json) -> Response {
{ ok: true, msg, data, }
}
///|
/// A failed response.
pub fn Response::failure(msg : String) -> Response {
{ ok: false, msg, data: Json::null(), }
}
///|
/// Encode a request as a single newline-terminated line.
pub fn encode_request(request : Request) -> String {
@json.to_json(request).stringify() + "\n"
}
///|
/// Encode a response as a single newline-terminated line.
pub fn encode_response(response : Response) -> String {
@json.to_json(response).stringify() + "\n"
}
///|
/// Decode one request line.
pub fn decode_request(line : String) -> Request? {
Some(@json.from_json(@json.parse(line))) catch {
_ => None
}
}
///|
/// Decode one response line.
pub fn decode_response(line : String) -> Response? {
Some(@json.from_json(@json.parse(line))) catch {
_ => None
}
}