@wirecat/mail-cli 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +4 -3
  3. package/dist/bin/mail.js +2 -2
  4. package/dist/bundle/{all-profiles-TKMBELRZ.js → all-profiles-52LCOKSL.js} +5 -5
  5. package/dist/bundle/{anthropic-7LMUAEIZ.js → anthropic-HPW7PLTT.js} +3 -3
  6. package/dist/bundle/{app-server-JGMIJDD4.js → app-server-Y7XSPVHJ.js} +39 -39
  7. package/dist/bundle/{bun-XRO6XJHQ.js → bun-B7YXDIMT.js} +2 -2
  8. package/dist/bundle/{bun-sqlite-Z54LEHVM.js → bun-sqlite-TTTKY7H2.js} +2 -2
  9. package/dist/bundle/{chunk-52F5DWS5.js → chunk-37MSU5O6.js} +1 -1
  10. package/dist/bundle/{chunk-5NYMNSSO.js → chunk-3RDTVH3H.js} +3 -3
  11. package/dist/bundle/{chunk-4E7BN3TC.js → chunk-4PXWXI2U.js} +2 -2
  12. package/dist/bundle/{chunk-GN62YOT5.js → chunk-4UOEUEZZ.js} +4 -4
  13. package/dist/bundle/{chunk-TQFCPUIM.js → chunk-5LY3OS7U.js} +1 -1
  14. package/dist/bundle/{chunk-QLEUNU6G.js → chunk-5SSFYIFF.js} +4 -4
  15. package/dist/bundle/{chunk-IK5TH3O7.js → chunk-732GKOLO.js} +10 -10
  16. package/dist/bundle/{chunk-UH4DDRHA.js → chunk-C2THROZU.js} +42 -42
  17. package/dist/bundle/{chunk-OTUOZ33L.js → chunk-C6E3VFUX.js} +2 -2
  18. package/dist/bundle/{chunk-YPQQ5J4X.js → chunk-DKKCQMDM.js} +16 -16
  19. package/dist/bundle/{chunk-6MAZWSC3.js → chunk-FGNTGLWA.js} +3 -3
  20. package/dist/bundle/{chunk-GQQJOH7A.js → chunk-FUXSKWWJ.js} +58 -46
  21. package/dist/bundle/{chunk-3KEVLSNN.js → chunk-FX77WAQL.js} +46 -8
  22. package/dist/bundle/{chunk-VEIZZRQ5.js → chunk-GX2M3AOO.js} +12 -3
  23. package/dist/bundle/{chunk-CEW6KIFA.js → chunk-H2JVXO5V.js} +32 -22
  24. package/dist/bundle/{chunk-ZANYM66S.js → chunk-HS25KDT3.js} +1 -1
  25. package/dist/bundle/{chunk-KM7Q67Z3.js → chunk-I2ARI6FI.js} +1 -1
  26. package/dist/bundle/{chunk-TWPDVUBR.js → chunk-IXHLMSHV.js} +2 -2
  27. package/dist/bundle/{chunk-BTQX2ZSZ.js → chunk-JDOOPGKW.js} +2 -2
  28. package/dist/bundle/{chunk-GK4M2SUP.js → chunk-JHNLU42I.js} +1 -1
  29. package/dist/bundle/{chunk-KHTO2Q6E.js → chunk-JXYFIUVX.js} +4 -4
  30. package/dist/bundle/{chunk-F6D6PJ34.js → chunk-K6SIGXRS.js} +5 -5
  31. package/dist/bundle/{chunk-QQB7WT7R.js → chunk-KALDYMKJ.js} +3 -3
  32. package/dist/bundle/{chunk-NK7EB63L.js → chunk-KXVZ373I.js} +960 -496
  33. package/dist/bundle/{chunk-GAH7N7HO.js → chunk-LCA5KC34.js} +44 -44
  34. package/dist/bundle/{chunk-TVREDYX4.js → chunk-LJDOPCPW.js} +100 -91
  35. package/dist/bundle/{chunk-3KOA73LS.js → chunk-MHNEMDH3.js} +14 -14
  36. package/dist/bundle/{chunk-MS53RALV.js → chunk-MXUUQXMA.js} +5 -5
  37. package/dist/bundle/{chunk-YO4CUH5E.js → chunk-N5WIUNOT.js} +1 -1
  38. package/dist/bundle/{chunk-EOEDICIE.js → chunk-O2F54PUO.js} +1 -1
  39. package/dist/bundle/{chunk-2LLNPNMB.js → chunk-O3DRPTWF.js} +1 -1
  40. package/dist/bundle/{chunk-FZ5FXIG6.js → chunk-PL6UPJIL.js} +1 -1
  41. package/dist/bundle/{chunk-75CWWETH.js → chunk-PYIJCWCH.js} +11 -11
  42. package/dist/bundle/{chunk-GWDCKUBI.js → chunk-QTWIZKUI.js} +30 -29
  43. package/dist/bundle/{chunk-7SXGLT6T.js → chunk-RMYYKBFW.js} +9 -8
  44. package/dist/bundle/{chunk-JVMVLITS.js → chunk-RORC2FW7.js} +4 -2
  45. package/dist/bundle/{chunk-K4FVKPDQ.js → chunk-RVIMMCHF.js} +9 -9
  46. package/dist/bundle/{chunk-T6464KYA.js → chunk-RZCFEASJ.js} +205 -204
  47. package/dist/bundle/{chunk-Z34ICZA3.js → chunk-SRMJ6LLC.js} +5 -5
  48. package/dist/bundle/{chunk-TWTATDM7.js → chunk-TL7PE6JU.js} +6 -6
  49. package/dist/bundle/{chunk-4FEMINO2.js → chunk-TLZWUADZ.js} +5 -5
  50. package/dist/bundle/{chunk-3E3WA2L6.js → chunk-TNQOKGJS.js} +21 -21
  51. package/dist/bundle/{chunk-MNOIRJEO.js → chunk-V7NZTJKU.js} +7 -7
  52. package/dist/bundle/{chunk-TE5U5OAM.js → chunk-VTJDEWMB.js} +2 -2
  53. package/dist/bundle/{chunk-J6MSJLIW.js → chunk-X7TFD5SX.js} +1 -1
  54. package/dist/bundle/{chunk-FDCGTODC.js → chunk-XD3TL4BK.js} +1 -1
  55. package/dist/bundle/{chunk-WLFLBWNH.js → chunk-XQVGIW4R.js} +2 -2
  56. package/dist/bundle/{chunk-JBBZCTTF.js → chunk-Y2HGQK7A.js} +1 -1
  57. package/dist/bundle/{chunk-WXAXYJJ7.js → chunk-YVIX7FHL.js} +5 -5
  58. package/dist/bundle/{chunk-CH6RXYAX.js → chunk-ZIJJNH47.js} +1 -1
  59. package/dist/bundle/{command-contract-WU434ZUL.js → command-contract-3HA7CZEQ.js} +13 -13
  60. package/dist/bundle/{decode-YB3ZDTES.js → decode-VLI3MAXE.js} +2 -2
  61. package/dist/bundle/{dist-A6GKSKFC.js → dist-C7LAAMNF.js} +14 -14
  62. package/dist/bundle/{echarts-YNGCOYHW.js → echarts-POTU3SHN.js} +2 -2
  63. package/dist/bundle/{embed-LWNHIRBQ.js → embed-J2DEEEUF.js} +3 -3
  64. package/dist/bundle/{embedder-2LYD3U37.js → embedder-5N56V7YJ.js} +6 -6
  65. package/dist/bundle/{first-run-N3HJKGU7.js → first-run-XILZE4PI.js} +5 -5
  66. package/dist/bundle/{first-screen-4U2DMV3G.js → first-screen-43VLEDWZ.js} +14 -14
  67. package/dist/bundle/{instructions-GJHHFV64.js → instructions-B7AUZXPL.js} +1 -1
  68. package/dist/bundle/mail.js +2 -2
  69. package/dist/bundle/{node-GFOH6FKP.js → node-SZAO6HTA.js} +2 -2
  70. package/dist/bundle/{node-sqlite-NBXQXBIU.js → node-sqlite-O34FFLRV.js} +2 -2
  71. package/dist/bundle/{oauth-56EFO5AQ.js → oauth-YYTLIPJT.js} +4 -4
  72. package/dist/bundle/{open-2V7QZQJP.js → open-SWJGI6H4.js} +1 -1
  73. package/dist/bundle/{openai-V3SVACVL.js → openai-GYXWCQOV.js} +10 -10
  74. package/dist/bundle/{process-XVDX4SPD.js → process-GLVWCUMK.js} +1 -1
  75. package/dist/bundle/{program-JTJB26P3.js → program-HW73TGIF.js} +1725 -1467
  76. package/dist/bundle/{prompt-YKWAI222.js → prompt-OZLHHGCD.js} +2 -2
  77. package/dist/bundle/{recognize-PAR66KVM.js → recognize-RDQSGHC5.js} +1 -1
  78. package/dist/bundle/{remote-CE43TKXC.js → remote-BKU5P3GK.js} +4 -4
  79. package/dist/bundle/{rerank-M7S7P2TW.js → rerank-EQIXYHKB.js} +4 -4
  80. package/dist/bundle/{serve-ENPMIV6J.js → serve-S6K5HKWG.js} +6 -6
  81. package/dist/bundle/{server-MBEZLVKZ.js → server-4VRVHPQC.js} +31 -31
  82. package/dist/bundle/server-WK6DRJX2.js +63 -0
  83. package/dist/bundle/{sqlite-runtime-HVGDHBJJ.js → sqlite-runtime-OOE455YM.js} +2 -2
  84. package/dist/bundle/{state-QZSOSKBD.js → state-OLPOJ2TY.js} +3 -3
  85. package/dist/bundle/{store-2BUX6LVX.js → store-6AVCPLH4.js} +9 -9
  86. package/dist/bundle/{store-UJ7SNKXI.js → store-WFV26CWY.js} +30 -12
  87. package/dist/bundle/store-maintenance-command-S6235I3W.js +52 -0
  88. package/dist/commands/account.d.ts.map +1 -1
  89. package/dist/commands/account.js +6 -5
  90. package/dist/commands/account.js.map +1 -1
  91. package/dist/commands/context.d.ts +19 -8
  92. package/dist/commands/context.d.ts.map +1 -1
  93. package/dist/commands/context.js +14 -10
  94. package/dist/commands/context.js.map +1 -1
  95. package/dist/commands/folders.js +4 -4
  96. package/dist/commands/folders.js.map +1 -1
  97. package/dist/commands/login.d.ts +2 -2
  98. package/dist/commands/login.d.ts.map +1 -1
  99. package/dist/commands/login.js +3 -5
  100. package/dist/commands/login.js.map +1 -1
  101. package/dist/commands/mailbox.d.ts +2 -1
  102. package/dist/commands/mailbox.d.ts.map +1 -1
  103. package/dist/commands/mailbox.js.map +1 -1
  104. package/dist/commands/session-start.d.ts +3 -0
  105. package/dist/commands/session-start.d.ts.map +1 -0
  106. package/dist/commands/session-start.js +117 -0
  107. package/dist/commands/session-start.js.map +1 -0
  108. package/dist/commands/session.d.ts.map +1 -1
  109. package/dist/commands/session.js +9 -117
  110. package/dist/commands/session.js.map +1 -1
  111. package/dist/commands/status.d.ts.map +1 -1
  112. package/dist/commands/status.js +8 -20
  113. package/dist/commands/status.js.map +1 -1
  114. package/dist/commands/sync.d.ts.map +1 -1
  115. package/dist/commands/sync.js +49 -26
  116. package/dist/commands/sync.js.map +1 -1
  117. package/dist/config.d.ts +2 -0
  118. package/dist/config.d.ts.map +1 -1
  119. package/dist/config.js +4 -0
  120. package/dist/config.js.map +1 -1
  121. package/dist/discovery.d.ts +4 -15
  122. package/dist/discovery.d.ts.map +1 -1
  123. package/dist/discovery.js +19 -92
  124. package/dist/discovery.js.map +1 -1
  125. package/dist/mail/backend.d.ts +36 -0
  126. package/dist/mail/backend.d.ts.map +1 -0
  127. package/dist/mail/backend.js +2 -0
  128. package/dist/mail/backend.js.map +1 -0
  129. package/dist/mail/imap/account.d.ts +5 -0
  130. package/dist/mail/imap/account.d.ts.map +1 -0
  131. package/dist/mail/imap/account.js +41 -0
  132. package/dist/mail/imap/account.js.map +1 -0
  133. package/dist/mail/imap/connection.d.ts +62 -0
  134. package/dist/mail/imap/connection.d.ts.map +1 -0
  135. package/dist/mail/imap/connection.js +188 -0
  136. package/dist/mail/imap/connection.js.map +1 -0
  137. package/dist/mail/{imap-errors.d.ts → imap/errors.d.ts} +1 -1
  138. package/dist/mail/imap/errors.d.ts.map +1 -0
  139. package/dist/mail/{imap-errors.js → imap/errors.js} +1 -1
  140. package/dist/mail/imap/errors.js.map +1 -0
  141. package/dist/mail/imap/index.d.ts +7 -0
  142. package/dist/mail/imap/index.d.ts.map +1 -0
  143. package/dist/mail/imap/index.js +11 -0
  144. package/dist/mail/imap/index.js.map +1 -0
  145. package/dist/mail/{imap-messages.d.ts → imap/messages.d.ts} +9 -9
  146. package/dist/mail/imap/messages.d.ts.map +1 -0
  147. package/dist/mail/imap/messages.js +66 -0
  148. package/dist/mail/imap/messages.js.map +1 -0
  149. package/dist/mail/imap/read.d.ts +7 -0
  150. package/dist/mail/imap/read.d.ts.map +1 -0
  151. package/dist/mail/imap/read.js +164 -0
  152. package/dist/mail/imap/read.js.map +1 -0
  153. package/dist/mail/rethread.d.ts +15 -0
  154. package/dist/mail/rethread.d.ts.map +1 -0
  155. package/dist/mail/rethread.js +52 -0
  156. package/dist/mail/rethread.js.map +1 -0
  157. package/dist/profile.d.ts +2 -1
  158. package/dist/profile.d.ts.map +1 -1
  159. package/dist/profile.js +1 -0
  160. package/dist/profile.js.map +1 -1
  161. package/dist/program.d.ts.map +1 -1
  162. package/dist/program.js +8 -1
  163. package/dist/program.js.map +1 -1
  164. package/dist/providers.d.ts +126 -26
  165. package/dist/providers.d.ts.map +1 -1
  166. package/dist/providers.js +141 -72
  167. package/dist/providers.js.map +1 -1
  168. package/dist/secret.d.ts +2 -1
  169. package/dist/secret.d.ts.map +1 -1
  170. package/dist/secret.js +1 -0
  171. package/dist/secret.js.map +1 -1
  172. package/dist/version.d.ts +1 -1
  173. package/dist/version.js +1 -1
  174. package/package.json +3 -2
  175. package/skills/mail-cli/SKILL.md +8 -0
  176. package/dist/bundle/server-JRTHUCVY.js +0 -63
  177. package/dist/bundle/store-maintenance-command-I7V5C44K.js +0 -52
  178. package/dist/mail/imap-errors.d.ts.map +0 -1
  179. package/dist/mail/imap-errors.js.map +0 -1
  180. package/dist/mail/imap-messages.d.ts.map +0 -1
  181. package/dist/mail/imap-messages.js +0 -66
  182. package/dist/mail/imap-messages.js.map +0 -1
  183. package/dist/mail/imap.d.ts +0 -67
  184. package/dist/mail/imap.d.ts.map +0 -1
  185. package/dist/mail/imap.js +0 -348
  186. package/dist/mail/imap.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account.js","sourceRoot":"","sources":["../../../src/mail/imap/account.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAEtC,2FAA2F;AAC3F,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,IAAiB,EACjB,KAAa,EACuE,EAAE;IACtF,MAAM,UAAU,GAAG,GAA0B,EAAE,CAC7C,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAC3B,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;SACjE,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;SACtF,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACf,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC;QACnB,QAAQ,EAAE,KAAK,CAAC,MAAM,EAAE,QAAQ,IAAI,IAAI;QACxC,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,IAAI,IAAI;KACrC,CAAC,CAAC,CACN,CAAA;IAEH,OAAO;QACL,UAAU;QAEV,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,MAAM,OAAO,GAAG,MAAM,UAAU,EAAE,CAAA;YAClC,MAAM,IAAI,GAAG,CAAC,GAAG,KAAe,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAA;YAC5G,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,OAAO,EAAE,CAAA;QACpF,CAAC;QAED,KAAK,EAAE,GAAG,EAAE,CACV,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAyB,EAAE;YAClD,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;YACzD,MAAM,OAAO,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAA;YAC1C,OAAO;gBACL,KAAK;gBACL,YAAY,EAAE,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;gBAClD,QAAQ,EAAE;oBACR,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC;oBACjB,SAAS,EAAE,GAAG,CAAC,WAAW,CAAC;oBAC3B,OAAO,EAAE,GAAG,CAAC,SAAS,CAAC;oBACvB,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC;oBACjB,OAAO,EAAE,GAAG,CAAC,SAAS,CAAC;oBACvB,QAAQ,EAAE,GAAG,CAAC,kBAAkB,CAAC;oBACjC,UAAU,EAAE,GAAG,CAAC,YAAY,CAAC;oBAC7B,QAAQ,EAAE,GAAG,CAAC,UAAU,CAAC;iBAC1B;gBACD,OAAO;aACR,CAAA;QACH,CAAC,CAAC;KACL,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,62 @@
1
+ import { ImapFlow } from "imapflow";
2
+ import type { MailEndpoint, RunBounds } from "../backend.js";
3
+ /**
4
+ * How the source spends a connection. The defaults come from measuring Yandex 360 (cli-testing,
5
+ * performance/mail/yandex-imap-sync): a header FETCH takes 0.1 to 0.7 s per message and the time per message grows with
6
+ * the size of the command, four connections read 2 to 3 times faster than one, and the server sometimes answers one
7
+ * message with `NO [UNAVAILABLE]` and then closes the connection. So a call reads 15 messages on each of 4 connections:
8
+ * a request of the budget (listBatch, fetchBatch) is 60 messages and takes about 9 s.
9
+ */
10
+ export interface ImapTuning extends RunBounds {
11
+ /** Messages in one FETCH command. */
12
+ listSize?: number;
13
+ /** UIDs in one FETCH that asks only for flags, date and size. */
14
+ quickSize?: number;
15
+ fetchSize?: number;
16
+ /** Bytes of message bodies one FETCH asks for, by RFC822.SIZE; a single larger message still goes alone. */
17
+ fetchBytes?: number;
18
+ /** Parallel connections one `list` or `fetchRaw` call spreads its commands over. */
19
+ connections?: number;
20
+ /** One command's own limit; the socket limit only catches a dead line. */
21
+ requestTimeoutMs?: number;
22
+ /** Full-jitter backoff before each retry of a command. */
23
+ backoff?: {
24
+ baseDelayMs: number;
25
+ maxDelayMs: number;
26
+ };
27
+ /** A hard ceiling per command; the budget and the deadline usually stop the retries first. */
28
+ maxAttempts?: number;
29
+ sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
30
+ random?: () => number;
31
+ /** Calls `fire` after `ms` unless the returned cancel runs first. */
32
+ expireAfter?: (ms: number, fire: () => void) => () => void;
33
+ }
34
+ export declare const IMAP_DEFAULTS: {
35
+ readonly listSize: 15;
36
+ readonly quickSize: 500;
37
+ readonly fetchSize: 15;
38
+ readonly fetchBytes: number;
39
+ readonly connections: 4;
40
+ readonly requestTimeoutMs: 90000;
41
+ readonly backoff: {
42
+ readonly baseDelayMs: 1000;
43
+ readonly maxDelayMs: 10000;
44
+ };
45
+ readonly maxAttempts: 8;
46
+ };
47
+ type Work<T> = (open: ImapFlow, validity: string) => Promise<T>;
48
+ export interface Connections {
49
+ connections: number;
50
+ /** Each folder's UIDVALIDITY as the run first saw it; a reconnect that finds another one stops the run. */
51
+ validities: Map<string, string>;
52
+ /** `work` on a connection with no folder selected, retried like every read. */
53
+ onServer<T>(work: (open: ImapFlow) => Promise<T>): Promise<T>;
54
+ inFolder<T>(folder: string, work: Work<T>, expected?: string, slot?: number): Promise<T>;
55
+ /** Runs `work` over the batches on up to `connections` connections; the first failure stops the others. */
56
+ spread<I, O, E = never>(folder: string, batches: I[], work: (open: ImapFlow, batch: I) => Promise<O>, expected: string | undefined, onError?: (error: unknown, batch: I) => E): Promise<(O | E)[]>;
57
+ close(): Promise<void>;
58
+ }
59
+ /** The connections of one source, opened on first use; the password never leaves this closure. */
60
+ export declare const imapConnections: (endpoint: MailEndpoint, password: string, tuning?: ImapTuning) => Connections;
61
+ export {};
62
+ //# sourceMappingURL=connection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../../src/mail/imap/connection.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AACnC,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAG5D;;;;;;GAMG;AACH,MAAM,WAAW,UAAW,SAAQ,SAAS;IAC3C,qCAAqC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4GAA4G;IAC5G,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,oFAAoF;IACpF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,0DAA0D;IAC1D,OAAO,CAAC,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAA;IACrD,8FAA8F;IAC9F,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3D,MAAM,CAAC,EAAE,MAAM,MAAM,CAAA;IACrB,qEAAqE;IACrE,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAA;CAC3D;AAED,eAAO,MAAM,aAAa;aACxB,QAAQ,EAAE,EAAE;aACZ,SAAS,EAAE,GAAG;aACd,SAAS,EAAE,EAAE;aACb,UAAU;aACV,WAAW,EAAE,CAAC;aACd,gBAAgB,EAAE,KAAM;aACxB,OAAO;iBAAI,WAAW,EAAE,IAAK;iBAAE,UAAU,EAAE,KAAM;;aACjD,WAAW,EAAE,CAAC;CACN,CAAA;AAOV,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;AAE/D,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAA;IACnB,2GAA2G;IAC3G,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,+EAA+E;IAC/E,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IAC7D,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IACxF,2GAA2G;IAC3G,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,KAAK,EACpB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,CAAC,EAAE,EACZ,IAAI,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EAC9C,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,GACxC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAA;IACrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAED,kGAAkG;AAClG,eAAO,MAAM,eAAe,aAAc,YAAY,YAAY,MAAM,WAAU,UAAU,KAAQ,WA4KnG,CAAA"}
@@ -0,0 +1,188 @@
1
+ import { backoffMs, CliError, realSleep } from "@wirecat/cli-base";
2
+ import { ImapFlow } from "imapflow";
3
+ import { mapImapError } from "./errors.js";
4
+ export const IMAP_DEFAULTS = {
5
+ listSize: 15,
6
+ quickSize: 500,
7
+ fetchSize: 15,
8
+ fetchBytes: 20 * 1024 * 1024,
9
+ connections: 4,
10
+ requestTimeoutMs: 90_000,
11
+ backoff: { baseDelayMs: 1_000, maxDelayMs: 10_000 },
12
+ maxAttempts: 8,
13
+ };
14
+ const realTimer = (ms, fire) => {
15
+ const timer = setTimeout(fire, ms);
16
+ return () => clearTimeout(timer);
17
+ };
18
+ /** The connections of one source, opened on first use; the password never leaves this closure. */
19
+ export const imapConnections = (endpoint, password, tuning = {}) => {
20
+ const { connections, requestTimeoutMs, backoff, maxAttempts } = { ...IMAP_DEFAULTS, ...tuning };
21
+ const { canRetry, timeLeftMs, signal, sleep = realSleep, random = Math.random, expireAfter = realTimer } = tuning;
22
+ const clients = [];
23
+ const validities = new Map();
24
+ let closed = false;
25
+ const connect = async (slot) => {
26
+ if (closed)
27
+ throw new CliError("cancelled", "the mail connection is already closed");
28
+ const existing = clients[slot];
29
+ if (existing?.usable)
30
+ return existing;
31
+ const next = new ImapFlow({
32
+ host: endpoint.host,
33
+ port: endpoint.port,
34
+ secure: endpoint.security === "tls",
35
+ doSTARTTLS: endpoint.security === "starttls",
36
+ auth: { user: endpoint.login, pass: password },
37
+ logger: false,
38
+ disableAutoIdle: true,
39
+ connectionTimeout: 15000,
40
+ greetingTimeout: 15000,
41
+ socketTimeout: 120000,
42
+ });
43
+ // An 'error' event with no listener would end the process, and the failing call already rejects.
44
+ next.on("error", () => { });
45
+ await next.connect();
46
+ // A close that ran while this one was logging in found nothing to close.
47
+ if (closed) {
48
+ next.close();
49
+ throw new CliError("cancelled", "the mail connection is already closed");
50
+ }
51
+ clients[slot] = next;
52
+ return next;
53
+ };
54
+ const drop = (slot) => {
55
+ const open = clients[slot];
56
+ clients[slot] = undefined;
57
+ try {
58
+ open?.close();
59
+ }
60
+ catch { }
61
+ };
62
+ /** One command's own deadline, cut to the run's: a stuck command closes its connection and fails as a timeout. */
63
+ const limited = async (slot, work) => {
64
+ const left = timeLeftMs?.();
65
+ const ms = left === undefined ? requestTimeoutMs : Math.max(1, Math.min(requestTimeoutMs, left));
66
+ let cancel = () => { };
67
+ const expired = new Promise((_, reject) => {
68
+ cancel = expireAfter(ms, () => {
69
+ drop(slot);
70
+ reject(Object.assign(new Error("request timed out"), { code: "RequestTimeout" }));
71
+ });
72
+ });
73
+ work.catch(() => { });
74
+ try {
75
+ return await Promise.race([work, expired]);
76
+ }
77
+ finally {
78
+ cancel();
79
+ }
80
+ };
81
+ const once = async (slot, folder, expected, work) => {
82
+ const open = await limited(slot, connect(slot));
83
+ if (folder === undefined)
84
+ return limited(slot, work(open, ""));
85
+ const lock = await limited(slot, open.getMailboxLock(folder, { readOnly: true }));
86
+ try {
87
+ // imapflow clears the selection when a SELECT fails or the server closes the mailbox, and a connection
88
+ // in that state still counts as usable: a command sent on it fails with NoMailboxSelected.
89
+ if (!open.mailbox)
90
+ throw Object.assign(new Error("no mailbox is selected"), { code: "NoMailboxSelected" });
91
+ const validity = String(open.mailbox.uidValidity);
92
+ const was = expected ?? validities.get(folder);
93
+ if (was !== undefined && validity !== was)
94
+ throw new CliError("invalid_response", "UIDVALIDITY changed during the run; run it again", {
95
+ reason: "mail.uidvalidity-changed",
96
+ });
97
+ return await limited(slot, work(open, validity));
98
+ }
99
+ finally {
100
+ try {
101
+ lock.release();
102
+ }
103
+ catch { }
104
+ }
105
+ };
106
+ /**
107
+ * Every call here only reads, so a failed command is safe to send again. A dropped line or a command that timed
108
+ * out closes its connection, waits and tries on a fresh one, for as long as the run's budget and deadline allow.
109
+ */
110
+ const retrying = async (slot, command) => {
111
+ for (let attempt = 1;; attempt++) {
112
+ try {
113
+ return await command();
114
+ }
115
+ catch (raw) {
116
+ const error = mapImapError(raw);
117
+ const transient = error.code === "network_error" || error.code === "timeout" || error.code === "provider_unavailable";
118
+ if (transient)
119
+ drop(slot);
120
+ if (!transient || closed || attempt >= maxAttempts || signal?.aborted)
121
+ throw error;
122
+ const wait = backoffMs(attempt, { ...backoff, retries: maxAttempts, maxRetryAfterMs: 0 }, random);
123
+ const left = timeLeftMs?.();
124
+ if (left !== undefined && left <= wait)
125
+ throw error;
126
+ if (canRetry && !canRetry())
127
+ throw error;
128
+ await sleep(wait, signal).catch(() => Promise.reject(signal?.reason ?? error));
129
+ }
130
+ }
131
+ };
132
+ const inFolder = (folder, work, expected, slot = 0) => retrying(slot, () => once(slot, folder, expected, work));
133
+ const spread = async (folder, batches, work, expected, onError) => {
134
+ const results = new Array(batches.length);
135
+ let next = 0;
136
+ let failure;
137
+ const worker = async (slot) => {
138
+ for (;;) {
139
+ const index = next++;
140
+ if (index >= batches.length || failure !== undefined)
141
+ return;
142
+ const batch = batches[index];
143
+ try {
144
+ results[index] = await inFolder(folder, (open) => work(open, batch), expected, slot);
145
+ }
146
+ catch (error) {
147
+ if (onError === undefined) {
148
+ failure ??= error;
149
+ return;
150
+ }
151
+ try {
152
+ results[index] = onError(error, batch);
153
+ }
154
+ catch (fatal) {
155
+ failure ??= fatal;
156
+ return;
157
+ }
158
+ }
159
+ }
160
+ };
161
+ await Promise.all(Array.from({ length: Math.min(connections, batches.length) }, (_, slot) => worker(slot)));
162
+ if (failure !== undefined)
163
+ throw failure;
164
+ return results;
165
+ };
166
+ return {
167
+ connections,
168
+ validities,
169
+ onServer: (work) => retrying(0, () => once(0, undefined, undefined, (open) => work(open))),
170
+ inFolder,
171
+ spread,
172
+ close: async () => {
173
+ closed = true;
174
+ const open = clients.splice(0);
175
+ await Promise.all(open.map(async (one) => {
176
+ if (!one)
177
+ return;
178
+ try {
179
+ await one.logout();
180
+ }
181
+ catch {
182
+ one.close();
183
+ }
184
+ }));
185
+ },
186
+ };
187
+ };
188
+ //# sourceMappingURL=connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../../../src/mail/imap/connection.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
2
  /** The server's error text can quote mail or names, so only its codes reach the caller. */
3
3
  export declare const mapImapError: (error: unknown) => CliError;
4
- //# sourceMappingURL=imap-errors.d.ts.map
4
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/mail/imap/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAK5C,2FAA2F;AAC3F,eAAO,MAAM,YAAY,UAAW,OAAO,KAAG,QAyD7C,CAAA"}
@@ -38,4 +38,4 @@ export const mapImapError = (error) => {
38
38
  });
39
39
  return new CliError("provider_error", `the mail server refused the request${e.responseStatus ? ` (${e.responseStatus})` : ""}`, causeOf(e.code ?? (responseCode || e.responseStatus)));
40
40
  };
41
- //# sourceMappingURL=imap-errors.js.map
41
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/mail/imap/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { MailBackend, MailEndpoint } from "../backend.js";
2
+ import { type ImapTuning } from "./connection.js";
3
+ export { IMAP_DEFAULTS, type ImapTuning } from "./connection.js";
4
+ export { mapImapError } from "./errors.js";
5
+ /** The IMAP adapter of the mail backend: read only, its connections opened on first use and closed by `close`. */
6
+ export declare const imapBackend: (endpoint: MailEndpoint, password: string, tuning?: ImapTuning) => MailBackend;
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/mail/imap/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAE9D,OAAO,EAAE,KAAK,UAAU,EAAmB,MAAM,iBAAiB,CAAA;AAGlE,OAAO,EAAE,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE1C,kHAAkH;AAClH,eAAO,MAAM,WAAW,aAAc,YAAY,YAAY,MAAM,WAAU,UAAU,KAAQ,WAG/F,CAAA"}
@@ -0,0 +1,11 @@
1
+ import { imapAccount } from "./account.js";
2
+ import { imapConnections } from "./connection.js";
3
+ import { imapReads } from "./read.js";
4
+ export { IMAP_DEFAULTS } from "./connection.js";
5
+ export { mapImapError } from "./errors.js";
6
+ /** The IMAP adapter of the mail backend: read only, its connections opened on first use and closed by `close`. */
7
+ export const imapBackend = (endpoint, password, tuning = {}) => {
8
+ const pool = imapConnections(endpoint, password, tuning);
9
+ return { ...imapReads(pool, tuning), ...imapAccount(pool, endpoint.login), close: pool.close };
10
+ };
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/mail/imap/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAmB,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAErC,OAAO,EAAE,aAAa,EAAmB,MAAM,iBAAiB,CAAA;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE1C,kHAAkH;AAClH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,QAAsB,EAAE,QAAgB,EAAE,MAAM,GAAe,EAAE,EAAe,EAAE;IAC5G,MAAM,IAAI,GAAG,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;IACxD,OAAO,EAAE,GAAG,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,GAAG,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAA;AAChG,CAAC,CAAA"}
@@ -1,18 +1,18 @@
1
- import type { Listed, Quick } from "@wirecat/cli-core/mail";
1
+ import { type Listed, type Quick } from "@wirecat/cli-core/mail";
2
2
  import type { FetchMessageObject, ListResponse } from "imapflow";
3
- /** The header fields a listing asks for, so a mailing list is told apart from a person's mail. */
3
+ /** cli-core's fields for the owner's rules and the probe, and the two that tie a reply to its thread. */
4
4
  export declare const HEADER_FIELDS: string[];
5
5
  export declare const quickOf: (message: FetchMessageObject, folder: string) => Quick;
6
6
  /** Without a stable id the Message-ID is the identity; without that, the UID, which means nothing without UIDVALIDITY. */
7
- export declare const idsOf: (messageId: string | undefined, folder: string, validity: string, uid: string) => {
7
+ export declare const idsOf: (messageId: string | undefined, folder: string, validity: string, uid: string, root?: string) => {
8
+ messageId: string;
9
+ threadId: string;
10
+ };
11
+ /** The ids of a message read as bytes, worked out as `listedOf` works them out from a listing. */
12
+ export declare const rawIdsOf: (raw: Uint8Array, folder: string, validity: string, uid: string) => {
8
13
  messageId: string;
9
14
  threadId: string;
10
15
  };
11
16
  export declare const listedOf: (message: FetchMessageObject, folder: string, validity: string) => Listed;
12
- /**
13
- * The Message-ID as the server's ENVELOPE gives it (imapflow trims the value of the first header), so the id of a
14
- * message read as bytes is the id `list` gives it.
15
- */
16
- export declare const messageIdOf: (raw: Uint8Array) => string | undefined;
17
17
  export declare const roleOf: (entry: ListResponse) => string | null;
18
- //# sourceMappingURL=imap-messages.d.ts.map
18
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../../src/mail/imap/messages.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,MAAM,EAGX,KAAK,KAAK,EACX,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAEhE,yGAAyG;AACzG,eAAO,MAAM,aAAa,UAAkE,CAAA;AA8B5F,eAAO,MAAM,OAAO,YAAa,kBAAkB,UAAU,MAAM,KAAG,KAYrE,CAAA;AAED,0HAA0H;AAC1H,eAAO,MAAM,KAAK,cAAe,MAAM,GAAG,SAAS,UAAU,MAAM,YAAY,MAAM,OAAO,MAAM,SAAS,MAAM;IAEtG,SAAS;IAAoB,QAAQ;CAC/C,CAAA;AAED,kGAAkG;AAClG,eAAO,MAAM,QAAQ,QAAS,UAAU,UAAU,MAAM,YAAY,MAAM,OAAO,MAAM;;;CAGtF,CAAA;AAED,eAAO,MAAM,QAAQ,YAAa,kBAAkB,UAAU,MAAM,YAAY,MAAM,KAAG,MAUxF,CAAA;AAED,eAAO,MAAM,MAAM,UAAW,YAAY,KAAG,MAAM,GAAG,IAGrD,CAAA"}
@@ -0,0 +1,66 @@
1
+ import { createHash } from "node:crypto";
2
+ import { headersOf, labelName, HEADER_FIELDS as MAIL_HEADER_FIELDS, } from "@wirecat/cli-core/mail";
3
+ /** cli-core's fields for the owner's rules and the probe, and the two that tie a reply to its thread. */
4
+ export const HEADER_FIELDS = [...MAIL_HEADER_FIELDS.split(" "), "IN-REPLY-TO", "REFERENCES"];
5
+ const digest = (id) => `imap:${createHash("sha256").update(id).digest("hex")}`;
6
+ const headTextOf = (raw) => {
7
+ const bytes = Buffer.from(raw.buffer, raw.byteOffset, raw.byteLength);
8
+ const end = [bytes.indexOf("\r\n\r\n"), bytes.indexOf("\n\n")]
9
+ .filter((at) => at !== -1)
10
+ .reduce((first, at) => Math.min(first, at), bytes.length);
11
+ return bytes.subarray(0, end).toString("utf8");
12
+ };
13
+ const fieldOf = (head, name) => new RegExp(`^${name}:[ \\t]*(.*)$`, "im").exec(head.replace(/\r?\n[ \t]+/g, " "))?.[1]?.trim();
14
+ /**
15
+ * The first Message-ID of References is the thread's first email (RFC 5322 §3.6.4); a reply with only In-Reply-To
16
+ * names its parent. A server without thread ids (Yandex, Mail.ru) then still keeps a conversation together.
17
+ */
18
+ const threadRootOf = (head) => /<[^<>\s]+>/.exec(fieldOf(head, "references") ?? "")?.[0] ??
19
+ /<[^<>\s]+>/.exec(fieldOf(head, "in-reply-to") ?? "")?.[0];
20
+ /** As memo names them, so the store's `label:Inbox` mailboxes keep matching; a flagged message is Starred anywhere. */
21
+ const labelsOf = (raw, flagged) => {
22
+ const names = new Set([...(raw ?? [])].map(labelName));
23
+ if (flagged)
24
+ names.add("Starred");
25
+ return [...names].sort();
26
+ };
27
+ export const quickOf = (message, folder) => {
28
+ const flags = message.flags ?? new Set();
29
+ const received = message.internalDate instanceof Date ? message.internalDate : new Date(message.internalDate ?? 0);
30
+ return {
31
+ uid: String(message.uid),
32
+ receivedAt: received.toISOString(),
33
+ folder,
34
+ ...(message.size === undefined ? {} : { size: message.size }),
35
+ seen: flags.has("\\Seen"),
36
+ flagged: flags.has("\\Flagged"),
37
+ labels: labelsOf(message.labels, flags.has("\\Flagged")),
38
+ };
39
+ };
40
+ /** Without a stable id the Message-ID is the identity; without that, the UID, which means nothing without UIDVALIDITY. */
41
+ export const idsOf = (messageId, folder, validity, uid, root) => {
42
+ const identity = messageId ?? `uid:${folder}:${validity}:${uid}`;
43
+ return { messageId: digest(identity), threadId: digest(root ?? identity) };
44
+ };
45
+ /** The ids of a message read as bytes, worked out as `listedOf` works them out from a listing. */
46
+ export const rawIdsOf = (raw, folder, validity, uid) => {
47
+ const head = headTextOf(raw);
48
+ return idsOf(fieldOf(head, "message-id") || undefined, folder, validity, uid, threadRootOf(head));
49
+ };
50
+ export const listedOf = (message, folder, validity) => {
51
+ const head = message.headers ? message.headers.toString("utf8") : "";
52
+ const ids = idsOf(message.envelope?.messageId, folder, validity, String(message.uid), threadRootOf(head));
53
+ return {
54
+ ...quickOf(message, folder),
55
+ // Gmail's X-GM-MSGID and X-GM-THRID, or OBJECTID's: stored Gmail mail is keyed on them.
56
+ ...(message.emailId ? { messageId: String(message.emailId) } : { messageId: ids.messageId }),
57
+ threadId: message.threadId ? String(message.threadId) : ids.threadId,
58
+ headers: headersOf(head),
59
+ };
60
+ };
61
+ export const roleOf = (entry) => {
62
+ if (entry.path.toUpperCase() === "INBOX")
63
+ return "inbox";
64
+ return entry.specialUse ? entry.specialUse.replace(/^\\/, "").toLowerCase() : null;
65
+ };
66
+ //# sourceMappingURL=messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.js","sourceRoot":"","sources":["../../../src/mail/imap/messages.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { MailSource } from "@wirecat/cli-core/mail";
2
+ import { type Connections, type ImapTuning } from "./connection.js";
3
+ type Reads = Required<Pick<MailSource, "listBatch" | "fetchBatch" | "search" | "present" | "quick" | "identify">> & Pick<MailSource, "list" | "fetchRaw">;
4
+ /** The calls cli-core's sync makes to read a folder; none of them changes the mailbox. */
5
+ export declare const imapReads: (pool: Connections, tuning?: ImapTuning) => Reads;
6
+ export {};
7
+ //# sourceMappingURL=read.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../../src/mail/imap/read.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAmB,MAAM,wBAAwB,CAAA;AAEzE,OAAO,EAAE,KAAK,WAAW,EAAiB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAwClF,KAAK,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,YAAY,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC,CAAC,GAC/G,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,CAAC,CAAA;AAEvC,0FAA0F;AAC1F,eAAO,MAAM,SAAS,SAAU,WAAW,WAAU,UAAU,KAAQ,KAuJtE,CAAA"}
@@ -0,0 +1,164 @@
1
+ import { CliError } from "@wirecat/cli-base";
2
+ import { IMAP_DEFAULTS } from "./connection.js";
3
+ import { mapImapError } from "./errors.js";
4
+ import { HEADER_FIELDS, listedOf, quickOf, rawIdsOf } from "./messages.js";
5
+ const chunks = (items, size) => {
6
+ const out = [];
7
+ for (let start = 0; start < items.length; start += size)
8
+ out.push(items.slice(start, start + size));
9
+ return out;
10
+ };
11
+ /** At most `count` messages and `bytes` of RFC822.SIZE per batch; a message larger than `bytes` goes alone. */
12
+ const bodyBatches = (items, count, bytes) => {
13
+ const out = [];
14
+ let current = [];
15
+ let size = 0;
16
+ for (const item of items) {
17
+ const next = item.size ?? 0;
18
+ if (current.length > 0 && (current.length >= count || size + next > bytes)) {
19
+ out.push(current);
20
+ current = [];
21
+ size = 0;
22
+ }
23
+ current.push(item);
24
+ size += next;
25
+ }
26
+ if (current.length > 0)
27
+ out.push(current);
28
+ return out;
29
+ };
30
+ const groupByFolder = (items) => {
31
+ const byFolder = new Map();
32
+ for (const item of items) {
33
+ const folder = item.folder ?? "INBOX";
34
+ const group = byFolder.get(folder);
35
+ if (group)
36
+ group.push(item);
37
+ else
38
+ byFolder.set(folder, [item]);
39
+ }
40
+ return byFolder;
41
+ };
42
+ /** The calls cli-core's sync makes to read a folder; none of them changes the mailbox. */
43
+ export const imapReads = (pool, tuning = {}) => {
44
+ const { listSize, quickSize, fetchSize, fetchBytes } = { ...IMAP_DEFAULTS, ...tuning };
45
+ const { validities, inFolder, spread, connections } = pool;
46
+ const search = async (since, folder) => {
47
+ const found = await inFolder(folder, async (open, validity) => {
48
+ const uids = await open.search({ since }, { uid: true });
49
+ // Message ids come free with OBJECTID and Gmail's extensions; without them the envelope costs a read per message.
50
+ const quick = !open.capabilities.has("OBJECTID") && !open.capabilities.has("X-GM-EXT-1");
51
+ return { ...(validity ? { validity } : {}), uids: (uids || []).map(String), ...(quick ? { quick } : {}) };
52
+ });
53
+ if (found.validity)
54
+ validities.set(folder, found.validity);
55
+ return found;
56
+ };
57
+ const validityOf = (searched, folder) => {
58
+ if (!searched.validity)
59
+ throw new CliError("invalid_response", "IMAP did not report UIDVALIDITY");
60
+ validities.set(folder, searched.validity);
61
+ return searched.validity;
62
+ };
63
+ return {
64
+ listBatch: listSize * connections,
65
+ fetchBatch: fetchSize * connections,
66
+ search,
67
+ present: (uids, folder) => inFolder(folder, async (open) => {
68
+ if (uids.length === 0)
69
+ return [];
70
+ const held = new Set((await open.search({ uid: uids.join(",") }, { uid: true })) || []);
71
+ return uids.filter((uid) => held.has(Number(uid)));
72
+ }),
73
+ list: async (since, folder, options) => {
74
+ const searched = options?.searched ?? (await search(since, folder));
75
+ const validity = validityOf(searched, folder);
76
+ // A message the server answers with `NO [UNAVAILABLE]` is simply absent here; the sync asks for it again later.
77
+ const listing = (batches) => spread(folder, batches, (open, batch) => open.fetchAll(batch.join(","), {
78
+ uid: true,
79
+ flags: true,
80
+ internalDate: true,
81
+ size: true,
82
+ envelope: true,
83
+ threadId: true,
84
+ labels: true,
85
+ headers: HEADER_FIELDS,
86
+ }, { uid: true }), validity, (error) => {
87
+ const mapped = mapImapError(error);
88
+ if (mapped.code !== "provider_unavailable")
89
+ throw mapped;
90
+ return mapped;
91
+ });
92
+ const batches = chunks(searched.uids, listSize);
93
+ const parts = await listing(batches);
94
+ const messages = [];
95
+ for (const [index, part] of parts.entries()) {
96
+ if (!(part instanceof Error))
97
+ for (const message of part)
98
+ messages.push(message);
99
+ // Yandex refuses even the headers of a message it can never read, and with it the whole FETCH.
100
+ else if (batches[index].length > 1)
101
+ for (const single of await listing(batches[index].map((uid) => [uid])))
102
+ if (!(single instanceof Error))
103
+ for (const message of single)
104
+ messages.push(message);
105
+ }
106
+ return messages.map((message) => listedOf(message, folder, validity));
107
+ },
108
+ quick: async (_since, folder, { searched }) => {
109
+ const validity = validityOf(searched, folder);
110
+ const parts = await spread(folder, chunks(searched.uids, quickSize), (open, batch) => open.fetchAll(batch.join(","), { uid: true, flags: true, internalDate: true, size: true }, { uid: true }), validity);
111
+ return parts.flat().map((message) => quickOf(message, folder));
112
+ },
113
+ identify: (raw, item) => {
114
+ const validity = validities.get(item.folder ?? "INBOX") ?? "";
115
+ return rawIdsOf(raw, item.folder ?? "INBOX", validity, item.uid);
116
+ },
117
+ fetchRaw: async (items) => {
118
+ const read = new Map();
119
+ const bodies = (folder, batches) => spread(folder, batches, async (open, batch) => {
120
+ const found = new Map();
121
+ const messages = await open.fetchAll(batch.map(({ uid }) => uid).join(","), { uid: true, source: true }, { uid: true });
122
+ for (const message of messages)
123
+ if (message.source)
124
+ found.set(String(message.uid), message.source);
125
+ return found;
126
+ }, undefined, (error) => {
127
+ // A throttled or dropped connection fails the whole run; one unreadable batch costs only itself.
128
+ const mapped = mapImapError(error);
129
+ if (["rate_limited", "authentication_error", "network_error", "timeout"].includes(mapped.code))
130
+ throw mapped;
131
+ return mapped;
132
+ });
133
+ for (const [folder, group] of groupByFolder(items)) {
134
+ const batches = bodyBatches(group, fetchSize, fetchBytes);
135
+ const answers = await bodies(folder, batches);
136
+ for (const [index, batch] of batches.entries()) {
137
+ let answer = answers[index];
138
+ // Yandex refuses a whole FETCH when one of its messages cannot be read; one by one, only that one is lost.
139
+ if (answer instanceof CliError && answer.code === "provider_unavailable" && batch.length > 1) {
140
+ const singles = await bodies(folder, batch.map((item) => [item]));
141
+ answer = new Map();
142
+ for (const [at, item] of batch.entries()) {
143
+ const single = singles[at];
144
+ if (single instanceof Map && single.has(item.uid))
145
+ answer.set(item.uid, single.get(item.uid));
146
+ else if (single instanceof Error)
147
+ read.set(item, single);
148
+ }
149
+ }
150
+ for (const item of batch) {
151
+ if (read.has(item))
152
+ continue;
153
+ const found = answer instanceof Map ? answer.get(item.uid) : undefined;
154
+ read.set(item, found ??
155
+ (answer instanceof Error ? answer : undefined) ??
156
+ new CliError("not_found", `email ${item.uid} was not returned`, { reason: "mail.not-returned" }));
157
+ }
158
+ }
159
+ }
160
+ return items.map((item) => read.get(item));
161
+ },
162
+ };
163
+ };
164
+ //# sourceMappingURL=read.js.map