Subject: [Jmap] Using the result of one method as a subsequent method argument

From: Neil Jenkins <[email-masked]>
Date: Mon, 02 Oct 2017 10:50:11 +1100

Hi all,
This week I have been doing a lot of copy editing to remove a huge
amount of duplicative text, which made the spec harder to read, and more
likely to contain inconsistencies between methods. My goal is that the
JMAP Mail spec should just be:

 1. The data type definitions
 2. Which standard method types can be used with each data type (e.g.
    the Thread object is not mutable directly by the client so there is
    no setThreads method).
 3. Any changes to the standard method for that particular type (extra
    arguments etc.)This makes the spec *much* shorter, more understandable and easier to
implement. Doing this also makes it easier to see where methods for a
particular type deviate from the “standard” JMAP get/set calls. As I
looked at this, I realised most of the inconsistencies were in the
“fetchRecords” type arguments, used to reduce round trips.If instead we had a generic way to refer to the result of a previous
method call in the arguments of a subsequent request, we can eliminate
the special cases, and provide more power. Much like the unix composable-small-
things philosophy.Here’s a proposal of how this could work:

Any argument name may be prefixed with a ‘#’. This means the argument
value is a back-reference to the output of a previous method call rather
than the real value. The value is a *ResultReference* object as
described below. When processing a method call, the server MUST first
check the arguments object for any names beginning with “#”. If found,
the back reference should be resolved and the value used as the “real”
argument. The method is then processed as normal.A *ResultReference* object has the following properties:

 * *resultOf*: String The client id of the method call to get the
   result from (the string given as the third item in the array for a
   method call).
 * *path*: String A pointer into the arguments. This is an RFC6901 JSON
   Pointer, except it also allows the use of * to map through an array
   (see description below).To resolve:

 1. Find the first response with a client id identical to the *resultOf*
    property of the *ResultReference* in the array of outputs from
    previously processed  method calls in the same request. If none,
    evaluation fails.

 2. If the response name is “error”, evaluation fails.

 3. Apply the *path* to the arguments object of the response (the second
    item in the response array) following the RFC6901 JSON pointer
    algorithm, except with the following addition in Section 4
    (Evaluation):

If the currently referenced value is a JSON array, the reference token
may be exactly the single character *, making the new referenced value
the result of applying the rest of the JSON pointer tokens to every item
in the array and returning the results in the same order in a new array.
If the result of applying the rest of the pointer tokens to a value was
itself an array, the items should be included individually in the output
array rather than including the array itself (i.e. the result is
flattened).
 4. If the type of the result is X, and the expected type of the
    argument is an array of type X, wrap the result in an array with a
    single item.As a simple example, suppose we have the following API request:

[[ "getMessageUpdates", { "sinceState": "abcdef" }, "t0" ], [
"getMessages", { "#ids": { "resultOf": "t0", "path": "/changed" }
}, "t1" ]]Now after executing the first method call the response array is:

[[ "messageUpdates", { "accountId": "1", "oldState": "abcdef",
"newState": "123456", "hasMoreUpdates": false, "changed": [ "msg1",
"msg4" ], "removed": [] }, "t0" ]]So to execute the getMessages call, we look through the arguments and
find there is one with a # prefix. To resolve this, we apply the
algorithm above:

 1. Find the first response with client id “t0”. The “messageUpdates”
    response fulfils this criterion.
 2. Check the response name is not “error”. It’s messageUpdates”, so
    this is fine.
 3. Apply the *path* as a JSON pointer to the arguments object. This
    simply selects the “changed” property, so the result of evaluating
    is: [ "msg1", "msg4" ]The JMAP server now continues to process the getMessages call as though
the arguments were:

{ "ids": [ "msg1", "msg4" ] }Now a more complicated example: fetch the “from”/”date”/”subject”
for every message in the first 10 threads in the Inbox (sorted
newest first):

[[ "getMessageList", { "filter": { inMailbox: "id_of_inbox" }, "sort": [
"date desc" ], "collapseThreads": true, "position": 0, "limit": 10 },
"t0" ], [ "getMessages", { "#ids": { "resultOf": "t0", "path": "/ids" },
"properties": [ "threadId" ] }, "t1" ], [ "getThreads", { "#ids": {
"resultOf": "t1", "path": "/list/*/threadId" } }, "t2" ], [
"getMessages", { "#ids": { "resultOf": "t2" "path": "/list/*/messageIds"
},  "properties": [ "from", "date", "subject" ] }, "t3" ]]After executing the first 3 method calls the response array is:

[[ "messageList", { "accountId": "1", "filter": { inMailbox:
"id_of_inbox" }, "sort": [ "date desc" ], "collapseThreads": true,
"state": "abcdefg", "canCalculateUpdates": true, "position": 0, "total":
101, "ids": [ "msg1023", "msg223", "msg110", "msg93", "msg91", "msg38",
"msg36", "msg33", "msg11", "msg1" ] }, "t0" ], [ "messages", {
"accountId": "1", "state": "123456", "list": [{ "id": "msg1023",
"threadId": "trd194", }, { "id": "msg223", "threadId": "trd114" }, ...
etc... ], "notFound": null }, "t1" ], [ "threads", { "accountId": "1",
"state": "123456", "list": [{ "id: "trd194", "messageIds": [ "msg1020",
"msg1021", "msg1023" ] }, { "id: "trd114", "messageIds": [ "msg201",
"msg223" ] }, ... etc... ], "notFound": null }, "t2" ]]So to execute the final getMessages call, we look through the arguments
and find there is one with a # prefix. To resolve this, we apply the
algorithm:

 1. Find the first response with client id “t2”. The “threads” response
    fulfils this criterion.
 2. Check the response name is not “error”. It’s threads”, so
    this is fine.
 3. Apply the *path* as a JSON pointer to the arguments object.
    Token-by-token:
   1. *list* – get the array of thread objects
   2. *** – for each of the items in the array:
     1. *messsageIds* – get the array of message ids
     2. Concatenate these into a single array of all the ids in
        the result.The JMAP server now continues to process the getMessages call as though
the arguments were:

{ "ids": [ "msg1020", "msg1021", "msg1023", "msg201", "msg223", etc...
],    "properties": [ "from", "date", "subject" ] }For reference, in case it’s clearer than my description, here’s a
JavaScript implementation of the pointer resolution algorithm, with the
addition to RFC6901 clearly marked:

function evaluatePointer ( value, pointer ) { if ( !pointer ) { return
value; } if ( pointer.charAt( 0 ) !== '/' ) { throw new Error( 'Invalid
pointer' ); } let token; let next = pointer.indexOf( '/', 1 ); if ( next
!== -1 ) { token = pointer.slice( 1, next ); pointer = pointer.slice(
next ); } else { token = pointer.slice( 1 ); pointer = ''; } token =
token.replace( /~1/g, '/' ).replace( /~0/g, '~' ); if ( Array.isArray(
value ) ) { if ( /^(?:0|[1-9][0-9]*)$/.test( token ) ) { return
evaluatePointer( value[ parseInt( token, 10 ) ], pointer ); }  /* start:
the only bit that differs from RFC6901 */ if ( token === '*' ) { /* Map
values to pointer */ value = value.map( item => evaluatePointer( item,
pointer ) ); /* Flatten output */ return value.reduce( ( output, item )
=> { if ( !Array.isArray( item ) ) { item = [ item ]; } return
output.concat( item ); }, [] ); } /* end */ } else if ( value !== null
&& typeof value === 'object' ) { return evaluatePointer( value[ token ],
pointer ); } throw new Error( 'Evaluation failed' ); }So:
 * What do people think of the result-reference concept to replace the
   fetchRecords etc. arguments and reduce the amount of special-casing?
 * Presuming agreement this is a good idea, is this syntax good? If not,
   please suggest an alternative! (I've tried to reuse RFC6901 as much
   as possible as the only relevant standard I'm aware of, but if you
   know of another standard that's more suitable please let me know).
The full change proposal can be seen in the pull request at
https://github.com/jmapio/jmap/pull/148.
Cheers,
Neil.

---

From: Bron Gondwana <[email-masked]>
Date: Mon, 02 Oct 2017 06:18:53 -0400

On Sun, 1 Oct 2017, at 19:50, Neil Jenkins wrote:
>
>  1. Find the first response with a client id identical to the
>     *resultOf* property of the *ResultReference* in the array of
>     outputs from previously processed  method calls in the same
>     request. If none, evaluation fails.

>  3. Apply the *path* to the arguments object of the response (the
>     second item in the response array) following the RFC6901 JSON
>     pointer algorithm, except with the following addition in Section 4
>     (Evaluation):

> the result of applying the rest of the JSON pointer tokens to every
> item in the array and returning the results in the same order in a new
> array. If the result of applying the rest of the pointer tokens to a
> value was itself an array, the items should be included individually
> in the output array rather than including the array itself (i.e. the
> result is flattened).
[...]

> So to execute the getMessages call, we look through the arguments and
>     is: [ "msg1", "msg4" ]
I guess we're not returning multiple responses, or if we are we're
guaranteeing an order?
A safer and clearer way might actually be to make the JSON pointer be a
more full path:

[[ "getMessageUpdates", { "sinceState": "abcdef" }, "t0" ], [
"getMessages", { "#ids": { "resultOf": "t0", "path":
"/messageUpdates/changed" } }, "t1" ]]

This removes the "ignore responses which are 'error'" special case.  I
guess if we wanted to make it more explicit fields:

[[ "getMessageUpdates", { "sinceState": "abcdef" }, "t0" ], [
"getMessages", { "#ids": { "resultOf": "t0",        "resultType" :
"messageUpdates", "path": "/changed" } }, "t1" ]]

Bron.

--
  Bron Gondwana, CEO, FastMail Pty Ltd
  [email-masked]

---

From: Bart Schaefer <[email-masked]>
Date: Mon, 2 Oct 2017 06:59:58 -0700

On Sun, Oct 1, 2017 at 4:50 PM, Neil Jenkins <[email-masked]> wrote:
>
> If instead we had a generic way to refer to the result of a previous method

Doesn't that violate HTTP statelessness?

Bron's subsequent suggestion is much more RESTy.

---

From: Bron Gondwana <[email-masked]>
Date: Mon, 02 Oct 2017 10:15:50 -0400

On Mon, 2 Oct 2017, at 09:59, Bart Schaefer wrote:
> On Sun, Oct 1, 2017 at 4:50 PM, Neil Jenkins <[email-masked]>> wrote:
>> 
>> If instead we had a generic way to refer to the result of a
>> previous method> 

JMAP methods are multiple method calls batched together, so a single
HTTP request can refer to results created by the previous method within
the same request.
The state already needs to be kept for ids of created resources from
previous methods to allow ID backreferences, so this is just extending
it to keep data (which will also be ID lists, realistically) from
previous get methods as well.
As a server implementer, there's very little difference in what I'd be
doing to support this - the fetchRecords in the current draft is also
implemented as a called to the next getFoos with the ids parameter pre-
filled from a previous dataset.
Bron.

---

From: "Chris Newman" <[email-masked]>
Date: Mon, 02 Oct 2017 17:54:00 -0700

RFC 5182 is useful to save round-trips. I support the concept in JMAP. 
I'd be careful about over-generalizing the back-reference model as that 
seems like the type of over-generalization that can make server 
implementations unnecessarily complex without significant added benefit. 
Also, RFC 5182 includes a mechanism that avoids sending data to the 
client that the client doesn't need, which may be worth thinking about 
in this case.

		- Chris

On 1 Oct 2017, at 16:50, Neil Jenkins wrote:

> “fetchRecords” type arguments, used to reduce round trips.If 
> instead we had a generic way to refer to the result of a previous
> *resultOf*
> second
> item
> },  "properties": [ "from", "date", "subject" ] }, "t3" ]]After 
> executing the first 3 method calls the response array is:
>
>
> "msg223" ] }, ... etc... ], "notFound": null }, "t2" ]]So to execute 
> the final getMessages call, we look through the arguments
> ],    "properties": [ "from", "date", "subject" ] }For reference, in 
> case it’s clearer than my description, here’s a
> https://urldefense.proofpoint.com/v2/url?u=https-3A__github.com_jmapio_jmap_pull_148&d=DwIFaQ&c=RoP1YumCXCgaWHvlZYR8PQcxBKCX5YTpkKY057SbK10&r=QBZgPENFbjFadxqU4HJ3ZDpRz3X1JlDY-keqMt52FFo&m=CtS0Ut-8SOYtTU2EShd7Qt1Wa66iG5eF637_nimF-OM&s=8kEj1hBS8M2M_XaHaB3x831iSoNkmvDpY8cNsW5gYiA&e= 
> .
> Cheers,
> Neil.

> _______________________________________________
> Jmap mailing list
> [email-masked]
> https://urldefense.proofpoint.com/v2/url?u=https-3A__www.ietf.org_mailman_listinfo_jmap&d=DwICAg&c=RoP1YumCXCgaWHvlZYR8PQcxBKCX5YTpkKY057SbK10&r=QBZgPENFbjFadxqU4HJ3ZDpRz3X1JlDY-keqMt52FFo&m=CtS0Ut-8SOYtTU2EShd7Qt1Wa66iG5eF637_nimF-OM&s=h4UnZPECDHeyTajis8WzolSW6T9P-tPU7kMtP8abZcM&e=

---

From: Neil Jenkins <[email-masked]>
Date: Tue, 03 Oct 2017 12:01:56 +1100

On Tue, 3 Oct 2017, at 11:54 AM, Chris Newman wrote:
> I'd be careful about over-generalizing the back-reference model as
> that seems like the type of over-generalization that can make server
I agree. I think that the current proposal is a nice balance of power
and ease of implementation (the array of previous results should
already be there, as you need it to return to the client once you've
finished processing all the methods, so resolving the references should
really be a single loop through a method's arguments and an
implementation of the pointer resolution, which is pretty trivial as I
showed in my first email).
Neil.

---

From: Bron Gondwana <[email-masked]>
Date: Mon, 02 Oct 2017 23:27:55 -0400

A difficulty with 5182 is that it requires indefinite storage of results
on the server (within a single connection lifetime), whereas at least
JMAP backreferences only require storage with the single session.
Also, you can wait until you have the full request before processing it,
so you can resolve which backreferences you will be needing even before
you start processing if you really want to optimise server storage and
start sending results before the batch is complete and throw away data
you won't need for later requests.
(though to be honest, I think it's more  efficient for most clients to
wait until you have everything and then blast it all at once in the
minimum number of packets and minimum battery use)
Bron.

On Mon, 2 Oct 2017, at 20:54, Chris Newman wrote:
> implementations unnecessarily complex without significant added
> benefit. Also, RFC 5182 includes a mechanism that avoids sending data
> to the client that the client doesn't need, which may be worth
> thinking about in this case.> - Chris

>> amount of duplicative text, which made the spec harder to read, and
>> more likely to contain inconsistencies between methods. My goal is
>> that the JMAP Mail spec should just be:

>>     the Thread object is not mutable directly by the client so there
>>     is no setThreads method).
>>  3. Any changes to the standard method for that particular type
>>     (extra arguments etc.)>> This makes the spec *much* shorter, more understandable and easier to
>> “fetchRecords” type arguments, used to reduce round trips.>> If instead we had a generic way to refer to the result of a previous
>> method call in the arguments of a subsequent request, we can
>> eliminate the special cases, and provide more power. Much like the
>> unix composable-small-things philosophy.>> Here’s a proposal of how this could work:

>> value is a back-reference to the output of a previous method call
>> rather than the real value. The value is a *ResultReference* object
>> as described below. When processing a method call, the server MUST
>> first check the arguments object for any names beginning with “#”. If
>> found, the back reference should be resolved and the value used as
>> the “real” argument. The method is then processed as normal.>> A *ResultReference* object has the following properties:

>>  * *path*: String A pointer into the arguments. This is an RFC6901
>>    JSON Pointer, except it also allows the use of * to map through an
>>    array (see description below).>> To resolve:

>>     pointer algorithm, except with the following addition in Section
>>     4 (Evaluation):

>> If the currently referenced value is a JSON array, the reference
>> token may be exactly the single character *, making the new
>> referenced value the result of applying the rest of the JSON pointer
>> tokens to every item in the array and returning the results in the
>> same order in a new array. If the result of applying the rest of the
>> pointer tokens to a value was itself an array, the items should be
>> included individually in the output array rather than including the
>> array itself (i.e. the result is flattened).
>>     argument is an array of type X, wrap the result in an array with
>>     a single item.>> As a simple example, suppose we have the following API request:

>> "getMessages", { "#ids": { "resultOf": "t0", "path": "/changed" } },
>> "t1" ]]>> Now after executing the first method call the response array is:

>> "msg4" ], "removed": [] }, "t0" ]]>> So to execute the getMessages call, we look through the arguments and

>>     simply selects the “changed” property, so the result of
>>     evaluating is: [ "msg1", "msg4" ]>> The JMAP server now continues to process the getMessages call as
>> though the arguments were:

>> { "ids": [ "msg1", "msg4" ] }
>>>> Now a more complicated example: fetch the “from”/”date”/”subject” for
>> every message in the first 10 threads in the Inbox (sorted newest
>> first):

>> [[ "getMessageList", { "filter": { inMailbox: "id_of_inbox" },
>> "sort": [ "date desc" ], "collapseThreads": true, "position": 0,
>> "limit": 10 }, "t0" ], [ "getMessages", { "#ids": { "resultOf": "t0",
>> "path": "/ids" }, "properties": [ "threadId" ] }, "t1" ], [
>> "getThreads", { "#ids": { "resultOf": "t1", "path":
>> "/list/*/threadId" } }, "t2" ], [ "getMessages", { "#ids": {
>> "resultOf": "t2" "path": "/list/*/messageIds" },  "properties": [
>> "from", "date", "subject" ] }, "t3" ]]>> After executing the first 3 method calls the response array is:

>> "state": "abcdefg", "canCalculateUpdates": true, "position": 0,
>> "total": 101, "ids": [ "msg1023", "msg223", "msg110", "msg93",
>> "msg91", "msg38", "msg36", "msg33", "msg11", "msg1" ] }, "t0" ], [
>> "messages", { "accountId": "1", "state": "123456", "list": [{ "id":
>> "msg1023", "threadId": "trd194", }, { "id": "msg223", "threadId":
>> "trd114" }, ... etc... ], "notFound": null }, "t1" ], [ "threads", {
>> "accountId": "1", "state": "123456", "list": [{ "id: "trd194",
>> "messageIds": [ "msg1020", "msg1021", "msg1023" ] }, { "id: "trd114",
>> "messageIds": [ "msg201", "msg223" ] }, ... etc... ], "notFound":
>> null }, "t2" ]]
>>>> So to execute the final getMessages call, we look through the
>> arguments and find there is one with a # prefix. To resolve this, we
>> apply the algorithm:

>>  1. Find the first response with client id “t2”. The “threads”
>>  2. Check the response name is not “error”. It’s threads”, so this is
>>     fine.
>>  3. Apply the *path* as a JSON pointer to the arguments object. Token-by-
>>     token:
>>      2. Concatenate these into a single array of all the ids in the
>>         result.>> The JMAP server now continues to process the getMessages call as
>> though the arguments were:

>> { "ids": [ "msg1020", "msg1021", "msg1023", "msg201", "msg223",
>> etc... ],    "properties": [ "from", "date", "subject" ] }>> For reference, in case it’s clearer than my description, here’s a
>> JavaScript implementation of the pointer resolution algorithm, with
>> the addition to RFC6901 clearly marked:

>> function evaluatePointer ( value, pointer ) { if ( !pointer ) {
>> return value; } if ( pointer.charAt( 0 ) !== '/' ) { throw new Error(
>> 'Invalid pointer' ); } let token; let next = pointer.indexOf( '/', 1
>> ); if ( next !== -1 ) { token = pointer.slice( 1, next ); pointer =
>> pointer.slice( next ); } else { token = pointer.slice( 1 ); pointer =
>> ''; } token = token.replace( /~1/g, '/' ).replace( /~0/g, '~' ); if (
>> Array.isArray( value ) ) { if ( /^(?:0|[1-9][0-9]*)$/.test( token ) )
>> { return evaluatePointer( value[ parseInt( token, 10 ) ], pointer );
>> }  /* start: the only bit that differs from RFC6901 */ if ( token ===
>> '*' ) { /* Map values to pointer */ value = value.map( item =>
>> evaluatePointer( item, pointer ) ); /* Flatten output */ return
>> value.reduce( ( output, item ) => { if ( !Array.isArray( item ) ) {
>> item = [ item ]; } return output.concat( item ); }, [] ); } /* end */
>> } else if ( value !== null && typeof value === 'object' ) { return
>> evaluatePointer( value[ token ], pointer ); } throw new Error(
>> 'Evaluation failed' ); }
>>>> So:
>>  * What do people think of the result-reference concept to replace
>>    the fetchRecords etc. arguments and reduce the amount of special-
>>    casing?
>>  * Presuming agreement this is a good idea, is this syntax good? If
>>    not, please suggest an alternative! (I've tried to reuse RFC6901
>>    as much as possible as the only relevant standard I'm aware of,
>>    but if you know of another standard that's more suitable please
>>    let me know).>> 
>> https://github.com/jmapio/jmap/pull/148[1].>> 
>> Cheers,
>> Neil.
>> 
>> https://urldefense.proofpoint.com/v2/url?u=https-3A__www.ietf.org_mailman_listinfo_jmap&d=DwICAg&c=RoP1YumCXCgaWHvlZYR8PQcxBKCX5YTpkKY057SbK10&r=QBZgPENFbjFadxqU4HJ3ZDpRz3X1JlDY-keqMt52FFo&m=CtS0Ut-8SOYtTU2EShd7Qt1Wa66iG5eF637_nimF-OM&s=h4UnZPECDHeyTajis8WzolSW6T9P-tPU7kMtP8abZcM&e=> _________________________________________________
> Jmap mailing list
> [email-masked]
> https://www.ietf.org/mailman/listinfo/jmap

Links:

  1. https://urldefense.proofpoint.com/v2/url?u=https-3A__github.com_jmapio_jmap_pull_148&d=DwMFaQ&c=RoP1YumCXCgaWHvlZYR8PQcxBKCX5YTpkKY057SbK10&r=QBZgPENFbjFadxqU4HJ3ZDpRz3X1JlDY-keqMt52FFo&m=CtS0Ut-8SOYtTU2EShd7Qt1Wa66iG5eF637_nimF-OM&s=8kEj1hBS8M2M_XaHaB3x831iSoNkmvDpY8cNsW5gYiA&e=

---

From: Josef 'Jeff' Sipek <[email-masked]>
Date: Tue, 24 Oct 2017 06:03:30 -0400

(I realize that this proposal got already merged...)

On Mon, Oct 02, 2017 at 17:54:00 -0700, Chris Newman wrote:
> RFC 5182 is useful to save round-trips. I support the concept in JMAP. I'd
> be careful about over-generalizing the back-reference model as that seems
> like the type of over-generalization that can make server implementations
> unnecessarily complex without significant added benefit.

Agreed.  The new back-reference scheme looks really powerful and
cool...

It (along with what I'm guessing ifInState issue #154 is about) reminds me
of NFSv4.1 (RFC 5661) COMPOUNDs and "current and saved filehandles" (with a
hint of single static assignment and predicated instructions).

In general, I don't have a problem with clients passing what amounts to
little scripts to the server for evaluation, but I'd like to avoid jmap
*accidentally* growing a scripting language.  (Sort of related: [1])

Sadly, this script-y way of accessing data doesn't really allow a caching
tier between the clients and servers.  (This is really the HTTP-as-a-tunnel
vs. REST discussion.)

> Also, RFC 5182
> includes a mechanism that avoids sending data to the client that the client
> doesn't need, which may be worth thinking about in this case.

You're talking about RFC 5182 not sending the saved search result to the
client at all, right?  (E.g., example 7 is *very* good at bandwidth
utilization)

Jeff.

[1] https://en.wikipedia.org/wiki/Greenspun%27s_tenth_rule

> > “fetchRecords” type arguments, used to reduce round trips.If instead we
> > had a generic way to refer to the result of a previous
> > "msg223" ] }, ... etc... ], "notFound": null }, "t2" ]]So to execute the
> > final getMessages call, we look through the arguments

-- 
Ready; T=0.01/0.01 06:04:46

---

From: Neil Jenkins <[email-masked]>
Date: Wed, 25 Oct 2017 15:14:54 +1100

On Tue, 24 Oct 2017, at 09:03 PM, Josef 'Jeff' Sipek wrote:
> In general, I don't have a problem with clients passing what
> amounts to> little scripts to the server for evaluation, but I'd like to
> avoid jmap> **accidentally** growing a scripting language.

Yeh, this is definitely something to avoid! We're not there yet,
although I concede it's a slippery slope…
> You're talking about RFC 5182 not sending the saved search
> result to the> client at all, right?  (E.g., example 7 is **very** good at bandwidth> utilization)

JMAP back references allow for an equivalent network-efficient
mechanism for most of what you can do in IMAP with 5182. Example 7 is
actually probably the main one that you *can't* do so efficiently
with back references. To save other people looking up the spec, this
is the example:
C: F282 SEARCH RETURN (SAVE) KEYWORD $Junk
C: F283 COPY $ "Junk"
C: F284 STORE $ +FLAGS.Silent (\Deleted)

This essentially finds all messages with the $Junk keyword and then
moves them to the Junk mailbox. If we make a small addition to the back-
reference object in JMAP, we could do this equivalently like so:
[[ "getMessageList", {
    "filter": {
        "hasKeyword": "$Junk"
    },
}, "0" ],
[ "setMessages", {
    "#update": {
        "resultOf": "0",
        "path": "/ids"
        "mapToValue"{
            "mailboxIds": {
                <JunkMailboxId>: true
            }
        }
    }
}, "1" ]]

Adding the *mapToValue* property to the back reference would mean it
takes the array result of the back-reference and converts it into an
object where each of those results is a key and the value for every one
is the *mapToValue* given. Yes, it's one small step towards (but still a
long way from!) the general-purpose scripting language worry.
Thoughts? Should we add this?
Neil.