My agentic slop goes here. Not intended for anyone else!
1(** Email changes operations using core JMAP Changes_args *)
2
3open Jmap.Methods
4
5(** Build Email/changes arguments *)
6let build_changes_args ~account_id ~since_state ?max_changes () =
7 let account_id_str = Jmap.Id.to_string account_id in
8 let max_changes_int = match max_changes with
9 | Some uint -> Some (Jmap.UInt.to_int uint)
10 | None -> None in
11 Changes_args.v
12 ~account_id:account_id_str
13 ~since_state
14 ?max_changes:max_changes_int
15 ()
16
17(** Convert Email/changes arguments to JSON *)
18let changes_args_to_json args =
19 Changes_args.to_json args
20
21(** Track changes since a given state *)
22type change_tracker = {
23 account_id : Jmap.Id.t;
24 current_state : string;
25 created : Jmap.Id.t list;
26 updated : Jmap.Id.t list;
27 destroyed : Jmap.Id.t list;
28}
29
30(** Create a new change tracker *)
31let create_tracker ~account_id ~initial_state =
32 {
33 account_id;
34 current_state = initial_state;
35 created = [];
36 updated = [];
37 destroyed = [];
38 }
39
40(** Update tracker with a Changes_response *)
41let update_tracker tracker response =
42 {
43 tracker with
44 current_state = Changes_response.new_state response;
45 created = tracker.created @ (List.map (fun s -> match Jmap.Id.of_string s with Ok id -> id | Error e -> failwith e) (Changes_response.created response));
46 updated = tracker.updated @ (List.map (fun s -> match Jmap.Id.of_string s with Ok id -> id | Error e -> failwith e) (Changes_response.updated response));
47 destroyed = tracker.destroyed @ (List.map (fun s -> match Jmap.Id.of_string s with Ok id -> id | Error e -> failwith e) (Changes_response.destroyed response));
48 }
49
50(** Get all changes since tracker was created *)
51let get_all_changes tracker =
52 (tracker.created, tracker.updated, tracker.destroyed)
53
54(** Get next batch of changes *)
55let get_next_changes ~account_id ~since_state ?(max_changes=500) () =
56 let max_changes_uint = match Jmap.UInt.of_int max_changes with
57 | Ok u -> u
58 | Error _ -> failwith ("Invalid max_changes: " ^ string_of_int max_changes) in
59 build_changes_args ~account_id ~since_state ~max_changes:max_changes_uint ()
60
61(** Check if there are pending changes *)
62let has_pending_changes response =
63 Changes_response.has_more_changes response
64
65(** Incremental sync helper *)
66module Sync = struct
67 type sync_state = {
68 account_id : Jmap.Id.t;
69 last_state : string;
70 pending_created : Jmap.Id.t list;
71 pending_updated : Jmap.Id.t list;
72 pending_destroyed : Jmap.Id.t list;
73 }
74
75 let init ~account_id ~initial_state =
76 {
77 account_id;
78 last_state = initial_state;
79 pending_created = [];
80 pending_updated = [];
81 pending_destroyed = [];
82 }
83
84 let add_response sync response =
85 let new_state = Changes_response.new_state response in
86 let created = Changes_response.created response in
87 let updated = Changes_response.updated response in
88 let destroyed = Changes_response.destroyed response in
89 {
90 sync with
91 last_state = new_state;
92 pending_created = sync.pending_created @ (List.map (fun s -> match Jmap.Id.of_string s with Ok id -> id | Error e -> failwith e) created);
93 pending_updated = sync.pending_updated @ (List.map (fun s -> match Jmap.Id.of_string s with Ok id -> id | Error e -> failwith e) updated);
94 pending_destroyed = sync.pending_destroyed @ (List.map (fun s -> match Jmap.Id.of_string s with Ok id -> id | Error e -> failwith e) destroyed);
95 }
96
97 let clear_pending sync =
98 {
99 sync with
100 pending_created = [];
101 pending_updated = [];
102 pending_destroyed = [];
103 }
104
105 let get_pending sync =
106 (sync.pending_created, sync.pending_updated, sync.pending_destroyed)
107
108 let needs_sync sync response =
109 Changes_response.has_more_changes response ||
110 sync.pending_created <> [] ||
111 sync.pending_updated <> [] ||
112 sync.pending_destroyed <> []
113end
114
115(** Utility to merge multiple change responses *)
116let merge_changes responses =
117 List.fold_left (fun (created, updated, destroyed) response ->
118 let c = List.map (fun id -> match Jmap.Id.of_string id with | Ok id_t -> id_t | Error _ -> failwith ("Invalid ID: " ^ id)) (Changes_response.created response) in
119 let u = List.map (fun id -> match Jmap.Id.of_string id with | Ok id_t -> id_t | Error _ -> failwith ("Invalid ID: " ^ id)) (Changes_response.updated response) in
120 let d = List.map (fun id -> match Jmap.Id.of_string id with | Ok id_t -> id_t | Error _ -> failwith ("Invalid ID: " ^ id)) (Changes_response.destroyed response) in
121 (created @ c, updated @ u, destroyed @ d)
122 ) ([], [], []) responses
123
124(** Get updated properties if available *)
125let get_updated_properties response =
126 Changes_response.updated_properties response