···74747575(** CLI configuration and cmdliner terms for JMAP tools. *)
7676module Cli = Cli
7777+7878+(** Method chaining with automatic result references.
7979+8080+ Provides a monadic interface for building JMAP requests where method
8181+ calls can reference results from previous calls in the same request.
8282+ Call IDs are generated automatically. *)
8383+module Chain = Jmap.Chain
+851
lib/core/chain.ml
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Anil Madhavapeddy. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+open Jmap_proto
77+88+(* Phantom types for handle kinds *)
99+type query
1010+type get
1111+type changes
1212+type set
1313+type query_changes
1414+type copy
1515+type import
1616+type parse
1717+1818+(* Internal handle representation with GADT for response type *)
1919+type (_, _) handle =
2020+ | Query_handle : {
2121+ call_id : string;
2222+ method_name : string;
2323+ } -> (query, Method.query_response) handle
2424+ | Query_changes_handle : {
2525+ call_id : string;
2626+ method_name : string;
2727+ } -> (query_changes, Method.query_changes_response) handle
2828+ | Email_get_handle : {
2929+ call_id : string;
3030+ method_name : string;
3131+ } -> (get, Email.t Method.get_response) handle
3232+ | Thread_get_handle : {
3333+ call_id : string;
3434+ method_name : string;
3535+ } -> (get, Thread.t Method.get_response) handle
3636+ | Mailbox_get_handle : {
3737+ call_id : string;
3838+ method_name : string;
3939+ } -> (get, Mailbox.t Method.get_response) handle
4040+ | Identity_get_handle : {
4141+ call_id : string;
4242+ method_name : string;
4343+ } -> (get, Identity.t Method.get_response) handle
4444+ | Submission_get_handle : {
4545+ call_id : string;
4646+ method_name : string;
4747+ } -> (get, Submission.t Method.get_response) handle
4848+ | Search_snippet_get_handle : {
4949+ call_id : string;
5050+ method_name : string;
5151+ } -> (get, Search_snippet.t Method.get_response) handle
5252+ | Vacation_get_handle : {
5353+ call_id : string;
5454+ method_name : string;
5555+ } -> (get, Vacation.t Method.get_response) handle
5656+ | Changes_handle : {
5757+ call_id : string;
5858+ method_name : string;
5959+ } -> (changes, Method.changes_response) handle
6060+ | Email_set_handle : {
6161+ call_id : string;
6262+ method_name : string;
6363+ } -> (set, Email.t Method.set_response) handle
6464+ | Mailbox_set_handle : {
6565+ call_id : string;
6666+ method_name : string;
6767+ } -> (set, Mailbox.t Method.set_response) handle
6868+ | Identity_set_handle : {
6969+ call_id : string;
7070+ method_name : string;
7171+ } -> (set, Identity.t Method.set_response) handle
7272+ | Submission_set_handle : {
7373+ call_id : string;
7474+ method_name : string;
7575+ } -> (set, Submission.t Method.set_response) handle
7676+ | Vacation_set_handle : {
7777+ call_id : string;
7878+ method_name : string;
7979+ } -> (set, Vacation.t Method.set_response) handle
8080+ | Email_copy_handle : {
8181+ call_id : string;
8282+ method_name : string;
8383+ } -> (copy, Email.t Method.copy_response) handle
8484+ | Raw_handle : {
8585+ call_id : string;
8686+ method_name : string;
8787+ } -> (unit, Jsont.Json.t) handle
8888+8989+let call_id : type k r. (k, r) handle -> string = function
9090+ | Query_handle h -> h.call_id
9191+ | Query_changes_handle h -> h.call_id
9292+ | Email_get_handle h -> h.call_id
9393+ | Thread_get_handle h -> h.call_id
9494+ | Mailbox_get_handle h -> h.call_id
9595+ | Identity_get_handle h -> h.call_id
9696+ | Submission_get_handle h -> h.call_id
9797+ | Search_snippet_get_handle h -> h.call_id
9898+ | Vacation_get_handle h -> h.call_id
9999+ | Changes_handle h -> h.call_id
100100+ | Email_set_handle h -> h.call_id
101101+ | Mailbox_set_handle h -> h.call_id
102102+ | Identity_set_handle h -> h.call_id
103103+ | Submission_set_handle h -> h.call_id
104104+ | Vacation_set_handle h -> h.call_id
105105+ | Email_copy_handle h -> h.call_id
106106+ | Raw_handle h -> h.call_id
107107+108108+let method_name : type k r. (k, r) handle -> string = function
109109+ | Query_handle h -> h.method_name
110110+ | Query_changes_handle h -> h.method_name
111111+ | Email_get_handle h -> h.method_name
112112+ | Thread_get_handle h -> h.method_name
113113+ | Mailbox_get_handle h -> h.method_name
114114+ | Identity_get_handle h -> h.method_name
115115+ | Submission_get_handle h -> h.method_name
116116+ | Search_snippet_get_handle h -> h.method_name
117117+ | Vacation_get_handle h -> h.method_name
118118+ | Changes_handle h -> h.method_name
119119+ | Email_set_handle h -> h.method_name
120120+ | Mailbox_set_handle h -> h.method_name
121121+ | Identity_set_handle h -> h.method_name
122122+ | Submission_set_handle h -> h.method_name
123123+ | Vacation_set_handle h -> h.method_name
124124+ | Email_copy_handle h -> h.method_name
125125+ | Raw_handle h -> h.method_name
126126+127127+(* Creation IDs *)
128128+type 'a create_id = string
129129+130130+let created_id cid = Id.of_string_exn ("#" ^ cid)
131131+let created_id_of_string s = Id.of_string_exn ("#" ^ s)
132132+133133+(* ID sources *)
134134+type id_source =
135135+ | Ids of Id.t list
136136+ | Ref of Invocation.result_reference
137137+138138+let ids lst = Ids lst
139139+let id x = Ids [x]
140140+141141+let make_ref ~call_id ~method_name ~path =
142142+ Ref (Invocation.result_reference_of_strings
143143+ ~result_of:call_id
144144+ ~name:method_name
145145+ ~path)
146146+147147+let from_query : type r. (query, r) handle -> id_source = fun h ->
148148+ make_ref ~call_id:(call_id h) ~method_name:(method_name h) ~path:"/ids"
149149+150150+let from_get_ids : type r. (get, r) handle -> id_source = fun h ->
151151+ make_ref ~call_id:(call_id h) ~method_name:(method_name h) ~path:"/list/*/id"
152152+153153+let from_get_field : type r. (get, r) handle -> string -> id_source = fun h field ->
154154+ make_ref ~call_id:(call_id h) ~method_name:(method_name h)
155155+ ~path:(Printf.sprintf "/list/*/%s" field)
156156+157157+let from_changes_created : type r. (changes, r) handle -> id_source = fun h ->
158158+ make_ref ~call_id:(call_id h) ~method_name:(method_name h) ~path:"/created"
159159+160160+let from_changes_updated : type r. (changes, r) handle -> id_source = fun h ->
161161+ make_ref ~call_id:(call_id h) ~method_name:(method_name h) ~path:"/updated"
162162+163163+let from_changes_destroyed : type r. (changes, r) handle -> id_source = fun h ->
164164+ make_ref ~call_id:(call_id h) ~method_name:(method_name h) ~path:"/destroyed"
165165+166166+let from_set_created : type r. (set, r) handle -> id_source = fun h ->
167167+ make_ref ~call_id:(call_id h) ~method_name:(method_name h) ~path:"/created/*/id"
168168+169169+let from_set_updated : type r. (set, r) handle -> id_source = fun h ->
170170+ make_ref ~call_id:(call_id h) ~method_name:(method_name h) ~path:"/updated"
171171+172172+let from_query_changes_removed : type r. (query_changes, r) handle -> id_source = fun h ->
173173+ make_ref ~call_id:(call_id h) ~method_name:(method_name h) ~path:"/removed"
174174+175175+let from_query_changes_added : type r. (query_changes, r) handle -> id_source = fun h ->
176176+ make_ref ~call_id:(call_id h) ~method_name:(method_name h) ~path:"/added/*/id"
177177+178178+let from_copy_created : type r. (copy, r) handle -> id_source = fun h ->
179179+ make_ref ~call_id:(call_id h) ~method_name:(method_name h) ~path:"/created/*/id"
180180+181181+let from_import_created : type r. (import, r) handle -> id_source = fun h ->
182182+ make_ref ~call_id:(call_id h) ~method_name:(method_name h) ~path:"/created/*/id"
183183+184184+(* Chain state *)
185185+type state = {
186186+ mutable next_id : int;
187187+ mutable next_create_id : int;
188188+ mutable invocations : Invocation.t list;
189189+}
190190+191191+(* Chain monad *)
192192+type 'a t = state -> 'a
193193+194194+let return x _state = x
195195+196196+let bind m f state =
197197+ let a = m state in
198198+ f a state
199199+200200+let map f m state =
201201+ f (m state)
202202+203203+let both a b state =
204204+ let x = a state in
205205+ let y = b state in
206206+ (x, y)
207207+208208+let ( let* ) = bind
209209+let ( let+ ) m f = map f m
210210+let ( and* ) = both
211211+let ( and+ ) = both
212212+213213+(* Building *)
214214+let fresh_call_id state =
215215+ let id = Printf.sprintf "c%d" state.next_id in
216216+ state.next_id <- state.next_id + 1;
217217+ id
218218+219219+let fresh_create_id () state =
220220+ let id = Printf.sprintf "k%d" state.next_create_id in
221221+ state.next_create_id <- state.next_create_id + 1;
222222+ id
223223+224224+let record_invocation inv state =
225225+ state.invocations <- inv :: state.invocations
226226+227227+let build ~capabilities chain =
228228+ let state = { next_id = 0; next_create_id = 0; invocations = [] } in
229229+ let result = chain state in
230230+ let request = Request.create
231231+ ~using:capabilities
232232+ ~method_calls:(List.rev state.invocations)
233233+ ()
234234+ in
235235+ (request, result)
236236+237237+let build_request ~capabilities chain =
238238+ fst (build ~capabilities chain)
239239+240240+(* JSON helpers - exported *)
241241+let json_null = Jsont.Null ((), Jsont.Meta.none)
242242+243243+let json_bool b = Jsont.Bool (b, Jsont.Meta.none)
244244+245245+let json_string s = Jsont.String (s, Jsont.Meta.none)
246246+247247+let json_int n = Jsont.Number (Int64.to_float n, Jsont.Meta.none)
248248+249249+let json_name s = (s, Jsont.Meta.none)
250250+251251+let json_obj fields =
252252+ let fields' = List.map (fun (k, v) -> (json_name k, v)) fields in
253253+ Jsont.Object (fields', Jsont.Meta.none)
254254+255255+let json_array items = Jsont.Array (items, Jsont.Meta.none)
256256+257257+(* JSON helpers - internal *)
258258+let json_of_id id =
259259+ Jsont.String (Id.to_string id, Jsont.Meta.none)
260260+261261+let json_of_id_list ids =
262262+ let items = List.map json_of_id ids in
263263+ Jsont.Array (items, Jsont.Meta.none)
264264+265265+let json_of_string_list strs =
266266+ let items = List.map json_string strs in
267267+ Jsont.Array (items, Jsont.Meta.none)
268268+269269+let json_map pairs =
270270+ let fields' = List.map (fun (k, v) -> (json_name k, v)) pairs in
271271+ Jsont.Object (fields', Jsont.Meta.none)
272272+273273+let encode_to_json jsont value =
274274+ match Jsont.Json.encode' jsont value with
275275+ | Ok j -> j
276276+ | Error _ -> json_obj []
277277+278278+let encode_list_to_json jsont values =
279279+ match Jsont.Json.encode' (Jsont.list jsont) values with
280280+ | Ok j -> j
281281+ | Error _ -> Jsont.Array ([], Jsont.Meta.none)
282282+283283+(* Add id_source to args *)
284284+let add_ids_arg args = function
285285+ | None -> args
286286+ | Some (Ids ids) ->
287287+ ("ids", json_of_id_list ids) :: args
288288+ | Some (Ref ref_) ->
289289+ let ref_json = encode_to_json Invocation.result_reference_jsont ref_ in
290290+ ("#ids", ref_json) :: args
291291+292292+let add_destroy_arg args = function
293293+ | None -> args
294294+ | Some (Ids ids) ->
295295+ ("destroy", json_of_id_list ids) :: args
296296+ | Some (Ref ref_) ->
297297+ let ref_json = encode_to_json Invocation.result_reference_jsont ref_ in
298298+ ("#destroy", ref_json) :: args
299299+300300+(* Query builder helper *)
301301+let build_query_args ~account_id ?filter ?filter_jsont ?sort ?position ?anchor
302302+ ?anchor_offset ?limit ?calculate_total () =
303303+ let args = [ ("accountId", json_of_id account_id) ] in
304304+ let args = match filter, filter_jsont with
305305+ | Some f, Some jsont -> ("filter", encode_to_json jsont f) :: args
306306+ | _ -> args
307307+ in
308308+ let args = match sort with
309309+ | None -> args
310310+ | Some comparators -> ("sort", encode_list_to_json Filter.comparator_jsont comparators) :: args
311311+ in
312312+ let args = match position with
313313+ | None -> args
314314+ | Some n -> ("position", json_int n) :: args
315315+ in
316316+ let args = match anchor with
317317+ | None -> args
318318+ | Some id -> ("anchor", json_of_id id) :: args
319319+ in
320320+ let args = match anchor_offset with
321321+ | None -> args
322322+ | Some n -> ("anchorOffset", json_int n) :: args
323323+ in
324324+ let args = match limit with
325325+ | None -> args
326326+ | Some n -> ("limit", json_int n) :: args
327327+ in
328328+ let args = match calculate_total with
329329+ | None -> args
330330+ | Some b -> ("calculateTotal", json_bool b) :: args
331331+ in
332332+ args
333333+334334+(* Changes builder helper *)
335335+let build_changes_args ~account_id ~since_state ?max_changes () =
336336+ let args = [
337337+ ("accountId", json_of_id account_id);
338338+ ("sinceState", json_string since_state);
339339+ ] in
340340+ let args = match max_changes with
341341+ | None -> args
342342+ | Some n -> ("maxChanges", json_int n) :: args
343343+ in
344344+ args
345345+346346+(* QueryChanges builder helper *)
347347+let build_query_changes_args ~account_id ~since_query_state ?filter ?filter_jsont
348348+ ?sort ?max_changes ?up_to_id ?calculate_total () =
349349+ let args = [
350350+ ("accountId", json_of_id account_id);
351351+ ("sinceQueryState", json_string since_query_state);
352352+ ] in
353353+ let args = match filter, filter_jsont with
354354+ | Some f, Some jsont -> ("filter", encode_to_json jsont f) :: args
355355+ | _ -> args
356356+ in
357357+ let args = match sort with
358358+ | None -> args
359359+ | Some comparators -> ("sort", encode_list_to_json Filter.comparator_jsont comparators) :: args
360360+ in
361361+ let args = match max_changes with
362362+ | None -> args
363363+ | Some n -> ("maxChanges", json_int n) :: args
364364+ in
365365+ let args = match up_to_id with
366366+ | None -> args
367367+ | Some id -> ("upToId", json_of_id id) :: args
368368+ in
369369+ let args = match calculate_total with
370370+ | None -> args
371371+ | Some b -> ("calculateTotal", json_bool b) :: args
372372+ in
373373+ args
374374+375375+(* Set builder helper *)
376376+let build_set_args ~account_id ?if_in_state ?create ?update ?destroy () =
377377+ let args = [ ("accountId", json_of_id account_id) ] in
378378+ let args = match if_in_state with
379379+ | None -> args
380380+ | Some s -> ("ifInState", json_string s) :: args
381381+ in
382382+ let args = match create with
383383+ | None | Some [] -> args
384384+ | Some items ->
385385+ let create_map = json_map (List.map (fun (cid, obj) -> (cid, obj)) items) in
386386+ ("create", create_map) :: args
387387+ in
388388+ let args = match update with
389389+ | None | Some [] -> args
390390+ | Some items ->
391391+ let update_map = json_map (List.map (fun (id, patch) -> (Id.to_string id, patch)) items) in
392392+ ("update", update_map) :: args
393393+ in
394394+ let args = add_destroy_arg args destroy in
395395+ args
396396+397397+(* Method builders *)
398398+399399+let email_query ~account_id ?filter ?sort ?position ?anchor ?anchor_offset
400400+ ?limit ?calculate_total ?collapse_threads () state =
401401+ let call_id = fresh_call_id state in
402402+ let args = build_query_args ~account_id ?filter
403403+ ~filter_jsont:Mail_filter.email_filter_jsont
404404+ ?sort ?position ?anchor ?anchor_offset ?limit ?calculate_total () in
405405+ let args = match collapse_threads with
406406+ | None -> args
407407+ | Some b -> ("collapseThreads", json_bool b) :: args
408408+ in
409409+ let inv = Invocation.create
410410+ ~name:"Email/query"
411411+ ~arguments:(json_obj args)
412412+ ~method_call_id:call_id
413413+ in
414414+ record_invocation inv state;
415415+ Query_handle { call_id; method_name = "Email/query" }
416416+417417+let email_get ~account_id ?ids ?properties ?body_properties
418418+ ?fetch_text_body_values ?fetch_html_body_values ?fetch_all_body_values
419419+ ?max_body_value_bytes () state =
420420+ let call_id = fresh_call_id state in
421421+ let args = [ ("accountId", json_of_id account_id) ] in
422422+ let args = add_ids_arg args ids in
423423+ let args = match properties with
424424+ | None -> args
425425+ | Some props -> ("properties", json_of_string_list props) :: args
426426+ in
427427+ let args = match body_properties with
428428+ | None -> args
429429+ | Some props -> ("bodyProperties", json_of_string_list props) :: args
430430+ in
431431+ let args = match fetch_text_body_values with
432432+ | None -> args
433433+ | Some b -> ("fetchTextBodyValues", json_bool b) :: args
434434+ in
435435+ let args = match fetch_html_body_values with
436436+ | None -> args
437437+ | Some b -> ("fetchHTMLBodyValues", json_bool b) :: args
438438+ in
439439+ let args = match fetch_all_body_values with
440440+ | None -> args
441441+ | Some b -> ("fetchAllBodyValues", json_bool b) :: args
442442+ in
443443+ let args = match max_body_value_bytes with
444444+ | None -> args
445445+ | Some n -> ("maxBodyValueBytes", json_int n) :: args
446446+ in
447447+ let inv = Invocation.create
448448+ ~name:"Email/get"
449449+ ~arguments:(json_obj args)
450450+ ~method_call_id:call_id
451451+ in
452452+ record_invocation inv state;
453453+ Email_get_handle { call_id; method_name = "Email/get" }
454454+455455+let email_changes ~account_id ~since_state ?max_changes () state =
456456+ let call_id = fresh_call_id state in
457457+ let args = build_changes_args ~account_id ~since_state ?max_changes () in
458458+ let inv = Invocation.create
459459+ ~name:"Email/changes"
460460+ ~arguments:(json_obj args)
461461+ ~method_call_id:call_id
462462+ in
463463+ record_invocation inv state;
464464+ Changes_handle { call_id; method_name = "Email/changes" }
465465+466466+let email_query_changes ~account_id ~since_query_state ?filter ?sort
467467+ ?max_changes ?up_to_id ?calculate_total () state =
468468+ let call_id = fresh_call_id state in
469469+ let args = build_query_changes_args ~account_id ~since_query_state
470470+ ?filter ~filter_jsont:Mail_filter.email_filter_jsont
471471+ ?sort ?max_changes ?up_to_id ?calculate_total () in
472472+ let inv = Invocation.create
473473+ ~name:"Email/queryChanges"
474474+ ~arguments:(json_obj args)
475475+ ~method_call_id:call_id
476476+ in
477477+ record_invocation inv state;
478478+ Query_changes_handle { call_id; method_name = "Email/queryChanges" }
479479+480480+let email_set ~account_id ?if_in_state ?create ?update ?destroy () state =
481481+ let call_id = fresh_call_id state in
482482+ let args = build_set_args ~account_id ?if_in_state ?create ?update ?destroy () in
483483+ let inv = Invocation.create
484484+ ~name:"Email/set"
485485+ ~arguments:(json_obj args)
486486+ ~method_call_id:call_id
487487+ in
488488+ record_invocation inv state;
489489+ Email_set_handle { call_id; method_name = "Email/set" }
490490+491491+let email_copy ~from_account_id ~account_id ?if_from_in_state ?if_in_state
492492+ ?create ?on_success_destroy_original ?destroy_from_if_in_state () state =
493493+ let call_id = fresh_call_id state in
494494+ let args = [
495495+ ("fromAccountId", json_of_id from_account_id);
496496+ ("accountId", json_of_id account_id);
497497+ ] in
498498+ let args = match if_from_in_state with
499499+ | None -> args
500500+ | Some s -> ("ifFromInState", json_string s) :: args
501501+ in
502502+ let args = match if_in_state with
503503+ | None -> args
504504+ | Some s -> ("ifInState", json_string s) :: args
505505+ in
506506+ let args = match create with
507507+ | None | Some [] -> args
508508+ | Some items ->
509509+ let create_map = json_map (List.map (fun (id, obj) -> (Id.to_string id, obj)) items) in
510510+ ("create", create_map) :: args
511511+ in
512512+ let args = match on_success_destroy_original with
513513+ | None -> args
514514+ | Some b -> ("onSuccessDestroyOriginal", json_bool b) :: args
515515+ in
516516+ let args = match destroy_from_if_in_state with
517517+ | None -> args
518518+ | Some s -> ("destroyFromIfInState", json_string s) :: args
519519+ in
520520+ let inv = Invocation.create
521521+ ~name:"Email/copy"
522522+ ~arguments:(json_obj args)
523523+ ~method_call_id:call_id
524524+ in
525525+ record_invocation inv state;
526526+ Email_copy_handle { call_id; method_name = "Email/copy" }
527527+528528+let thread_get ~account_id ?ids () state =
529529+ let call_id = fresh_call_id state in
530530+ let args = [ ("accountId", json_of_id account_id) ] in
531531+ let args = add_ids_arg args ids in
532532+ let inv = Invocation.create
533533+ ~name:"Thread/get"
534534+ ~arguments:(json_obj args)
535535+ ~method_call_id:call_id
536536+ in
537537+ record_invocation inv state;
538538+ Thread_get_handle { call_id; method_name = "Thread/get" }
539539+540540+let thread_changes ~account_id ~since_state ?max_changes () state =
541541+ let call_id = fresh_call_id state in
542542+ let args = build_changes_args ~account_id ~since_state ?max_changes () in
543543+ let inv = Invocation.create
544544+ ~name:"Thread/changes"
545545+ ~arguments:(json_obj args)
546546+ ~method_call_id:call_id
547547+ in
548548+ record_invocation inv state;
549549+ Changes_handle { call_id; method_name = "Thread/changes" }
550550+551551+let mailbox_query ~account_id ?filter ?sort ?position ?anchor ?anchor_offset
552552+ ?limit ?calculate_total () state =
553553+ let call_id = fresh_call_id state in
554554+ let args = build_query_args ~account_id ?filter
555555+ ~filter_jsont:Mail_filter.mailbox_filter_jsont
556556+ ?sort ?position ?anchor ?anchor_offset ?limit ?calculate_total () in
557557+ let inv = Invocation.create
558558+ ~name:"Mailbox/query"
559559+ ~arguments:(json_obj args)
560560+ ~method_call_id:call_id
561561+ in
562562+ record_invocation inv state;
563563+ Query_handle { call_id; method_name = "Mailbox/query" }
564564+565565+let mailbox_get ~account_id ?ids ?properties () state =
566566+ let call_id = fresh_call_id state in
567567+ let args = [ ("accountId", json_of_id account_id) ] in
568568+ let args = add_ids_arg args ids in
569569+ let args = match properties with
570570+ | None -> args
571571+ | Some props -> ("properties", json_of_string_list props) :: args
572572+ in
573573+ let inv = Invocation.create
574574+ ~name:"Mailbox/get"
575575+ ~arguments:(json_obj args)
576576+ ~method_call_id:call_id
577577+ in
578578+ record_invocation inv state;
579579+ Mailbox_get_handle { call_id; method_name = "Mailbox/get" }
580580+581581+let mailbox_changes ~account_id ~since_state ?max_changes () state =
582582+ let call_id = fresh_call_id state in
583583+ let args = build_changes_args ~account_id ~since_state ?max_changes () in
584584+ let inv = Invocation.create
585585+ ~name:"Mailbox/changes"
586586+ ~arguments:(json_obj args)
587587+ ~method_call_id:call_id
588588+ in
589589+ record_invocation inv state;
590590+ Changes_handle { call_id; method_name = "Mailbox/changes" }
591591+592592+let mailbox_query_changes ~account_id ~since_query_state ?filter ?sort
593593+ ?max_changes ?up_to_id ?calculate_total () state =
594594+ let call_id = fresh_call_id state in
595595+ let args = build_query_changes_args ~account_id ~since_query_state
596596+ ?filter ~filter_jsont:Mail_filter.mailbox_filter_jsont
597597+ ?sort ?max_changes ?up_to_id ?calculate_total () in
598598+ let inv = Invocation.create
599599+ ~name:"Mailbox/queryChanges"
600600+ ~arguments:(json_obj args)
601601+ ~method_call_id:call_id
602602+ in
603603+ record_invocation inv state;
604604+ Query_changes_handle { call_id; method_name = "Mailbox/queryChanges" }
605605+606606+let mailbox_set ~account_id ?if_in_state ?create ?update ?destroy
607607+ ?on_destroy_remove_emails () state =
608608+ let call_id = fresh_call_id state in
609609+ let args = build_set_args ~account_id ?if_in_state ?create ?update ?destroy () in
610610+ let args = match on_destroy_remove_emails with
611611+ | None -> args
612612+ | Some b -> ("onDestroyRemoveEmails", json_bool b) :: args
613613+ in
614614+ let inv = Invocation.create
615615+ ~name:"Mailbox/set"
616616+ ~arguments:(json_obj args)
617617+ ~method_call_id:call_id
618618+ in
619619+ record_invocation inv state;
620620+ Mailbox_set_handle { call_id; method_name = "Mailbox/set" }
621621+622622+let identity_get ~account_id ?ids ?properties () state =
623623+ let call_id = fresh_call_id state in
624624+ let args = [ ("accountId", json_of_id account_id) ] in
625625+ let args = add_ids_arg args ids in
626626+ let args = match properties with
627627+ | None -> args
628628+ | Some props -> ("properties", json_of_string_list props) :: args
629629+ in
630630+ let inv = Invocation.create
631631+ ~name:"Identity/get"
632632+ ~arguments:(json_obj args)
633633+ ~method_call_id:call_id
634634+ in
635635+ record_invocation inv state;
636636+ Identity_get_handle { call_id; method_name = "Identity/get" }
637637+638638+let identity_changes ~account_id ~since_state ?max_changes () state =
639639+ let call_id = fresh_call_id state in
640640+ let args = build_changes_args ~account_id ~since_state ?max_changes () in
641641+ let inv = Invocation.create
642642+ ~name:"Identity/changes"
643643+ ~arguments:(json_obj args)
644644+ ~method_call_id:call_id
645645+ in
646646+ record_invocation inv state;
647647+ Changes_handle { call_id; method_name = "Identity/changes" }
648648+649649+let identity_set ~account_id ?if_in_state ?create ?update ?destroy () state =
650650+ let call_id = fresh_call_id state in
651651+ let args = build_set_args ~account_id ?if_in_state ?create ?update ?destroy () in
652652+ let inv = Invocation.create
653653+ ~name:"Identity/set"
654654+ ~arguments:(json_obj args)
655655+ ~method_call_id:call_id
656656+ in
657657+ record_invocation inv state;
658658+ Identity_set_handle { call_id; method_name = "Identity/set" }
659659+660660+let email_submission_query ~account_id ?filter ?sort ?position ?anchor
661661+ ?anchor_offset ?limit ?calculate_total () state =
662662+ let call_id = fresh_call_id state in
663663+ let args = build_query_args ~account_id ?filter
664664+ ~filter_jsont:Mail_filter.submission_filter_jsont
665665+ ?sort ?position ?anchor ?anchor_offset ?limit ?calculate_total () in
666666+ let inv = Invocation.create
667667+ ~name:"EmailSubmission/query"
668668+ ~arguments:(json_obj args)
669669+ ~method_call_id:call_id
670670+ in
671671+ record_invocation inv state;
672672+ Query_handle { call_id; method_name = "EmailSubmission/query" }
673673+674674+let email_submission_get ~account_id ?ids ?properties () state =
675675+ let call_id = fresh_call_id state in
676676+ let args = [ ("accountId", json_of_id account_id) ] in
677677+ let args = add_ids_arg args ids in
678678+ let args = match properties with
679679+ | None -> args
680680+ | Some props -> ("properties", json_of_string_list props) :: args
681681+ in
682682+ let inv = Invocation.create
683683+ ~name:"EmailSubmission/get"
684684+ ~arguments:(json_obj args)
685685+ ~method_call_id:call_id
686686+ in
687687+ record_invocation inv state;
688688+ Submission_get_handle { call_id; method_name = "EmailSubmission/get" }
689689+690690+let email_submission_changes ~account_id ~since_state ?max_changes () state =
691691+ let call_id = fresh_call_id state in
692692+ let args = build_changes_args ~account_id ~since_state ?max_changes () in
693693+ let inv = Invocation.create
694694+ ~name:"EmailSubmission/changes"
695695+ ~arguments:(json_obj args)
696696+ ~method_call_id:call_id
697697+ in
698698+ record_invocation inv state;
699699+ Changes_handle { call_id; method_name = "EmailSubmission/changes" }
700700+701701+let email_submission_query_changes ~account_id ~since_query_state ?filter ?sort
702702+ ?max_changes ?up_to_id ?calculate_total () state =
703703+ let call_id = fresh_call_id state in
704704+ let args = build_query_changes_args ~account_id ~since_query_state
705705+ ?filter ~filter_jsont:Mail_filter.submission_filter_jsont
706706+ ?sort ?max_changes ?up_to_id ?calculate_total () in
707707+ let inv = Invocation.create
708708+ ~name:"EmailSubmission/queryChanges"
709709+ ~arguments:(json_obj args)
710710+ ~method_call_id:call_id
711711+ in
712712+ record_invocation inv state;
713713+ Query_changes_handle { call_id; method_name = "EmailSubmission/queryChanges" }
714714+715715+let email_submission_set ~account_id ?if_in_state ?create ?update ?destroy
716716+ ?on_success_update_email ?on_success_destroy_email () state =
717717+ let call_id = fresh_call_id state in
718718+ let args = build_set_args ~account_id ?if_in_state ?create ?update ?destroy () in
719719+ let args = match on_success_update_email with
720720+ | None | Some [] -> args
721721+ | Some items ->
722722+ let update_map = json_map items in
723723+ ("onSuccessUpdateEmail", update_map) :: args
724724+ in
725725+ let args = match on_success_destroy_email with
726726+ | None | Some [] -> args
727727+ | Some ids ->
728728+ ("onSuccessDestroyEmail", json_of_string_list ids) :: args
729729+ in
730730+ let inv = Invocation.create
731731+ ~name:"EmailSubmission/set"
732732+ ~arguments:(json_obj args)
733733+ ~method_call_id:call_id
734734+ in
735735+ record_invocation inv state;
736736+ Submission_set_handle { call_id; method_name = "EmailSubmission/set" }
737737+738738+let search_snippet_get ~account_id ~filter ~email_ids () state =
739739+ let call_id = fresh_call_id state in
740740+ let args = [ ("accountId", json_of_id account_id) ] in
741741+ let args = ("filter", encode_to_json Mail_filter.email_filter_jsont filter) :: args in
742742+ let args = match email_ids with
743743+ | Ids ids -> ("emailIds", json_of_id_list ids) :: args
744744+ | Ref ref_ ->
745745+ let ref_json = encode_to_json Invocation.result_reference_jsont ref_ in
746746+ ("#emailIds", ref_json) :: args
747747+ in
748748+ let inv = Invocation.create
749749+ ~name:"SearchSnippet/get"
750750+ ~arguments:(json_obj args)
751751+ ~method_call_id:call_id
752752+ in
753753+ record_invocation inv state;
754754+ Search_snippet_get_handle { call_id; method_name = "SearchSnippet/get" }
755755+756756+let vacation_response_get ~account_id ?properties () state =
757757+ let call_id = fresh_call_id state in
758758+ let args = [ ("accountId", json_of_id account_id) ] in
759759+ let args = match properties with
760760+ | None -> args
761761+ | Some props -> ("properties", json_of_string_list props) :: args
762762+ in
763763+ let inv = Invocation.create
764764+ ~name:"VacationResponse/get"
765765+ ~arguments:(json_obj args)
766766+ ~method_call_id:call_id
767767+ in
768768+ record_invocation inv state;
769769+ Vacation_get_handle { call_id; method_name = "VacationResponse/get" }
770770+771771+let vacation_response_set ~account_id ?if_in_state ~update () state =
772772+ let call_id = fresh_call_id state in
773773+ let args = [ ("accountId", json_of_id account_id) ] in
774774+ let args = match if_in_state with
775775+ | None -> args
776776+ | Some s -> ("ifInState", json_string s) :: args
777777+ in
778778+ let args = ("update", json_map [("singleton", update)]) :: args in
779779+ let inv = Invocation.create
780780+ ~name:"VacationResponse/set"
781781+ ~arguments:(json_obj args)
782782+ ~method_call_id:call_id
783783+ in
784784+ record_invocation inv state;
785785+ Vacation_set_handle { call_id; method_name = "VacationResponse/set" }
786786+787787+let raw_invocation ~name ~arguments state =
788788+ let call_id = fresh_call_id state in
789789+ let inv = Invocation.create
790790+ ~name
791791+ ~arguments
792792+ ~method_call_id:call_id
793793+ in
794794+ record_invocation inv state;
795795+ Raw_handle { call_id; method_name = name }
796796+797797+(* Response parsing *)
798798+799799+let find_invocation ~call_id response =
800800+ List.find_opt
801801+ (fun inv -> Invocation.method_call_id inv = call_id)
802802+ (Response.method_responses response)
803803+804804+let parse : type k r. (k, r) handle -> Response.t -> (r, Jsont.Error.t) result =
805805+ fun handle response ->
806806+ let cid = call_id handle in
807807+ match find_invocation ~call_id:cid response with
808808+ | None ->
809809+ Error (Jsont.Error.msgf Jsont.Meta.none "No response found for call_id: %s" cid)
810810+ | Some inv ->
811811+ let args = Invocation.arguments inv in
812812+ match handle with
813813+ | Query_handle _ ->
814814+ Jsont.Json.decode' Method.query_response_jsont args
815815+ | Query_changes_handle _ ->
816816+ Jsont.Json.decode' Method.query_changes_response_jsont args
817817+ | Email_get_handle _ ->
818818+ Jsont.Json.decode' (Method.get_response_jsont Email.jsont) args
819819+ | Thread_get_handle _ ->
820820+ Jsont.Json.decode' (Method.get_response_jsont Thread.jsont) args
821821+ | Mailbox_get_handle _ ->
822822+ Jsont.Json.decode' (Method.get_response_jsont Mailbox.jsont) args
823823+ | Identity_get_handle _ ->
824824+ Jsont.Json.decode' (Method.get_response_jsont Identity.jsont) args
825825+ | Submission_get_handle _ ->
826826+ Jsont.Json.decode' (Method.get_response_jsont Submission.jsont) args
827827+ | Search_snippet_get_handle _ ->
828828+ Jsont.Json.decode' (Method.get_response_jsont Search_snippet.jsont) args
829829+ | Vacation_get_handle _ ->
830830+ Jsont.Json.decode' (Method.get_response_jsont Vacation.jsont) args
831831+ | Changes_handle _ ->
832832+ Jsont.Json.decode' Method.changes_response_jsont args
833833+ | Email_set_handle _ ->
834834+ Jsont.Json.decode' (Method.set_response_jsont Email.jsont) args
835835+ | Mailbox_set_handle _ ->
836836+ Jsont.Json.decode' (Method.set_response_jsont Mailbox.jsont) args
837837+ | Identity_set_handle _ ->
838838+ Jsont.Json.decode' (Method.set_response_jsont Identity.jsont) args
839839+ | Submission_set_handle _ ->
840840+ Jsont.Json.decode' (Method.set_response_jsont Submission.jsont) args
841841+ | Vacation_set_handle _ ->
842842+ Jsont.Json.decode' (Method.set_response_jsont Vacation.jsont) args
843843+ | Email_copy_handle _ ->
844844+ Jsont.Json.decode' (Method.copy_response_jsont Email.jsont) args
845845+ | Raw_handle _ ->
846846+ Ok args
847847+848848+let parse_exn handle response =
849849+ match parse handle response with
850850+ | Ok r -> r
851851+ | Error e -> failwith (Jsont.Error.to_string e)
+556
lib/core/chain.mli
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Anil Madhavapeddy. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** JMAP method chaining with automatic result references.
77+88+ This module provides a monadic interface for building JMAP requests
99+ where method calls can reference results from previous calls in the
1010+ same request. Call IDs are generated automatically.
1111+1212+ {2 Basic Example}
1313+1414+ Query for emails and fetch their details in a single request:
1515+ {[
1616+ let open Jmap.Chain in
1717+ let request, emails = build ~capabilities:[core; mail] begin
1818+ let* query = email_query ~account_id
1919+ ~filter:(Condition { in_mailbox = Some inbox_id; _ })
2020+ ~limit:50L ()
2121+ in
2222+ let* emails = email_get ~account_id
2323+ ~ids:(from_query query)
2424+ ~properties:["subject"; "from"; "receivedAt"]
2525+ ()
2626+ in
2727+ return emails
2828+ end in
2929+ match Client.request client request with
3030+ | Ok response ->
3131+ let emails = parse emails response in
3232+ ...
3333+ ]}
3434+3535+ {2 Creation and Submission}
3636+3737+ Create a draft email and submit it in one request:
3838+ {[
3939+ let* set_h, draft_cid = email_set ~account_id
4040+ ~create:[email_create ~mailbox_ids:[drafts_id] ~subject:"Hello" ...]
4141+ ()
4242+ in
4343+ let* _ = email_submission_set ~account_id
4444+ ~create:[submission_create
4545+ ~email_id:(created_id draft_cid)
4646+ ~identity_id]
4747+ ()
4848+ in
4949+ return set_h
5050+ ]}
5151+5252+ {2 Multi-step Chains}
5353+5454+ The RFC 8620 example - fetch from/date/subject for all emails in
5555+ the first 10 threads in the inbox:
5656+ {[
5757+ let* q = email_query ~account_id
5858+ ~filter:(Condition { in_mailbox = Some inbox_id; _ })
5959+ ~sort:[comparator ~is_ascending:false "receivedAt"]
6060+ ~collapse_threads:true ~limit:10L ()
6161+ in
6262+ let* e1 = email_get ~account_id
6363+ ~ids:(from_query q)
6464+ ~properties:["threadId"]
6565+ ()
6666+ in
6767+ let* threads = thread_get ~account_id
6868+ ~ids:(from_get_field e1 "threadId")
6969+ ()
7070+ in
7171+ let* e2 = email_get ~account_id
7272+ ~ids:(from_get_field threads "emailIds")
7373+ ~properties:["from"; "receivedAt"; "subject"]
7474+ ()
7575+ in
7676+ return e2
7777+ ]} *)
7878+7979+(** {1 Handles}
8080+8181+ Method invocations return handles that encode both the method kind
8282+ (for building result references) and the exact response type
8383+ (for type-safe parsing). *)
8484+8585+(** Phantom type for query method handles. *)
8686+type query
8787+8888+(** Phantom type for get method handles. *)
8989+type get
9090+9191+(** Phantom type for changes method handles. *)
9292+type changes
9393+9494+(** Phantom type for set method handles. *)
9595+type set
9696+9797+(** Phantom type for query_changes method handles. *)
9898+type query_changes
9999+100100+(** Phantom type for copy method handles. *)
101101+type copy
102102+103103+(** Phantom type for import method handles. *)
104104+type import
105105+106106+(** Phantom type for parse method handles. *)
107107+type parse
108108+109109+(** A handle to a method invocation.
110110+111111+ The first type parameter indicates the method kind (query/get/changes/set/...),
112112+ used for building result references. The second type parameter is the
113113+ parsed response type, enabling type-safe parsing via {!parse}. *)
114114+type (_, _) handle
115115+116116+val call_id : (_, _) handle -> string
117117+(** [call_id h] returns the auto-generated call ID for this invocation. *)
118118+119119+val method_name : (_, _) handle -> string
120120+(** [method_name h] returns the method name (e.g., "Email/query"). *)
121121+122122+(** {1 Creation IDs}
123123+124124+ When creating objects via [/set] methods, you can reference the
125125+ server-assigned ID before the request completes using creation IDs. *)
126126+127127+type 'a create_id
128128+(** A creation ID for an object of type ['a]. Used to reference
129129+ newly created objects within the same request. *)
130130+131131+val created_id : _ create_id -> Jmap_proto.Id.t
132132+(** [created_id cid] returns a placeholder ID (["#cN"]) that the server
133133+ will substitute with the real ID. Use this to reference a created
134134+ object in subsequent method calls within the same request. *)
135135+136136+val created_id_of_string : string -> Jmap_proto.Id.t
137137+(** [created_id_of_string s] returns a placeholder ID for a string creation ID.
138138+ For example, [created_id_of_string "draft1"] returns ["#draft1"]. *)
139139+140140+(** {1 ID Sources}
141141+142142+ Methods that accept IDs can take them either as concrete values
143143+ or as references to results from previous method calls. *)
144144+145145+type id_source =
146146+ | Ids of Jmap_proto.Id.t list
147147+ (** Concrete list of IDs. *)
148148+ | Ref of Jmap_proto.Invocation.result_reference
149149+ (** Back-reference to a previous method's result. *)
150150+151151+val ids : Jmap_proto.Id.t list -> id_source
152152+(** [ids lst] provides concrete IDs. *)
153153+154154+val id : Jmap_proto.Id.t -> id_source
155155+(** [id x] provides a single concrete ID. *)
156156+157157+(** {2 References from Query} *)
158158+159159+val from_query : (query, _) handle -> id_source
160160+(** [from_query h] references [/ids] from a query response. *)
161161+162162+(** {2 References from Get} *)
163163+164164+val from_get_ids : (get, _) handle -> id_source
165165+(** [from_get_ids h] references [/list/*/id] from a get response. *)
166166+167167+val from_get_field : (get, _) handle -> string -> id_source
168168+(** [from_get_field h field] references [/list/*/field] from a get response.
169169+ Common fields: ["threadId"], ["emailIds"], ["mailboxIds"]. *)
170170+171171+(** {2 References from Changes} *)
172172+173173+val from_changes_created : (changes, _) handle -> id_source
174174+(** [from_changes_created h] references [/created] from a changes response. *)
175175+176176+val from_changes_updated : (changes, _) handle -> id_source
177177+(** [from_changes_updated h] references [/updated] from a changes response. *)
178178+179179+val from_changes_destroyed : (changes, _) handle -> id_source
180180+(** [from_changes_destroyed h] references [/destroyed] from a changes response. *)
181181+182182+(** {2 References from Set} *)
183183+184184+val from_set_created : (set, _) handle -> id_source
185185+(** [from_set_created h] references [/created/*/id] - IDs of objects created
186186+ by a set operation. *)
187187+188188+val from_set_updated : (set, _) handle -> id_source
189189+(** [from_set_updated h] references [/updated] - IDs of objects updated. *)
190190+191191+(** {2 References from QueryChanges} *)
192192+193193+val from_query_changes_removed : (query_changes, _) handle -> id_source
194194+(** [from_query_changes_removed h] references [/removed] from queryChanges. *)
195195+196196+val from_query_changes_added : (query_changes, _) handle -> id_source
197197+(** [from_query_changes_added h] references [/added/*/id] from queryChanges. *)
198198+199199+(** {2 References from Copy} *)
200200+201201+val from_copy_created : (copy, _) handle -> id_source
202202+(** [from_copy_created h] references [/created/*/id] from copy response. *)
203203+204204+(** {2 References from Import} *)
205205+206206+val from_import_created : (import, _) handle -> id_source
207207+(** [from_import_created h] references [/created/*/id] from import response. *)
208208+209209+(** {1 Chain Monad}
210210+211211+ A monad for building JMAP requests with automatic call ID generation
212212+ and invocation collection. *)
213213+214214+type 'a t
215215+(** A chain computation that produces ['a] (typically a handle). *)
216216+217217+val return : 'a -> 'a t
218218+(** [return x] is a computation that produces [x] without adding any
219219+ method invocations. *)
220220+221221+val bind : 'a t -> ('a -> 'b t) -> 'b t
222222+(** [bind m f] sequences computations, threading the chain state. *)
223223+224224+val map : ('a -> 'b) -> 'a t -> 'b t
225225+(** [map f m] applies [f] to the result of [m]. *)
226226+227227+val both : 'a t -> 'b t -> ('a * 'b) t
228228+(** [both a b] runs both computations, returning their results as a pair. *)
229229+230230+(** {2 Syntax} *)
231231+232232+val ( let* ) : 'a t -> ('a -> 'b t) -> 'b t
233233+val ( let+ ) : 'a t -> ('a -> 'b) -> 'b t
234234+val ( and* ) : 'a t -> 'b t -> ('a * 'b) t
235235+val ( and+ ) : 'a t -> 'b t -> ('a * 'b) t
236236+237237+(** {1 Building Requests} *)
238238+239239+val build :
240240+ capabilities:string list ->
241241+ 'a t ->
242242+ Jmap_proto.Request.t * 'a
243243+(** [build ~capabilities chain] runs the chain computation, returning
244244+ the JMAP request and the final value (typically a handle for parsing). *)
245245+246246+val build_request :
247247+ capabilities:string list ->
248248+ 'a t ->
249249+ Jmap_proto.Request.t
250250+(** [build_request ~capabilities chain] is like {!build} but discards
251251+ the final value. *)
252252+253253+(** {1 Method Builders}
254254+255255+ Each builder returns a handle wrapped in the chain monad.
256256+ Call IDs are assigned automatically based on invocation order. *)
257257+258258+(** {2 Email Methods} *)
259259+260260+val email_query :
261261+ account_id:Jmap_proto.Id.t ->
262262+ ?filter:Jmap_proto.Mail_filter.email_filter ->
263263+ ?sort:Jmap_proto.Filter.comparator list ->
264264+ ?position:int64 ->
265265+ ?anchor:Jmap_proto.Id.t ->
266266+ ?anchor_offset:int64 ->
267267+ ?limit:int64 ->
268268+ ?calculate_total:bool ->
269269+ ?collapse_threads:bool ->
270270+ unit ->
271271+ (query, Jmap_proto.Method.query_response) handle t
272272+273273+val email_get :
274274+ account_id:Jmap_proto.Id.t ->
275275+ ?ids:id_source ->
276276+ ?properties:string list ->
277277+ ?body_properties:string list ->
278278+ ?fetch_text_body_values:bool ->
279279+ ?fetch_html_body_values:bool ->
280280+ ?fetch_all_body_values:bool ->
281281+ ?max_body_value_bytes:int64 ->
282282+ unit ->
283283+ (get, Jmap_proto.Email.t Jmap_proto.Method.get_response) handle t
284284+285285+val email_changes :
286286+ account_id:Jmap_proto.Id.t ->
287287+ since_state:string ->
288288+ ?max_changes:int64 ->
289289+ unit ->
290290+ (changes, Jmap_proto.Method.changes_response) handle t
291291+292292+val email_query_changes :
293293+ account_id:Jmap_proto.Id.t ->
294294+ since_query_state:string ->
295295+ ?filter:Jmap_proto.Mail_filter.email_filter ->
296296+ ?sort:Jmap_proto.Filter.comparator list ->
297297+ ?max_changes:int64 ->
298298+ ?up_to_id:Jmap_proto.Id.t ->
299299+ ?calculate_total:bool ->
300300+ unit ->
301301+ (query_changes, Jmap_proto.Method.query_changes_response) handle t
302302+303303+val email_set :
304304+ account_id:Jmap_proto.Id.t ->
305305+ ?if_in_state:string ->
306306+ ?create:(string * Jsont.Json.t) list ->
307307+ ?update:(Jmap_proto.Id.t * Jsont.Json.t) list ->
308308+ ?destroy:id_source ->
309309+ unit ->
310310+ (set, Jmap_proto.Email.t Jmap_proto.Method.set_response) handle t
311311+(** Build an Email/set invocation.
312312+313313+ [create] is a list of [(creation_id, email_object)] pairs where
314314+ [creation_id] is a client-chosen string (e.g., "draft1") and
315315+ [email_object] is the JSON representation of the email to create.
316316+317317+ Use {!created_id_of_string} to reference created objects in later calls. *)
318318+319319+val email_copy :
320320+ from_account_id:Jmap_proto.Id.t ->
321321+ account_id:Jmap_proto.Id.t ->
322322+ ?if_from_in_state:string ->
323323+ ?if_in_state:string ->
324324+ ?create:(Jmap_proto.Id.t * Jsont.Json.t) list ->
325325+ ?on_success_destroy_original:bool ->
326326+ ?destroy_from_if_in_state:string ->
327327+ unit ->
328328+ (copy, Jmap_proto.Email.t Jmap_proto.Method.copy_response) handle t
329329+(** Build an Email/copy invocation.
330330+331331+ [create] maps source email IDs to override objects. The source email
332332+ is copied to the target account with any overridden properties. *)
333333+334334+(** {2 Thread Methods} *)
335335+336336+val thread_get :
337337+ account_id:Jmap_proto.Id.t ->
338338+ ?ids:id_source ->
339339+ unit ->
340340+ (get, Jmap_proto.Thread.t Jmap_proto.Method.get_response) handle t
341341+342342+val thread_changes :
343343+ account_id:Jmap_proto.Id.t ->
344344+ since_state:string ->
345345+ ?max_changes:int64 ->
346346+ unit ->
347347+ (changes, Jmap_proto.Method.changes_response) handle t
348348+349349+(** {2 Mailbox Methods} *)
350350+351351+val mailbox_query :
352352+ account_id:Jmap_proto.Id.t ->
353353+ ?filter:Jmap_proto.Mail_filter.mailbox_filter ->
354354+ ?sort:Jmap_proto.Filter.comparator list ->
355355+ ?position:int64 ->
356356+ ?anchor:Jmap_proto.Id.t ->
357357+ ?anchor_offset:int64 ->
358358+ ?limit:int64 ->
359359+ ?calculate_total:bool ->
360360+ unit ->
361361+ (query, Jmap_proto.Method.query_response) handle t
362362+363363+val mailbox_get :
364364+ account_id:Jmap_proto.Id.t ->
365365+ ?ids:id_source ->
366366+ ?properties:string list ->
367367+ unit ->
368368+ (get, Jmap_proto.Mailbox.t Jmap_proto.Method.get_response) handle t
369369+370370+val mailbox_changes :
371371+ account_id:Jmap_proto.Id.t ->
372372+ since_state:string ->
373373+ ?max_changes:int64 ->
374374+ unit ->
375375+ (changes, Jmap_proto.Method.changes_response) handle t
376376+377377+val mailbox_query_changes :
378378+ account_id:Jmap_proto.Id.t ->
379379+ since_query_state:string ->
380380+ ?filter:Jmap_proto.Mail_filter.mailbox_filter ->
381381+ ?sort:Jmap_proto.Filter.comparator list ->
382382+ ?max_changes:int64 ->
383383+ ?up_to_id:Jmap_proto.Id.t ->
384384+ ?calculate_total:bool ->
385385+ unit ->
386386+ (query_changes, Jmap_proto.Method.query_changes_response) handle t
387387+388388+val mailbox_set :
389389+ account_id:Jmap_proto.Id.t ->
390390+ ?if_in_state:string ->
391391+ ?create:(string * Jsont.Json.t) list ->
392392+ ?update:(Jmap_proto.Id.t * Jsont.Json.t) list ->
393393+ ?destroy:id_source ->
394394+ ?on_destroy_remove_emails:bool ->
395395+ unit ->
396396+ (set, Jmap_proto.Mailbox.t Jmap_proto.Method.set_response) handle t
397397+398398+(** {2 Identity Methods} *)
399399+400400+val identity_get :
401401+ account_id:Jmap_proto.Id.t ->
402402+ ?ids:id_source ->
403403+ ?properties:string list ->
404404+ unit ->
405405+ (get, Jmap_proto.Identity.t Jmap_proto.Method.get_response) handle t
406406+407407+val identity_changes :
408408+ account_id:Jmap_proto.Id.t ->
409409+ since_state:string ->
410410+ ?max_changes:int64 ->
411411+ unit ->
412412+ (changes, Jmap_proto.Method.changes_response) handle t
413413+414414+val identity_set :
415415+ account_id:Jmap_proto.Id.t ->
416416+ ?if_in_state:string ->
417417+ ?create:(string * Jsont.Json.t) list ->
418418+ ?update:(Jmap_proto.Id.t * Jsont.Json.t) list ->
419419+ ?destroy:id_source ->
420420+ unit ->
421421+ (set, Jmap_proto.Identity.t Jmap_proto.Method.set_response) handle t
422422+423423+(** {2 EmailSubmission Methods} *)
424424+425425+val email_submission_query :
426426+ account_id:Jmap_proto.Id.t ->
427427+ ?filter:Jmap_proto.Mail_filter.submission_filter ->
428428+ ?sort:Jmap_proto.Filter.comparator list ->
429429+ ?position:int64 ->
430430+ ?anchor:Jmap_proto.Id.t ->
431431+ ?anchor_offset:int64 ->
432432+ ?limit:int64 ->
433433+ ?calculate_total:bool ->
434434+ unit ->
435435+ (query, Jmap_proto.Method.query_response) handle t
436436+437437+val email_submission_get :
438438+ account_id:Jmap_proto.Id.t ->
439439+ ?ids:id_source ->
440440+ ?properties:string list ->
441441+ unit ->
442442+ (get, Jmap_proto.Submission.t Jmap_proto.Method.get_response) handle t
443443+444444+val email_submission_changes :
445445+ account_id:Jmap_proto.Id.t ->
446446+ since_state:string ->
447447+ ?max_changes:int64 ->
448448+ unit ->
449449+ (changes, Jmap_proto.Method.changes_response) handle t
450450+451451+val email_submission_query_changes :
452452+ account_id:Jmap_proto.Id.t ->
453453+ since_query_state:string ->
454454+ ?filter:Jmap_proto.Mail_filter.submission_filter ->
455455+ ?sort:Jmap_proto.Filter.comparator list ->
456456+ ?max_changes:int64 ->
457457+ ?up_to_id:Jmap_proto.Id.t ->
458458+ ?calculate_total:bool ->
459459+ unit ->
460460+ (query_changes, Jmap_proto.Method.query_changes_response) handle t
461461+462462+val email_submission_set :
463463+ account_id:Jmap_proto.Id.t ->
464464+ ?if_in_state:string ->
465465+ ?create:(string * Jsont.Json.t) list ->
466466+ ?update:(Jmap_proto.Id.t * Jsont.Json.t) list ->
467467+ ?destroy:id_source ->
468468+ ?on_success_update_email:(string * Jsont.Json.t) list ->
469469+ ?on_success_destroy_email:string list ->
470470+ unit ->
471471+ (set, Jmap_proto.Submission.t Jmap_proto.Method.set_response) handle t
472472+(** Build an EmailSubmission/set invocation.
473473+474474+ [on_success_update_email] and [on_success_destroy_email] take creation IDs
475475+ (like ["#draft1"]) or real email IDs to update/destroy the email after
476476+ successful submission. *)
477477+478478+(** {2 SearchSnippet Methods} *)
479479+480480+val search_snippet_get :
481481+ account_id:Jmap_proto.Id.t ->
482482+ filter:Jmap_proto.Mail_filter.email_filter ->
483483+ email_ids:id_source ->
484484+ unit ->
485485+ (get, Jmap_proto.Search_snippet.t Jmap_proto.Method.get_response) handle t
486486+(** Build a SearchSnippet/get invocation. Note that the filter must match
487487+ the filter used in the Email/query that produced the email IDs. *)
488488+489489+(** {2 VacationResponse Methods} *)
490490+491491+val vacation_response_get :
492492+ account_id:Jmap_proto.Id.t ->
493493+ ?properties:string list ->
494494+ unit ->
495495+ (get, Jmap_proto.Vacation.t Jmap_proto.Method.get_response) handle t
496496+497497+val vacation_response_set :
498498+ account_id:Jmap_proto.Id.t ->
499499+ ?if_in_state:string ->
500500+ update:Jsont.Json.t ->
501501+ unit ->
502502+ (set, Jmap_proto.Vacation.t Jmap_proto.Method.set_response) handle t
503503+(** VacationResponse is a singleton - you can only update "singleton". *)
504504+505505+(** {1 Response Parsing} *)
506506+507507+val parse :
508508+ (_, 'resp) handle ->
509509+ Jmap_proto.Response.t ->
510510+ ('resp, Jsont.Error.t) result
511511+(** [parse handle response] extracts and parses the response for [handle].
512512+513513+ The response type is determined by the handle's type parameter,
514514+ providing compile-time type safety. *)
515515+516516+val parse_exn : (_, 'resp) handle -> Jmap_proto.Response.t -> 'resp
517517+(** [parse_exn handle response] is like {!parse} but raises on error. *)
518518+519519+(** {1 JSON Helpers}
520520+521521+ Convenience functions for building JSON patch objects for /set methods. *)
522522+523523+val json_null : Jsont.Json.t
524524+(** A JSON null value. Use to unset a property. *)
525525+526526+val json_bool : bool -> Jsont.Json.t
527527+(** [json_bool b] creates a JSON boolean. *)
528528+529529+val json_string : string -> Jsont.Json.t
530530+(** [json_string s] creates a JSON string. *)
531531+532532+val json_int : int64 -> Jsont.Json.t
533533+(** [json_int n] creates a JSON number from an int64. *)
534534+535535+val json_obj : (string * Jsont.Json.t) list -> Jsont.Json.t
536536+(** [json_obj fields] creates a JSON object from key-value pairs. *)
537537+538538+val json_array : Jsont.Json.t list -> Jsont.Json.t
539539+(** [json_array items] creates a JSON array. *)
540540+541541+(** {1 Creation ID Helpers} *)
542542+543543+val fresh_create_id : unit -> 'a create_id t
544544+(** [fresh_create_id ()] generates a fresh creation ID within the chain.
545545+ The ID is unique within the request. *)
546546+547547+(** {1 Low-Level Access}
548548+549549+ For users who need direct access to the underlying invocation. *)
550550+551551+val raw_invocation :
552552+ name:string ->
553553+ arguments:Jsont.Json.t ->
554554+ (unit, Jsont.Json.t) handle t
555555+(** [raw_invocation ~name ~arguments] adds a raw method invocation.
556556+ Use this for methods not yet supported by the high-level API. *)
+4-61
lib/core/jmap.ml
···428428 val to_string : t -> (string, Error.t) result
429429end
430430431431-(** {1 Private Interface} *)
431431+(** {1 Request Chaining} *)
432432433433-(** Private module for internal use by Jmap_eio.
433433+(** JMAP method chaining with automatic result references.
434434435435- This exposes the underlying Jsont codecs for serialization. *)
436436-module Private = struct
437437- module Session = struct
438438- let jsont = Proto.Session.jsont
439439- end
440440-441441- module Request = struct
442442- let jsont = Proto.Request.jsont
443443- end
444444-445445- module Response = struct
446446- let jsont = Proto.Response.jsont
447447- end
448448-449449- module Mailbox = struct
450450- let jsont = Proto.Mailbox.jsont
451451- end
452452-453453- module Email = struct
454454- let jsont = Proto.Email.jsont
455455- end
456456-457457- module Thread = struct
458458- let jsont = Proto.Thread.jsont
459459- end
460460-461461- module Identity = struct
462462- let jsont = Proto.Identity.jsont
463463- end
464464-465465- module Submission = struct
466466- let jsont = Proto.Submission.jsont
467467- end
468468-469469- module Vacation = struct
470470- let jsont = Proto.Vacation.jsont
471471- end
472472-473473- module Blob = struct
474474- let upload_response_jsont = Proto.Blob.upload_response_jsont
475475- end
476476-477477- module Method = struct
478478- let get_response_jsont = Proto.Method.get_response_jsont
479479- let query_response_jsont = Proto.Method.query_response_jsont
480480- let changes_response_jsont = Proto.Method.changes_response_jsont
481481- let set_response_jsont = Proto.Method.set_response_jsont
482482- end
483483-484484- module Mail_filter = struct
485485- let email_filter_jsont = Proto.Mail_filter.email_filter_jsont
486486- let mailbox_filter_jsont = Proto.Mail_filter.mailbox_filter_jsont
487487- let submission_filter_jsont = Proto.Mail_filter.submission_filter_jsont
488488- end
489489-490490- module Filter = struct
491491- let comparator_jsont = Proto.Filter.comparator_jsont
492492- end
493493-end
435435+ See {!Chain} for the full interface. *)
436436+module Chain = Chain
+5-61
lib/core/jmap.mli
···453453 val to_string : t -> (string, Error.t) result
454454end
455455456456-(** {1 Private Interface} *)
457457-458458-(** Private module for internal use by Jmap_eio.
459459-460460- This exposes the underlying Jsont codecs for serialization. *)
461461-module Private : sig
462462- module Session : sig
463463- val jsont : Proto.Session.t Jsont.t
464464- end
465465-466466- module Request : sig
467467- val jsont : Proto.Request.t Jsont.t
468468- end
469469-470470- module Response : sig
471471- val jsont : Proto.Response.t Jsont.t
472472- end
473473-474474- module Mailbox : sig
475475- val jsont : Proto.Mailbox.t Jsont.t
476476- end
477477-478478- module Email : sig
479479- val jsont : Proto.Email.t Jsont.t
480480- end
481481-482482- module Thread : sig
483483- val jsont : Proto.Thread.t Jsont.t
484484- end
485485-486486- module Identity : sig
487487- val jsont : Proto.Identity.t Jsont.t
488488- end
456456+(** {1 Request Chaining} *)
489457490490- module Submission : sig
491491- val jsont : Proto.Submission.t Jsont.t
492492- end
458458+(** JMAP method chaining with automatic result references.
493459494494- module Vacation : sig
495495- val jsont : Proto.Vacation.t Jsont.t
496496- end
497497-498498- module Blob : sig
499499- val upload_response_jsont : Proto.Blob.upload_response Jsont.t
500500- end
501501-502502- module Method : sig
503503- val get_response_jsont : 'a Jsont.t -> 'a Proto.Method.get_response Jsont.t
504504- val query_response_jsont : Proto.Method.query_response Jsont.t
505505- val changes_response_jsont : Proto.Method.changes_response Jsont.t
506506- val set_response_jsont : 'a Jsont.t -> 'a Proto.Method.set_response Jsont.t
507507- end
508508-509509- module Mail_filter : sig
510510- val email_filter_jsont : Proto.Mail_filter.email_filter Jsont.t
511511- val mailbox_filter_jsont : Proto.Mail_filter.mailbox_filter Jsont.t
512512- val submission_filter_jsont : Proto.Mail_filter.submission_filter Jsont.t
513513- end
514514-515515- module Filter : sig
516516- val comparator_jsont : Proto.Filter.comparator Jsont.t
517517- end
518518-end
460460+ This module provides a monadic interface for building JMAP requests
461461+ where method calls can reference results from previous calls. *)
462462+module Chain = Chain