You write the request.jmapc writes the client.
JMAP is the IETF standard for mail, contacts and calendars: one JSON API over HTTP in place of IMAP, CardDAV and CalDAV.
{
"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)
}jmapc is a compiler for it. 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.
Verification
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/queryLanguages
Three 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.
Coverage
Everything JMAP has standardised
Not a subset chosen to make a demo work: the protocol as the standards bodies have published it so far.
- 15 capabilities
Every one IANA lists — mail, submission, contacts, calendars, principals, sieve, quota, blob, push and the rest — is in the data model a request is checked against. One that is not, a vendor’s own among them, is described in a schema file and checked like any other.
- 81 methods
Across twenty-one types, all generated and checked the same way.
Email/query,CalendarEvent/copyandSieveScript/validateare the same kind of thing to jmapc.
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