@remit/mailbox-service 0.0.112 → 0.0.114

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 (270) hide show
  1. package/dist/account-credentials.d.ts +83 -0
  2. package/dist/account-credentials.js +104 -0
  3. package/dist/attribute-mapper.d.ts +36 -0
  4. package/dist/attribute-mapper.js +119 -0
  5. package/dist/body-parse.d.ts +28 -0
  6. package/{src/body-parse.ts → dist/body-parse.js} +18 -26
  7. package/dist/body-part-mapper.d.ts +92 -0
  8. package/dist/body-part-mapper.js +301 -0
  9. package/dist/body-sync-queue.d.ts +59 -0
  10. package/dist/body-sync-queue.js +56 -0
  11. package/dist/body-sync-terminal.d.ts +58 -0
  12. package/dist/body-sync-terminal.js +95 -0
  13. package/dist/body-sync.d.ts +351 -0
  14. package/dist/body-sync.js +1204 -0
  15. package/dist/calendar-parts.d.ts +23 -0
  16. package/dist/calendar-parts.js +18 -0
  17. package/dist/classification-backfill.d.ts +91 -0
  18. package/dist/classification-backfill.js +180 -0
  19. package/dist/classify-message.d.ts +31 -0
  20. package/dist/classify-message.js +89 -0
  21. package/dist/connection-factory.d.ts +54 -0
  22. package/dist/connection-factory.js +54 -0
  23. package/dist/connection-test.d.ts +41 -0
  24. package/dist/connection-test.js +152 -0
  25. package/dist/external-move.d.ts +94 -0
  26. package/dist/external-move.js +173 -0
  27. package/dist/filter-operations.d.ts +42 -0
  28. package/dist/filter-operations.js +211 -0
  29. package/dist/filters/filter-config.d.ts +14 -0
  30. package/dist/filters/filter-config.js +10 -0
  31. package/{src/filters/list-id.ts → dist/filters/list-id.d.ts} +2 -16
  32. package/dist/filters/list-id.js +26 -0
  33. package/dist/filters/match.d.ts +69 -0
  34. package/dist/filters/match.js +165 -0
  35. package/dist/filters/pipeline.d.ts +64 -0
  36. package/dist/filters/pipeline.js +100 -0
  37. package/{src/flag-push-terminal.ts → dist/flag-push-terminal.d.ts} +16 -72
  38. package/dist/flag-push-terminal.js +63 -0
  39. package/dist/flag-push.d.ts +69 -0
  40. package/dist/flag-push.js +111 -0
  41. package/dist/flag-queue.d.ts +175 -0
  42. package/dist/flag-queue.js +291 -0
  43. package/dist/folder-operations.d.ts +39 -0
  44. package/dist/folder-operations.js +112 -0
  45. package/dist/folder-placement.d.ts +13 -0
  46. package/dist/folder-placement.js +23 -0
  47. package/dist/heuristics/authenticationResults.d.ts +10 -0
  48. package/dist/heuristics/authenticationResults.js +187 -0
  49. package/dist/heuristics/classifyByHeaders.d.ts +82 -0
  50. package/dist/heuristics/classifyByHeaders.js +365 -0
  51. package/dist/heuristics/classifyPlacement.d.ts +67 -0
  52. package/dist/heuristics/classifyPlacement.js +153 -0
  53. package/dist/heuristics/machineSenders.d.ts +21 -0
  54. package/{src/heuristics/machineSenders.ts → dist/heuristics/machineSenders.js} +34 -40
  55. package/dist/heuristics/senderMismatch.d.ts +81 -0
  56. package/dist/heuristics/senderMismatch.js +192 -0
  57. package/dist/heuristics/socialDomains.d.ts +14 -0
  58. package/{src/heuristics/socialDomains.ts → dist/heuristics/socialDomains.js} +9 -9
  59. package/dist/heuristics/transactionalDomains.d.ts +18 -0
  60. package/{src/heuristics/transactionalDomains.ts → dist/heuristics/transactionalDomains.js} +8 -8
  61. package/dist/imapflow-connection.d.ts +377 -0
  62. package/dist/imapflow-connection.js +1147 -0
  63. package/dist/index.d.ts +63 -0
  64. package/dist/index.js +65 -0
  65. package/dist/list-id-backfill.d.ts +83 -0
  66. package/dist/list-id-backfill.js +132 -0
  67. package/dist/mailbox-cursor-rebuild.d.ts +74 -0
  68. package/dist/mailbox-cursor-rebuild.js +71 -0
  69. package/{src/mailbox-cursor.ts → dist/mailbox-cursor.d.ts} +20 -98
  70. package/dist/mailbox-cursor.js +102 -0
  71. package/dist/mailbox-intent.d.ts +41 -0
  72. package/{src/mailbox-intent.ts → dist/mailbox-intent.js} +38 -64
  73. package/dist/mailbox-management.d.ts +240 -0
  74. package/dist/mailbox-management.js +576 -0
  75. package/dist/mailbox-presence.d.ts +52 -0
  76. package/{src/mailbox-presence.ts → dist/mailbox-presence.js} +19 -30
  77. package/dist/mailbox-queue.d.ts +129 -0
  78. package/dist/mailbox-queue.js +216 -0
  79. package/dist/mailbox-sync.d.ts +93 -0
  80. package/dist/mailbox-sync.js +490 -0
  81. package/dist/mailbox-upstream.d.ts +38 -0
  82. package/{src/mailbox-upstream.ts → dist/mailbox-upstream.js} +37 -47
  83. package/dist/message-category.d.ts +51 -0
  84. package/{src/message-category.ts → dist/message-category.js} +28 -71
  85. package/dist/message-move.d.ts +285 -0
  86. package/dist/message-move.js +858 -0
  87. package/dist/message-parser.d.ts +14 -0
  88. package/dist/message-parser.js +17 -0
  89. package/{src/message-presence.ts → dist/message-presence.d.ts} +1 -13
  90. package/dist/message-presence.js +29 -0
  91. package/dist/message-read.d.ts +51 -0
  92. package/dist/message-read.js +31 -0
  93. package/dist/message-sync.d.ts +345 -0
  94. package/dist/message-sync.js +1358 -0
  95. package/dist/mime-walker.d.ts +102 -0
  96. package/dist/mime-walker.js +198 -0
  97. package/dist/organize-job.d.ts +20 -0
  98. package/dist/organize-job.js +37 -0
  99. package/dist/outbox-attachment-content.d.ts +37 -0
  100. package/dist/outbox-attachment-content.js +50 -0
  101. package/dist/outbox-attachment-filename.d.ts +19 -0
  102. package/dist/outbox-attachment-filename.js +64 -0
  103. package/dist/outbox-attachment.d.ts +148 -0
  104. package/dist/outbox-attachment.js +273 -0
  105. package/dist/outbox-queue.d.ts +73 -0
  106. package/dist/outbox-queue.js +280 -0
  107. package/{src/outbox-status.ts → dist/outbox-status.d.ts} +1 -6
  108. package/dist/outbox-status.js +18 -0
  109. package/dist/ownership.d.ts +24 -0
  110. package/dist/ownership.js +83 -0
  111. package/dist/placement-move-terminal.d.ts +67 -0
  112. package/dist/placement-move-terminal.js +88 -0
  113. package/dist/placement-move.d.ts +83 -0
  114. package/dist/placement-move.js +216 -0
  115. package/{src/placement-settled.ts → dist/placement-settled.d.ts} +13 -68
  116. package/dist/placement-settled.js +75 -0
  117. package/dist/quarantine.d.ts +95 -0
  118. package/dist/quarantine.js +131 -0
  119. package/dist/refusal.d.ts +31 -0
  120. package/dist/refusal.js +3 -0
  121. package/{src/restore-source-placement.ts → dist/restore-source-placement.d.ts} +29 -97
  122. package/dist/restore-source-placement.js +79 -0
  123. package/dist/search-text-backfill.d.ts +15 -0
  124. package/dist/search-text-backfill.js +33 -0
  125. package/dist/snippet.d.ts +61 -0
  126. package/dist/snippet.js +129 -0
  127. package/dist/spam-report.d.ts +86 -0
  128. package/dist/spam-report.js +241 -0
  129. package/dist/sync-warning.d.ts +21 -0
  130. package/dist/sync-warning.js +26 -0
  131. package/dist/sync-watermarks.d.ts +138 -0
  132. package/dist/sync-watermarks.js +205 -0
  133. package/dist/test-helpers/fake-imap.d.ts +151 -0
  134. package/dist/test-helpers/fake-imap.js +508 -0
  135. package/dist/test-helpers/folder-roles.d.ts +34 -0
  136. package/dist/test-helpers/folder-roles.js +47 -0
  137. package/dist/test-helpers/mime-tree-generator.d.ts +82 -0
  138. package/dist/test-helpers/mime-tree-generator.js +329 -0
  139. package/dist/test-helpers/test-store.d.ts +69 -0
  140. package/dist/test-helpers/test-store.js +193 -0
  141. package/dist/text/normalizer.d.ts +20 -0
  142. package/dist/text/normalizer.js +102 -0
  143. package/dist/thread-message-rows.d.ts +69 -0
  144. package/dist/thread-message-rows.js +40 -0
  145. package/dist/thread-root.d.ts +7 -0
  146. package/dist/thread-root.js +25 -0
  147. package/dist/types.d.ts +343 -0
  148. package/dist/types.js +19 -0
  149. package/package.json +86 -88
  150. package/src/account-credentials.ts +0 -210
  151. package/src/attribute-mapper.test.ts +0 -104
  152. package/src/attribute-mapper.ts +0 -148
  153. package/src/body-parse.test.ts +0 -55
  154. package/src/body-part-mapper.property.test.ts +0 -354
  155. package/src/body-part-mapper.ts +0 -410
  156. package/src/body-sync-queue.test.ts +0 -64
  157. package/src/body-sync-queue.ts +0 -131
  158. package/src/body-sync-terminal.test.ts +0 -148
  159. package/src/body-sync-terminal.ts +0 -174
  160. package/src/body-sync.test.ts +0 -925
  161. package/src/body-sync.ts +0 -1891
  162. package/src/calendar-parts.ts +0 -41
  163. package/src/classification-backfill.test.ts +0 -285
  164. package/src/classification-backfill.ts +0 -363
  165. package/src/classify-message.test.ts +0 -142
  166. package/src/classify-message.ts +0 -116
  167. package/src/connection-factory.ts +0 -102
  168. package/src/connection-test.ts +0 -208
  169. package/src/credentials.test.ts +0 -181
  170. package/src/external-move.test.ts +0 -262
  171. package/src/external-move.ts +0 -285
  172. package/src/filter-operations.test.ts +0 -319
  173. package/src/filter-operations.ts +0 -377
  174. package/src/filter-update.test.ts +0 -313
  175. package/src/filters/filter-config.test.ts +0 -87
  176. package/src/filters/filter-config.ts +0 -34
  177. package/src/filters/list-id.test.ts +0 -50
  178. package/src/filters/match.test.ts +0 -392
  179. package/src/filters/match.ts +0 -225
  180. package/src/filters/pipeline.test.ts +0 -199
  181. package/src/filters/pipeline.ts +0 -170
  182. package/src/flag-push.test.ts +0 -112
  183. package/src/flag-push.ts +0 -198
  184. package/src/flag-queue.test.ts +0 -197
  185. package/src/flag-queue.ts +0 -509
  186. package/src/folder-operations.test.ts +0 -273
  187. package/src/folder-operations.ts +0 -217
  188. package/src/folder-placement.ts +0 -56
  189. package/src/forwarded-message.test.ts +0 -122
  190. package/src/heuristics/authenticationResults.test.ts +0 -189
  191. package/src/heuristics/authenticationResults.ts +0 -212
  192. package/src/heuristics/classifyByHeaders.test.ts +0 -889
  193. package/src/heuristics/classifyByHeaders.ts +0 -467
  194. package/src/heuristics/classifyPlacement.test.ts +0 -366
  195. package/src/heuristics/classifyPlacement.ts +0 -211
  196. package/src/heuristics/classifyRealMail.test.ts +0 -242
  197. package/src/heuristics/machineSenders.test.ts +0 -101
  198. package/src/heuristics/senderMismatch.test.ts +0 -346
  199. package/src/heuristics/senderMismatch.ts +0 -259
  200. package/src/imapflow-connect-retry.test.ts +0 -97
  201. package/src/imapflow-connection.test.ts +0 -503
  202. package/src/imapflow-connection.ts +0 -1476
  203. package/src/index.ts +0 -397
  204. package/src/list-id-backfill.test.ts +0 -207
  205. package/src/list-id-backfill.ts +0 -274
  206. package/src/mailbox-cursor-rebuild.test.ts +0 -171
  207. package/src/mailbox-cursor-rebuild.ts +0 -146
  208. package/src/mailbox-cursor.test.ts +0 -141
  209. package/src/mailbox-delete-intent.test.ts +0 -119
  210. package/src/mailbox-management.test.ts +0 -243
  211. package/src/mailbox-management.ts +0 -838
  212. package/src/mailbox-presence.test.ts +0 -74
  213. package/src/mailbox-queue.ts +0 -397
  214. package/src/mailbox-rename-intent.test.ts +0 -220
  215. package/src/mailbox-rename-subtree.test.ts +0 -287
  216. package/src/mailbox-sync.test.ts +0 -286
  217. package/src/mailbox-sync.ts +0 -724
  218. package/src/message-move.test.ts +0 -726
  219. package/src/message-move.ts +0 -1518
  220. package/src/message-parser.ts +0 -26
  221. package/src/message-presence.test.ts +0 -50
  222. package/src/message-read.ts +0 -87
  223. package/src/message-save-retry.test.ts +0 -90
  224. package/src/message-sync-flags.test.ts +0 -167
  225. package/src/message-sync.test.ts +0 -762
  226. package/src/message-sync.ts +0 -2133
  227. package/src/mime-walker.test.ts +0 -311
  228. package/src/mime-walker.ts +0 -306
  229. package/src/organize-job.ts +0 -93
  230. package/src/outbox-attachment-content.test.ts +0 -153
  231. package/src/outbox-attachment-content.ts +0 -104
  232. package/src/outbox-attachment-filename.test.ts +0 -132
  233. package/src/outbox-attachment-filename.ts +0 -76
  234. package/src/outbox-attachment.test.ts +0 -626
  235. package/src/outbox-attachment.ts +0 -536
  236. package/src/outbox-failed-recovery.test.ts +0 -299
  237. package/src/outbox-queue.test.ts +0 -315
  238. package/src/outbox-queue.ts +0 -525
  239. package/src/ownership.ts +0 -140
  240. package/src/placement-move-terminal.ts +0 -145
  241. package/src/placement-move.ts +0 -363
  242. package/src/placement-settled.test.ts +0 -147
  243. package/src/placement.test.ts +0 -404
  244. package/src/quarantine.test.ts +0 -133
  245. package/src/quarantine.ts +0 -229
  246. package/src/refusal.ts +0 -29
  247. package/src/snippet.test.ts +0 -213
  248. package/src/snippet.ts +0 -152
  249. package/src/spam-report.test.ts +0 -329
  250. package/src/spam-report.ts +0 -357
  251. package/src/sync-warning.test.ts +0 -213
  252. package/src/sync-warning.ts +0 -56
  253. package/src/sync-watermarks.test.ts +0 -409
  254. package/src/sync-watermarks.ts +0 -319
  255. package/src/system-flag-wire-format.test.ts +0 -77
  256. package/src/test-helpers/fake-imap.ts +0 -690
  257. package/src/test-helpers/folder-roles.ts +0 -66
  258. package/src/test-helpers/mime-tree-generator.ts +0 -461
  259. package/src/test-helpers/test-queue.ts +0 -56
  260. package/src/test-helpers/test-store.ts +0 -299
  261. package/src/text/normalizer.test.ts +0 -223
  262. package/src/text/normalizer.ts +0 -140
  263. package/src/thread-message-rows.ts +0 -77
  264. package/src/thread-root.test.ts +0 -29
  265. package/src/thread-root.ts +0 -51
  266. package/src/types/mailparser-augment.d.ts +0 -23
  267. package/src/types/natural-porter-stemmer.d.ts +0 -50
  268. package/src/types/stopword.d.ts +0 -69
  269. package/src/types.ts +0 -371
  270. package/tsconfig.json +0 -8
@@ -0,0 +1,51 @@
1
+ import type { IThreadMessageRepository, ThreadMessageItem } from "@remit/data-ports";
2
+ export type MessageCategoryValue = ThreadMessageItem["category"];
3
+ /**
4
+ * RFC 034 Decision 3.1: `Message.category` is written once and never mutated
5
+ * after — RFC 030's message-list GSI sort key depends on it never churning.
6
+ * "Already decided" is any real category, so a message that carries one is
7
+ * never re-categorized by a re-entrant pass.
8
+ *
9
+ * `uncategorized` fails this test whether or not the classifier has run, which
10
+ * is deliberate: this asks what the row holds, not what was done to it. Whether
11
+ * the classifier has run is {@link Message.classificationState}, and it is the
12
+ * skip guard in `BodySyncService.syncBodies` — not this one — that keeps a
13
+ * declined message from being examined twice.
14
+ */
15
+ export declare const hasDecidedCategory: (category: MessageCategoryValue | undefined) => boolean;
16
+ export interface MessageCategoryDenormalizeDeps {
17
+ threadMessageService: Pick<IThreadMessageRepository, "findAllByMessageId" | "update">;
18
+ }
19
+ export interface MessageCategoryDenormalizeInput {
20
+ category: MessageCategoryValue;
21
+ snippet?: string;
22
+ listId?: string;
23
+ }
24
+ /**
25
+ * Write a message's category (and, when the caller has them, its snippet and
26
+ * `List-Id`) onto every ThreadMessage row that indexes it. The thread row is
27
+ * what the message list renders and what the category filter matches, so it
28
+ * must never disagree with the Message row it mirrors.
29
+ *
30
+ * More than one row per messageId is schema-legal but not normally produced,
31
+ * and this iterates for the same reason `message-move.ts` does (see the model
32
+ * stated at its `deleteThreadMessagesForMessage`): the key permits it and
33
+ * nothing enforces otherwise. It is NOT the second mailbox a message appears
34
+ * in — `deriveMessageId` and `deriveThreadMessageId` are both
35
+ * mailbox-independent, so INBOX and Archive resolve to one row, and a copy
36
+ * gets its own messageId. The reachable case is thread-root drift: the same
37
+ * message re-saved under different `References`, which mints a second
38
+ * threadId and so a second row. Iterating is therefore hardening against a
39
+ * legal state, not a repair for one the sync path manufactures, which is why
40
+ * the tree's other single-row `messageId` lookups are correct as they stand.
41
+ * `flag-queue.ts` iterates the same list.
42
+ *
43
+ * Rows are looked up by messageId, so this does not depend on the RFC822
44
+ * Message-ID header — a headerless message still gets denormalized, matching
45
+ * the unconditional Message.category write. The composite set is built per
46
+ * row, never reused: `mailboxId` and `isRead` can differ between two rows for
47
+ * one message, and it is passed at all so that a future key-attribute
48
+ * addition touching the lsi3/lsi4/lsi5/gsi2 sort keys keeps the index rows
49
+ * consistent.
50
+ */
51
+ export declare const denormalizeMessageCategory: (deps: MessageCategoryDenormalizeDeps, accountConfigId: string, messageId: string, update: MessageCategoryDenormalizeInput) => Promise<void>;
@@ -1,12 +1,5 @@
1
- import type {
2
- IThreadMessageRepository,
3
- ThreadMessageItem,
4
- } from "@remit/data-ports";
5
1
  import { NotFoundError } from "@remit/data-ports/errors";
6
2
  import { MessageCategory } from "@remit/domain-enums";
7
-
8
- export type MessageCategoryValue = ThreadMessageItem["category"];
9
-
10
3
  /**
11
4
  * RFC 034 Decision 3.1: `Message.category` is written once and never mutated
12
5
  * after — RFC 030's message-list GSI sort key depends on it never churning.
@@ -19,37 +12,15 @@ export type MessageCategoryValue = ThreadMessageItem["category"];
19
12
  * skip guard in `BodySyncService.syncBodies` — not this one — that keeps a
20
13
  * declined message from being examined twice.
21
14
  */
22
- export const hasDecidedCategory = (
23
- category: MessageCategoryValue | undefined,
24
- ): boolean =>
25
- category !== undefined && category !== MessageCategory.uncategorized;
26
-
27
- export interface MessageCategoryDenormalizeDeps {
28
- threadMessageService: Pick<
29
- IThreadMessageRepository,
30
- "findAllByMessageId" | "update"
31
- >;
32
- }
33
-
34
- export interface MessageCategoryDenormalizeInput {
35
- category: MessageCategoryValue;
36
- snippet?: string;
37
- listId?: string;
38
- }
39
-
15
+ export const hasDecidedCategory = (category) => category !== undefined && category !== MessageCategory.uncategorized;
40
16
  /**
41
17
  * A row is skipped only when every field the denormalization would write
42
18
  * already matches. `snippet` and `listId` are absent from the update when the
43
19
  * message has neither, and an absent field is not a mismatch.
44
20
  */
45
- const alreadyDenormalized = (
46
- row: ThreadMessageItem,
47
- update: MessageCategoryDenormalizeInput,
48
- ): boolean =>
49
- row.category === update.category &&
50
- (update.snippet === undefined || row.snippet === update.snippet) &&
51
- (update.listId === undefined || row.listId === update.listId);
52
-
21
+ const alreadyDenormalized = (row, update) => row.category === update.category &&
22
+ (update.snippet === undefined || row.snippet === update.snippet) &&
23
+ (update.listId === undefined || row.listId === update.listId);
53
24
  /**
54
25
  * Write a message's category (and, when the caller has them, its snippet and
55
26
  * `List-Id`) onto every ThreadMessage row that indexes it. The thread row is
@@ -77,42 +48,28 @@ const alreadyDenormalized = (
77
48
  * addition touching the lsi3/lsi4/lsi5/gsi2 sort keys keeps the index rows
78
49
  * consistent.
79
50
  */
80
- export const denormalizeMessageCategory = async (
81
- deps: MessageCategoryDenormalizeDeps,
82
- accountConfigId: string,
83
- messageId: string,
84
- update: MessageCategoryDenormalizeInput,
85
- ): Promise<void> => {
86
- const rows = await deps.threadMessageService.findAllByMessageId(
87
- accountConfigId,
88
- messageId,
89
- );
90
- if (rows.length === 0) {
91
- throw new NotFoundError(`ThreadMessage not found for message ${messageId}`);
92
- }
93
-
94
- const written = {
95
- category: update.category,
96
- ...(update.snippet ? { snippet: update.snippet } : {}),
97
- ...(update.listId ? { listId: update.listId } : {}),
98
- };
99
-
100
- for (const row of rows) {
101
- if (alreadyDenormalized(row, written)) continue;
102
- await deps.threadMessageService.update(
103
- accountConfigId,
104
- row.threadMessageId,
105
- written,
106
- {
107
- composites: {
108
- sentDate: row.sentDate,
109
- mailboxId: row.mailboxId,
110
- isRead: row.isRead,
111
- isDeleted: row.isDeleted,
112
- hasStars: row.hasStars,
113
- hasAttachment: row.hasAttachment,
114
- },
115
- },
116
- );
117
- }
51
+ export const denormalizeMessageCategory = async (deps, accountConfigId, messageId, update) => {
52
+ const rows = await deps.threadMessageService.findAllByMessageId(accountConfigId, messageId);
53
+ if (rows.length === 0) {
54
+ throw new NotFoundError(`ThreadMessage not found for message ${messageId}`);
55
+ }
56
+ const written = {
57
+ category: update.category,
58
+ ...(update.snippet ? { snippet: update.snippet } : {}),
59
+ ...(update.listId ? { listId: update.listId } : {}),
60
+ };
61
+ for (const row of rows) {
62
+ if (alreadyDenormalized(row, written))
63
+ continue;
64
+ await deps.threadMessageService.update(accountConfigId, row.threadMessageId, written, {
65
+ composites: {
66
+ sentDate: row.sentDate,
67
+ mailboxId: row.mailboxId,
68
+ isRead: row.isRead,
69
+ isDeleted: row.isDeleted,
70
+ hasStars: row.hasStars,
71
+ hasAttachment: row.hasAttachment,
72
+ },
73
+ });
74
+ }
118
75
  };
@@ -0,0 +1,285 @@
1
+ import { type FifoQueueProducer, type IAddressRepository, type IMailboxRepository, type IMailboxSpecialUseRepository, type IMessageRepository, type IThreadMessageRepository } from "@remit/data-ports";
2
+ import { FolderRoleUnresolvedError } from "@remit/data-ports/errors";
3
+ import { type RoleResolution, type TrashAssuranceLevel } from "@remit/data-ports/folder-role";
4
+ import { MUTATION_EVENT_SCHEMA_VERSION } from "@remit/data-ports/mutation-events";
5
+ /**
6
+ * What a delete batch did not claim. A row whose placement changed between the
7
+ * read that decided this delete and the mark that recorded it belongs to
8
+ * whoever changed it (docs/architecture/imap-mutations.md R3), so this delete
9
+ * leaves it alone — and says which, because the client removed it optimistically
10
+ * and a row that reappears unexplained is a failure the user cannot act on.
11
+ */
12
+ export interface DeleteMessagesOutcome {
13
+ refusedMessageIds: string[];
14
+ }
15
+ /**
16
+ * Event types for message move/delete operations.
17
+ * These match the worker event types in remit-imap-worker/events.ts.
18
+ */
19
+ export interface MessageDeleteEvent {
20
+ type: "MESSAGE_DELETE";
21
+ schemaVersion: typeof MUTATION_EVENT_SCHEMA_VERSION;
22
+ eventId: string;
23
+ timestamp: number;
24
+ accountId: string;
25
+ messageId: string;
26
+ mailboxId: string;
27
+ mailboxPath: string;
28
+ uid: number;
29
+ operation: "move_to_trash" | "permanent_delete";
30
+ destinationMailboxId?: string;
31
+ destinationMailboxPath?: string;
32
+ }
33
+ export interface MessageMoveEvent {
34
+ type: "MESSAGE_MOVE";
35
+ eventId: string;
36
+ timestamp: number;
37
+ accountId: string;
38
+ messageId: string;
39
+ sourceMailboxId: string;
40
+ sourceMailboxPath: string;
41
+ destinationMailboxId: string;
42
+ destinationMailboxPath: string;
43
+ uid: number;
44
+ }
45
+ export interface EmptyTrashEvent {
46
+ type: "EMPTY_TRASH";
47
+ schemaVersion: typeof MUTATION_EVENT_SCHEMA_VERSION;
48
+ eventId: string;
49
+ timestamp: number;
50
+ accountId: string;
51
+ trashMailboxId: string;
52
+ trashMailboxPath: string;
53
+ /** The folder's UIDVALIDITY at consent time; the worker re-checks it. */
54
+ trashUidValidity: number;
55
+ }
56
+ export interface MessageCopyEvent {
57
+ type: "MESSAGE_COPY";
58
+ eventId: string;
59
+ timestamp: number;
60
+ accountId: string;
61
+ sourceMessageId: string;
62
+ newMessageId: string;
63
+ sourceMailboxId: string;
64
+ sourceMailboxPath: string;
65
+ destinationMailboxId: string;
66
+ destinationMailboxPath: string;
67
+ uid: number;
68
+ }
69
+ export type MessageMoveQueueEvent = MessageDeleteEvent | MessageMoveEvent | EmptyTrashEvent | MessageCopyEvent;
70
+ /**
71
+ * Logger interface
72
+ */
73
+ export interface MessageMoveLogger {
74
+ info(obj: Record<string, unknown>, msg: string): void;
75
+ error(obj: Record<string, unknown>, msg: string): void;
76
+ warn?(obj: Record<string, unknown>, msg: string): void;
77
+ }
78
+ /**
79
+ * Configuration for MessageMoveService
80
+ */
81
+ export interface MessageMoveConfig {
82
+ messageService: IMessageRepository;
83
+ mailboxService: IMailboxRepository;
84
+ mailboxSpecialUseService: IMailboxSpecialUseRepository;
85
+ threadMessageService: IThreadMessageRepository;
86
+ addressService: IAddressRepository;
87
+ queue: FifoQueueProducer<MessageMoveQueueEvent>;
88
+ logger?: MessageMoveLogger;
89
+ moveSettleTimeoutMs?: number;
90
+ moveSettlePollMs?: number;
91
+ }
92
+ /**
93
+ * The account resolves no Trash folder, so a move-to-Trash delete has nowhere
94
+ * to put the mail and an Empty Trash has nothing it may empty. A designed,
95
+ * user-facing outcome carrying its own text and a 409 the backend returns
96
+ * verbatim: the absence of a Trash folder is a blocker the user resolves by
97
+ * appointing one, never a licence to expunge.
98
+ */
99
+ export declare class NoTrashMailboxError extends FolderRoleUnresolvedError {
100
+ name: string;
101
+ constructor(accountId: string);
102
+ }
103
+ /**
104
+ * The folder the user appointed as Trash is gone from the server. Distinct from
105
+ * having no Trash at all: there is a decision on file, and the repair is to
106
+ * point it at a folder that exists — not to let reader pick one instead.
107
+ */
108
+ export declare class StaleTrashAppointmentError extends FolderRoleUnresolvedError {
109
+ name: string;
110
+ constructor(accountId: string);
111
+ }
112
+ /**
113
+ * A folder resolves as Trash by its name alone. Enough to move mail into,
114
+ * never enough to expunge — raised by Empty Trash and by a delete that would
115
+ * expunge a message already sitting in that folder.
116
+ */
117
+ export declare class UnconfirmedTrashMailboxError extends FolderRoleUnresolvedError {
118
+ name: string;
119
+ constructor(accountId: string);
120
+ }
121
+ /**
122
+ * The Trash folder a verb may act on at the assurance it demands, or the coded
123
+ * refusal naming which of the three reasons stopped it. Every Trash gate goes
124
+ * through here — the delete and the expunge — so no two of them can tell a
125
+ * user different things about the same account.
126
+ */
127
+ export declare const requireTrashMailbox: <T>(resolution: RoleResolution<T>, level: TrashAssuranceLevel, accountId: string) => T;
128
+ /**
129
+ * Options for delete operations
130
+ */
131
+ export interface DeleteOptions {
132
+ /** Move to Trash instead of permanent delete. Default: true */
133
+ toTrash?: boolean;
134
+ /** Permanently delete even if in Trash. Default: false */
135
+ permanent?: boolean;
136
+ }
137
+ /**
138
+ * Service for moving, copying, and deleting messages.
139
+ *
140
+ * Implements optimistic local-first pattern:
141
+ * 1. Updates local state immediately (Message + ThreadMessage)
142
+ * 2. Enqueues event to SQS for worker to sync to IMAP
143
+ *
144
+ * Following RFC 016 for message deletion and moving.
145
+ */
146
+ export declare class MessageMoveService {
147
+ private messageService;
148
+ private mailboxService;
149
+ private mailboxSpecialUseService;
150
+ private threadMessageService;
151
+ private addressService;
152
+ private queue;
153
+ private log;
154
+ private moveSettleTimeoutMs;
155
+ private moveSettlePollMs;
156
+ constructor(config: MessageMoveConfig);
157
+ /**
158
+ * Delete a message. By default moves to Trash.
159
+ *
160
+ * @param messageId - Message to delete
161
+ * @param accountId - Account ID
162
+ * @param options - Delete options (toTrash, permanent)
163
+ */
164
+ deleteMessage: (accountConfigId: string, messageId: string, accountId: string, options?: DeleteOptions) => Promise<DeleteMessagesOutcome>;
165
+ /**
166
+ * Delete multiple messages using batch operations.
167
+ * By default moves to Trash, unless permanent is specified or already in Trash.
168
+ *
169
+ * @param messageIds - Messages to delete
170
+ * @param accountId - Account ID
171
+ * @param options - Delete options (toTrash, permanent)
172
+ */
173
+ deleteMessages: (accountConfigId: string, messageIds: string[], accountId: string, options?: DeleteOptions) => Promise<DeleteMessagesOutcome>;
174
+ /**
175
+ * Move a message to another mailbox.
176
+ *
177
+ * Waits, never reconciles (imap-mutations R2): the folder and uid go to the
178
+ * server as one pair, and once the worker has moved the destination's own
179
+ * message at that uid there is no reference left to repair. The person is
180
+ * looking at the result, so blocking for the sub-second a move takes is the
181
+ * cheap half; on timeout the move refuses ahead of any local write.
182
+ *
183
+ * @param messageId - Message to move
184
+ * @param destinationMailboxId - Destination mailbox ID
185
+ * @param accountId - Account ID
186
+ */
187
+ moveMessage: (accountConfigId: string, messageId: string, destinationMailboxId: string, accountId: string) => Promise<void>;
188
+ private moveSettledMessage;
189
+ /**
190
+ * Move multiple messages to another mailbox.
191
+ *
192
+ * @param messageIds - Messages to move
193
+ * @param destinationMailboxId - Destination mailbox ID
194
+ * @param accountId - Account ID
195
+ */
196
+ moveMessages: (accountConfigId: string, messageIds: string[], destinationMailboxId: string, accountId: string) => Promise<void>;
197
+ /**
198
+ * Copy a message to another mailbox.
199
+ * Creates a new message record locally and enqueues IMAP COPY.
200
+ *
201
+ * @param messageId - Message to copy
202
+ * @param destinationMailboxId - Destination mailbox ID
203
+ * @param accountId - Account ID
204
+ * @returns The new message ID for the copy
205
+ */
206
+ copyMessage: (accountConfigId: string, messageId: string, destinationMailboxId: string, accountId: string) => Promise<string>;
207
+ private copySettledMessage;
208
+ /**
209
+ * Copy multiple messages to another mailbox.
210
+ *
211
+ * @param messageIds - Messages to copy
212
+ * @param destinationMailboxId - Destination mailbox ID
213
+ * @param accountId - Account ID
214
+ * @returns Array of new message IDs for the copies
215
+ */
216
+ copyMessages: (accountConfigId: string, messageIds: string[], destinationMailboxId: string, accountId: string) => Promise<string[]>;
217
+ /**
218
+ * Restore a message from Trash to its original mailbox.
219
+ *
220
+ * @param messageId - Message to restore
221
+ * @param accountId - Account ID
222
+ */
223
+ restoreMessage: (accountConfigId: string, messageId: string, accountId: string) => Promise<void>;
224
+ /**
225
+ * Empty the Trash mailbox: an EXPUNGE of everything in it, with no undo.
226
+ *
227
+ * Demands confirmed evidence: the folder the user appointed or the one the
228
+ * server flagged \Trash — never one that merely reads like a trash folder,
229
+ * and never the fallback behind an appointment that went missing. A user
230
+ * with an ordinary folder called `Deleted` would otherwise lose its contents
231
+ * permanently. Each of the three ways that evidence can be absent refuses
232
+ * under its own reason, so the surface can name the remedy instead of
233
+ * offering a silent no-op.
234
+ *
235
+ * Reports the rows it marked, read once here, so the number the caller shows
236
+ * a user is the number this operation acted on. Pressed twice before the
237
+ * worker runs it re-marks the same rows and reports the same number: the
238
+ * folder really does still hold them, and the second expunge finds nothing
239
+ * to do.
240
+ */
241
+ emptyTrash: (accountConfigId: string, accountId: string) => Promise<{
242
+ deletedCount: number;
243
+ }>;
244
+ private refusePlacement;
245
+ private settledPlacement;
246
+ /**
247
+ * Settle a whole batch before any of it is acted on, so one unsettled row
248
+ * refuses the batch whole rather than leaving part of it expunged, and the
249
+ * batch's total wait is one row's ceiling rather than the sum across it.
250
+ */
251
+ private settledPlacements;
252
+ /**
253
+ * Update ThreadMessage for move operations.
254
+ * Updates mailboxId and optionally isDeleted.
255
+ */
256
+ private updateThreadMessageForMove;
257
+ /**
258
+ * Delete every ThreadMessage row that points at this messageId.
259
+ *
260
+ * A row is keyed by (threadId, messageId), so this is one row in practice —
261
+ * a message filed in several folders keeps a single row, because its
262
+ * messageId is derived from the Message-ID header and not from the mailbox.
263
+ * The query stays a list because the key permits more than one thread per
264
+ * message and nothing enforces otherwise.
265
+ *
266
+ * Deleting up-front in the permanent-delete optimistic step prevents stale
267
+ * rows from leaking into mailbox listings while IMAP catches up (#212).
268
+ */
269
+ private deleteThreadMessagesForMessage;
270
+ /**
271
+ * Update ThreadMessage.isDeleted flag only.
272
+ */
273
+ private updateThreadMessageDeleted;
274
+ /**
275
+ * Enqueue a message move/delete event, grouped by accountId so events for
276
+ * different accounts can be processed in parallel.
277
+ */
278
+ private enqueueEvent;
279
+ /**
280
+ * Enqueue multiple events in batches of at most {@link QUEUE_BATCH_LIMIT}.
281
+ */
282
+ private enqueueEventsBatch;
283
+ private sendEventsBatch;
284
+ private handBackUnsentMove;
285
+ }