···1+(** JMAP Mail Extension Library (RFC 8621).
2+3+ This library extends the core JMAP protocol with email-specific
4+ functionality as defined in RFC 8621. It provides types and signatures
5+ for interacting with JMAP Mail data types: Mailbox, Thread, Email,
6+ SearchSnippet, Identity, EmailSubmission, and VacationResponse.
7+8+ Requires the core Jmap library and Jmap_unix library for network operations.
9+10+ @see <https://www.rfc-editor.org/rfc/rfc8621.html> RFC 8621: JMAP for Mail
11+*)
12+13+open Jmap.Types
14+15+(** {1 Core Types} *)
16+module Types = Jmap_email_types
17+18+(** {1 Mailbox}
19+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2> RFC 8621, Section 2 *)
20+module Mailbox = Jmap_mailbox
21+22+(** {1 Thread}
23+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-3> RFC 8621, Section 3 *)
24+module Thread = Jmap_thread
25+26+(** {1 Search Snippet}
27+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-5> RFC 8621, Section 5 *)
28+module SearchSnippet = Jmap_search_snippet
29+30+(** {1 Identity}
31+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-6> RFC 8621, Section 6 *)
32+module Identity = Jmap_identity
33+34+(** {1 Email Submission}
35+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7> RFC 8621, Section 7 *)
36+module Submission = Jmap_submission
37+38+(** {1 Vacation Response}
39+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-8> RFC 8621, Section 8 *)
40+module Vacation = Jmap_vacation
41+42+(** {1 Example Usage}
43+44+ The following example demonstrates using the JMAP Email library to fetch unread emails
45+ from a specific sender.
46+47+{[
48+ (* OCaml 5.1 required for Lwt let operators *)
49+ open Lwt.Syntax
50+ open Jmap
51+ open Jmap.Types
52+ open Jmap.Wire
53+ open Jmap.Methods
54+ open Jmap_email
55+ open Jmap.Unix
56+57+ let list_unread_from_sender ctx session sender_email =
58+ (* Find the primary mail account *)
59+ let primary_mail_account_id =
60+ Hashtbl.find session.primary_accounts capability_mail
61+ in
62+ (* Construct the filter *)
63+ let filter : filter =
64+ Filter_operator (Filter_operator.v
65+ ~operator:`AND
66+ ~conditions:[
67+ Filter_condition (Yojson.Safe.to_basic (`Assoc [
68+ ("from", `String sender_email);
69+ ]));
70+ Filter_condition (Yojson.Safe.to_basic (`Assoc [
71+ ("hasKeyword", `String keyword_seen);
72+ ("value", `Bool false);
73+ ]));
74+ ]
75+ ())
76+ in
77+ (* Prepare the Email/query invocation *)
78+ let query_args = Query_args.v
79+ ~account_id:primary_mail_account_id
80+ ~filter
81+ ~sort:[
82+ Comparator.v
83+ ~property:"receivedAt"
84+ ~is_ascending:false
85+ ()
86+ ]
87+ ~position:0
88+ ~limit:20 (* Get latest 20 *)
89+ ~calculate_total:false
90+ ~collapse_threads:false
91+ ()
92+ in
93+ let query_invocation = Invocation.v
94+ ~method_name:"Email/query"
95+ ~arguments:(* Yojson conversion of query_args needed here *)
96+ ~method_call_id:"q1"
97+ ()
98+ in
99+100+ (* Prepare the Email/get invocation using a back-reference *)
101+ let get_args = Get_args.v
102+ ~account_id:primary_mail_account_id
103+ ~properties:["id"; "subject"; "receivedAt"; "from"]
104+ ()
105+ in
106+ let get_invocation = Invocation.v
107+ ~method_name:"Email/get"
108+ ~arguments:(* Yojson conversion of get_args, with ids replaced by a ResultReference to q1 needed here *)
109+ ~method_call_id:"g1"
110+ ()
111+ in
112+113+ (* Prepare the JMAP request *)
114+ let request = Request.v
115+ ~using:[ Jmap.capability_core; capability_mail ]
116+ ~method_calls:[ query_invocation; get_invocation ]
117+ ()
118+ in
119+120+ (* Send the request *)
121+ let* response = Jmap.Unix.request ctx request in
122+123+ (* Process the response (extract Email/get results) *)
124+ (* ... Omitted: find the Email/get response in response.method_responses ... *)
125+ Lwt.return_unit
126+]}
127+*)
128+129+(** Capability URI for JMAP Mail.
130+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-1.3.1> RFC 8621, Section 1.3.1 *)
131+val capability_mail : string
132+133+(** Capability URI for JMAP Submission.
134+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-1.3.2> RFC 8621, Section 1.3.2 *)
135+val capability_submission : string
136+137+(** Capability URI for JMAP Vacation Response.
138+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-1.3.3> RFC 8621, Section 1.3.3 *)
139+val capability_vacationresponse : string
140+141+(** Type name for EmailDelivery push notifications.
142+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-1.5> RFC 8621, Section 1.5 *)
143+val push_event_type_email_delivery : string
144+145+(** JMAP keywords corresponding to IMAP system flags.
146+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.1> RFC 8621, Section 4.1.1 *)
147+val keyword_draft : string
148+val keyword_seen : string
149+val keyword_flagged : string
150+val keyword_answered : string
151+152+(** Common JMAP keywords from RFC 5788.
153+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.1> RFC 8621, Section 4.1.1 *)
154+val keyword_forwarded : string
155+val keyword_phishing : string
156+val keyword_junk : string
157+val keyword_notjunk : string
158+159+(** Functions to manipulate email flags/keywords *)
160+module Keyword_ops : sig
161+ (** Add a keyword/flag to an email *)
162+ val add : Types.Email.t -> Types.Keywords.keyword -> Types.Email.t
163+164+ (** Remove a keyword/flag from an email *)
165+ val remove : Types.Email.t -> Types.Keywords.keyword -> Types.Email.t
166+167+ (** Mark an email as seen/read *)
168+ val mark_as_seen : Types.Email.t -> Types.Email.t
169+170+ (** Mark an email as unseen/unread *)
171+ val mark_as_unseen : Types.Email.t -> Types.Email.t
172+173+ (** Mark an email as flagged/important *)
174+ val mark_as_flagged : Types.Email.t -> Types.Email.t
175+176+ (** Remove flagged/important marking from an email *)
177+ val unmark_flagged : Types.Email.t -> Types.Email.t
178+179+ (** Mark an email as a draft *)
180+ val mark_as_draft : Types.Email.t -> Types.Email.t
181+182+ (** Remove draft marking from an email *)
183+ val unmark_draft : Types.Email.t -> Types.Email.t
184+185+ (** Mark an email as answered/replied *)
186+ val mark_as_answered : Types.Email.t -> Types.Email.t
187+188+ (** Remove answered/replied marking from an email *)
189+ val unmark_answered : Types.Email.t -> Types.Email.t
190+191+ (** Mark an email as forwarded *)
192+ val mark_as_forwarded : Types.Email.t -> Types.Email.t
193+194+ (** Mark an email as spam/junk *)
195+ val mark_as_junk : Types.Email.t -> Types.Email.t
196+197+ (** Mark an email as not spam/junk *)
198+ val mark_as_not_junk : Types.Email.t -> Types.Email.t
199+200+ (** Mark an email as phishing *)
201+ val mark_as_phishing : Types.Email.t -> Types.Email.t
202+203+ (** Add a custom keyword to an email *)
204+ val add_custom : Types.Email.t -> string -> Types.Email.t
205+206+ (** Remove a custom keyword from an email *)
207+ val remove_custom : Types.Email.t -> string -> Types.Email.t
208+209+ (** Create a patch object to add a keyword to emails *)
210+ val add_keyword_patch : Types.Keywords.keyword -> Jmap.Methods.patch_object
211+212+ (** Create a patch object to remove a keyword from emails *)
213+ val remove_keyword_patch : Types.Keywords.keyword -> Jmap.Methods.patch_object
214+215+ (** Create a patch object to mark emails as seen/read *)
216+ val mark_seen_patch : unit -> Jmap.Methods.patch_object
217+218+ (** Create a patch object to mark emails as unseen/unread *)
219+ val mark_unseen_patch : unit -> Jmap.Methods.patch_object
220+end
221+222+(** Conversion functions for JMAP/IMAP compatibility *)
223+module Conversion : sig
224+ (** Convert a JMAP keyword variant to IMAP flag *)
225+ val keyword_to_imap_flag : Types.Keywords.keyword -> string
226+227+ (** Convert an IMAP flag to JMAP keyword variant *)
228+ val imap_flag_to_keyword : string -> Types.Keywords.keyword
229+230+ (** Check if a string is valid for use as a custom keyword according to RFC 8621 *)
231+ val is_valid_custom_keyword : string -> bool
232+233+ (** Get the JMAP protocol string representation of a keyword *)
234+ val keyword_to_string : Types.Keywords.keyword -> string
235+236+ (** Parse a JMAP protocol string into a keyword variant *)
237+ val string_to_keyword : string -> Types.Keywords.keyword
238+end
239+240+(** {1 Helper Functions} *)
241+242+(** Email query filter helpers *)
243+module Email_filter : sig
244+ (** Create a filter to find messages in a specific mailbox *)
245+ val in_mailbox : id -> Jmap.Methods.Filter.t
246+247+ (** Create a filter to find messages with a specific keyword/flag *)
248+ val has_keyword : Types.Keywords.keyword -> Jmap.Methods.Filter.t
249+250+ (** Create a filter to find messages without a specific keyword/flag *)
251+ val not_has_keyword : Types.Keywords.keyword -> Jmap.Methods.Filter.t
252+253+ (** Create a filter to find unread messages *)
254+ val unread : unit -> Jmap.Methods.Filter.t
255+256+ (** Create a filter to find messages with a specific subject *)
257+ val subject : string -> Jmap.Methods.Filter.t
258+259+ (** Create a filter to find messages from a specific sender *)
260+ val from : string -> Jmap.Methods.Filter.t
261+262+ (** Create a filter to find messages sent to a specific recipient *)
263+ val to_ : string -> Jmap.Methods.Filter.t
264+265+ (** Create a filter to find messages with attachments *)
266+ val has_attachment : unit -> Jmap.Methods.Filter.t
267+268+ (** Create a filter to find messages received before a date *)
269+ val before : date -> Jmap.Methods.Filter.t
270+271+ (** Create a filter to find messages received after a date *)
272+ val after : date -> Jmap.Methods.Filter.t
273+274+ (** Create a filter to find messages with size larger than the given bytes *)
275+ val larger_than : uint -> Jmap.Methods.Filter.t
276+277+ (** Create a filter to find messages with size smaller than the given bytes *)
278+ val smaller_than : uint -> Jmap.Methods.Filter.t
279+end
280+281+(** Common email sorting comparators *)
282+module Email_sort : sig
283+ (** Sort by received date (most recent first) *)
284+ val received_newest_first : unit -> Jmap.Methods.Comparator.t
285+286+ (** Sort by received date (oldest first) *)
287+ val received_oldest_first : unit -> Jmap.Methods.Comparator.t
288+289+ (** Sort by sent date (most recent first) *)
290+ val sent_newest_first : unit -> Jmap.Methods.Comparator.t
291+292+ (** Sort by sent date (oldest first) *)
293+ val sent_oldest_first : unit -> Jmap.Methods.Comparator.t
294+295+ (** Sort by subject (A-Z) *)
296+ val subject_asc : unit -> Jmap.Methods.Comparator.t
297+298+ (** Sort by subject (Z-A) *)
299+ val subject_desc : unit -> Jmap.Methods.Comparator.t
300+301+ (** Sort by size (largest first) *)
302+ val size_largest_first : unit -> Jmap.Methods.Comparator.t
303+304+ (** Sort by size (smallest first) *)
305+ val size_smallest_first : unit -> Jmap.Methods.Comparator.t
306+307+ (** Sort by from address (A-Z) *)
308+ val from_asc : unit -> Jmap.Methods.Comparator.t
309+310+ (** Sort by from address (Z-A) *)
311+ val from_desc : unit -> Jmap.Methods.Comparator.t
312+end
313+314+(** High-level email operations are implemented in the Jmap.Unix.Email module *)
···1+(** Common types for JMAP Mail (RFC 8621). *)
2+3+open Jmap.Types
4+5+(** Represents an email address with an optional name.
6+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.2.3> RFC 8621, Section 4.1.2.3 *)
7+module Email_address : sig
8+ type t
9+10+ (** Get the display name for the address (if any) *)
11+ val name : t -> string option
12+13+ (** Get the email address *)
14+ val email : t -> string
15+16+ (** Create a new email address *)
17+ val v :
18+ ?name:string ->
19+ email:string ->
20+ unit -> t
21+end
22+23+(** Represents a group of email addresses.
24+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.2.4> RFC 8621, Section 4.1.2.4 *)
25+module Email_address_group : sig
26+ type t
27+28+ (** Get the name of the group (if any) *)
29+ val name : t -> string option
30+31+ (** Get the list of addresses in the group *)
32+ val addresses : t -> Email_address.t list
33+34+ (** Create a new address group *)
35+ val v :
36+ ?name:string ->
37+ addresses:Email_address.t list ->
38+ unit -> t
39+end
40+41+(** Represents a header field (name and raw value).
42+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.3> RFC 8621, Section 4.1.3 *)
43+module Email_header : sig
44+ type t
45+46+ (** Get the header field name *)
47+ val name : t -> string
48+49+ (** Get the raw header field value *)
50+ val value : t -> string
51+52+ (** Create a new header field *)
53+ val v :
54+ name:string ->
55+ value:string ->
56+ unit -> t
57+end
58+59+(** Represents a body part within an Email's MIME structure.
60+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.4> RFC 8621, Section 4.1.4 *)
61+module Email_body_part : sig
62+ type t
63+64+ (** Get the part ID (null only for multipart types) *)
65+ val id : t -> string option
66+67+ (** Get the blob ID (null only for multipart types) *)
68+ val blob_id : t -> id option
69+70+ (** Get the size of the part in bytes *)
71+ val size : t -> uint
72+73+ (** Get the list of headers for this part *)
74+ val headers : t -> Email_header.t list
75+76+ (** Get the filename (if any) *)
77+ val name : t -> string option
78+79+ (** Get the MIME type *)
80+ val mime_type : t -> string
81+82+ (** Get the charset (if any) *)
83+ val charset : t -> string option
84+85+ (** Get the content disposition (if any) *)
86+ val disposition : t -> string option
87+88+ (** Get the content ID (if any) *)
89+ val cid : t -> string option
90+91+ (** Get the list of languages (if any) *)
92+ val language : t -> string list option
93+94+ (** Get the content location (if any) *)
95+ val location : t -> string option
96+97+ (** Get the sub-parts (only for multipart types) *)
98+ val sub_parts : t -> t list option
99+100+ (** Get any other requested headers (header properties) *)
101+ val other_headers : t -> Yojson.Safe.t string_map
102+103+ (** Create a new body part *)
104+ val v :
105+ ?id:string ->
106+ ?blob_id:id ->
107+ size:uint ->
108+ headers:Email_header.t list ->
109+ ?name:string ->
110+ mime_type:string ->
111+ ?charset:string ->
112+ ?disposition:string ->
113+ ?cid:string ->
114+ ?language:string list ->
115+ ?location:string ->
116+ ?sub_parts:t list ->
117+ ?other_headers:Yojson.Safe.t string_map ->
118+ unit -> t
119+end
120+121+(** Represents the decoded value of a text body part.
122+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.4> RFC 8621, Section 4.1.4 *)
123+module Email_body_value : sig
124+ type t
125+126+ (** Get the decoded text content *)
127+ val value : t -> string
128+129+ (** Check if there was an encoding problem *)
130+ val has_encoding_problem : t -> bool
131+132+ (** Check if the content was truncated *)
133+ val is_truncated : t -> bool
134+135+ (** Create a new body value *)
136+ val v :
137+ value:string ->
138+ ?encoding_problem:bool ->
139+ ?truncated:bool ->
140+ unit -> t
141+end
142+143+(** Type to represent email message flags/keywords.
144+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1.1> RFC 8621, Section 4.1.1 *)
145+module Keywords : sig
146+ (** Represents different types of JMAP keywords *)
147+ type keyword =
148+ | Draft (** "$draft": The Email is a draft the user is composing *)
149+ | Seen (** "$seen": The Email has been read *)
150+ | Flagged (** "$flagged": The Email has been flagged for urgent/special attention *)
151+ | Answered (** "$answered": The Email has been replied to *)
152+153+ (* Common extension keywords from RFC 5788 *)
154+ | Forwarded (** "$forwarded": The Email has been forwarded *)
155+ | Phishing (** "$phishing": The Email is likely to be phishing *)
156+ | Junk (** "$junk": The Email is spam/junk *)
157+ | NotJunk (** "$notjunk": The Email is explicitly marked as not spam/junk *)
158+ | Custom of string (** Arbitrary user-defined keyword *)
159+160+ (** A set of keywords applied to an email *)
161+ type t = keyword list
162+163+ (** Check if an email has the draft flag *)
164+ val is_draft : t -> bool
165+166+ (** Check if an email has been read *)
167+ val is_seen : t -> bool
168+169+ (** Check if an email has neither been read nor is a draft *)
170+ val is_unread : t -> bool
171+172+ (** Check if an email has been flagged *)
173+ val is_flagged : t -> bool
174+175+ (** Check if an email has been replied to *)
176+ val is_answered : t -> bool
177+178+ (** Check if an email has been forwarded *)
179+ val is_forwarded : t -> bool
180+181+ (** Check if an email is marked as likely phishing *)
182+ val is_phishing : t -> bool
183+184+ (** Check if an email is marked as junk/spam *)
185+ val is_junk : t -> bool
186+187+ (** Check if an email is explicitly marked as not junk/spam *)
188+ val is_not_junk : t -> bool
189+190+ (** Check if a specific custom keyword is set *)
191+ val has_keyword : t -> string -> bool
192+193+ (** Get a list of all custom keywords (excluding system keywords) *)
194+ val custom_keywords : t -> string list
195+196+ (** Add a keyword to the set *)
197+ val add : t -> keyword -> t
198+199+ (** Remove a keyword from the set *)
200+ val remove : t -> keyword -> t
201+202+ (** Create an empty keyword set *)
203+ val empty : unit -> t
204+205+ (** Create a new keyword set with the specified keywords *)
206+ val of_list : keyword list -> t
207+208+ (** Get the string representation of a keyword as used in the JMAP protocol *)
209+ val to_string : keyword -> string
210+211+ (** Parse a string into a keyword *)
212+ val of_string : string -> keyword
213+214+ (** Convert keyword set to string map representation as used in JMAP *)
215+ val to_map : t -> bool string_map
216+end
217+218+(** Email properties enum.
219+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1> RFC 8621, Section 4.1 *)
220+type email_property =
221+ | Id (** The id of the email *)
222+ | BlobId (** The id of the blob containing the raw message *)
223+ | ThreadId (** The id of the thread this email belongs to *)
224+ | MailboxIds (** The mailboxes this email belongs to *)
225+ | Keywords (** The keywords/flags for this email *)
226+ | Size (** Size of the message in bytes *)
227+ | ReceivedAt (** When the message was received by the server *)
228+ | MessageId (** Value of the Message-ID header *)
229+ | InReplyTo (** Value of the In-Reply-To header *)
230+ | References (** Value of the References header *)
231+ | Sender (** Value of the Sender header *)
232+ | From (** Value of the From header *)
233+ | To (** Value of the To header *)
234+ | Cc (** Value of the Cc header *)
235+ | Bcc (** Value of the Bcc header *)
236+ | ReplyTo (** Value of the Reply-To header *)
237+ | Subject (** Value of the Subject header *)
238+ | SentAt (** Value of the Date header *)
239+ | HasAttachment (** Whether the email has attachments *)
240+ | Preview (** Preview text of the email *)
241+ | BodyStructure (** MIME structure of the email *)
242+ | BodyValues (** Decoded body part values *)
243+ | TextBody (** Text body parts *)
244+ | HtmlBody (** HTML body parts *)
245+ | Attachments (** Attachments *)
246+ | Header of string (** Specific header *)
247+ | Other of string (** Extension property *)
248+249+(** Represents an Email object.
250+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.1> RFC 8621, Section 4.1 *)
251+module Email : sig
252+ (** Email type *)
253+ type t
254+255+ (** ID of the email *)
256+ val id : t -> id option
257+258+ (** ID of the blob containing the raw message *)
259+ val blob_id : t -> id option
260+261+ (** ID of the thread this email belongs to *)
262+ val thread_id : t -> id option
263+264+ (** The set of mailbox IDs this email belongs to *)
265+ val mailbox_ids : t -> bool id_map option
266+267+ (** The set of keywords/flags for this email *)
268+ val keywords : t -> Keywords.t option
269+270+ (** Size of the message in bytes *)
271+ val size : t -> uint option
272+273+ (** When the message was received by the server *)
274+ val received_at : t -> date option
275+276+ (** Subject of the email (if requested) *)
277+ val subject : t -> string option
278+279+ (** Preview text of the email (if requested) *)
280+ val preview : t -> string option
281+282+ (** From addresses (if requested) *)
283+ val from : t -> Email_address.t list option
284+285+ (** To addresses (if requested) *)
286+ val to_ : t -> Email_address.t list option
287+288+ (** CC addresses (if requested) *)
289+ val cc : t -> Email_address.t list option
290+291+ (** Message ID values (if requested) *)
292+ val message_id : t -> string list option
293+294+ (** Get whether the email has attachments (if requested) *)
295+ val has_attachment : t -> bool option
296+297+ (** Get text body parts (if requested) *)
298+ val text_body : t -> Email_body_part.t list option
299+300+ (** Get HTML body parts (if requested) *)
301+ val html_body : t -> Email_body_part.t list option
302+303+ (** Get attachments (if requested) *)
304+ val attachments : t -> Email_body_part.t list option
305+306+ (** Create a new Email object from a server response or for a new email *)
307+ val create :
308+ ?id:id ->
309+ ?blob_id:id ->
310+ ?thread_id:id ->
311+ ?mailbox_ids:bool id_map ->
312+ ?keywords:Keywords.t ->
313+ ?size:uint ->
314+ ?received_at:date ->
315+ ?subject:string ->
316+ ?preview:string ->
317+ ?from:Email_address.t list ->
318+ ?to_:Email_address.t list ->
319+ ?cc:Email_address.t list ->
320+ ?message_id:string list ->
321+ ?has_attachment:bool ->
322+ ?text_body:Email_body_part.t list ->
323+ ?html_body:Email_body_part.t list ->
324+ ?attachments:Email_body_part.t list ->
325+ unit -> t
326+327+ (** Create a patch object for updating email properties *)
328+ val make_patch :
329+ ?add_keywords:Keywords.t ->
330+ ?remove_keywords:Keywords.t ->
331+ ?add_mailboxes:id list ->
332+ ?remove_mailboxes:id list ->
333+ unit -> Jmap.Methods.patch_object
334+335+ (** Extract the ID from an email, returning a Result *)
336+ val get_id : t -> (id, string) result
337+338+ (** Take the ID from an email (fails with an exception if not present) *)
339+ val take_id : t -> id
340+end
341+342+(** Email import options.
343+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.5> RFC 8621, Section 4.5 *)
344+type email_import_options = {
345+ import_to_mailboxes : id list;
346+ import_keywords : Keywords.t option;
347+ import_received_at : date option;
348+}
349+350+(** Email copy options.
351+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-4.6> RFC 8621, Section 4.6 *)
352+type email_copy_options = {
353+ copy_to_account_id : id;
354+ copy_to_mailboxes : id list;
355+ copy_on_success_destroy_original : bool option;
356+}
357+358+(** Convert a property variant to its string representation *)
359+val email_property_to_string : email_property -> string
360+361+(** Parse a string into a property variant *)
362+val string_to_email_property : string -> email_property
363+364+(** Get a list of common properties useful for displaying email lists *)
365+val common_email_properties : email_property list
366+367+(** Get a list of common properties for detailed email view *)
368+val detailed_email_properties : email_property list
···1+(* JMAP Identity. *)
2+3+open Jmap.Types
4+open Jmap.Methods
5+6+(* Identity object. *)
7+type t = {
8+ id_value: id;
9+ name_value: string;
10+ email_value: string;
11+ reply_to_value: Jmap_email_types.Email_address.t list option;
12+ bcc_value: Jmap_email_types.Email_address.t list option;
13+ text_signature_value: string;
14+ html_signature_value: string;
15+ may_delete_value: bool;
16+}
17+18+(* Get the identity ID (immutable, server-set) *)
19+let id t = t.id_value
20+21+(* Get the display name (defaults to "") *)
22+let name t = t.name_value
23+24+(* Get the email address (immutable) *)
25+let email t = t.email_value
26+27+(* Get the reply-to addresses (if any) *)
28+let reply_to t = t.reply_to_value
29+30+(* Get the bcc addresses (if any) *)
31+let bcc t = t.bcc_value
32+33+(* Get the plain text signature (defaults to "") *)
34+let text_signature t = t.text_signature_value
35+36+(* Get the HTML signature (defaults to "") *)
37+let html_signature t = t.html_signature_value
38+39+(* Check if this identity may be deleted (server-set) *)
40+let may_delete t = t.may_delete_value
41+42+(* Create a new identity object *)
43+let v ~id ?(name="") ~email ?reply_to ?bcc ?(text_signature="") ?(html_signature="") ~may_delete () = {
44+ id_value = id;
45+ name_value = name;
46+ email_value = email;
47+ reply_to_value = reply_to;
48+ bcc_value = bcc;
49+ text_signature_value = text_signature;
50+ html_signature_value = html_signature;
51+ may_delete_value = may_delete;
52+}
53+54+(* Types and functions for identity creation and updates *)
55+module Create = struct
56+ type t = {
57+ name_value: string option;
58+ email_value: string;
59+ reply_to_value: Jmap_email_types.Email_address.t list option;
60+ bcc_value: Jmap_email_types.Email_address.t list option;
61+ text_signature_value: string option;
62+ html_signature_value: string option;
63+ }
64+65+ (* Get the name (if specified) *)
66+ let name t = t.name_value
67+68+ (* Get the email address *)
69+ let email t = t.email_value
70+71+ (* Get the reply-to addresses (if any) *)
72+ let reply_to t = t.reply_to_value
73+74+ (* Get the bcc addresses (if any) *)
75+ let bcc t = t.bcc_value
76+77+ (* Get the plain text signature (if specified) *)
78+ let text_signature t = t.text_signature_value
79+80+ (* Get the HTML signature (if specified) *)
81+ let html_signature t = t.html_signature_value
82+83+ (* Create a new identity creation object *)
84+ let v ?name ~email ?reply_to ?bcc ?text_signature ?html_signature () = {
85+ name_value = name;
86+ email_value = email;
87+ reply_to_value = reply_to;
88+ bcc_value = bcc;
89+ text_signature_value = text_signature;
90+ html_signature_value = html_signature;
91+ }
92+93+ (* Server response with info about the created identity *)
94+ module Response = struct
95+ type t = {
96+ id_value: id;
97+ may_delete_value: bool;
98+ }
99+100+ (* Get the server-assigned ID for the created identity *)
101+ let id t = t.id_value
102+103+ (* Check if this identity may be deleted *)
104+ let may_delete t = t.may_delete_value
105+106+ (* Create a new response object *)
107+ let v ~id ~may_delete () = {
108+ id_value = id;
109+ may_delete_value = may_delete;
110+ }
111+ end
112+end
113+114+(* Identity object for update.
115+ Patch object, specific structure not enforced here. *)
116+type update = patch_object
117+118+(* Server-set/computed info for updated identity.
119+ Contains only changed server-set props. *)
120+module Update_response = struct
121+ (* We use the same type as main identity *)
122+ type identity_update = t
123+ type t = identity_update
124+125+ (* Convert to a full Identity object (contains only changed server-set props) *)
126+ let to_identity t = (t : t :> t)
127+128+ (* Create from a full Identity object *)
129+ let of_identity t = (t : t :> t)
130+end
···1+(** JMAP Identity.
2+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-6> RFC 8621, Section 6 *)
3+4+open Jmap.Types
5+open Jmap.Methods
6+7+(** Identity object.
8+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-6> RFC 8621, Section 6 *)
9+type t
10+11+(** Get the identity ID (immutable, server-set) *)
12+val id : t -> id
13+14+(** Get the display name (defaults to "") *)
15+val name : t -> string
16+17+(** Get the email address (immutable) *)
18+val email : t -> string
19+20+(** Get the reply-to addresses (if any) *)
21+val reply_to : t -> Jmap_email_types.Email_address.t list option
22+23+(** Get the bcc addresses (if any) *)
24+val bcc : t -> Jmap_email_types.Email_address.t list option
25+26+(** Get the plain text signature (defaults to "") *)
27+val text_signature : t -> string
28+29+(** Get the HTML signature (defaults to "") *)
30+val html_signature : t -> string
31+32+(** Check if this identity may be deleted (server-set) *)
33+val may_delete : t -> bool
34+35+(** Create a new identity object *)
36+val v :
37+ id:id ->
38+ ?name:string ->
39+ email:string ->
40+ ?reply_to:Jmap_email_types.Email_address.t list ->
41+ ?bcc:Jmap_email_types.Email_address.t list ->
42+ ?text_signature:string ->
43+ ?html_signature:string ->
44+ may_delete:bool ->
45+ unit -> t
46+47+(** Types and functions for identity creation and updates *)
48+module Create : sig
49+ type t
50+51+ (** Get the name (if specified) *)
52+ val name : t -> string option
53+54+ (** Get the email address *)
55+ val email : t -> string
56+57+ (** Get the reply-to addresses (if any) *)
58+ val reply_to : t -> Jmap_email_types.Email_address.t list option
59+60+ (** Get the bcc addresses (if any) *)
61+ val bcc : t -> Jmap_email_types.Email_address.t list option
62+63+ (** Get the plain text signature (if specified) *)
64+ val text_signature : t -> string option
65+66+ (** Get the HTML signature (if specified) *)
67+ val html_signature : t -> string option
68+69+ (** Create a new identity creation object *)
70+ val v :
71+ ?name:string ->
72+ email:string ->
73+ ?reply_to:Jmap_email_types.Email_address.t list ->
74+ ?bcc:Jmap_email_types.Email_address.t list ->
75+ ?text_signature:string ->
76+ ?html_signature:string ->
77+ unit -> t
78+79+ (** Server response with info about the created identity *)
80+ module Response : sig
81+ type t
82+83+ (** Get the server-assigned ID for the created identity *)
84+ val id : t -> id
85+86+ (** Check if this identity may be deleted *)
87+ val may_delete : t -> bool
88+89+ (** Create a new response object *)
90+ val v :
91+ id:id ->
92+ may_delete:bool ->
93+ unit -> t
94+ end
95+end
96+97+(** Identity object for update.
98+ Patch object, specific structure not enforced here.
99+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-6.3> RFC 8621, Section 6.3 *)
100+type update = patch_object
101+102+(** Server-set/computed info for updated identity.
103+ Contains only changed server-set props.
104+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-6.3> RFC 8621, Section 6.3 *)
105+module Update_response : sig
106+ type t
107+108+ (** Convert to a full Identity object (contains only changed server-set props) *)
109+ val to_identity : t -> t
110+111+ (** Create from a full Identity object *)
112+ val of_identity : t -> t
113+end
114+
···1+(** JMAP Mailbox.
2+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2> RFC 8621, Section 2 *)
3+4+open Jmap.Types
5+open Jmap.Methods
6+7+(** Standard mailbox roles as defined in RFC 8621.
8+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2> RFC 8621, Section 2 *)
9+type role =
10+ | Inbox (** Messages in the primary inbox *)
11+ | Archive (** Archived messages *)
12+ | Drafts (** Draft messages being composed *)
13+ | Sent (** Messages that have been sent *)
14+ | Trash (** Messages that have been deleted *)
15+ | Junk (** Messages determined to be spam *)
16+ | Important (** Messages deemed important *)
17+ | Other of string (** Custom or non-standard role *)
18+ | None (** No specific role assigned *)
19+20+(** Mailbox property identifiers.
21+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2> RFC 8621, Section 2 *)
22+type property =
23+ | Id (** The id of the mailbox *)
24+ | Name (** The name of the mailbox *)
25+ | ParentId (** The id of the parent mailbox *)
26+ | Role (** The role of the mailbox *)
27+ | SortOrder (** The sort order of the mailbox *)
28+ | TotalEmails (** The total number of emails in the mailbox *)
29+ | UnreadEmails (** The number of unread emails in the mailbox *)
30+ | TotalThreads (** The total number of threads in the mailbox *)
31+ | UnreadThreads (** The number of unread threads in the mailbox *)
32+ | MyRights (** The rights the user has for the mailbox *)
33+ | IsSubscribed (** Whether the mailbox is subscribed to *)
34+ | Other of string (** Any server-specific extension properties *)
35+36+(** Mailbox access rights.
37+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2> RFC 8621, Section 2 *)
38+type mailbox_rights = {
39+ may_read_items : bool;
40+ may_add_items : bool;
41+ may_remove_items : bool;
42+ may_set_seen : bool;
43+ may_set_keywords : bool;
44+ may_create_child : bool;
45+ may_rename : bool;
46+ may_delete : bool;
47+ may_submit : bool;
48+}
49+50+(** Mailbox object.
51+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2> RFC 8621, Section 2 *)
52+type mailbox = {
53+ mailbox_id : id; (** immutable, server-set *)
54+ name : string;
55+ parent_id : id option;
56+ role : role option;
57+ sort_order : uint; (* default: 0 *)
58+ total_emails : uint; (** server-set *)
59+ unread_emails : uint; (** server-set *)
60+ total_threads : uint; (** server-set *)
61+ unread_threads : uint; (** server-set *)
62+ my_rights : mailbox_rights; (** server-set *)
63+ is_subscribed : bool;
64+}
65+66+(** Mailbox object for creation.
67+ Excludes server-set fields.
68+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2> RFC 8621, Section 2 *)
69+type mailbox_create = {
70+ mailbox_create_name : string;
71+ mailbox_create_parent_id : id option;
72+ mailbox_create_role : role option;
73+ mailbox_create_sort_order : uint option;
74+ mailbox_create_is_subscribed : bool option;
75+}
76+77+(** Mailbox object for update.
78+ Patch object, specific structure not enforced here.
79+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2.5> RFC 8621, Section 2.5 *)
80+type mailbox_update = patch_object
81+82+(** Server-set info for created mailbox.
83+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2.5> RFC 8621, Section 2.5 *)
84+type mailbox_created_info = {
85+ mailbox_created_id : id;
86+ mailbox_created_role : role option; (** If default used *)
87+ mailbox_created_sort_order : uint; (** If default used *)
88+ mailbox_created_total_emails : uint;
89+ mailbox_created_unread_emails : uint;
90+ mailbox_created_total_threads : uint;
91+ mailbox_created_unread_threads : uint;
92+ mailbox_created_my_rights : mailbox_rights;
93+ mailbox_created_is_subscribed : bool; (** If default used *)
94+}
95+96+(** Server-set/computed info for updated mailbox.
97+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2.5> RFC 8621, Section 2.5 *)
98+type mailbox_updated_info = mailbox (* Contains only changed server-set props *)
99+100+(** FilterCondition for Mailbox/query.
101+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-2.3> RFC 8621, Section 2.3 *)
102+type mailbox_filter_condition = {
103+ filter_parent_id : id option option; (* Use option option for explicit null *)
104+ filter_name : string option;
105+ filter_role : role option option; (* Use option option for explicit null *)
106+ filter_has_any_role : bool option;
107+ filter_is_subscribed : bool option;
108+}
109+110+(** {2 Role and Property Conversion Functions} *)
111+112+(** Convert a role variant to its string representation *)
113+val role_to_string : role -> string
114+115+(** Parse a string into a role variant *)
116+val string_to_role : string -> role
117+118+(** Convert a property variant to its string representation *)
119+val property_to_string : property -> string
120+121+(** Parse a string into a property variant *)
122+val string_to_property : string -> property
123+124+(** Get a list of common properties useful for displaying mailboxes *)
125+val common_properties : property list
126+127+(** Get a list of all standard properties *)
128+val all_properties : property list
129+130+(** Check if a property is a count property (TotalEmails, UnreadEmails, etc.) *)
131+val is_count_property : property -> bool
132+133+(** {2 Mailbox Creation and Manipulation} *)
134+135+(** Create a set of default rights with all permissions *)
136+val default_rights : unit -> mailbox_rights
137+138+(** Create a set of read-only rights *)
139+val readonly_rights : unit -> mailbox_rights
140+141+(** Create a new mailbox object with minimal required fields *)
142+val create :
143+ name:string ->
144+ ?parent_id:id ->
145+ ?role:role ->
146+ ?sort_order:uint ->
147+ ?is_subscribed:bool ->
148+ unit -> mailbox_create
149+150+(** Build a patch object for updating mailbox properties *)
151+val update :
152+ ?name:string ->
153+ ?parent_id:id option ->
154+ ?role:role option ->
155+ ?sort_order:uint ->
156+ ?is_subscribed:bool ->
157+ unit -> mailbox_update
158+159+(** Get the list of standard role names and their string representations *)
160+val standard_role_names : (role * string) list
161+162+(** {2 Filter Construction} *)
163+164+(** Create a filter to match mailboxes with a specific role *)
165+val filter_has_role : role -> Jmap.Methods.Filter.t
166+167+(** Create a filter to match mailboxes with no role *)
168+val filter_has_no_role : unit -> Jmap.Methods.Filter.t
169+170+(** Create a filter to match mailboxes that are child of a given parent *)
171+val filter_has_parent : id -> Jmap.Methods.Filter.t
172+173+(** Create a filter to match mailboxes at the root level (no parent) *)
174+val filter_is_root : unit -> Jmap.Methods.Filter.t
175+176+(** Create a filter to match subscribed mailboxes *)
177+val filter_is_subscribed : unit -> Jmap.Methods.Filter.t
178+179+(** Create a filter to match unsubscribed mailboxes *)
180+val filter_is_not_subscribed : unit -> Jmap.Methods.Filter.t
181+182+(** Create a filter to match mailboxes by name (using case-insensitive substring matching) *)
183+val filter_name_contains : string -> Jmap.Methods.Filter.t
+9
jmap-email/jmap_search_snippet.ml
···000000000
···1+(* JMAP Search Snippet. *)
2+3+(* SearchSnippet object.
4+ Note: Does not have an 'id' property. *)
5+type t = {
6+ email_id : Jmap.Types.id;
7+ subject : string option;
8+ preview : string option;
9+}
+11
jmap-email/jmap_search_snippet.mli
···00000000000
···1+(** JMAP Search Snippet.
2+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-5> RFC 8621, Section 5 *)
3+4+(** SearchSnippet object.
5+ Note: Does not have an 'id' property.
6+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-5> RFC 8621, Section 5 *)
7+type t = {
8+ email_id : Jmap.Types.id;
9+ subject : string option;
10+ preview : string option;
11+}
···1+(** JMAP Email Submission.
2+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7> RFC 8621, Section 7 *)
3+4+open Jmap.Types
5+open Jmap.Methods
6+7+(** Address object for Envelope.
8+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7> RFC 8621, Section 7 *)
9+type envelope_address = {
10+ env_addr_email : string;
11+ env_addr_parameters : Yojson.Safe.t string_map option;
12+}
13+14+(** Envelope object.
15+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7> RFC 8621, Section 7 *)
16+type envelope = {
17+ env_mail_from : envelope_address;
18+ env_rcpt_to : envelope_address list;
19+}
20+21+(** Delivery status for a recipient.
22+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7> RFC 8621, Section 7 *)
23+type delivery_status = {
24+ delivery_smtp_reply : string;
25+ delivery_delivered : [ `Queued | `Yes | `No | `Unknown ];
26+ delivery_displayed : [ `Yes | `Unknown ];
27+}
28+29+(** EmailSubmission object.
30+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7> RFC 8621, Section 7 *)
31+type email_submission = {
32+ email_sub_id : id; (** immutable, server-set *)
33+ identity_id : id; (** immutable *)
34+ email_id : id; (** immutable *)
35+ thread_id : id; (** immutable, server-set *)
36+ envelope : envelope option; (** immutable *)
37+ send_at : utc_date; (** immutable, server-set *)
38+ undo_status : [ `Pending | `Final | `Canceled ];
39+ delivery_status : delivery_status string_map option; (** server-set *)
40+ dsn_blob_ids : id list; (** server-set *)
41+ mdn_blob_ids : id list; (** server-set *)
42+}
43+44+(** EmailSubmission object for creation.
45+ Excludes server-set fields.
46+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7> RFC 8621, Section 7 *)
47+type email_submission_create = {
48+ email_sub_create_identity_id : id;
49+ email_sub_create_email_id : id;
50+ email_sub_create_envelope : envelope option;
51+}
52+53+(** EmailSubmission object for update.
54+ Only undoStatus can be updated (to 'canceled').
55+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7> RFC 8621, Section 7 *)
56+type email_submission_update = patch_object
57+58+(** Server-set info for created email submission.
59+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7.5> RFC 8621, Section 7.5 *)
60+type email_submission_created_info = {
61+ email_sub_created_id : id;
62+ email_sub_created_thread_id : id;
63+ email_sub_created_send_at : utc_date;
64+}
65+66+(** Server-set/computed info for updated email submission.
67+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7.5> RFC 8621, Section 7.5 *)
68+type email_submission_updated_info = email_submission (* Contains only changed server-set props *)
69+70+(** FilterCondition for EmailSubmission/query.
71+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7.3> RFC 8621, Section 7.3 *)
72+type email_submission_filter_condition = {
73+ filter_identity_ids : id list option;
74+ filter_email_ids : id list option;
75+ filter_thread_ids : id list option;
76+ filter_undo_status : [ `Pending | `Final | `Canceled ] option;
77+ filter_before : utc_date option;
78+ filter_after : utc_date option;
79+}
80+81+(** EmailSubmission/get: Args type (specialized from ['record Get_args.t]). *)
82+module Email_submission_get_args : sig
83+ type t = email_submission Get_args.t
84+end
85+86+(** EmailSubmission/get: Response type (specialized from ['record Get_response.t]). *)
87+module Email_submission_get_response : sig
88+ type t = email_submission Get_response.t
89+end
90+91+(** EmailSubmission/changes: Args type (specialized from [Changes_args.t]). *)
92+module Email_submission_changes_args : sig
93+ type t = Changes_args.t
94+end
95+96+(** EmailSubmission/changes: Response type (specialized from [Changes_response.t]). *)
97+module Email_submission_changes_response : sig
98+ type t = Changes_response.t
99+end
100+101+(** EmailSubmission/query: Args type (specialized from [Query_args.t]). *)
102+module Email_submission_query_args : sig
103+ type t = Query_args.t
104+end
105+106+(** EmailSubmission/query: Response type (specialized from [Query_response.t]). *)
107+module Email_submission_query_response : sig
108+ type t = Query_response.t
109+end
110+111+(** EmailSubmission/queryChanges: Args type (specialized from [Query_changes_args.t]). *)
112+module Email_submission_query_changes_args : sig
113+ type t = Query_changes_args.t
114+end
115+116+(** EmailSubmission/queryChanges: Response type (specialized from [Query_changes_response.t]). *)
117+module Email_submission_query_changes_response : sig
118+ type t = Query_changes_response.t
119+end
120+121+(** EmailSubmission/set: Args type (specialized from [('c, 'u) set_args]).
122+ Includes onSuccess arguments.
123+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-7.5> RFC 8621, Section 7.5 *)
124+type email_submission_set_args = {
125+ set_account_id : id;
126+ set_if_in_state : string option;
127+ set_create : email_submission_create id_map option;
128+ set_update : email_submission_update id_map option;
129+ set_destroy : id list option;
130+ set_on_success_destroy_email : id list option;
131+}
132+133+(** EmailSubmission/set: Response type (specialized from [('c, 'u) Set_response.t]). *)
134+module Email_submission_set_response : sig
135+ type t = (email_submission_created_info, email_submission_updated_info) Set_response.t
136+end
+19
jmap-email/jmap_thread.ml
···0000000000000000000
···1+(* JMAP Thread. *)
2+3+open Jmap.Types
4+5+(* Thread object. *)
6+module Thread = struct
7+ type t = {
8+ id_value: id;
9+ email_ids_value: id list;
10+ }
11+12+ let id t = t.id_value
13+ let email_ids t = t.email_ids_value
14+15+ let v ~id ~email_ids = {
16+ id_value = id;
17+ email_ids_value = email_ids;
18+ }
19+end
+15
jmap-email/jmap_thread.mli
···000000000000000
···1+(** JMAP Thread.
2+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-3> RFC 8621, Section 3 *)
3+4+open Jmap.Types
5+6+(** Thread object.
7+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-3> RFC 8621, Section 3 *)
8+module Thread : sig
9+ type t
10+11+ val id : t -> id
12+ val email_ids : t -> id list
13+14+ val v : id:id -> email_ids:id list -> t
15+end
···1+(** JMAP Vacation Response.
2+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-8> RFC 8621, Section 8 *)
3+4+open Jmap.Types
5+open Jmap.Methods
6+open Jmap.Error
7+8+(** VacationResponse object.
9+ Note: id is always "singleton".
10+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-8> RFC 8621, Section 8 *)
11+module Vacation_response : sig
12+ type t
13+14+ (** Id of the vacation response (immutable, server-set, MUST be "singleton") *)
15+ val id : t -> id
16+ val is_enabled : t -> bool
17+ val from_date : t -> utc_date option
18+ val to_date : t -> utc_date option
19+ val subject : t -> string option
20+ val text_body : t -> string option
21+ val html_body : t -> string option
22+23+ val v :
24+ id:id ->
25+ is_enabled:bool ->
26+ ?from_date:utc_date ->
27+ ?to_date:utc_date ->
28+ ?subject:string ->
29+ ?text_body:string ->
30+ ?html_body:string ->
31+ unit ->
32+ t
33+end
34+35+(** VacationResponse object for update.
36+ Patch object, specific structure not enforced here.
37+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-8.2> RFC 8621, Section 8.2 *)
38+type vacation_response_update = patch_object
39+40+(** VacationResponse/get: Args type (specialized from ['record get_args]). *)
41+module Vacation_response_get_args : sig
42+ type t = Vacation_response.t Get_args.t
43+44+ val v :
45+ account_id:id ->
46+ ?ids:id list ->
47+ ?properties:string list ->
48+ unit ->
49+ t
50+end
51+52+(** VacationResponse/get: Response type (specialized from ['record get_response]). *)
53+module Vacation_response_get_response : sig
54+ type t = Vacation_response.t Get_response.t
55+56+ val v :
57+ account_id:id ->
58+ state:string ->
59+ list:Vacation_response.t list ->
60+ not_found:id list ->
61+ unit ->
62+ t
63+end
64+65+(** VacationResponse/set: Args type.
66+ Only allows update, id must be "singleton".
67+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-8.2> RFC 8621, Section 8.2 *)
68+module Vacation_response_set_args : sig
69+ type t
70+71+ val account_id : t -> id
72+ val if_in_state : t -> string option
73+ val update : t -> vacation_response_update id_map option
74+75+ val v :
76+ account_id:id ->
77+ ?if_in_state:string ->
78+ ?update:vacation_response_update id_map ->
79+ unit ->
80+ t
81+end
82+83+(** VacationResponse/set: Response type.
84+ @see <https://www.rfc-editor.org/rfc/rfc8621.html#section-8.2> RFC 8621, Section 8.2 *)
85+module Vacation_response_set_response : sig
86+ type t
87+88+ val account_id : t -> id
89+ val old_state : t -> string option
90+ val new_state : t -> string
91+ val updated : t -> Vacation_response.t option id_map option
92+ val not_updated : t -> Set_error.t id_map option
93+94+ val v :
95+ account_id:id ->
96+ ?old_state:string ->
97+ new_state:string ->
98+ ?updated:Vacation_response.t option id_map ->
99+ ?not_updated:Set_error.t id_map ->
100+ unit ->
101+ t
102+end
···1+(* Unix-specific JMAP client implementation interface. *)
2+3+open Jmap
4+open Jmap.Types
5+open Jmap.Error
6+open Jmap.Session
7+open Jmap.Wire
8+9+(* Configuration options for a JMAP client context *)
10+type client_config = {
11+ connect_timeout : float option; (* Connection timeout in seconds *)
12+ request_timeout : float option; (* Request timeout in seconds *)
13+ max_concurrent_requests : int option; (* Maximum concurrent requests *)
14+ max_request_size : int option; (* Maximum request size in bytes *)
15+ user_agent : string option; (* User-Agent header value *)
16+ authentication_header : string option; (* Custom Authentication header name *)
17+}
18+19+(* Authentication method options *)
20+type auth_method =
21+ | Basic of string * string (* Basic auth with username and password *)
22+ | Bearer of string (* Bearer token auth *)
23+ | Custom of (string * string) (* Custom header name and value *)
24+ | Session_cookie of (string * string) (* Session cookie name and value *)
25+ | No_auth (* No authentication *)
26+27+(* The internal state of a JMAP client connection *)
28+type context = {
29+ config: client_config;
30+ mutable session_url: Uri.t option;
31+ mutable session: Session.t option;
32+ mutable auth: auth_method;
33+}
34+35+(* Represents an active EventSource connection *)
36+type event_source_connection = {
37+ event_url: Uri.t;
38+ mutable is_connected: bool;
39+}
40+41+(* A request builder for constructing and sending JMAP requests *)
42+type request_builder = {
43+ ctx: context;
44+ mutable using: string list;
45+ mutable method_calls: Invocation.t list;
46+}
47+48+(* Create default configuration options *)
49+let default_config () = {
50+ connect_timeout = Some 30.0;
51+ request_timeout = Some 300.0;
52+ max_concurrent_requests = Some 4;
53+ max_request_size = Some (1024 * 1024 * 10); (* 10 MB *)
54+ user_agent = Some "OCaml JMAP Unix Client/1.0";
55+ authentication_header = None;
56+}
57+58+(* Create a client context with the specified configuration *)
59+let create_client ?(config = default_config ()) () = {
60+ config;
61+ session_url = None;
62+ session = None;
63+ auth = No_auth;
64+}
65+66+(* Mock implementation for the Unix connection *)
67+let connect ctx ?session_url ?username ~host ?port ?auth_method () =
68+ (* In a real implementation, this would use Unix HTTP functions *)
69+ let auth = match auth_method with
70+ | Some auth -> auth
71+ | None -> No_auth
72+ in
73+74+ (* Store the auth method for future requests *)
75+ ctx.auth <- auth;
76+77+ (* Set session URL, either directly or after discovery *)
78+ let session_url = match session_url with
79+ | Some url -> url
80+ | None ->
81+ (* In a real implementation, this would perform RFC 8620 discovery *)
82+ let proto = "https" in
83+ let host_with_port = match port with
84+ | Some p -> host ^ ":" ^ string_of_int p
85+ | None -> host
86+ in
87+ Uri.of_string (proto ^ "://" ^ host_with_port ^ "/.well-known/jmap")
88+ in
89+ ctx.session_url <- Some session_url;
90+91+ (* Create a mock session object for this example *)
92+ let caps = Hashtbl.create 4 in
93+ Hashtbl.add caps Jmap.capability_core (`Assoc []);
94+95+ let accounts = Hashtbl.create 1 in
96+ let acct = Account.v
97+ ~name:"user@example.com"
98+ ~is_personal:true
99+ ~is_read_only:false
100+ ()
101+ in
102+ Hashtbl.add accounts "u1" acct;
103+104+ let primary = Hashtbl.create 1 in
105+ Hashtbl.add primary Jmap.capability_core "u1";
106+107+ let api_url =
108+ Uri.of_string ("https://" ^ host ^ "/api/jmap")
109+ in
110+111+ let session = Session.v
112+ ~capabilities:caps
113+ ~accounts
114+ ~primary_accounts:primary
115+ ~username:"user@example.com"
116+ ~api_url
117+ ~download_url:(Uri.of_string ("https://" ^ host ^ "/download/{accountId}/{blobId}"))
118+ ~upload_url:(Uri.of_string ("https://" ^ host ^ "/upload/{accountId}"))
119+ ~event_source_url:(Uri.of_string ("https://" ^ host ^ "/eventsource"))
120+ ~state:"1"
121+ ()
122+ in
123+124+ ctx.session <- Some session;
125+ Ok (ctx, session)
126+127+(* Create a request builder for constructing a JMAP request *)
128+let build ctx = {
129+ ctx;
130+ using = [Jmap.capability_core]; (* Default to core capability *)
131+ method_calls = [];
132+}
133+134+(* Set the using capabilities for a request *)
135+let using builder capabilities =
136+ { builder with using = capabilities }
137+138+(* Add a method call to a request builder *)
139+let add_method_call builder name args id =
140+ let call = Invocation.v
141+ ~method_name:name
142+ ~arguments:args
143+ ~method_call_id:id
144+ ()
145+ in
146+ { builder with method_calls = builder.method_calls @ [call] }
147+148+(* Create a reference to a previous method call result *)
149+let create_reference result_of name =
150+ Jmap.Wire.Result_reference.v
151+ ~result_of
152+ ~name
153+ ~path:"" (* In a real implementation, this would include a JSON pointer *)
154+ ()
155+156+(* Execute a request and return the response *)
157+let execute builder =
158+ match builder.ctx.session with
159+ | None -> Error (protocol_error "No active session")
160+ | Some session ->
161+ (* In a real implementation, this would create and send an HTTP request *)
162+163+ (* Create a mock response for this implementation *)
164+ let results = List.map (fun call ->
165+ let method_name = Invocation.method_name call in
166+ let call_id = Invocation.method_call_id call in
167+ if method_name = "Core/echo" then
168+ (* Echo method implementation *)
169+ Ok call
170+ else
171+ (* For other methods, return a method error *)
172+ Error (
173+ Method_error.v
174+ ~description:(Method_error_description.v
175+ ~description:"Method not implemented in mock"
176+ ())
177+ `ServerUnavailable,
178+ "Mock implementation"
179+ )
180+ ) builder.method_calls in
181+182+ let resp = Response.v
183+ ~method_responses:results
184+ ~session_state:(session |> Session.state)
185+ ()
186+ in
187+ Ok resp
188+189+(* Perform a JMAP API request *)
190+let request ctx req =
191+ match ctx.session_url, ctx.session with
192+ | None, _ -> Error (protocol_error "No session URL configured")
193+ | _, None -> Error (protocol_error "No active session")
194+ | Some url, Some session ->
195+ (* In a real implementation, this would serialize the request and send it *)
196+197+ (* Mock response implementation *)
198+ let method_calls = Request.method_calls req in
199+ let results = List.map (fun call ->
200+ let method_name = Invocation.method_name call in
201+ let call_id = Invocation.method_call_id call in
202+ if method_name = "Core/echo" then
203+ (* Echo method implementation *)
204+ Ok call
205+ else
206+ (* For other methods, return a method error *)
207+ Error (
208+ Method_error.v
209+ ~description:(Method_error_description.v
210+ ~description:"Method not implemented in mock"
211+ ())
212+ `ServerUnavailable,
213+ "Mock implementation"
214+ )
215+ ) method_calls in
216+217+ let resp = Response.v
218+ ~method_responses:results
219+ ~session_state:(session |> Session.state)
220+ ()
221+ in
222+ Ok resp
223+224+(* Upload binary data *)
225+let upload ctx ~account_id ~content_type ~data_stream =
226+ match ctx.session with
227+ | None -> Error (protocol_error "No active session")
228+ | Some session ->
229+ (* In a real implementation, would upload the data stream *)
230+231+ (* Mock success response *)
232+ let response = Jmap.Binary.Upload_response.v
233+ ~account_id
234+ ~blob_id:"b123456"
235+ ~type_:content_type
236+ ~size:1024 (* Mock size *)
237+ ()
238+ in
239+ Ok response
240+241+(* Download binary data *)
242+let download ctx ~account_id ~blob_id ?content_type ?name =
243+ match ctx.session with
244+ | None -> Error (protocol_error "No active session")
245+ | Some session ->
246+ (* In a real implementation, would download the data and return a stream *)
247+248+ (* Mock data stream - in real code, this would be read from the HTTP response *)
249+ let mock_data = "This is mock downloaded data for blob " ^ blob_id in
250+ let seq = Seq.cons mock_data Seq.empty in
251+ Ok seq
252+253+(* Copy blobs between accounts *)
254+let copy_blobs ctx ~from_account_id ~account_id ~blob_ids =
255+ match ctx.session with
256+ | None -> Error (protocol_error "No active session")
257+ | Some session ->
258+ (* In a real implementation, would perform server-side copy *)
259+260+ (* Mock success response with first blob copied and second failed *)
261+ let copied = Hashtbl.create 1 in
262+ Hashtbl.add copied (List.hd blob_ids) "b999999";
263+264+ let response = Jmap.Binary.Blob_copy_response.v
265+ ~from_account_id
266+ ~account_id
267+ ~copied
268+ ()
269+ in
270+ Ok response
271+272+(* Connect to the EventSource for push notifications *)
273+let connect_event_source ctx ?types ?close_after ?ping =
274+ match ctx.session with
275+ | None -> Error (protocol_error "No active session")
276+ | Some session ->
277+ (* In a real implementation, would connect to EventSource URL *)
278+279+ (* Create mock connection *)
280+ let event_url = Session.event_source_url session in
281+ let conn = { event_url; is_connected = true } in
282+283+ (* Create a mock event sequence *)
284+ let mock_state_change =
285+ let changed = Hashtbl.create 1 in
286+ let account_id = "u1" in
287+ let state_map = Hashtbl.create 2 in
288+ Hashtbl.add state_map "Email" "s123";
289+ Hashtbl.add state_map "Mailbox" "s456";
290+ Hashtbl.add changed account_id state_map;
291+292+ Push.State_change.v ~changed ()
293+ in
294+295+ let ping_data =
296+ Push.Event_source_ping_data.v ~interval:30 ()
297+ in
298+299+ (* Create a sequence with one state event and one ping event *)
300+ let events = Seq.cons (`State mock_state_change)
301+ (Seq.cons (`Ping ping_data) Seq.empty) in
302+303+ Ok (conn, events)
304+305+(* Create a websocket connection for JMAP over WebSocket *)
306+let connect_websocket ctx =
307+ match ctx.session with
308+ | None -> Error (protocol_error "No active session")
309+ | Some session ->
310+ (* In a real implementation, would connect via WebSocket *)
311+312+ (* Mock connection *)
313+ let event_url = Session.api_url session in
314+ let conn = { event_url; is_connected = true } in
315+ Ok conn
316+317+(* Send a message over a websocket connection *)
318+let websocket_send conn req =
319+ if not conn.is_connected then
320+ Error (protocol_error "WebSocket not connected")
321+ else
322+ (* In a real implementation, would send over WebSocket *)
323+324+ (* Mock response (same as request function) *)
325+ let method_calls = Request.method_calls req in
326+ let results = List.map (fun call ->
327+ let method_name = Invocation.method_name call in
328+ let call_id = Invocation.method_call_id call in
329+ if method_name = "Core/echo" then
330+ Ok call
331+ else
332+ Error (
333+ Method_error.v
334+ ~description:(Method_error_description.v
335+ ~description:"Method not implemented in mock"
336+ ())
337+ `ServerUnavailable,
338+ "Mock implementation"
339+ )
340+ ) method_calls in
341+342+ let resp = Response.v
343+ ~method_responses:results
344+ ~session_state:"1"
345+ ()
346+ in
347+ Ok resp
348+349+(* Close an EventSource or WebSocket connection *)
350+let close_connection conn =
351+ if not conn.is_connected then
352+ Error (protocol_error "Connection already closed")
353+ else begin
354+ conn.is_connected <- false;
355+ Ok ()
356+ end
357+358+(* Close the JMAP connection context *)
359+let close ctx =
360+ ctx.session <- None;
361+ ctx.session_url <- None;
362+ Ok ()
363+364+(* Helper functions for common tasks *)
365+366+(* Helper to get a single object by ID *)
367+let get_object ctx ~method_name ~account_id ~object_id ?properties =
368+ let properties_param = match properties with
369+ | Some props -> `List (List.map (fun p -> `String p) props)
370+ | None -> `Null
371+ in
372+373+ let args = `Assoc [
374+ ("accountId", `String account_id);
375+ ("ids", `List [`String object_id]);
376+ ("properties", properties_param);
377+ ] in
378+379+ let request_builder = build ctx
380+ |> add_method_call method_name args "r1"
381+ in
382+383+ match execute request_builder with
384+ | Error e -> Error e
385+ | Ok response ->
386+ (* Find the method response and extract the list with the object *)
387+ match response |> Response.method_responses with
388+ | [Ok invocation] when Invocation.method_name invocation = method_name ^ "/get" ->
389+ let args = Invocation.arguments invocation in
390+ begin match Yojson.Safe.Util.member "list" args with
391+ | `List [obj] -> Ok obj
392+ | _ -> Error (protocol_error "Object not found or invalid response")
393+ end
394+ | _ ->
395+ Error (protocol_error "Method response not found")
396+397+(* Helper to set up the connection with minimal options *)
398+let quick_connect ~host ~username ~password =
399+ let ctx = create_client () in
400+ connect ctx ~host ~auth_method:(Basic(username, password)) ()
401+402+(* Perform a Core/echo request to test connectivity *)
403+let echo ctx ?data () =
404+ let data = match data with
405+ | Some d -> d
406+ | None -> `Assoc [("hello", `String "world")]
407+ in
408+409+ let request_builder = build ctx
410+ |> add_method_call "Core/echo" data "echo1"
411+ in
412+413+ match execute request_builder with
414+ | Error e -> Error e
415+ | Ok response ->
416+ (* Find the Core/echo response and extract the echoed data *)
417+ match response |> Response.method_responses with
418+ | [Ok invocation] when Invocation.method_name invocation = "Core/echo" ->
419+ Ok (Invocation.arguments invocation)
420+ | _ ->
421+ Error (protocol_error "Echo response not found")
422+423+(* High-level email operations *)
424+module Email = struct
425+ open Jmap_email.Types
426+427+ (* Get an email by ID *)
428+ let get_email ctx ~account_id ~email_id ?properties () =
429+ let props = match properties with
430+ | Some p -> p
431+ | None -> List.map email_property_to_string detailed_email_properties
432+ in
433+434+ match get_object ctx ~method_name:"Email/get" ~account_id ~object_id:email_id ~properties:props with
435+ | Error e -> Error e
436+ | Ok json ->
437+ (* In a real implementation, would parse the JSON into an Email.t structure *)
438+ let mock_email = Email.create
439+ ~id:email_id
440+ ~thread_id:"t12345"
441+ ~mailbox_ids:(let h = Hashtbl.create 1 in Hashtbl.add h "inbox" true; h)
442+ ~keywords:(Keywords.of_list [Keywords.Seen])
443+ ~subject:"Mock Email Subject"
444+ ~preview:"This is a mock email..."
445+ ~from:[Email_address.v ~name:"Sender Name" ~email:"sender@example.com" ()]
446+ ~to_:[Email_address.v ~email:"recipient@example.com" ()]
447+ ()
448+ in
449+ Ok mock_email
450+451+ (* Search for emails using a filter *)
452+ let search_emails ctx ~account_id ~filter ?sort ?limit ?position ?properties () =
453+ (* Create the query args *)
454+ let args = `Assoc [
455+ ("accountId", `String account_id);
456+ ("filter", Jmap.Methods.Filter.to_json filter);
457+ ("sort", match sort with
458+ | Some s -> `List [] (* Would convert sort params *)
459+ | None -> `List [`Assoc [("property", `String "receivedAt"); ("isAscending", `Bool false)]]);
460+ ("limit", match limit with
461+ | Some l -> `Int l
462+ | None -> `Int 20);
463+ ("position", match position with
464+ | Some p -> `Int p
465+ | None -> `Int 0);
466+ ] in
467+468+ let request_builder = build ctx
469+ |> add_method_call "Email/query" args "q1"
470+ in
471+472+ (* If properties were provided, add a Email/get method call as well *)
473+ let request_builder = match properties with
474+ | Some _ ->
475+ let get_args = `Assoc [
476+ ("accountId", `String account_id);
477+ ("#ids", `Assoc [
478+ ("resultOf", `String "q1");
479+ ("name", `String "Email/query");
480+ ("path", `String "/ids")
481+ ]);
482+ ("properties", match properties with
483+ | Some p -> `List (List.map (fun prop -> `String prop) p)
484+ | None -> `Null);
485+ ] in
486+ add_method_call request_builder "Email/get" get_args "g1"
487+ | None -> request_builder
488+ in
489+490+ match execute request_builder with
491+ | Error e -> Error e
492+ | Ok response ->
493+ (* Find the query response and extract the IDs *)
494+ match Response.method_responses response with
495+ | [Ok q_inv; Ok g_inv]
496+ when Invocation.method_name q_inv = "Email/query"
497+ && Invocation.method_name g_inv = "Email/get" ->
498+499+ (* Extract IDs from query response *)
500+ let q_args = Invocation.arguments q_inv in
501+ let ids = match Yojson.Safe.Util.member "ids" q_args with
502+ | `List l -> List.map Yojson.Safe.Util.to_string l
503+ | _ -> []
504+ in
505+506+ (* Extract emails from get response *)
507+ let g_args = Invocation.arguments g_inv in
508+ (* In a real implementation, would parse each email in the list *)
509+ let emails = List.map (fun id ->
510+ Email.create
511+ ~id
512+ ~thread_id:("t" ^ id)
513+ ~subject:(Printf.sprintf "Mock Email %s" id)
514+ ()
515+ ) ids in
516+517+ Ok (ids, Some emails)
518+519+ | [Ok q_inv] when Invocation.method_name q_inv = "Email/query" ->
520+ (* If only query was performed (no properties requested) *)
521+ let q_args = Invocation.arguments q_inv in
522+ let ids = match Yojson.Safe.Util.member "ids" q_args with
523+ | `List l -> List.map Yojson.Safe.Util.to_string l
524+ | _ -> []
525+ in
526+527+ Ok (ids, None)
528+529+ | _ ->
530+ Error (protocol_error "Query response not found")
531+532+ (* Mark multiple emails with a keyword *)
533+ let mark_emails ctx ~account_id ~email_ids ~keyword () =
534+ (* Create the set args with a patch to add the keyword *)
535+ let keyword_patch = Jmap_email.Keyword_ops.add_keyword_patch keyword in
536+537+ (* Create patches map for each email *)
538+ let update = Hashtbl.create (List.length email_ids) in
539+ List.iter (fun id ->
540+ Hashtbl.add update id keyword_patch
541+ ) email_ids;
542+543+ let args = `Assoc [
544+ ("accountId", `String account_id);
545+ ("update", `Assoc (
546+ List.map (fun id ->
547+ (id, `Assoc (List.map (fun (path, value) ->
548+ (path, value)
549+ ) keyword_patch))
550+ ) email_ids
551+ ));
552+ ] in
553+554+ let request_builder = build ctx
555+ |> add_method_call "Email/set" args "s1"
556+ in
557+558+ match execute request_builder with
559+ | Error e -> Error e
560+ | Ok response ->
561+ (* In a real implementation, would check for errors *)
562+ Ok ()
563+564+ (* Mark emails as seen/read *)
565+ let mark_as_seen ctx ~account_id ~email_ids () =
566+ mark_emails ctx ~account_id ~email_ids ~keyword:Keywords.Seen ()
567+568+ (* Mark emails as unseen/unread *)
569+ let mark_as_unseen ctx ~account_id ~email_ids () =
570+ let keyword_patch = Jmap_email.Keyword_ops.mark_unseen_patch () in
571+572+ (* Create patches map for each email *)
573+ let update = Hashtbl.create (List.length email_ids) in
574+ List.iter (fun id ->
575+ Hashtbl.add update id keyword_patch
576+ ) email_ids;
577+578+ let args = `Assoc [
579+ ("accountId", `String account_id);
580+ ("update", `Assoc (
581+ List.map (fun id ->
582+ (id, `Assoc (List.map (fun (path, value) ->
583+ (path, value)
584+ ) keyword_patch))
585+ ) email_ids
586+ ));
587+ ] in
588+589+ let request_builder = build ctx
590+ |> add_method_call "Email/set" args "s1"
591+ in
592+593+ match execute request_builder with
594+ | Error e -> Error e
595+ | Ok _response -> Ok ()
596+597+ (* Move emails to a different mailbox *)
598+ let move_emails ctx ~account_id ~email_ids ~mailbox_id ?remove_from_mailboxes () =
599+ (* Create patch to add to destination mailbox *)
600+ let add_patch = [("mailboxIds/" ^ mailbox_id, `Bool true)] in
601+602+ (* If remove_from_mailboxes is specified, add patches to remove *)
603+ let remove_patch = match remove_from_mailboxes with
604+ | Some mailboxes ->
605+ List.map (fun mbx -> ("mailboxIds/" ^ mbx, `Null)) mailboxes
606+ | None -> []
607+ in
608+609+ (* Combine patches *)
610+ let patches = add_patch @ remove_patch in
611+612+ (* Create patches map for each email *)
613+ let update = Hashtbl.create (List.length email_ids) in
614+ List.iter (fun id ->
615+ Hashtbl.add update id patches
616+ ) email_ids;
617+618+ let args = `Assoc [
619+ ("accountId", `String account_id);
620+ ("update", `Assoc (
621+ List.map (fun id ->
622+ (id, `Assoc (List.map (fun (path, value) ->
623+ (path, value)
624+ ) patches))
625+ ) email_ids
626+ ));
627+ ] in
628+629+ let request_builder = build ctx
630+ |> add_method_call "Email/set" args "s1"
631+ in
632+633+ match execute request_builder with
634+ | Error e -> Error e
635+ | Ok _response -> Ok ()
636+637+ (* Import an RFC822 message *)
638+ let import_email ctx ~account_id ~rfc822 ~mailbox_ids ?keywords ?received_at () =
639+ (* In a real implementation, would first upload the message as a blob *)
640+ let mock_blob_id = "b9876" in
641+642+ (* Create the Email/import call *)
643+ let args = `Assoc [
644+ ("accountId", `String account_id);
645+ ("emails", `Assoc [
646+ ("msg1", `Assoc [
647+ ("blobId", `String mock_blob_id);
648+ ("mailboxIds", `Assoc (
649+ List.map (fun id -> (id, `Bool true)) mailbox_ids
650+ ));
651+ ("keywords", match keywords with
652+ | Some kws ->
653+ `Assoc (List.map (fun k ->
654+ (Types.Keywords.to_string k, `Bool true)) kws)
655+ | None -> `Null);
656+ ("receivedAt", match received_at with
657+ | Some d -> `String (string_of_float d) (* Would format as RFC3339 *)
658+ | None -> `Null);
659+ ])
660+ ]);
661+ ] in
662+663+ let request_builder = build ctx
664+ |> add_method_call "Email/import" args "i1"
665+ in
666+667+ match execute request_builder with
668+ | Error e -> Error e
669+ | Ok response ->
670+ (* In a real implementation, would extract the created ID *)
671+ Ok "e12345"
672+end
···1+(** Unix-specific JMAP client implementation interface.
2+3+ This module provides functions to interact with a JMAP server using
4+ Unix sockets for network communication.
5+6+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-4> RFC 8620, Section 4
7+*)
8+9+(** Configuration options for a JMAP client context *)
10+type client_config = {
11+ connect_timeout : float option; (** Connection timeout in seconds *)
12+ request_timeout : float option; (** Request timeout in seconds *)
13+ max_concurrent_requests : int option; (** Maximum concurrent requests *)
14+ max_request_size : int option; (** Maximum request size in bytes *)
15+ user_agent : string option; (** User-Agent header value *)
16+ authentication_header : string option; (** Custom Authentication header name *)
17+}
18+19+(** Authentication method options *)
20+type auth_method =
21+ | Basic of string * string (** Basic auth with username and password *)
22+ | Bearer of string (** Bearer token auth *)
23+ | Custom of (string * string) (** Custom header name and value *)
24+ | Session_cookie of (string * string) (** Session cookie name and value *)
25+ | No_auth (** No authentication *)
26+27+(** Represents an active JMAP connection context. Opaque type. *)
28+type context
29+30+(** Represents an active EventSource connection. Opaque type. *)
31+type event_source_connection
32+33+(** A request builder for constructing and sending JMAP requests *)
34+type request_builder
35+36+(** Create default configuration options *)
37+val default_config : unit -> client_config
38+39+(** Create a client context with the specified configuration
40+ @return The context object used for JMAP API calls
41+*)
42+val create_client :
43+ ?config:client_config ->
44+ unit ->
45+ context
46+47+(** Connect to a JMAP server and retrieve the session.
48+ This handles discovery (if needed) and authentication.
49+ @param ctx The client context.
50+ @param ?session_url Optional direct URL to the Session resource.
51+ @param ?username Optional username (e.g., email address) for discovery.
52+ @param ?auth_method Authentication method to use (default Basic).
53+ @param credentials Authentication credentials.
54+ @return A result with either (context, session) or an error.
55+*)
56+val connect :
57+ context ->
58+ ?session_url:Uri.t ->
59+ ?username:string ->
60+ host:string ->
61+ ?port:int ->
62+ ?auth_method:auth_method ->
63+ unit ->
64+ (context * Jmap.Session.Session.t) Jmap.Error.result
65+66+(** Create a request builder for constructing a JMAP request.
67+ @param ctx The client context.
68+ @return A request builder object.
69+*)
70+val build : context -> request_builder
71+72+(** Set the using capabilities for a request.
73+ @param builder The request builder.
74+ @param capabilities List of capability URIs to use.
75+ @return The updated request builder.
76+*)
77+val using : request_builder -> string list -> request_builder
78+79+(** Add a method call to a request builder.
80+ @param builder The request builder.
81+ @param name Method name (e.g., "Email/get").
82+ @param args Method arguments.
83+ @param id Method call ID.
84+ @return The updated request builder.
85+*)
86+val add_method_call :
87+ request_builder ->
88+ string ->
89+ Yojson.Safe.t ->
90+ string ->
91+ request_builder
92+93+(** Create a reference to a previous method call result.
94+ @param result_of Method call ID to reference.
95+ @param name Path in the response.
96+ @return A ResultReference to use in another method call.
97+*)
98+val create_reference : string -> string -> Jmap.Wire.Result_reference.t
99+100+(** Execute a request and return the response.
101+ @param builder The request builder to execute.
102+ @return The JMAP response from the server.
103+*)
104+val execute : request_builder -> Jmap.Wire.Response.t Jmap.Error.result
105+106+(** Perform a JMAP API request.
107+ @param ctx The connection context.
108+ @param request The JMAP request object.
109+ @return The JMAP response from the server.
110+*)
111+val request : context -> Jmap.Wire.Request.t -> Jmap.Wire.Response.t Jmap.Error.result
112+113+(** Upload binary data.
114+ @param ctx The connection context.
115+ @param account_id The target account ID.
116+ @param content_type The MIME type of the data.
117+ @param data_stream A stream providing the binary data chunks.
118+ @return A result with either an upload response or an error.
119+*)
120+val upload :
121+ context ->
122+ account_id:Jmap.Types.id ->
123+ content_type:string ->
124+ data_stream:string Seq.t ->
125+ Jmap.Binary.Upload_response.t Jmap.Error.result
126+127+(** Download binary data.
128+ @param ctx The connection context.
129+ @param account_id The account ID.
130+ @param blob_id The blob ID to download.
131+ @param ?content_type The desired Content-Type for the download response.
132+ @param ?name The desired filename for the download response.
133+ @return A result with either a stream of data chunks or an error.
134+*)
135+val download :
136+ context ->
137+ account_id:Jmap.Types.id ->
138+ blob_id:Jmap.Types.id ->
139+ ?content_type:string ->
140+ ?name:string ->
141+ (string Seq.t) Jmap.Error.result
142+143+(** Copy blobs between accounts.
144+ @param ctx The connection context.
145+ @param from_account_id Source account ID.
146+ @param account_id Destination account ID.
147+ @param blob_ids List of blob IDs to copy.
148+ @return A result with either the copy response or an error.
149+*)
150+val copy_blobs :
151+ context ->
152+ from_account_id:Jmap.Types.id ->
153+ account_id:Jmap.Types.id ->
154+ blob_ids:Jmap.Types.id list ->
155+ Jmap.Binary.Blob_copy_response.t Jmap.Error.result
156+157+(** Connect to the EventSource for push notifications.
158+ @param ctx The connection context.
159+ @param ?types List of types to subscribe to (default "*").
160+ @param ?close_after Request server to close after first state event.
161+ @param ?ping Request ping interval in seconds (default 0).
162+ @return A result with either a tuple of connection handle and event stream, or an error.
163+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.3> RFC 8620, Section 7.3 *)
164+val connect_event_source :
165+ context ->
166+ ?types:string list ->
167+ ?close_after:[`State | `No] ->
168+ ?ping:Jmap.Types.uint ->
169+ (event_source_connection *
170+ ([`State of Jmap.Push.State_change.t | `Ping of Jmap.Push.Event_source_ping_data.t ] Seq.t)) Jmap.Error.result
171+172+(** Create a websocket connection for JMAP over WebSocket.
173+ @param ctx The connection context.
174+ @return A result with either a websocket connection or an error.
175+ @see <https://www.rfc-editor.org/rfc/rfc8887.html> RFC 8887 *)
176+val connect_websocket :
177+ context ->
178+ event_source_connection Jmap.Error.result
179+180+(** Send a message over a websocket connection.
181+ @param conn The websocket connection.
182+ @param request The JMAP request to send.
183+ @return A result with either the response or an error.
184+*)
185+val websocket_send :
186+ event_source_connection ->
187+ Jmap.Wire.Request.t ->
188+ Jmap.Wire.Response.t Jmap.Error.result
189+190+(** Close an EventSource or WebSocket connection.
191+ @param conn The connection handle.
192+ @return A result with either unit or an error.
193+*)
194+val close_connection : event_source_connection -> unit Jmap.Error.result
195+196+(** Close the JMAP connection context.
197+ @return A result with either unit or an error.
198+*)
199+val close : context -> unit Jmap.Error.result
200+201+(** {2 Helper Methods for Common Tasks} *)
202+203+(** Helper to get a single object by ID.
204+ @param ctx The context.
205+ @param method_name The get method (e.g., "Email/get").
206+ @param account_id The account ID.
207+ @param object_id The ID of the object to get.
208+ @param ?properties Optional list of properties to fetch.
209+ @return A result with either the object as JSON or an error.
210+*)
211+val get_object :
212+ context ->
213+ method_name:string ->
214+ account_id:Jmap.Types.id ->
215+ object_id:Jmap.Types.id ->
216+ ?properties:string list ->
217+ Yojson.Safe.t Jmap.Error.result
218+219+(** Helper to set up the connection with minimal options.
220+ @param host The JMAP server hostname.
221+ @param username Username for basic auth.
222+ @param password Password for basic auth.
223+ @return A result with either (context, session) or an error.
224+*)
225+val quick_connect :
226+ host:string ->
227+ username:string ->
228+ password:string ->
229+ (context * Jmap.Session.Session.t) Jmap.Error.result
230+231+(** Perform a Core/echo request to test connectivity.
232+ @param ctx The JMAP connection context.
233+ @param ?data Optional data to echo back.
234+ @return A result with either the response or an error.
235+*)
236+val echo :
237+ context ->
238+ ?data:Yojson.Safe.t ->
239+ unit ->
240+ Yojson.Safe.t Jmap.Error.result
241+242+(** {2 Email Operations} *)
243+244+(** High-level email operations that map to JMAP email methods *)
245+module Email : sig
246+ open Jmap_email.Types
247+248+ (** Get an email by ID
249+ @param ctx The JMAP client context
250+ @param account_id The account ID
251+ @param email_id The email ID to fetch
252+ @param ?properties Optional list of properties to fetch
253+ @return The email object or an error
254+ *)
255+ val get_email :
256+ context ->
257+ account_id:Jmap.Types.id ->
258+ email_id:Jmap.Types.id ->
259+ ?properties:string list ->
260+ unit ->
261+ Email.t Jmap.Error.result
262+263+ (** Search for emails using a filter
264+ @param ctx The JMAP client context
265+ @param account_id The account ID
266+ @param filter The search filter
267+ @param ?sort Optional sort criteria (default received date newest first)
268+ @param ?limit Optional maximum number of results
269+ @param ?properties Optional properties to fetch for the matching emails
270+ @return The list of matching email IDs and optionally the email objects
271+ *)
272+ val search_emails :
273+ context ->
274+ account_id:Jmap.Types.id ->
275+ filter:Jmap.Methods.Filter.t ->
276+ ?sort:Jmap.Methods.Comparator.t list ->
277+ ?limit:Jmap.Types.uint ->
278+ ?position:int ->
279+ ?properties:string list ->
280+ unit ->
281+ (Jmap.Types.id list * Email.t list option) Jmap.Error.result
282+283+ (** Mark multiple emails with a keyword
284+ @param ctx The JMAP client context
285+ @param account_id The account ID
286+ @param email_ids List of email IDs to update
287+ @param keyword The keyword to add
288+ @return The result of the operation
289+ *)
290+ val mark_emails :
291+ context ->
292+ account_id:Jmap.Types.id ->
293+ email_ids:Jmap.Types.id list ->
294+ keyword:Keywords.keyword ->
295+ unit ->
296+ unit Jmap.Error.result
297+298+ (** Mark emails as seen/read
299+ @param ctx The JMAP client context
300+ @param account_id The account ID
301+ @param email_ids List of email IDs to mark
302+ @return The result of the operation
303+ *)
304+ val mark_as_seen :
305+ context ->
306+ account_id:Jmap.Types.id ->
307+ email_ids:Jmap.Types.id list ->
308+ unit ->
309+ unit Jmap.Error.result
310+311+ (** Mark emails as unseen/unread
312+ @param ctx The JMAP client context
313+ @param account_id The account ID
314+ @param email_ids List of email IDs to mark
315+ @return The result of the operation
316+ *)
317+ val mark_as_unseen :
318+ context ->
319+ account_id:Jmap.Types.id ->
320+ email_ids:Jmap.Types.id list ->
321+ unit ->
322+ unit Jmap.Error.result
323+324+ (** Move emails to a different mailbox
325+ @param ctx The JMAP client context
326+ @param account_id The account ID
327+ @param email_ids List of email IDs to move
328+ @param mailbox_id Destination mailbox ID
329+ @param ?remove_from_mailboxes Optional list of source mailbox IDs to remove from
330+ @return The result of the operation
331+ *)
332+ val move_emails :
333+ context ->
334+ account_id:Jmap.Types.id ->
335+ email_ids:Jmap.Types.id list ->
336+ mailbox_id:Jmap.Types.id ->
337+ ?remove_from_mailboxes:Jmap.Types.id list ->
338+ unit ->
339+ unit Jmap.Error.result
340+341+ (** Import an RFC822 message
342+ @param ctx The JMAP client context
343+ @param account_id The account ID
344+ @param rfc822 Raw message content
345+ @param mailbox_ids Mailboxes to add the message to
346+ @param ?keywords Optional keywords to set
347+ @param ?received_at Optional received timestamp
348+ @return The ID of the imported email
349+ *)
350+ val import_email :
351+ context ->
352+ account_id:Jmap.Types.id ->
353+ rfc822:string ->
354+ mailbox_ids:Jmap.Types.id list ->
355+ ?keywords:Keywords.t ->
356+ ?received_at:Jmap.Types.date ->
357+ unit ->
358+ Jmap.Types.id Jmap.Error.result
359+end
···1+(* JMAP Core Protocol Library Interface (RFC 8620) *)
2+3+module Types = Jmap_types
4+module Error = Jmap_error
5+module Wire = Jmap_wire
6+module Session = Jmap_session
7+module Methods = Jmap_methods
8+module Binary = Jmap_binary
9+module Push = Jmap_push
10+11+(* Capability URI for JMAP Core. *)
12+let capability_core = "urn:ietf:params:jmap:core"
13+14+(* Check if a session supports a given capability. *)
15+let supports_capability session capability =
16+ let caps = Session.Session.capabilities session in
17+ Hashtbl.mem caps capability
18+19+(* Get the primary account ID for a given capability. *)
20+let get_primary_account session capability =
21+ let primary_accounts = Session.Session.primary_accounts session in
22+ match Hashtbl.find_opt primary_accounts capability with
23+ | Some account_id -> Ok account_id
24+ | None -> Error (Error.protocol_error ("No primary account for capability: " ^ capability))
25+26+(* Get the download URL for a blob. *)
27+let get_download_url session ~account_id ~blob_id ?name ?content_type () =
28+ let base_url = Session.Session.download_url session in
29+ let url_str = Uri.to_string base_url in
30+ let url_str = url_str ^ "/accounts/" ^ account_id ^ "/blobs/" ^ blob_id in
31+ let url = Uri.of_string url_str in
32+ let url = match name with
33+ | Some name -> Uri.add_query_param url ("name", [name])
34+ | None -> url
35+ in
36+ match content_type with
37+ | Some ct -> Uri.add_query_param url ("type", [ct])
38+ | None -> url
39+40+(* Get the upload URL for a blob. *)
41+let get_upload_url session ~account_id =
42+ let base_url = Session.Session.upload_url session in
43+ let url_str = Uri.to_string base_url in
44+ let url_str = url_str ^ "/accounts/" ^ account_id in
45+ Uri.of_string url_str
···1+(** JMAP Core Protocol Library Interface (RFC 8620)
2+3+ This library provides OCaml types and function signatures for interacting
4+ with a JMAP server according to the core protocol specification in RFC 8620.
5+6+ Modules:
7+ - {!Jmap.Types}: Basic data types (Id, Date, etc.).
8+ - {!Jmap.Error}: Error types (ProblemDetails, MethodError, SetError).
9+ - {!Jmap.Wire}: Request and Response structures.
10+ - {!Jmap.Session}: Session object and discovery.
11+ - {!Jmap.Methods}: Standard method patterns (/get, /set, etc.) and Core/echo.
12+ - {!Jmap.Binary}: Binary data upload/download types.
13+ - {!Jmap.Push}: Push notification types (StateChange, PushSubscription).
14+15+ For email-specific extensions (RFC 8621), see the Jmap_email library.
16+ For Unix-specific implementation, see the Jmap_unix library.
17+18+ @see <https://www.rfc-editor.org/rfc/rfc8620.html> RFC 8620: Core JMAP
19+*)
20+21+(** {1 Core JMAP Types and Modules} *)
22+23+module Types = Jmap_types
24+module Error = Jmap_error
25+module Wire = Jmap_wire
26+module Session = Jmap_session
27+module Methods = Jmap_methods
28+module Binary = Jmap_binary
29+module Push = Jmap_push
30+31+(** {1 Example Usage}
32+33+ The following example demonstrates using the Core JMAP library with the Unix implementation
34+ to make a simple echo request.
35+36+{[
37+ (* OCaml 5.1 required for Lwt let operators *)
38+ open Lwt.Syntax
39+ open Jmap
40+ open Jmap.Types
41+ open Jmap.Wire
42+ open Jmap.Methods
43+ open Jmap.Unix
44+45+ let simple_echo_request ctx session =
46+ (* Prepare an echo invocation *)
47+ let echo_args = Yojson.Safe.to_basic (`Assoc [
48+ ("hello", `String "world");
49+ ("array", `List [`Int 1; `Int 2; `Int 3]);
50+ ]) in
51+52+ let echo_invocation = Invocation.v
53+ ~method_name:"Core/echo"
54+ ~arguments:echo_args
55+ ~method_call_id:"echo1"
56+ ()
57+ in
58+59+ (* Prepare the JMAP request *)
60+ let request = Request.v
61+ ~using:[capability_core]
62+ ~method_calls:[echo_invocation]
63+ ()
64+ in
65+66+ (* Send the request *)
67+ let* response = Jmap.Unix.request ctx request in
68+69+ (* Process the response *)
70+ match Wire.find_method_response response "echo1" with
71+ | Some (method_name, args, _) when method_name = "Core/echo" ->
72+ (* Echo response should contain the same arguments we sent *)
73+ let hello_value = match Yojson.Safe.Util.member "hello" args with
74+ | `String s -> s
75+ | _ -> "not found"
76+ in
77+ Printf.printf "Echo response received: hello=%s\n" hello_value;
78+ Lwt.return_unit
79+ | _ ->
80+ Printf.eprintf "Echo response not found or unexpected format\n";
81+ Lwt.return_unit
82+83+ let main () =
84+ (* Authentication details are placeholder *)
85+ let credentials = "my_auth_token" in
86+ let* (ctx, session) = Jmap.Unix.connect ~host:"jmap.example.com" ~credentials in
87+ let* () = simple_echo_request ctx session in
88+ Jmap.Unix.close ctx
89+90+ (* Lwt_main.run (main ()) *)
91+]}
92+*)
93+94+(** Capability URI for JMAP Core.
95+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2> RFC 8620, Section 2 *)
96+val capability_core : string
97+98+(** {1 Convenience Functions} *)
99+100+(** Check if a session supports a given capability.
101+ @param session The session object.
102+ @param capability The capability URI to check.
103+ @return True if supported, false otherwise.
104+*)
105+val supports_capability : Jmap_session.Session.t -> string -> bool
106+107+(** Get the primary account ID for a given capability.
108+ @param session The session object.
109+ @param capability The capability URI.
110+ @return The account ID or an error if not found.
111+*)
112+val get_primary_account : Jmap_session.Session.t -> string -> (Jmap_types.id, Error.error) result
113+114+(** Get the download URL for a blob.
115+ @param session The session object.
116+ @param account_id The account ID.
117+ @param blob_id The blob ID.
118+ @param ?name Optional filename for the download.
119+ @param ?content_type Optional content type for the download.
120+ @return The download URL.
121+*)
122+val get_download_url :
123+ Jmap_session.Session.t ->
124+ account_id:Jmap_types.id ->
125+ blob_id:Jmap_types.id ->
126+ ?name:string ->
127+ ?content_type:string ->
128+ unit ->
129+ Uri.t
130+131+(** Get the upload URL for a blob.
132+ @param session The session object.
133+ @param account_id The account ID.
134+ @return The upload URL.
135+*)
136+val get_upload_url : Jmap_session.Session.t -> account_id:Jmap_types.id -> Uri.t
···1+(** JMAP Binary Data Handling.
2+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-6> RFC 8620, Section 6 *)
3+4+open Jmap_types
5+open Jmap_error
6+7+(** Response from uploading binary data.
8+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-6.1> RFC 8620, Section 6.1 *)
9+module Upload_response : sig
10+ type t
11+12+ val account_id : t -> id
13+ val blob_id : t -> id
14+ val type_ : t -> string
15+ val size : t -> uint
16+17+ val v :
18+ account_id:id ->
19+ blob_id:id ->
20+ type_:string ->
21+ size:uint ->
22+ unit ->
23+ t
24+end
25+26+(** Arguments for Blob/copy.
27+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-6.3> RFC 8620, Section 6.3 *)
28+module Blob_copy_args : sig
29+ type t
30+31+ val from_account_id : t -> id
32+ val account_id : t -> id
33+ val blob_ids : t -> id list
34+35+ val v :
36+ from_account_id:id ->
37+ account_id:id ->
38+ blob_ids:id list ->
39+ unit ->
40+ t
41+end
42+43+(** Response for Blob/copy.
44+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-6.3> RFC 8620, Section 6.3 *)
45+module Blob_copy_response : sig
46+ type t
47+48+ val from_account_id : t -> id
49+ val account_id : t -> id
50+ val copied : t -> id id_map option
51+ val not_copied : t -> Set_error.t id_map option
52+53+ val v :
54+ from_account_id:id ->
55+ account_id:id ->
56+ ?copied:id id_map ->
57+ ?not_copied:Set_error.t id_map ->
58+ unit ->
59+ t
60+end
···1+(** Standard JMAP Methods and Core/echo.
2+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-4> RFC 8620, Section 4
3+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5> RFC 8620, Section 5 *)
4+5+open Jmap_types
6+open Jmap_error
7+8+(** Generic representation of a record type. Actual types defined elsewhere. *)
9+type generic_record
10+11+(** Arguments for /get methods.
12+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.1> RFC 8620, Section 5.1 *)
13+module Get_args : sig
14+ type 'record t
15+16+ val account_id : 'record t -> id
17+ val ids : 'record t -> id list option
18+ val properties : 'record t -> string list option
19+20+ val v :
21+ account_id:id ->
22+ ?ids:id list ->
23+ ?properties:string list ->
24+ unit ->
25+ 'record t
26+end
27+28+(** Response for /get methods.
29+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.1> RFC 8620, Section 5.1 *)
30+module Get_response : sig
31+ type 'record t
32+33+ val account_id : 'record t -> id
34+ val state : 'record t -> string
35+ val list : 'record t -> 'record list
36+ val not_found : 'record t -> id list
37+38+ val v :
39+ account_id:id ->
40+ state:string ->
41+ list:'record list ->
42+ not_found:id list ->
43+ unit ->
44+ 'record t
45+end
46+47+(** Arguments for /changes methods.
48+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.2> RFC 8620, Section 5.2 *)
49+module Changes_args : sig
50+ type t
51+52+ val account_id : t -> id
53+ val since_state : t -> string
54+ val max_changes : t -> uint option
55+56+ val v :
57+ account_id:id ->
58+ since_state:string ->
59+ ?max_changes:uint ->
60+ unit ->
61+ t
62+end
63+64+(** Response for /changes methods.
65+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.2> RFC 8620, Section 5.2 *)
66+module Changes_response : sig
67+ type t
68+69+ val account_id : t -> id
70+ val old_state : t -> string
71+ val new_state : t -> string
72+ val has_more_changes : t -> bool
73+ val created : t -> id list
74+ val updated : t -> id list
75+ val destroyed : t -> id list
76+ val updated_properties : t -> string list option
77+78+ val v :
79+ account_id:id ->
80+ old_state:string ->
81+ new_state:string ->
82+ has_more_changes:bool ->
83+ created:id list ->
84+ updated:id list ->
85+ destroyed:id list ->
86+ ?updated_properties:string list ->
87+ unit ->
88+ t
89+end
90+91+(** Patch object for /set update.
92+ A list of (JSON Pointer path, value) pairs.
93+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.3> RFC 8620, Section 5.3 *)
94+type patch_object = (json_pointer * Yojson.Safe.t) list
95+96+(** Arguments for /set methods.
97+ ['create_record] is the record type without server-set/immutable fields.
98+ ['update_record] is the patch object type (usually [patch_object]).
99+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.3> RFC 8620, Section 5.3 *)
100+module Set_args : sig
101+ type ('create_record, 'update_record) t
102+103+ val account_id : ('a, 'b) t -> id
104+ val if_in_state : ('a, 'b) t -> string option
105+ val create : ('a, 'b) t -> 'a id_map option
106+ val update : ('a, 'b) t -> 'b id_map option
107+ val destroy : ('a, 'b) t -> id list option
108+ val on_success_destroy_original : ('a, 'b) t -> bool option
109+ val destroy_from_if_in_state : ('a, 'b) t -> string option
110+ val on_destroy_remove_emails : ('a, 'b) t -> bool option
111+112+ val v :
113+ account_id:id ->
114+ ?if_in_state:string ->
115+ ?create:'a id_map ->
116+ ?update:'b id_map ->
117+ ?destroy:id list ->
118+ ?on_success_destroy_original:bool ->
119+ ?destroy_from_if_in_state:string ->
120+ ?on_destroy_remove_emails:bool ->
121+ unit ->
122+ ('a, 'b) t
123+end
124+125+(** Response for /set methods.
126+ ['created_record_info] is the server-set info for created records.
127+ ['updated_record_info] is the server-set/computed info for updated records.
128+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.3> RFC 8620, Section 5.3 *)
129+module Set_response : sig
130+ type ('created_record_info, 'updated_record_info) t
131+132+ val account_id : ('a, 'b) t -> id
133+ val old_state : ('a, 'b) t -> string option
134+ val new_state : ('a, 'b) t -> string
135+ val created : ('a, 'b) t -> 'a id_map option
136+ val updated : ('a, 'b) t -> 'b option id_map option
137+ val destroyed : ('a, 'b) t -> id list option
138+ val not_created : ('a, 'b) t -> Set_error.t id_map option
139+ val not_updated : ('a, 'b) t -> Set_error.t id_map option
140+ val not_destroyed : ('a, 'b) t -> Set_error.t id_map option
141+142+ val v :
143+ account_id:id ->
144+ ?old_state:string ->
145+ new_state:string ->
146+ ?created:'a id_map ->
147+ ?updated:'b option id_map ->
148+ ?destroyed:id list ->
149+ ?not_created:Set_error.t id_map ->
150+ ?not_updated:Set_error.t id_map ->
151+ ?not_destroyed:Set_error.t id_map ->
152+ unit ->
153+ ('a, 'b) t
154+end
155+156+(** Arguments for /copy methods.
157+ ['copy_record_override] contains the record id and override properties.
158+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.4> RFC 8620, Section 5.4 *)
159+module Copy_args : sig
160+ type 'copy_record_override t
161+162+ val from_account_id : 'a t -> id
163+ val if_from_in_state : 'a t -> string option
164+ val account_id : 'a t -> id
165+ val if_in_state : 'a t -> string option
166+ val create : 'a t -> 'a id_map
167+ val on_success_destroy_original : 'a t -> bool
168+ val destroy_from_if_in_state : 'a t -> string option
169+170+ val v :
171+ from_account_id:id ->
172+ ?if_from_in_state:string ->
173+ account_id:id ->
174+ ?if_in_state:string ->
175+ create:'a id_map ->
176+ ?on_success_destroy_original:bool ->
177+ ?destroy_from_if_in_state:string ->
178+ unit ->
179+ 'a t
180+end
181+182+(** Response for /copy methods.
183+ ['created_record_info] is the server-set info for the created copy.
184+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.4> RFC 8620, Section 5.4 *)
185+module Copy_response : sig
186+ type 'created_record_info t
187+188+ val from_account_id : 'a t -> id
189+ val account_id : 'a t -> id
190+ val old_state : 'a t -> string option
191+ val new_state : 'a t -> string
192+ val created : 'a t -> 'a id_map option
193+ val not_created : 'a t -> Set_error.t id_map option
194+195+ val v :
196+ from_account_id:id ->
197+ account_id:id ->
198+ ?old_state:string ->
199+ new_state:string ->
200+ ?created:'a id_map ->
201+ ?not_created:Set_error.t id_map ->
202+ unit ->
203+ 'a t
204+end
205+206+(** Module for generic filter representation.
207+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.5> RFC 8620, Section 5.5 *)
208+module Filter : sig
209+ type t
210+211+ (** Create a filter from a raw JSON condition *)
212+ val condition : Yojson.Safe.t -> t
213+214+ (** Create a filter with a logical operator (AND, OR, NOT) *)
215+ val operator : [ `AND | `OR | `NOT ] -> t list -> t
216+217+ (** Combine filters with AND *)
218+ val and_ : t list -> t
219+220+ (** Combine filters with OR *)
221+ val or_ : t list -> t
222+223+ (** Negate a filter with NOT *)
224+ val not_ : t -> t
225+226+ (** Convert a filter to JSON *)
227+ val to_json : t -> Yojson.Safe.t
228+229+ (** Predefined filter helpers *)
230+231+ (** Create a filter for a text property containing a string *)
232+ val text_contains : string -> string -> t
233+234+ (** Create a filter for a property being equal to a value *)
235+ val property_equals : string -> Yojson.Safe.t -> t
236+237+ (** Create a filter for a property being not equal to a value *)
238+ val property_not_equals : string -> Yojson.Safe.t -> t
239+240+ (** Create a filter for a property being greater than a value *)
241+ val property_gt : string -> Yojson.Safe.t -> t
242+243+ (** Create a filter for a property being greater than or equal to a value *)
244+ val property_ge : string -> Yojson.Safe.t -> t
245+246+ (** Create a filter for a property being less than a value *)
247+ val property_lt : string -> Yojson.Safe.t -> t
248+249+ (** Create a filter for a property being less than or equal to a value *)
250+ val property_le : string -> Yojson.Safe.t -> t
251+252+ (** Create a filter for a property value being in a list *)
253+ val property_in : string -> Yojson.Safe.t list -> t
254+255+ (** Create a filter for a property value not being in a list *)
256+ val property_not_in : string -> Yojson.Safe.t list -> t
257+258+ (** Create a filter for a property being present (not null) *)
259+ val property_exists : string -> t
260+261+ (** Create a filter for a string property starting with a prefix *)
262+ val string_starts_with : string -> string -> t
263+264+ (** Create a filter for a string property ending with a suffix *)
265+ val string_ends_with : string -> string -> t
266+end
267+268+269+270+(** Comparator for sorting.
271+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.5> RFC 8620, Section 5.5 *)
272+module Comparator : sig
273+ type t
274+275+ val property : t -> string
276+ val is_ascending : t -> bool option
277+ val collation : t -> string option
278+ val keyword : t -> string option
279+ val other_fields : t -> Yojson.Safe.t string_map
280+281+ val v :
282+ property:string ->
283+ ?is_ascending:bool ->
284+ ?collation:string ->
285+ ?keyword:string ->
286+ ?other_fields:Yojson.Safe.t string_map ->
287+ unit ->
288+ t
289+end
290+291+(** Arguments for /query methods.
292+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.5> RFC 8620, Section 5.5 *)
293+module Query_args : sig
294+ type t
295+296+ val account_id : t -> id
297+ val filter : t -> Filter.t option
298+ val sort : t -> Comparator.t list option
299+ val position : t -> jint option
300+ val anchor : t -> id option
301+ val anchor_offset : t -> jint option
302+ val limit : t -> uint option
303+ val calculate_total : t -> bool option
304+ val collapse_threads : t -> bool option
305+ val sort_as_tree : t -> bool option
306+ val filter_as_tree : t -> bool option
307+308+ val v :
309+ account_id:id ->
310+ ?filter:Filter.t ->
311+ ?sort:Comparator.t list ->
312+ ?position:jint ->
313+ ?anchor:id ->
314+ ?anchor_offset:jint ->
315+ ?limit:uint ->
316+ ?calculate_total:bool ->
317+ ?collapse_threads:bool ->
318+ ?sort_as_tree:bool ->
319+ ?filter_as_tree:bool ->
320+ unit ->
321+ t
322+end
323+324+(** Response for /query methods.
325+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.5> RFC 8620, Section 5.5 *)
326+module Query_response : sig
327+ type t
328+329+ val account_id : t -> id
330+ val query_state : t -> string
331+ val can_calculate_changes : t -> bool
332+ val position : t -> uint
333+ val ids : t -> id list
334+ val total : t -> uint option
335+ val limit : t -> uint option
336+337+ val v :
338+ account_id:id ->
339+ query_state:string ->
340+ can_calculate_changes:bool ->
341+ position:uint ->
342+ ids:id list ->
343+ ?total:uint ->
344+ ?limit:uint ->
345+ unit ->
346+ t
347+end
348+349+(** Item indicating an added record in /queryChanges.
350+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.6> RFC 8620, Section 5.6 *)
351+module Added_item : sig
352+ type t
353+354+ val id : t -> id
355+ val index : t -> uint
356+357+ val v :
358+ id:id ->
359+ index:uint ->
360+ unit ->
361+ t
362+end
363+364+(** Arguments for /queryChanges methods.
365+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.6> RFC 8620, Section 5.6 *)
366+module Query_changes_args : sig
367+ type t
368+369+ val account_id : t -> id
370+ val filter : t -> Filter.t option
371+ val sort : t -> Comparator.t list option
372+ val since_query_state : t -> string
373+ val max_changes : t -> uint option
374+ val up_to_id : t -> id option
375+ val calculate_total : t -> bool option
376+ val collapse_threads : t -> bool option
377+378+ val v :
379+ account_id:id ->
380+ ?filter:Filter.t ->
381+ ?sort:Comparator.t list ->
382+ since_query_state:string ->
383+ ?max_changes:uint ->
384+ ?up_to_id:id ->
385+ ?calculate_total:bool ->
386+ ?collapse_threads:bool ->
387+ unit ->
388+ t
389+end
390+391+(** Response for /queryChanges methods.
392+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-5.6> RFC 8620, Section 5.6 *)
393+module Query_changes_response : sig
394+ type t
395+396+ val account_id : t -> id
397+ val old_query_state : t -> string
398+ val new_query_state : t -> string
399+ val total : t -> uint option
400+ val removed : t -> id list
401+ val added : t -> Added_item.t list
402+403+ val v :
404+ account_id:id ->
405+ old_query_state:string ->
406+ new_query_state:string ->
407+ ?total:uint ->
408+ removed:id list ->
409+ added:Added_item.t list ->
410+ unit ->
411+ t
412+end
413+414+(** Core/echo method: Arguments are mirrored in the response.
415+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-4> RFC 8620, Section 4 *)
416+type core_echo_args = Yojson.Safe.t
417+type core_echo_response = Yojson.Safe.t
···1+(** JMAP Push Notifications.
2+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7> RFC 8620, Section 7 *)
3+4+open Jmap_types
5+open Jmap_methods
6+open Jmap_error
7+8+(** TypeState object map (TypeName -> StateString).
9+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.1> RFC 8620, Section 7.1 *)
10+type type_state = string string_map
11+12+(** StateChange object.
13+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.1> RFC 8620, Section 7.1 *)
14+module State_change : sig
15+ type t
16+17+ val changed : t -> type_state id_map
18+19+ val v :
20+ changed:type_state id_map ->
21+ unit ->
22+ t
23+end
24+25+(** PushSubscription encryption keys.
26+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2> RFC 8620, Section 7.2 *)
27+module Push_encryption_keys : sig
28+ type t
29+30+ (** P-256 ECDH public key (URL-safe base64) *)
31+ val p256dh : t -> string
32+33+ (** Authentication secret (URL-safe base64) *)
34+ val auth : t -> string
35+36+ val v :
37+ p256dh:string ->
38+ auth:string ->
39+ unit ->
40+ t
41+end
42+43+(** PushSubscription object.
44+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2> RFC 8620, Section 7.2 *)
45+module Push_subscription : sig
46+ type t
47+48+ (** Id of the subscription (server-set, immutable) *)
49+ val id : t -> id
50+51+ (** Device client id (immutable) *)
52+ val device_client_id : t -> string
53+54+ (** Notification URL (immutable) *)
55+ val url : t -> Uri.t
56+57+ (** Encryption keys (immutable) *)
58+ val keys : t -> Push_encryption_keys.t option
59+ val verification_code : t -> string option
60+ val expires : t -> utc_date option
61+ val types : t -> string list option
62+63+ val v :
64+ id:id ->
65+ device_client_id:string ->
66+ url:Uri.t ->
67+ ?keys:Push_encryption_keys.t ->
68+ ?verification_code:string ->
69+ ?expires:utc_date ->
70+ ?types:string list ->
71+ unit ->
72+ t
73+end
74+75+(** PushSubscription object for creation (omits server-set fields).
76+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2> RFC 8620, Section 7.2 *)
77+module Push_subscription_create : sig
78+ type t
79+80+ val device_client_id : t -> string
81+ val url : t -> Uri.t
82+ val keys : t -> Push_encryption_keys.t option
83+ val expires : t -> utc_date option
84+ val types : t -> string list option
85+86+ val v :
87+ device_client_id:string ->
88+ url:Uri.t ->
89+ ?keys:Push_encryption_keys.t ->
90+ ?expires:utc_date ->
91+ ?types:string list ->
92+ unit ->
93+ t
94+end
95+96+(** PushSubscription object for update patch.
97+ Only verification_code and expires can be updated.
98+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2> RFC 8620, Section 7.2
99+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2.2> RFC 8620, Section 7.2.2 *)
100+type push_subscription_update = patch_object
101+102+(** Arguments for PushSubscription/get.
103+ Extends standard /get args.
104+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2.1> RFC 8620, Section 7.2.1 *)
105+module Push_subscription_get_args : sig
106+ type t
107+108+ val ids : t -> id list option
109+ val properties : t -> string list option
110+111+ val v :
112+ ?ids:id list ->
113+ ?properties:string list ->
114+ unit ->
115+ t
116+end
117+118+(** Response for PushSubscription/get.
119+ Extends standard /get response.
120+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2.1> RFC 8620, Section 7.2.1 *)
121+module Push_subscription_get_response : sig
122+ type t
123+124+ val list : t -> Push_subscription.t list
125+ val not_found : t -> id list
126+127+ val v :
128+ list:Push_subscription.t list ->
129+ not_found:id list ->
130+ unit ->
131+ t
132+end
133+134+(** Arguments for PushSubscription/set.
135+ Extends standard /set args.
136+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2.2> RFC 8620, Section 7.2.2 *)
137+module Push_subscription_set_args : sig
138+ type t
139+140+ val create : t -> Push_subscription_create.t id_map option
141+ val update : t -> push_subscription_update id_map option
142+ val destroy : t -> id list option
143+144+ val v :
145+ ?create:Push_subscription_create.t id_map ->
146+ ?update:push_subscription_update id_map ->
147+ ?destroy:id list ->
148+ unit ->
149+ t
150+end
151+152+(** Server-set information for created PushSubscription.
153+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2.2> RFC 8620, Section 7.2.2 *)
154+module Push_subscription_created_info : sig
155+ type t
156+157+ val id : t -> id
158+ val expires : t -> utc_date option
159+160+ val v :
161+ id:id ->
162+ ?expires:utc_date ->
163+ unit ->
164+ t
165+end
166+167+(** Server-set information for updated PushSubscription.
168+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2.2> RFC 8620, Section 7.2.2 *)
169+module Push_subscription_updated_info : sig
170+ type t
171+172+ val expires : t -> utc_date option
173+174+ val v :
175+ ?expires:utc_date ->
176+ unit ->
177+ t
178+end
179+180+(** Response for PushSubscription/set.
181+ Extends standard /set response.
182+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2.2> RFC 8620, Section 7.2.2 *)
183+module Push_subscription_set_response : sig
184+ type t
185+186+ val created : t -> Push_subscription_created_info.t id_map option
187+ val updated : t -> Push_subscription_updated_info.t option id_map option
188+ val destroyed : t -> id list option
189+ val not_created : t -> Set_error.t id_map option
190+ val not_updated : t -> Set_error.t id_map option
191+ val not_destroyed : t -> Set_error.t id_map option
192+193+ val v :
194+ ?created:Push_subscription_created_info.t id_map ->
195+ ?updated:Push_subscription_updated_info.t option id_map ->
196+ ?destroyed:id list ->
197+ ?not_created:Set_error.t id_map ->
198+ ?not_updated:Set_error.t id_map ->
199+ ?not_destroyed:Set_error.t id_map ->
200+ unit ->
201+ t
202+end
203+204+(** PushVerification object.
205+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.2.2> RFC 8620, Section 7.2.2 *)
206+module Push_verification : sig
207+ type t
208+209+ val push_subscription_id : t -> id
210+ val verification_code : t -> string
211+212+ val v :
213+ push_subscription_id:id ->
214+ verification_code:string ->
215+ unit ->
216+ t
217+end
218+219+(** Data for EventSource ping event.
220+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-7.3> RFC 8620, Section 7.3 *)
221+module Event_source_ping_data : sig
222+ type t
223+224+ val interval : t -> uint
225+226+ val v :
227+ interval:uint ->
228+ unit ->
229+ t
230+end
···1+(* JMAP Session Resource. *)
2+3+open Jmap_types
4+5+(* Account capability information.
6+ The value is capability-specific. *)
7+type account_capability_value = Yojson.Safe.t
8+9+(* Server capability information.
10+ The value is capability-specific. *)
11+type server_capability_value = Yojson.Safe.t
12+13+(* Core capability information. *)
14+module Core_capability = struct
15+ type t = {
16+ max_size_upload: uint;
17+ max_concurrent_upload: uint;
18+ max_size_request: uint;
19+ max_concurrent_requests: uint;
20+ max_calls_in_request: uint;
21+ max_objects_in_get: uint;
22+ max_objects_in_set: uint;
23+ collation_algorithms: string list;
24+ }
25+26+ let max_size_upload t = t.max_size_upload
27+ let max_concurrent_upload t = t.max_concurrent_upload
28+ let max_size_request t = t.max_size_request
29+ let max_concurrent_requests t = t.max_concurrent_requests
30+ let max_calls_in_request t = t.max_calls_in_request
31+ let max_objects_in_get t = t.max_objects_in_get
32+ let max_objects_in_set t = t.max_objects_in_set
33+ let collation_algorithms t = t.collation_algorithms
34+35+ let v ~max_size_upload ~max_concurrent_upload ~max_size_request
36+ ~max_concurrent_requests ~max_calls_in_request ~max_objects_in_get
37+ ~max_objects_in_set ~collation_algorithms () =
38+ { max_size_upload; max_concurrent_upload; max_size_request;
39+ max_concurrent_requests; max_calls_in_request; max_objects_in_get;
40+ max_objects_in_set; collation_algorithms }
41+end
42+43+(* An Account object. *)
44+module Account = struct
45+ type t = {
46+ name: string;
47+ is_personal: bool;
48+ is_read_only: bool;
49+ account_capabilities: account_capability_value string_map;
50+ }
51+52+ let name t = t.name
53+ let is_personal t = t.is_personal
54+ let is_read_only t = t.is_read_only
55+ let account_capabilities t = t.account_capabilities
56+57+ let v ~name ?(is_personal=true) ?(is_read_only=false)
58+ ?(account_capabilities=Hashtbl.create 0) () =
59+ { name; is_personal; is_read_only; account_capabilities }
60+end
61+62+(* The Session object. *)
63+module Session = struct
64+ type t = {
65+ capabilities: server_capability_value string_map;
66+ accounts: Account.t id_map;
67+ primary_accounts: id string_map;
68+ username: string;
69+ api_url: Uri.t;
70+ download_url: Uri.t;
71+ upload_url: Uri.t;
72+ event_source_url: Uri.t;
73+ state: string;
74+ }
75+76+ let capabilities t = t.capabilities
77+ let accounts t = t.accounts
78+ let primary_accounts t = t.primary_accounts
79+ let username t = t.username
80+ let api_url t = t.api_url
81+ let download_url t = t.download_url
82+ let upload_url t = t.upload_url
83+ let event_source_url t = t.event_source_url
84+ let state t = t.state
85+86+ let v ~capabilities ~accounts ~primary_accounts ~username
87+ ~api_url ~download_url ~upload_url ~event_source_url ~state () =
88+ { capabilities; accounts; primary_accounts; username;
89+ api_url; download_url; upload_url; event_source_url; state }
90+end
91+92+(* Function to perform service autodiscovery.
93+ Returns the session URL if found. *)
94+let discover ~domain =
95+ (* This is a placeholder implementation - would need to be completed in Unix implementation *)
96+ let well_known_url = Uri.of_string ("https://" ^ domain ^ "/.well-known/jmap") in
97+ Some well_known_url
98+99+(* Function to fetch the session object from a given URL.
100+ Requires authentication handling (details TBD/outside this signature). *)
101+let get_session ~url =
102+ (* This is a placeholder implementation - would need to be completed in Unix implementation *)
103+ let empty_map () = Hashtbl.create 0 in
104+ Session.v
105+ ~capabilities:(empty_map ())
106+ ~accounts:(empty_map ())
107+ ~primary_accounts:(empty_map ())
108+ ~username:"placeholder"
109+ ~api_url:url
110+ ~download_url:url
111+ ~upload_url:url
112+ ~event_source_url:url
113+ ~state:"placeholder"
114+ ()
···1+(** JMAP Session Resource.
2+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2> RFC 8620, Section 2 *)
3+4+open Jmap_types
5+6+(** Account capability information.
7+ The value is capability-specific.
8+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2> RFC 8620, Section 2 *)
9+type account_capability_value = Yojson.Safe.t
10+11+(** Server capability information.
12+ The value is capability-specific.
13+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2> RFC 8620, Section 2 *)
14+type server_capability_value = Yojson.Safe.t
15+16+(** Core capability information.
17+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2> RFC 8620, Section 2 *)
18+module Core_capability : sig
19+ type t
20+21+ val max_size_upload : t -> uint
22+ val max_concurrent_upload : t -> uint
23+ val max_size_request : t -> uint
24+ val max_concurrent_requests : t -> uint
25+ val max_calls_in_request : t -> uint
26+ val max_objects_in_get : t -> uint
27+ val max_objects_in_set : t -> uint
28+ val collation_algorithms : t -> string list
29+30+ val v :
31+ max_size_upload:uint ->
32+ max_concurrent_upload:uint ->
33+ max_size_request:uint ->
34+ max_concurrent_requests:uint ->
35+ max_calls_in_request:uint ->
36+ max_objects_in_get:uint ->
37+ max_objects_in_set:uint ->
38+ collation_algorithms:string list ->
39+ unit ->
40+ t
41+end
42+43+(** An Account object.
44+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2> RFC 8620, Section 2 *)
45+module Account : sig
46+ type t
47+48+ val name : t -> string
49+ val is_personal : t -> bool
50+ val is_read_only : t -> bool
51+ val account_capabilities : t -> account_capability_value string_map
52+53+ val v :
54+ name:string ->
55+ ?is_personal:bool ->
56+ ?is_read_only:bool ->
57+ ?account_capabilities:account_capability_value string_map ->
58+ unit ->
59+ t
60+end
61+62+(** The Session object.
63+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2> RFC 8620, Section 2 *)
64+module Session : sig
65+ type t
66+67+ val capabilities : t -> server_capability_value string_map
68+ val accounts : t -> Account.t id_map
69+ val primary_accounts : t -> id string_map
70+ val username : t -> string
71+ val api_url : t -> Uri.t
72+ val download_url : t -> Uri.t
73+ val upload_url : t -> Uri.t
74+ val event_source_url : t -> Uri.t
75+ val state : t -> string
76+77+ val v :
78+ capabilities:server_capability_value string_map ->
79+ accounts:Account.t id_map ->
80+ primary_accounts:id string_map ->
81+ username:string ->
82+ api_url:Uri.t ->
83+ download_url:Uri.t ->
84+ upload_url:Uri.t ->
85+ event_source_url:Uri.t ->
86+ state:string ->
87+ unit ->
88+ t
89+end
90+91+(** Function to perform service autodiscovery.
92+ Returns the session URL if found.
93+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-2.2> RFC 8620, Section 2.2 *)
94+val discover : domain:string -> Uri.t option
95+96+(** Function to fetch the session object from a given URL.
97+ Requires authentication handling (details TBD/outside this signature). *)
98+val get_session : url:Uri.t -> Session.t
+32
jmap/jmap_types.ml
···00000000000000000000000000000000
···1+(* Basic JMAP types as defined in RFC 8620. *)
2+3+(* The Id data type.
4+ A string of 1 to 255 octets, using URL-safe base64 characters. *)
5+type id = string
6+7+(* The Int data type.
8+ An integer in the range [-2^53+1, 2^53-1]. Represented as OCaml's standard [int]. *)
9+type jint = int
10+11+(* The UnsignedInt data type.
12+ An integer in the range [0, 2^53-1]. Represented as OCaml's standard [int]. *)
13+type uint = int
14+15+(* The Date data type.
16+ A string in RFC 3339 "date-time" format.
17+ Represented as a float using Unix time. *)
18+type date = float
19+20+(* The UTCDate data type.
21+ A string in RFC 3339 "date-time" format, restricted to UTC (Z timezone).
22+ Represented as a float using Unix time. *)
23+type utc_date = float
24+25+(* Represents a JSON object used as a map String -> V. *)
26+type 'v string_map = (string, 'v) Hashtbl.t
27+28+(* Represents a JSON object used as a map Id -> V. *)
29+type 'v id_map = (id, 'v) Hashtbl.t
30+31+(* Represents a JSON Pointer path with JMAP extensions. *)
32+type json_pointer = string
+38
jmap/jmap_types.mli
···00000000000000000000000000000000000000
···1+(** Basic JMAP types as defined in RFC 8620. *)
2+3+(** The Id data type.
4+ A string of 1 to 255 octets, using URL-safe base64 characters.
5+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.2> RFC 8620, Section 1.2 *)
6+type id = string
7+8+(** The Int data type.
9+ An integer in the range [-2^53+1, 2^53-1]. Represented as OCaml's standard [int].
10+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.3> RFC 8620, Section 1.3 *)
11+type jint = int
12+13+(** The UnsignedInt data type.
14+ An integer in the range [0, 2^53-1]. Represented as OCaml's standard [int].
15+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.3> RFC 8620, Section 1.3 *)
16+type uint = int
17+18+(** The Date data type.
19+ A string in RFC 3339 "date-time" format.
20+ Represented as a float using Unix time.
21+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.4> RFC 8620, Section 1.4 *)
22+type date = float
23+24+(** The UTCDate data type.
25+ A string in RFC 3339 "date-time" format, restricted to UTC (Z timezone).
26+ Represented as a float using Unix time.
27+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-1.4> RFC 8620, Section 1.4 *)
28+type utc_date = float
29+30+(** Represents a JSON object used as a map String -> V. *)
31+type 'v string_map = (string, 'v) Hashtbl.t
32+33+(** Represents a JSON object used as a map Id -> V. *)
34+type 'v id_map = (id, 'v) Hashtbl.t
35+36+(** Represents a JSON Pointer path with JMAP extensions.
37+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.7> RFC 8620, Section 3.7 *)
38+type json_pointer = string
···1+(* JMAP Wire Protocol Structures (Request/Response). *)
2+3+open Jmap_types
4+5+(* An invocation tuple within a request or response. *)
6+module Invocation = struct
7+ type t = {
8+ method_name: string;
9+ arguments: Yojson.Safe.t;
10+ method_call_id: string;
11+ }
12+13+ let method_name t = t.method_name
14+ let arguments t = t.arguments
15+ let method_call_id t = t.method_call_id
16+17+ let v ?(arguments=`Assoc []) ~method_name ~method_call_id () =
18+ { method_name; arguments; method_call_id }
19+end
20+21+(* Method error type with context. *)
22+type method_error = Jmap_error.Method_error.t * string
23+24+(* A response invocation part, which can be a standard response or an error. *)
25+type response_invocation = (Invocation.t, method_error) result
26+27+(* A reference to a previous method call's result. *)
28+module Result_reference = struct
29+ type t = {
30+ result_of: string;
31+ name: string;
32+ path: json_pointer;
33+ }
34+35+ let result_of t = t.result_of
36+ let name t = t.name
37+ let path t = t.path
38+39+ let v ~result_of ~name ~path () =
40+ { result_of; name; path }
41+end
42+43+(* The Request object. *)
44+module Request = struct
45+ type t = {
46+ using: string list;
47+ method_calls: Invocation.t list;
48+ created_ids: id id_map option;
49+ }
50+51+ let using t = t.using
52+ let method_calls t = t.method_calls
53+ let created_ids t = t.created_ids
54+55+ let v ~using ~method_calls ?created_ids () =
56+ { using; method_calls; created_ids }
57+end
58+59+(* The Response object. *)
60+module Response = struct
61+ type t = {
62+ method_responses: response_invocation list;
63+ created_ids: id id_map option;
64+ session_state: string;
65+ }
66+67+ let method_responses t = t.method_responses
68+ let created_ids t = t.created_ids
69+ let session_state t = t.session_state
70+71+ let v ~method_responses ?created_ids ~session_state () =
72+ { method_responses; created_ids; session_state }
73+end
···1+(** JMAP Wire Protocol Structures (Request/Response). *)
2+3+open Jmap_types
4+5+(** An invocation tuple within a request or response.
6+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.2> RFC 8620, Section 3.2 *)
7+module Invocation : sig
8+ type t
9+10+ val method_name : t -> string
11+ val arguments : t -> Yojson.Safe.t
12+ val method_call_id : t -> string
13+14+ val v :
15+ ?arguments:Yojson.Safe.t ->
16+ method_name:string ->
17+ method_call_id:string ->
18+ unit ->
19+ t
20+end
21+22+(** Method error type with context.
23+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.6.2> RFC 8620, Section 3.6.2 *)
24+type method_error = Jmap_error.Method_error.t * string
25+26+(** A response invocation part, which can be a standard response or an error.
27+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.4> RFC 8620, Section 3.4
28+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.6.2> RFC 8620, Section 3.6.2 *)
29+type response_invocation = (Invocation.t, method_error) result
30+31+(** A reference to a previous method call's result.
32+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.7> RFC 8620, Section 3.7 *)
33+module Result_reference : sig
34+ type t
35+36+ val result_of : t -> string
37+ val name : t -> string
38+ val path : t -> json_pointer
39+40+ val v :
41+ result_of:string ->
42+ name:string ->
43+ path:json_pointer ->
44+ unit ->
45+ t
46+end
47+48+(** The Request object.
49+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.3> RFC 8620, Section 3.3 *)
50+module Request : sig
51+ type t
52+53+ val using : t -> string list
54+ val method_calls : t -> Invocation.t list
55+ val created_ids : t -> id id_map option
56+57+ val v :
58+ using:string list ->
59+ method_calls:Invocation.t list ->
60+ ?created_ids:id id_map ->
61+ unit ->
62+ t
63+end
64+65+(** The Response object.
66+ @see <https://www.rfc-editor.org/rfc/rfc8620.html#section-3.4> RFC 8620, Section 3.4 *)
67+module Response : sig
68+ type t
69+70+ val method_responses : t -> response_invocation list
71+ val created_ids : t -> id id_map option
72+ val session_state : t -> string
73+74+ val v :
75+ method_responses:response_invocation list ->
76+ ?created_ids:id id_map ->
77+ session_state:string ->
78+ unit ->
79+ t
80+end