@remit/mailbox-service 0.0.112 → 0.0.113
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.
- package/dist/account-credentials.d.ts +83 -0
- package/dist/account-credentials.js +104 -0
- package/dist/attribute-mapper.d.ts +36 -0
- package/dist/attribute-mapper.js +119 -0
- package/dist/body-parse.d.ts +28 -0
- package/{src/body-parse.ts → dist/body-parse.js} +18 -26
- package/dist/body-part-mapper.d.ts +92 -0
- package/dist/body-part-mapper.js +301 -0
- package/dist/body-sync-queue.d.ts +59 -0
- package/dist/body-sync-queue.js +56 -0
- package/dist/body-sync-terminal.d.ts +58 -0
- package/dist/body-sync-terminal.js +95 -0
- package/dist/body-sync.d.ts +351 -0
- package/dist/body-sync.js +1204 -0
- package/dist/calendar-parts.d.ts +23 -0
- package/dist/calendar-parts.js +18 -0
- package/dist/classification-backfill.d.ts +91 -0
- package/dist/classification-backfill.js +180 -0
- package/dist/classify-message.d.ts +31 -0
- package/dist/classify-message.js +89 -0
- package/dist/connection-factory.d.ts +54 -0
- package/dist/connection-factory.js +54 -0
- package/dist/connection-test.d.ts +41 -0
- package/dist/connection-test.js +152 -0
- package/dist/external-move.d.ts +94 -0
- package/dist/external-move.js +173 -0
- package/dist/filter-operations.d.ts +42 -0
- package/dist/filter-operations.js +211 -0
- package/dist/filters/filter-config.d.ts +14 -0
- package/dist/filters/filter-config.js +10 -0
- package/{src/filters/list-id.ts → dist/filters/list-id.d.ts} +2 -16
- package/dist/filters/list-id.js +26 -0
- package/dist/filters/match.d.ts +69 -0
- package/dist/filters/match.js +165 -0
- package/dist/filters/pipeline.d.ts +64 -0
- package/dist/filters/pipeline.js +100 -0
- package/{src/flag-push-terminal.ts → dist/flag-push-terminal.d.ts} +16 -72
- package/dist/flag-push-terminal.js +63 -0
- package/dist/flag-push.d.ts +69 -0
- package/dist/flag-push.js +111 -0
- package/dist/flag-queue.d.ts +175 -0
- package/dist/flag-queue.js +291 -0
- package/dist/folder-operations.d.ts +39 -0
- package/dist/folder-operations.js +112 -0
- package/dist/folder-placement.d.ts +13 -0
- package/dist/folder-placement.js +23 -0
- package/dist/heuristics/authenticationResults.d.ts +10 -0
- package/dist/heuristics/authenticationResults.js +187 -0
- package/dist/heuristics/classifyByHeaders.d.ts +82 -0
- package/dist/heuristics/classifyByHeaders.js +365 -0
- package/dist/heuristics/classifyPlacement.d.ts +67 -0
- package/dist/heuristics/classifyPlacement.js +153 -0
- package/dist/heuristics/machineSenders.d.ts +21 -0
- package/{src/heuristics/machineSenders.ts → dist/heuristics/machineSenders.js} +34 -40
- package/dist/heuristics/senderMismatch.d.ts +81 -0
- package/dist/heuristics/senderMismatch.js +192 -0
- package/dist/heuristics/socialDomains.d.ts +14 -0
- package/{src/heuristics/socialDomains.ts → dist/heuristics/socialDomains.js} +9 -9
- package/dist/heuristics/transactionalDomains.d.ts +18 -0
- package/{src/heuristics/transactionalDomains.ts → dist/heuristics/transactionalDomains.js} +8 -8
- package/dist/imapflow-connection.d.ts +377 -0
- package/dist/imapflow-connection.js +1147 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +65 -0
- package/dist/list-id-backfill.d.ts +83 -0
- package/dist/list-id-backfill.js +132 -0
- package/dist/mailbox-cursor-rebuild.d.ts +74 -0
- package/dist/mailbox-cursor-rebuild.js +71 -0
- package/{src/mailbox-cursor.ts → dist/mailbox-cursor.d.ts} +20 -98
- package/dist/mailbox-cursor.js +102 -0
- package/dist/mailbox-intent.d.ts +41 -0
- package/{src/mailbox-intent.ts → dist/mailbox-intent.js} +38 -64
- package/dist/mailbox-management.d.ts +240 -0
- package/dist/mailbox-management.js +576 -0
- package/dist/mailbox-presence.d.ts +52 -0
- package/{src/mailbox-presence.ts → dist/mailbox-presence.js} +19 -30
- package/dist/mailbox-queue.d.ts +126 -0
- package/dist/mailbox-queue.js +214 -0
- package/dist/mailbox-sync.d.ts +93 -0
- package/dist/mailbox-sync.js +490 -0
- package/dist/mailbox-upstream.d.ts +38 -0
- package/{src/mailbox-upstream.ts → dist/mailbox-upstream.js} +37 -47
- package/dist/message-category.d.ts +51 -0
- package/{src/message-category.ts → dist/message-category.js} +28 -71
- package/dist/message-move.d.ts +285 -0
- package/dist/message-move.js +858 -0
- package/dist/message-parser.d.ts +14 -0
- package/dist/message-parser.js +17 -0
- package/{src/message-presence.ts → dist/message-presence.d.ts} +1 -13
- package/dist/message-presence.js +29 -0
- package/dist/message-read.d.ts +51 -0
- package/dist/message-read.js +31 -0
- package/dist/message-sync.d.ts +345 -0
- package/dist/message-sync.js +1358 -0
- package/dist/mime-walker.d.ts +102 -0
- package/dist/mime-walker.js +198 -0
- package/dist/organize-job.d.ts +20 -0
- package/dist/organize-job.js +37 -0
- package/dist/outbox-attachment-content.d.ts +37 -0
- package/dist/outbox-attachment-content.js +50 -0
- package/dist/outbox-attachment-filename.d.ts +19 -0
- package/dist/outbox-attachment-filename.js +64 -0
- package/dist/outbox-attachment.d.ts +148 -0
- package/dist/outbox-attachment.js +273 -0
- package/dist/outbox-queue.d.ts +73 -0
- package/dist/outbox-queue.js +284 -0
- package/{src/outbox-status.ts → dist/outbox-status.d.ts} +1 -6
- package/dist/outbox-status.js +18 -0
- package/dist/ownership.d.ts +24 -0
- package/dist/ownership.js +83 -0
- package/dist/placement-move-terminal.d.ts +67 -0
- package/dist/placement-move-terminal.js +88 -0
- package/dist/placement-move.d.ts +83 -0
- package/dist/placement-move.js +216 -0
- package/{src/placement-settled.ts → dist/placement-settled.d.ts} +13 -68
- package/dist/placement-settled.js +75 -0
- package/dist/quarantine.d.ts +95 -0
- package/dist/quarantine.js +131 -0
- package/dist/refusal.d.ts +31 -0
- package/dist/refusal.js +3 -0
- package/{src/restore-source-placement.ts → dist/restore-source-placement.d.ts} +29 -97
- package/dist/restore-source-placement.js +79 -0
- package/dist/search-text-backfill.d.ts +15 -0
- package/dist/search-text-backfill.js +33 -0
- package/dist/snippet.d.ts +61 -0
- package/dist/snippet.js +129 -0
- package/dist/spam-report.d.ts +86 -0
- package/dist/spam-report.js +241 -0
- package/dist/sync-warning.d.ts +21 -0
- package/dist/sync-warning.js +26 -0
- package/dist/sync-watermarks.d.ts +138 -0
- package/dist/sync-watermarks.js +205 -0
- package/dist/test-helpers/fake-imap.d.ts +151 -0
- package/dist/test-helpers/fake-imap.js +508 -0
- package/dist/test-helpers/folder-roles.d.ts +34 -0
- package/dist/test-helpers/folder-roles.js +47 -0
- package/dist/test-helpers/mime-tree-generator.d.ts +82 -0
- package/dist/test-helpers/mime-tree-generator.js +329 -0
- package/dist/test-helpers/test-store.d.ts +69 -0
- package/dist/test-helpers/test-store.js +193 -0
- package/dist/text/normalizer.d.ts +20 -0
- package/dist/text/normalizer.js +102 -0
- package/dist/thread-message-rows.d.ts +69 -0
- package/dist/thread-message-rows.js +40 -0
- package/dist/thread-root.d.ts +7 -0
- package/dist/thread-root.js +25 -0
- package/dist/types.d.ts +343 -0
- package/dist/types.js +19 -0
- package/package.json +86 -88
- package/src/account-credentials.ts +0 -210
- package/src/attribute-mapper.test.ts +0 -104
- package/src/attribute-mapper.ts +0 -148
- package/src/body-parse.test.ts +0 -55
- package/src/body-part-mapper.property.test.ts +0 -354
- package/src/body-part-mapper.ts +0 -410
- package/src/body-sync-queue.test.ts +0 -64
- package/src/body-sync-queue.ts +0 -131
- package/src/body-sync-terminal.test.ts +0 -148
- package/src/body-sync-terminal.ts +0 -174
- package/src/body-sync.test.ts +0 -925
- package/src/body-sync.ts +0 -1891
- package/src/calendar-parts.ts +0 -41
- package/src/classification-backfill.test.ts +0 -285
- package/src/classification-backfill.ts +0 -363
- package/src/classify-message.test.ts +0 -142
- package/src/classify-message.ts +0 -116
- package/src/connection-factory.ts +0 -102
- package/src/connection-test.ts +0 -208
- package/src/credentials.test.ts +0 -181
- package/src/external-move.test.ts +0 -262
- package/src/external-move.ts +0 -285
- package/src/filter-operations.test.ts +0 -319
- package/src/filter-operations.ts +0 -377
- package/src/filter-update.test.ts +0 -313
- package/src/filters/filter-config.test.ts +0 -87
- package/src/filters/filter-config.ts +0 -34
- package/src/filters/list-id.test.ts +0 -50
- package/src/filters/match.test.ts +0 -392
- package/src/filters/match.ts +0 -225
- package/src/filters/pipeline.test.ts +0 -199
- package/src/filters/pipeline.ts +0 -170
- package/src/flag-push.test.ts +0 -112
- package/src/flag-push.ts +0 -198
- package/src/flag-queue.test.ts +0 -197
- package/src/flag-queue.ts +0 -509
- package/src/folder-operations.test.ts +0 -273
- package/src/folder-operations.ts +0 -217
- package/src/folder-placement.ts +0 -56
- package/src/forwarded-message.test.ts +0 -122
- package/src/heuristics/authenticationResults.test.ts +0 -189
- package/src/heuristics/authenticationResults.ts +0 -212
- package/src/heuristics/classifyByHeaders.test.ts +0 -889
- package/src/heuristics/classifyByHeaders.ts +0 -467
- package/src/heuristics/classifyPlacement.test.ts +0 -366
- package/src/heuristics/classifyPlacement.ts +0 -211
- package/src/heuristics/classifyRealMail.test.ts +0 -242
- package/src/heuristics/machineSenders.test.ts +0 -101
- package/src/heuristics/senderMismatch.test.ts +0 -346
- package/src/heuristics/senderMismatch.ts +0 -259
- package/src/imapflow-connect-retry.test.ts +0 -97
- package/src/imapflow-connection.test.ts +0 -503
- package/src/imapflow-connection.ts +0 -1476
- package/src/index.ts +0 -397
- package/src/list-id-backfill.test.ts +0 -207
- package/src/list-id-backfill.ts +0 -274
- package/src/mailbox-cursor-rebuild.test.ts +0 -171
- package/src/mailbox-cursor-rebuild.ts +0 -146
- package/src/mailbox-cursor.test.ts +0 -141
- package/src/mailbox-delete-intent.test.ts +0 -119
- package/src/mailbox-management.test.ts +0 -243
- package/src/mailbox-management.ts +0 -838
- package/src/mailbox-presence.test.ts +0 -74
- package/src/mailbox-queue.ts +0 -397
- package/src/mailbox-rename-intent.test.ts +0 -220
- package/src/mailbox-rename-subtree.test.ts +0 -287
- package/src/mailbox-sync.test.ts +0 -286
- package/src/mailbox-sync.ts +0 -724
- package/src/message-move.test.ts +0 -726
- package/src/message-move.ts +0 -1518
- package/src/message-parser.ts +0 -26
- package/src/message-presence.test.ts +0 -50
- package/src/message-read.ts +0 -87
- package/src/message-save-retry.test.ts +0 -90
- package/src/message-sync-flags.test.ts +0 -167
- package/src/message-sync.test.ts +0 -762
- package/src/message-sync.ts +0 -2133
- package/src/mime-walker.test.ts +0 -311
- package/src/mime-walker.ts +0 -306
- package/src/organize-job.ts +0 -93
- package/src/outbox-attachment-content.test.ts +0 -153
- package/src/outbox-attachment-content.ts +0 -104
- package/src/outbox-attachment-filename.test.ts +0 -132
- package/src/outbox-attachment-filename.ts +0 -76
- package/src/outbox-attachment.test.ts +0 -626
- package/src/outbox-attachment.ts +0 -536
- package/src/outbox-failed-recovery.test.ts +0 -299
- package/src/outbox-queue.test.ts +0 -315
- package/src/outbox-queue.ts +0 -525
- package/src/ownership.ts +0 -140
- package/src/placement-move-terminal.ts +0 -145
- package/src/placement-move.ts +0 -363
- package/src/placement-settled.test.ts +0 -147
- package/src/placement.test.ts +0 -404
- package/src/quarantine.test.ts +0 -133
- package/src/quarantine.ts +0 -229
- package/src/refusal.ts +0 -29
- package/src/snippet.test.ts +0 -213
- package/src/snippet.ts +0 -152
- package/src/spam-report.test.ts +0 -329
- package/src/spam-report.ts +0 -357
- package/src/sync-warning.test.ts +0 -213
- package/src/sync-warning.ts +0 -56
- package/src/sync-watermarks.test.ts +0 -409
- package/src/sync-watermarks.ts +0 -319
- package/src/system-flag-wire-format.test.ts +0 -77
- package/src/test-helpers/fake-imap.ts +0 -690
- package/src/test-helpers/folder-roles.ts +0 -66
- package/src/test-helpers/mime-tree-generator.ts +0 -461
- package/src/test-helpers/test-queue.ts +0 -56
- package/src/test-helpers/test-store.ts +0 -299
- package/src/text/normalizer.test.ts +0 -223
- package/src/text/normalizer.ts +0 -140
- package/src/thread-message-rows.ts +0 -77
- package/src/thread-root.test.ts +0 -29
- package/src/thread-root.ts +0 -51
- package/src/types/mailparser-augment.d.ts +0 -23
- package/src/types/natural-porter-stemmer.d.ts +0 -50
- package/src/types/stopword.d.ts +0 -69
- package/src/types.ts +0 -371
- 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
|
-
|
|
47
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
+
}
|