You write the request.jmapc writes the client.
jmapc is a compiler for JMAP. The request is the JSON the specification already defines; jmapc checks it against the specification and writes a client that can only call it correctly.
{
"methodCalls": [
["Email/query", {
"filter": {"inMailbox": "{{mailboxId}}"},
"limit": "{{limit}}"
}, "search"],
["Email/get", {
"#ids": {"resultOf": "search", "name": "Email/query", "path": "/ids"},
"properties": ["id", "subject", "from", "receivedAt"]
}, "fetch"]
],
"_returns": "fetch"
}jmapc generateres, err := client.ListInboxEmails(ctx, c, client.ListInboxEmailsParams{
MailboxID: inbox,
Limit: 25,
})
if err != nil {
return err
}
for _, email := range res.List {
fmt.Println(email.ReceivedAt, email.From[0].Email, *email.Subject)
}A request that is wrong never reaches a server
Every request is read against the specification before a line of code is generated — as you type, when you build, and against a running server when you ask. A misspelling comes back with the name you meant.
requests/BadQuery.jmap.json: methodCalls[0].arguments.filter.hasAttachmnt: EmailFilterCondition has no property "hasAttachmnt"
did you mean "hasAttachment"?
requests/BadQuery.jmap.json: methodCalls[1].arguments.#ids.name: the referenced call is Email/query, but the reference names Email/get
call "c0" invokes Email/queryThree languages, and almost nothing to install
The runtime is generated alongside the requests rather than shipped as a library, so what a project takes on is the generated directory and little else.
- Go
The generated package uses the standard library and nothing outside it. Transmission is an
http.Clientyou already have.- Rust
serdeandserde_json, and no more: no HTTP stack, no TLS backend and no async runtime come with it. Transmission is aTransportyou implement over whichever client the program already uses.- TypeScript
No dependencies at all. The only thing the platform has to provide is
fetch.
A compiler, not a wrapper
These five all follow from that one difference.
- No API of its own
The request file is JMAP. What you learn is the specification, and it is the same knowledge in all three languages.
- A response shaped like the question
The result carries the properties the request asked for and nothing else. Ask for one more and the type grows; ask for one that does not exist and the build stops.
- Errors at every level JMAP reports them
A failed request, a failed method call, a /set that answers 200 with a refusal — the generated code tells them apart so the caller does not have to.
- Push and paging as loops, not plumbing
A request marked as watched follows the changes the server reports. One marked as paged reads a result the server returns a part at a time.
- Tools around the edges
A command that sends a request to a real server, a JMAP server to test your own code against, and a schema file for the capabilities jmapc does not know yet.
Install
The client is generated with go generate, so record the tool in the module that uses it. A Rust or TypeScript project has no Go toolchain to run go tool with, and takes a binary from the releases instead.
go get -tool github.com/linyows/jmapc/cmd/jmapc