Skip to Content
Property sets

Property sets

Six requests reading the same fourteen properties of an Email get six record types, one per call, and a function that renders a message is written for one of them and converts from the other five. Name the shape instead, and the six answer with one type.

The sets sit in properties.json, beside the requests. It is not a JMAP request, so it does not carry the .jmap.json extension, and a project that names no set does not need the file at all.

{ "EmailSummary": { "doc": "EmailSummary is what a list of messages shows.", "type": "Email", "properties": ["id", "threadId", "subject", "from", "receivedAt", "preview", "hasAttachment"] }, "EmailWithFlags": { "extends": "EmailSummary", "properties": ["mailboxIds", "keywords"] } }
Member
typeThe data type the properties are selected from. Required, unless extends fixes it already.
propertiesThe property names, in the order the generated fields are written in. Required.
extendsThe set this one adds to. Optional.
docWhat the set is for, carried into the generated type’s comment. Optional.

A request asks for a set by name, in place of the list:

["Email/get", {"#ids": {"resultOf": "search", "name": "Email/query", "path": "/ids"}, "properties": "@EmailSummary"}, "fetch"]

What reaches the server is the list the set holds. A set is resolved while the code is generated and a {{name}} is filled in by the caller at run time, which is why the two are written differently.

The generated type takes the set’s name — EmailSummary, not ListInboxEmailsFetchEmail — so every request asking for the set answers with that one type, and a function written for it takes what any of them returned. The property list is written once as well: adding a property to what the application reads is one edit rather than one edit per request, and no request can be left behind still asking for less.

A set that extends another embeds it, so a function written for the base takes a record of the derived set without anything being copied between two structs:

func subjectOf(e client.EmailSummary) string { ... } subjectOf(changed.EmailSummary)

Rust flattens the base into the derived struct and TypeScript extends the interface, so the same holds in each.

A set’s name is the name of a type a caller writes, so a request cannot take it: a request file named EmailSummary.jmap.json beside a set of that name is reported rather than quietly renamed.

The type of a set holds the id where its data type has one, since a /get returns the id whatever it is asked for. Such a set cannot be asked of a method that makes no such promise, as Email/parse, whose emails have no id, does not; write the properties out there instead. A set of a type with no id, such as EmailBodyPart, holds none, and any method may ask for it.

A call that asks for a set narrows nothing else about the records it reads, so a set and bodyProperties in one call is reported. Narrowing the body parts changes the record type with it, since the fields referring to a body part refer to the narrowed type instead, and a set narrowed in one call and not in another would be two shapes under one name. Write the properties out in that call, or name a set for the body parts as well:

["Email/get", {"ids": "{{ids}}", "properties": ["id", "subject", "textBody"], "bodyProperties": "@BodyPartSummary"}, "fetch"]
Last updated on