@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
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import type { MessageItem } from "@remit/data-ports";
|
|
2
|
-
import { MessageStatus } from "@remit/domain-enums";
|
|
3
|
-
|
|
4
2
|
/**
|
|
5
3
|
* Whether the message row's placement is still an unconfirmed local write
|
|
6
4
|
* (issue #496). While a move is in flight the row carries the destination in
|
|
@@ -21,12 +19,7 @@ import { MessageStatus } from "@remit/domain-enums";
|
|
|
21
19
|
* same mismatched pair. Reading `moving` alone let a move enqueued behind a
|
|
22
20
|
* delete bind to the row mid-delete and be dropped when the delete settled.
|
|
23
21
|
*/
|
|
24
|
-
export const isPlacementUnsettled
|
|
25
|
-
message: Pick<MessageItem, "status">,
|
|
26
|
-
): boolean =>
|
|
27
|
-
message.status === MessageStatus.moving ||
|
|
28
|
-
message.status === MessageStatus.deleting;
|
|
29
|
-
|
|
22
|
+
export declare const isPlacementUnsettled: (message: Pick<MessageItem, "status">) => boolean;
|
|
30
23
|
/**
|
|
31
24
|
* Whether the row's `uid` belongs to a folder other than the one `mailboxId`
|
|
32
25
|
* names — the state that makes the pair a lie rather than merely incomplete.
|
|
@@ -46,44 +39,17 @@ export const isPlacementUnsettled = (
|
|
|
46
39
|
* untouched, so anything that resolves a uid off a row it did not settle
|
|
47
40
|
* itself asks this rather than the binding below (issue #1217).
|
|
48
41
|
*/
|
|
49
|
-
export const carriesForeignUid
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
>,
|
|
54
|
-
>(
|
|
55
|
-
message: T,
|
|
56
|
-
): message is T & { originalMailboxId: string; originalUid: number } =>
|
|
57
|
-
message.originalUid !== undefined &&
|
|
58
|
-
message.originalUid === message.uid &&
|
|
59
|
-
message.originalMailboxId !== undefined &&
|
|
60
|
-
message.originalMailboxId !== message.mailboxId;
|
|
61
|
-
|
|
42
|
+
export declare const carriesForeignUid: <T extends Pick<MessageItem, "mailboxId" | "uid" | "originalMailboxId" | "originalUid">>(message: T) => message is T & {
|
|
43
|
+
originalMailboxId: string;
|
|
44
|
+
originalUid: number;
|
|
45
|
+
};
|
|
62
46
|
/**
|
|
63
47
|
* The folder the row's `uid` is a uid in: the source while a move has not
|
|
64
48
|
* confirmed, otherwise the folder the row is in. A fetch by uid is only safe
|
|
65
49
|
* from this folder; from any other the same number names a different message.
|
|
66
50
|
*/
|
|
67
|
-
export const uidMailboxIdOf
|
|
68
|
-
|
|
69
|
-
MessageItem,
|
|
70
|
-
"mailboxId" | "uid" | "originalMailboxId" | "originalUid"
|
|
71
|
-
>,
|
|
72
|
-
): string =>
|
|
73
|
-
carriesForeignUid(message) ? message.originalMailboxId : message.mailboxId;
|
|
74
|
-
|
|
75
|
-
export const bindsForeignUid = (
|
|
76
|
-
message: Pick<
|
|
77
|
-
MessageItem,
|
|
78
|
-
"status" | "mailboxId" | "uid" | "originalMailboxId" | "originalUid"
|
|
79
|
-
>,
|
|
80
|
-
): boolean => isPlacementUnsettled(message) && carriesForeignUid(message);
|
|
81
|
-
|
|
82
|
-
/** A copy at `uid: 0`, which names nothing on the server until COPYUID lands (#1387). */
|
|
83
|
-
const awaitsCopiedUid = (
|
|
84
|
-
message: Pick<MessageItem, "status" | "uid">,
|
|
85
|
-
): boolean => message.status === MessageStatus.moving && message.uid === 0;
|
|
86
|
-
|
|
51
|
+
export declare const uidMailboxIdOf: (message: Pick<MessageItem, "mailboxId" | "uid" | "originalMailboxId" | "originalUid">) => string;
|
|
52
|
+
export declare const bindsForeignUid: (message: Pick<MessageItem, "status" | "mailboxId" | "uid" | "originalMailboxId" | "originalUid">) => boolean;
|
|
87
53
|
/**
|
|
88
54
|
* `in_flight` — a mutation is still working on the row, so waiting resolves it.
|
|
89
55
|
* That includes a row mid-retry: `message-move`, `message-delete` and
|
|
@@ -100,22 +66,11 @@ const awaitsCopiedUid = (
|
|
|
100
66
|
* IMAP rather than leaving a mismatched pair behind (#1005).
|
|
101
67
|
*/
|
|
102
68
|
export type PlacementBinding = "consistent" | "in_flight";
|
|
103
|
-
|
|
104
|
-
export const placementBindingOf = (
|
|
105
|
-
message: Pick<
|
|
106
|
-
MessageItem,
|
|
107
|
-
"status" | "mailboxId" | "uid" | "originalMailboxId" | "originalUid"
|
|
108
|
-
>,
|
|
109
|
-
): PlacementBinding =>
|
|
110
|
-
bindsForeignUid(message) || awaitsCopiedUid(message)
|
|
111
|
-
? "in_flight"
|
|
112
|
-
: "consistent";
|
|
113
|
-
|
|
69
|
+
export declare const placementBindingOf: (message: Pick<MessageItem, "status" | "mailboxId" | "uid" | "originalMailboxId" | "originalUid">) => PlacementBinding;
|
|
114
70
|
export interface PlacementSettleOptions {
|
|
115
|
-
|
|
116
|
-
|
|
71
|
+
timeoutMs: number;
|
|
72
|
+
pollMs: number;
|
|
117
73
|
}
|
|
118
|
-
|
|
119
74
|
/**
|
|
120
75
|
* Block a dependent mutation until the row's placement settles, then hand back
|
|
121
76
|
* the confirmed row — the wait half of docs/architecture/imap-mutations.md R2.
|
|
@@ -124,16 +79,6 @@ export interface PlacementSettleOptions {
|
|
|
124
79
|
* when the deadline passes; the caller decides what a dependency that never
|
|
125
80
|
* settled means for it.
|
|
126
81
|
*/
|
|
127
|
-
export const waitForPlacementToSettle
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
{ timeoutMs, pollMs }: PlacementSettleOptions,
|
|
131
|
-
): Promise<MessageItem> => {
|
|
132
|
-
const deadline = Date.now() + timeoutMs;
|
|
133
|
-
let message = await messageService.get(messageId);
|
|
134
|
-
while (isPlacementUnsettled(message) && Date.now() < deadline) {
|
|
135
|
-
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
136
|
-
message = await messageService.get(messageId);
|
|
137
|
-
}
|
|
138
|
-
return message;
|
|
139
|
-
};
|
|
82
|
+
export declare const waitForPlacementToSettle: (messageService: {
|
|
83
|
+
get(messageId: string): Promise<MessageItem>;
|
|
84
|
+
}, messageId: string, { timeoutMs, pollMs }: PlacementSettleOptions) => Promise<MessageItem>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { MessageStatus } from "@remit/domain-enums";
|
|
2
|
+
/**
|
|
3
|
+
* Whether the message row's placement is still an unconfirmed local write
|
|
4
|
+
* (issue #496). While a move is in flight the row carries the destination in
|
|
5
|
+
* `mailboxId` but the SOURCE folder's `uid` — the pair is only made consistent
|
|
6
|
+
* when the IMAP move confirms and `updateUid` writes the destination's COPYUID.
|
|
7
|
+
* Any dependent mutation that resolves a folder and a uid from such a row
|
|
8
|
+
* therefore addresses the destination's OWN message at that uid: a different
|
|
9
|
+
* message, on a mailbox Remit is never the only client of.
|
|
10
|
+
*
|
|
11
|
+
* `syncStatus` is not the signal. An ordinary freshly-synced inbound row is
|
|
12
|
+
* `pending` forever (nothing on the inbound path promotes it), so keying off it
|
|
13
|
+
* defers every outbound mutation in the product. `status` is what an actual
|
|
14
|
+
* mutation writes and what settling clears.
|
|
15
|
+
*
|
|
16
|
+
* Both in-flight states count, per the R3 state table: a delete points the row
|
|
17
|
+
* at Trash with the source's uid exactly as a move points it at the
|
|
18
|
+
* destination, so a dependent mutation that reads a `deleting` row resolves the
|
|
19
|
+
* same mismatched pair. Reading `moving` alone let a move enqueued behind a
|
|
20
|
+
* delete bind to the row mid-delete and be dropped when the delete settled.
|
|
21
|
+
*/
|
|
22
|
+
export const isPlacementUnsettled = (message) => message.status === MessageStatus.moving ||
|
|
23
|
+
message.status === MessageStatus.deleting;
|
|
24
|
+
/**
|
|
25
|
+
* Whether the row's `uid` belongs to a folder other than the one `mailboxId`
|
|
26
|
+
* names — the state that makes the pair a lie rather than merely incomplete.
|
|
27
|
+
*
|
|
28
|
+
* The optimistic half of a move is the only writer of this shape: it points the row at the
|
|
29
|
+
* destination and records the pre-move pair, leaving `uid` untouched until the
|
|
30
|
+
* server confirms and `updateUid` replaces it. A row with no `originalUid` was
|
|
31
|
+
* never moved, so its uid names nothing but itself — a freshly copied row is
|
|
32
|
+
* `moving` with `uid: 0` until COPYUID lands, which is a uid that is not ready,
|
|
33
|
+
* not a uid that names somebody else's message.
|
|
34
|
+
*
|
|
35
|
+
* Answered without reference to `status`, because `status` is not what clears
|
|
36
|
+
* it: `updateUid` does, dropping `originalUid` in the same statement that
|
|
37
|
+
* writes the destination's own, and a restore does by pointing `mailboxId`
|
|
38
|
+
* back at `originalMailboxId`. An operation that rewrites `status` on its way
|
|
39
|
+
* past — Empty Trash marking a whole folder `deleting` — leaves the pair
|
|
40
|
+
* untouched, so anything that resolves a uid off a row it did not settle
|
|
41
|
+
* itself asks this rather than the binding below (issue #1217).
|
|
42
|
+
*/
|
|
43
|
+
export const carriesForeignUid = (message) => message.originalUid !== undefined &&
|
|
44
|
+
message.originalUid === message.uid &&
|
|
45
|
+
message.originalMailboxId !== undefined &&
|
|
46
|
+
message.originalMailboxId !== message.mailboxId;
|
|
47
|
+
/**
|
|
48
|
+
* The folder the row's `uid` is a uid in: the source while a move has not
|
|
49
|
+
* confirmed, otherwise the folder the row is in. A fetch by uid is only safe
|
|
50
|
+
* from this folder; from any other the same number names a different message.
|
|
51
|
+
*/
|
|
52
|
+
export const uidMailboxIdOf = (message) => carriesForeignUid(message) ? message.originalMailboxId : message.mailboxId;
|
|
53
|
+
export const bindsForeignUid = (message) => isPlacementUnsettled(message) && carriesForeignUid(message);
|
|
54
|
+
/** A copy at `uid: 0`, which names nothing on the server until COPYUID lands (#1387). */
|
|
55
|
+
const awaitsCopiedUid = (message) => message.status === MessageStatus.moving && message.uid === 0;
|
|
56
|
+
export const placementBindingOf = (message) => bindsForeignUid(message) || awaitsCopiedUid(message)
|
|
57
|
+
? "in_flight"
|
|
58
|
+
: "consistent";
|
|
59
|
+
/**
|
|
60
|
+
* Block a dependent mutation until the row's placement settles, then hand back
|
|
61
|
+
* the confirmed row — the wait half of docs/architecture/imap-mutations.md R2.
|
|
62
|
+
* A move ordinarily settles in well under a second, so blocking is the cheap
|
|
63
|
+
* option the doc's default guidance names. The row comes back still unsettled
|
|
64
|
+
* when the deadline passes; the caller decides what a dependency that never
|
|
65
|
+
* settled means for it.
|
|
66
|
+
*/
|
|
67
|
+
export const waitForPlacementToSettle = async (messageService, messageId, { timeoutMs, pollMs }) => {
|
|
68
|
+
const deadline = Date.now() + timeoutMs;
|
|
69
|
+
let message = await messageService.get(messageId);
|
|
70
|
+
while (isPlacementUnsettled(message) && Date.now() < deadline) {
|
|
71
|
+
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
72
|
+
message = await messageService.get(messageId);
|
|
73
|
+
}
|
|
74
|
+
return message;
|
|
75
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { IMailboxSpecialUseRepository, IQuarantineRepository, MessageData, MessageItem, QuarantineItem, QuarantineMimeNodeItem } from "@remit/data-ports";
|
|
2
|
+
type CanonicalRole = NonNullable<QuarantineItem["mailboxRole"]>;
|
|
3
|
+
type FailureStage = QuarantineItem["failureStage"];
|
|
4
|
+
type FailureCode = QuarantineItem["failureCode"];
|
|
5
|
+
/**
|
|
6
|
+
* The canonical role of the folder a quarantined message arrived in, read from
|
|
7
|
+
* the server's own RFC 6154 SPECIAL-USE declaration — the same source the
|
|
8
|
+
* appointment flow seeds from. A folder the server declares nothing about has
|
|
9
|
+
* no role, which is the normal state for a plain folder and is why the field
|
|
10
|
+
* is optional. `Important` is a SPECIAL-USE with no canonical role and drops
|
|
11
|
+
* out here rather than being invented into one.
|
|
12
|
+
*/
|
|
13
|
+
export declare const resolveMailboxRole: (mailboxPath: string, specialUse: readonly string[]) => CanonicalRole | undefined;
|
|
14
|
+
/** Everything about the quarantine that is the same for every uid in a round. */
|
|
15
|
+
export interface QuarantineContext {
|
|
16
|
+
accountId: string;
|
|
17
|
+
accountConfigId: string;
|
|
18
|
+
mailboxId: string;
|
|
19
|
+
mailboxPath: string;
|
|
20
|
+
uidValidity: number;
|
|
21
|
+
/** Rounds tried before the message was set aside. */
|
|
22
|
+
attempts: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The repro fingerprint: what the message declared itself to be. Every field
|
|
26
|
+
* is optional-by-absence because a message can fail before its shape was read,
|
|
27
|
+
* and a required column would force the writer to invent a value or drop the
|
|
28
|
+
* record.
|
|
29
|
+
*/
|
|
30
|
+
export interface QuarantineMessageShape {
|
|
31
|
+
contentType?: string;
|
|
32
|
+
transferEncoding?: string;
|
|
33
|
+
charset?: string;
|
|
34
|
+
sizeBytes?: number;
|
|
35
|
+
structure: QuarantineMimeNodeItem[];
|
|
36
|
+
messageIdHash?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface QuarantineFailure {
|
|
39
|
+
stage: FailureStage;
|
|
40
|
+
code: FailureCode;
|
|
41
|
+
/** Parser error text. Stored, shown on screen, never published. */
|
|
42
|
+
message: string;
|
|
43
|
+
partPath?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Message shape off the rows metadata sync already wrote. The body path has no
|
|
47
|
+
* FETCH result to read — it streams raw bytes — but the MIME tree it needs was
|
|
48
|
+
* walked into BodyPart rows when the message's metadata was synced, so the
|
|
49
|
+
* fingerprint comes from there rather than from re-reading headers the parser
|
|
50
|
+
* has just refused.
|
|
51
|
+
*/
|
|
52
|
+
export declare const shapeFromMessageData: (message: Pick<MessageItem, "rfc822Size">, data: MessageData, messageIdHeader: string | undefined) => QuarantineMessageShape;
|
|
53
|
+
/**
|
|
54
|
+
* The set of messages already set aside, held for the duration of one sync
|
|
55
|
+
* round. A round loads it once and filters against it in memory: the list is
|
|
56
|
+
* small by design — a growing one is a bug being reported, not a page to
|
|
57
|
+
* paginate — and a lookup per message would put a query on the hot path for a
|
|
58
|
+
* state that is almost always empty.
|
|
59
|
+
*/
|
|
60
|
+
export declare class QuarantinedUids {
|
|
61
|
+
private readonly keys;
|
|
62
|
+
constructor(entries: readonly QuarantineItem[]);
|
|
63
|
+
get size(): number;
|
|
64
|
+
has(mailboxId: string, uidValidity: number, uid: number): boolean;
|
|
65
|
+
}
|
|
66
|
+
export interface QuarantineLogger {
|
|
67
|
+
info(obj: Record<string, unknown>, msg: string): void;
|
|
68
|
+
warn(obj: Record<string, unknown>, msg: string): void;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Writes the record a message becomes when the sync path cannot apply it
|
|
72
|
+
* (issue #72), and loads the set a round filters against.
|
|
73
|
+
*
|
|
74
|
+
* Only a message defect reaches this class. Deciding that is the caller's job
|
|
75
|
+
* and it is made at exactly one kind of catch site — one narrow enough that
|
|
76
|
+
* the error can only have come from the message itself. An S3, queue or
|
|
77
|
+
* database failure propagates; writing one here would advance a cursor past
|
|
78
|
+
* mail that is fine and tell the user it was unreadable.
|
|
79
|
+
*/
|
|
80
|
+
export declare class QuarantineService {
|
|
81
|
+
private readonly repository;
|
|
82
|
+
private readonly mailboxSpecialUseService;
|
|
83
|
+
private readonly workerVersion;
|
|
84
|
+
private readonly log;
|
|
85
|
+
constructor(repository: IQuarantineRepository, mailboxSpecialUseService: IMailboxSpecialUseRepository, workerVersion: string, log: QuarantineLogger);
|
|
86
|
+
load(accountConfigId: string): Promise<QuarantinedUids>;
|
|
87
|
+
/**
|
|
88
|
+
* Set a message aside. Resolves only once the row is durable, because the
|
|
89
|
+
* caller's next act is to let a cursor move past this uid — and a cursor
|
|
90
|
+
* that moves past work no record survives is the silent loss this whole
|
|
91
|
+
* feature exists to end.
|
|
92
|
+
*/
|
|
93
|
+
record(context: QuarantineContext, uid: number, failure: QuarantineFailure, shape?: QuarantineMessageShape): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
export {};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { quarantineMessageIdHash, ROOT_PART_PATH } from "@remit/data-ports/id";
|
|
2
|
+
import { CanonicalMailboxRole } from "@remit/domain-enums";
|
|
3
|
+
const CANONICAL_ROLES = new Set(Object.values(CanonicalMailboxRole));
|
|
4
|
+
/**
|
|
5
|
+
* The canonical role of the folder a quarantined message arrived in, read from
|
|
6
|
+
* the server's own RFC 6154 SPECIAL-USE declaration — the same source the
|
|
7
|
+
* appointment flow seeds from. A folder the server declares nothing about has
|
|
8
|
+
* no role, which is the normal state for a plain folder and is why the field
|
|
9
|
+
* is optional. `Important` is a SPECIAL-USE with no canonical role and drops
|
|
10
|
+
* out here rather than being invented into one.
|
|
11
|
+
*/
|
|
12
|
+
export const resolveMailboxRole = (mailboxPath, specialUse) => {
|
|
13
|
+
if (mailboxPath.toUpperCase() === "INBOX")
|
|
14
|
+
return CanonicalMailboxRole.Inbox;
|
|
15
|
+
const matched = specialUse.find((use) => CANONICAL_ROLES.has(use));
|
|
16
|
+
return matched;
|
|
17
|
+
};
|
|
18
|
+
const EMPTY_SHAPE = { structure: [] };
|
|
19
|
+
/**
|
|
20
|
+
* Message shape off the rows metadata sync already wrote. The body path has no
|
|
21
|
+
* FETCH result to read — it streams raw bytes — but the MIME tree it needs was
|
|
22
|
+
* walked into BodyPart rows when the message's metadata was synced, so the
|
|
23
|
+
* fingerprint comes from there rather than from re-reading headers the parser
|
|
24
|
+
* has just refused.
|
|
25
|
+
*/
|
|
26
|
+
export const shapeFromMessageData = (message, data, messageIdHeader) => {
|
|
27
|
+
const parts = [...data.bodyPart].sort((a, b) => a.partPath.localeCompare(b.partPath, "en", { numeric: true }));
|
|
28
|
+
const root = parts.find((part) => part.partPath === ROOT_PART_PATH);
|
|
29
|
+
const rootCharset = root
|
|
30
|
+
? data.bodyPartParameter.find((param) => param.bodyPartId === root.bodyPartId &&
|
|
31
|
+
param.parameterName.toLowerCase() === "charset")?.parameterValue
|
|
32
|
+
: undefined;
|
|
33
|
+
return {
|
|
34
|
+
...(root ? { contentType: `${root.mediaType}/${root.mediaSubtype}` } : {}),
|
|
35
|
+
...(root ? { transferEncoding: root.transferEncoding } : {}),
|
|
36
|
+
...(rootCharset ? { charset: rootCharset } : {}),
|
|
37
|
+
...(message.rfc822Size ? { sizeBytes: message.rfc822Size } : {}),
|
|
38
|
+
structure: parts.map((part) => ({
|
|
39
|
+
depth: partPathDepth(part.partPath),
|
|
40
|
+
contentType: `${part.mediaType}/${part.mediaSubtype}`,
|
|
41
|
+
})),
|
|
42
|
+
...hashOf(messageIdHeader),
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
const partPathDepth = (partPath) => partPath === ROOT_PART_PATH ? 0 : partPath.split(".").length;
|
|
46
|
+
const hashOf = (messageIdHeader) => {
|
|
47
|
+
const messageIdHash = quarantineMessageIdHash(messageIdHeader);
|
|
48
|
+
return messageIdHash ? { messageIdHash } : {};
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* The set of messages already set aside, held for the duration of one sync
|
|
52
|
+
* round. A round loads it once and filters against it in memory: the list is
|
|
53
|
+
* small by design — a growing one is a bug being reported, not a page to
|
|
54
|
+
* paginate — and a lookup per message would put a query on the hot path for a
|
|
55
|
+
* state that is almost always empty.
|
|
56
|
+
*/
|
|
57
|
+
export class QuarantinedUids {
|
|
58
|
+
keys;
|
|
59
|
+
constructor(entries) {
|
|
60
|
+
this.keys = new Set(entries.map((entry) => uidKey(entry.mailboxId, entry.uidValidity, entry.uid)));
|
|
61
|
+
}
|
|
62
|
+
get size() {
|
|
63
|
+
return this.keys.size;
|
|
64
|
+
}
|
|
65
|
+
has(mailboxId, uidValidity, uid) {
|
|
66
|
+
return this.keys.has(uidKey(mailboxId, uidValidity, uid));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const uidKey = (mailboxId, uidValidity, uid) => `${mailboxId}:${uidValidity}:${uid}`;
|
|
70
|
+
/**
|
|
71
|
+
* Writes the record a message becomes when the sync path cannot apply it
|
|
72
|
+
* (issue #72), and loads the set a round filters against.
|
|
73
|
+
*
|
|
74
|
+
* Only a message defect reaches this class. Deciding that is the caller's job
|
|
75
|
+
* and it is made at exactly one kind of catch site — one narrow enough that
|
|
76
|
+
* the error can only have come from the message itself. An S3, queue or
|
|
77
|
+
* database failure propagates; writing one here would advance a cursor past
|
|
78
|
+
* mail that is fine and tell the user it was unreadable.
|
|
79
|
+
*/
|
|
80
|
+
export class QuarantineService {
|
|
81
|
+
repository;
|
|
82
|
+
mailboxSpecialUseService;
|
|
83
|
+
workerVersion;
|
|
84
|
+
log;
|
|
85
|
+
constructor(repository, mailboxSpecialUseService, workerVersion, log) {
|
|
86
|
+
this.repository = repository;
|
|
87
|
+
this.mailboxSpecialUseService = mailboxSpecialUseService;
|
|
88
|
+
this.workerVersion = workerVersion;
|
|
89
|
+
this.log = log;
|
|
90
|
+
}
|
|
91
|
+
async load(accountConfigId) {
|
|
92
|
+
return new QuarantinedUids(await this.repository.listByAccountConfigId(accountConfigId));
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Set a message aside. Resolves only once the row is durable, because the
|
|
96
|
+
* caller's next act is to let a cursor move past this uid — and a cursor
|
|
97
|
+
* that moves past work no record survives is the silent loss this whole
|
|
98
|
+
* feature exists to end.
|
|
99
|
+
*/
|
|
100
|
+
async record(context, uid, failure, shape = EMPTY_SHAPE) {
|
|
101
|
+
// Resolved here rather than per round: a round almost never writes a row,
|
|
102
|
+
// so the lookup belongs on the write and not on the hot path.
|
|
103
|
+
const specialUse = await this.mailboxSpecialUseService.listByMailboxId(context.mailboxId);
|
|
104
|
+
const mailboxRole = resolveMailboxRole(context.mailboxPath, specialUse.map((entry) => entry.specialUse));
|
|
105
|
+
await this.repository.upsert({
|
|
106
|
+
accountConfigId: context.accountConfigId,
|
|
107
|
+
accountId: context.accountId,
|
|
108
|
+
mailboxId: context.mailboxId,
|
|
109
|
+
uidValidity: context.uidValidity,
|
|
110
|
+
uid,
|
|
111
|
+
...(mailboxRole ? { mailboxRole } : {}),
|
|
112
|
+
mailboxPath: context.mailboxPath,
|
|
113
|
+
quarantinedAt: Date.now(),
|
|
114
|
+
attempts: context.attempts,
|
|
115
|
+
failureStage: failure.stage,
|
|
116
|
+
failureCode: failure.code,
|
|
117
|
+
failureMessage: failure.message,
|
|
118
|
+
...(failure.partPath ? { failurePartPath: failure.partPath } : {}),
|
|
119
|
+
workerVersion: this.workerVersion,
|
|
120
|
+
...shape,
|
|
121
|
+
});
|
|
122
|
+
this.log.warn({
|
|
123
|
+
mailboxId: context.mailboxId,
|
|
124
|
+
mailboxPath: context.mailboxPath,
|
|
125
|
+
uid,
|
|
126
|
+
uidValidity: context.uidValidity,
|
|
127
|
+
failureStage: failure.stage,
|
|
128
|
+
failureCode: failure.code,
|
|
129
|
+
}, "Message quarantined; cursor may advance past it");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { MailboxItem } from "@remit/data-ports";
|
|
2
|
+
export type AccessMode = "read" | "act";
|
|
3
|
+
export type Refusal = {
|
|
4
|
+
kind: "Invalid";
|
|
5
|
+
message: string;
|
|
6
|
+
} | {
|
|
7
|
+
kind: "Forbidden";
|
|
8
|
+
message: string;
|
|
9
|
+
} | {
|
|
10
|
+
kind: "NotFound";
|
|
11
|
+
message: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: "Conflict";
|
|
14
|
+
message: string;
|
|
15
|
+
} | {
|
|
16
|
+
kind: "NotSettled";
|
|
17
|
+
message: string;
|
|
18
|
+
mailboxId: string;
|
|
19
|
+
syncStatus: MailboxItem["syncStatus"];
|
|
20
|
+
};
|
|
21
|
+
export interface Refused {
|
|
22
|
+
ok: false;
|
|
23
|
+
refusal: Refusal;
|
|
24
|
+
}
|
|
25
|
+
export type Refusable<T> = {
|
|
26
|
+
ok: true;
|
|
27
|
+
value: T;
|
|
28
|
+
} | Refused;
|
|
29
|
+
export declare const accept: <T>(value: T) => Refusable<T>;
|
|
30
|
+
export declare const refuse: (refusal: Refusal) => Refused;
|
|
31
|
+
export declare const invalid: (message: string) => Refused;
|
package/dist/refusal.js
ADDED
|
@@ -1,52 +1,37 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
IMessageRepository,
|
|
3
|
-
IThreadMessageRepository,
|
|
4
|
-
MessageItem,
|
|
5
|
-
ThreadMessageItem,
|
|
6
|
-
} from "@remit/data-ports";
|
|
7
|
-
import { isNotFoundError } from "@remit/data-ports/errors";
|
|
8
|
-
import { MessageStatus } from "@remit/domain-enums";
|
|
9
|
-
import { buildThreadMessageMoveRevert } from "./thread-message-rows.js";
|
|
10
|
-
|
|
1
|
+
import type { IMessageRepository, IThreadMessageRepository, MessageItem, ThreadMessageItem } from "@remit/data-ports";
|
|
11
2
|
export interface RestoreSourcePlacementDeps {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
IThreadMessageRepository,
|
|
15
|
-
"findAllByMessageId" | "update"
|
|
16
|
-
>;
|
|
3
|
+
messageService: Pick<IMessageRepository, "transitionPlacement">;
|
|
4
|
+
threadMessageService: Pick<IThreadMessageRepository, "findAllByMessageId" | "update">;
|
|
17
5
|
}
|
|
18
|
-
|
|
19
6
|
/** Whether the row was still owed a restore when this ran. */
|
|
20
7
|
export type RestoreSourcePlacementOutcome = "restored" | "superseded";
|
|
21
|
-
|
|
22
8
|
export interface RestoreSourcePlacementInput {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
9
|
+
accountConfigId: string;
|
|
10
|
+
messageId: string;
|
|
11
|
+
sourceMailboxId: string;
|
|
12
|
+
uid: number;
|
|
13
|
+
/**
|
|
14
|
+
* What the row records about the mutation that will now never happen.
|
|
15
|
+
* `synced` where the row is a faithful projection of the source again and
|
|
16
|
+
* nothing was refused. `abandoned` where a mutation the user asked for gave
|
|
17
|
+
* up, which is a failure the row has to carry (imap-mutations R3).
|
|
18
|
+
*/
|
|
19
|
+
syncStatus: MessageItem["syncStatus"];
|
|
20
|
+
/**
|
|
21
|
+
* Which mutation gave up, for the row to carry alongside `abandoned`. The
|
|
22
|
+
* hand-back is about to set `status` back to `active`, and `status` was the
|
|
23
|
+
* only field naming it — without this the client can see that something was
|
|
24
|
+
* abandoned and not what, which is how a move that handed back was reported
|
|
25
|
+
* as a failed delete (issue #1229). `none` accompanies every other
|
|
26
|
+
* `syncStatus`, so the field never outlives its gate.
|
|
27
|
+
*/
|
|
28
|
+
abandonedMutation: MessageItem["abandonedMutation"];
|
|
29
|
+
/**
|
|
30
|
+
* The listing rows, where the caller has already read them to pick this
|
|
31
|
+
* outcome. Omitted, they are read here.
|
|
32
|
+
*/
|
|
33
|
+
threadMessages?: ThreadMessageItem[];
|
|
48
34
|
}
|
|
49
|
-
|
|
50
35
|
/**
|
|
51
36
|
* Put a row back on the pre-mutation source pair and hand its listing rows back
|
|
52
37
|
* with it.
|
|
@@ -97,57 +82,4 @@ export interface RestoreSourcePlacementInput {
|
|
|
97
82
|
* `.catch()` and cost the record its ack and its resync, which is the contract
|
|
98
83
|
* the terminal resolvers state: never re-thrown.
|
|
99
84
|
*/
|
|
100
|
-
export const restoreSourcePlacement
|
|
101
|
-
deps: RestoreSourcePlacementDeps,
|
|
102
|
-
input: RestoreSourcePlacementInput,
|
|
103
|
-
): Promise<RestoreSourcePlacementOutcome> => {
|
|
104
|
-
const {
|
|
105
|
-
accountConfigId,
|
|
106
|
-
messageId,
|
|
107
|
-
sourceMailboxId,
|
|
108
|
-
uid,
|
|
109
|
-
syncStatus,
|
|
110
|
-
abandonedMutation,
|
|
111
|
-
} = input;
|
|
112
|
-
|
|
113
|
-
const skipNotFound = (error: unknown): void => {
|
|
114
|
-
if (isNotFoundError(error)) return;
|
|
115
|
-
throw error;
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
const restored = await deps.messageService.transitionPlacement(
|
|
119
|
-
messageId,
|
|
120
|
-
{ status: [MessageStatus.moving, MessageStatus.deleting] },
|
|
121
|
-
{
|
|
122
|
-
mailboxId: sourceMailboxId,
|
|
123
|
-
uid,
|
|
124
|
-
status: MessageStatus.active,
|
|
125
|
-
syncStatus,
|
|
126
|
-
abandonedMutation,
|
|
127
|
-
},
|
|
128
|
-
);
|
|
129
|
-
if (!restored) return "superseded";
|
|
130
|
-
|
|
131
|
-
const threadMessages =
|
|
132
|
-
input.threadMessages ??
|
|
133
|
-
(await deps.threadMessageService.findAllByMessageId(
|
|
134
|
-
accountConfigId,
|
|
135
|
-
messageId,
|
|
136
|
-
));
|
|
137
|
-
for (const threadMessage of threadMessages) {
|
|
138
|
-
const args = buildThreadMessageMoveRevert(
|
|
139
|
-
threadMessage,
|
|
140
|
-
uid,
|
|
141
|
-
sourceMailboxId,
|
|
142
|
-
);
|
|
143
|
-
await deps.threadMessageService
|
|
144
|
-
.update(
|
|
145
|
-
threadMessage.accountConfigId,
|
|
146
|
-
threadMessage.threadMessageId,
|
|
147
|
-
args.set,
|
|
148
|
-
{ composites: args.composites },
|
|
149
|
-
)
|
|
150
|
-
.catch(skipNotFound);
|
|
151
|
-
}
|
|
152
|
-
return "restored";
|
|
153
|
-
};
|
|
85
|
+
export declare const restoreSourcePlacement: (deps: RestoreSourcePlacementDeps, input: RestoreSourcePlacementInput) => Promise<RestoreSourcePlacementOutcome>;
|