@remit/imap-worker 0.0.72 → 0.0.73
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/package.json +1 -1
- package/src/handlers/append-sent-message.ts +1 -1
- package/src/handlers/empty-trash.test.ts +118 -3
- package/src/handlers/empty-trash.ts +90 -31
- package/src/handlers/flag-push-placement-race.test.ts +18 -4
- package/src/handlers/flag-push.ts +1 -1
- package/src/handlers/mailbox-management.ts +1 -1
- package/src/handlers/message-copy.test.ts +56 -25
- package/src/handlers/message-copy.ts +43 -20
- package/src/handlers/message-delete-terminal.ts +11 -7
- package/src/handlers/message-delete.test.ts +225 -88
- package/src/handlers/message-delete.ts +92 -12
- package/src/handlers/message-move-terminal.test.ts +65 -20
- package/src/handlers/message-move-terminal.ts +10 -6
- package/src/handlers/message-move.test.ts +26 -17
- package/src/handlers/message-move.ts +27 -10
- package/src/handlers/placement-move-push.ts +2 -1
- package/src/handlers/sync-message-body.ts +1 -1
- package/src/handlers/sync-messages.ts +1 -1
- package/src/handlers/restore-source-placement.ts +0 -135
- package/src/handlers/thread-message-rows.ts +0 -77
- package/src/is-not-found.ts +0 -17
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { getClient } from "@remit/backend/client";
|
|
2
|
-
import {
|
|
2
|
+
import { isNotFoundError } from "@remit/data-ports/errors";
|
|
3
|
+
import {
|
|
4
|
+
MessageMutation,
|
|
5
|
+
MessageStatus,
|
|
6
|
+
MessageSyncStatus,
|
|
7
|
+
} from "@remit/domain-enums";
|
|
3
8
|
import type { Logger } from "@remit/logger-lambda";
|
|
4
9
|
import {
|
|
5
10
|
guardConnectionCursor,
|
|
6
11
|
type IImapConnection,
|
|
7
12
|
isCursorRebuildNeeded,
|
|
8
|
-
isPlacementUnsettled,
|
|
9
13
|
MailboxCursorPausedError,
|
|
10
14
|
reconcileStaleMessage,
|
|
11
15
|
} from "@remit/mailbox-service";
|
|
@@ -14,7 +18,6 @@ import { isAccountDeleted } from "../account-check.js";
|
|
|
14
18
|
import { createConnectionScopeWithCredentials } from "../connection-scope.js";
|
|
15
19
|
import { emitEvent } from "../emit.js";
|
|
16
20
|
import type { MessageCopyEvent } from "../events.js";
|
|
17
|
-
import { isNotFoundError } from "../is-not-found.js";
|
|
18
21
|
import { withOAuthLifecycle } from "../with-oauth-lifecycle.js";
|
|
19
22
|
import { buildLifecycleDeps } from "../with-oauth-lifecycle-deps.js";
|
|
20
23
|
import {
|
|
@@ -147,7 +150,12 @@ export const handleMessageCopy = async (
|
|
|
147
150
|
// This copy already settled — `updateUid` cleared `status: moving` when the
|
|
148
151
|
// server confirmed it. A redelivery reaching here would COPY the message a
|
|
149
152
|
// second time, and COPY has no source-side effect to make that a no-op.
|
|
150
|
-
|
|
153
|
+
//
|
|
154
|
+
// `moving` specifically, not the shared unsettled predicate: the question is
|
|
155
|
+
// whether THIS copy is still outstanding, and a `deleting` row is somebody
|
|
156
|
+
// else's work — reading it as unsettled is what would let the second COPY
|
|
157
|
+
// through.
|
|
158
|
+
if (copyRow.status !== MessageStatus.moving) {
|
|
151
159
|
log.info(
|
|
152
160
|
{ accountId, newMessageId, uid: copyRow.uid, status: copyRow.status },
|
|
153
161
|
"Skipping MESSAGE_COPY: the copy already settled against confirmed IMAP state",
|
|
@@ -206,17 +214,15 @@ export const handleMessageCopy = async (
|
|
|
206
214
|
};
|
|
207
215
|
|
|
208
216
|
const settleCopied = async (newUid: number): Promise<void> => {
|
|
217
|
+
// `updateUid` writes the confirmed uid, `active` and `synced` in one
|
|
218
|
+
// statement, so the second write this used to make said nothing the
|
|
219
|
+
// first had not — and said it without a predicate.
|
|
209
220
|
await messageService.updateUid(
|
|
210
221
|
newMessageId,
|
|
211
222
|
newUid,
|
|
212
223
|
destinationMailboxId,
|
|
213
224
|
);
|
|
214
225
|
|
|
215
|
-
await messageService.update(newMessageId, {
|
|
216
|
-
status: MessageStatus.active,
|
|
217
|
-
syncStatus: MessageSyncStatus.synced,
|
|
218
|
-
});
|
|
219
|
-
|
|
220
226
|
const threadMessage = await threadMessageService.findByMessageId(
|
|
221
227
|
account.accountConfigId,
|
|
222
228
|
newMessageId,
|
|
@@ -293,10 +299,20 @@ export const handleMessageCopy = async (
|
|
|
293
299
|
// that turns out to be there. Silence buys the safer half of one pair
|
|
294
300
|
// and the worse half of the other.
|
|
295
301
|
const settleBroken = async (reason: string): Promise<void> => {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
302
|
+
// A transition, not a plain update (imap-mutations R3): the copy row
|
|
303
|
+
// is only this handler's to settle while its own placement is still
|
|
304
|
+
// unsettled. The row it leaves is `deleted`, so no listing carries it
|
|
305
|
+
// and the marker is read by nothing — it is written anyway, because a
|
|
306
|
+
// give-up that does not name itself is the gap #1229 came from.
|
|
307
|
+
await messageService.transitionPlacement(
|
|
308
|
+
newMessageId,
|
|
309
|
+
{ status: MessageStatus.moving },
|
|
310
|
+
{
|
|
311
|
+
status: MessageStatus.deleted,
|
|
312
|
+
syncStatus: MessageSyncStatus.abandoned,
|
|
313
|
+
abandonedMutation: MessageMutation.copy,
|
|
314
|
+
},
|
|
315
|
+
);
|
|
300
316
|
log.error(
|
|
301
317
|
{
|
|
302
318
|
alert: "message_copy_unconfirmed",
|
|
@@ -495,10 +511,15 @@ export const handleMessageCopy = async (
|
|
|
495
511
|
{ sourceMessageId, uid },
|
|
496
512
|
"Source message not found on IMAP, marking copy as failed",
|
|
497
513
|
);
|
|
498
|
-
await messageService.
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
514
|
+
await messageService.transitionPlacement(
|
|
515
|
+
newMessageId,
|
|
516
|
+
{ status: MessageStatus.moving },
|
|
517
|
+
{
|
|
518
|
+
status: MessageStatus.deleted,
|
|
519
|
+
syncStatus: MessageSyncStatus.abandoned,
|
|
520
|
+
abandonedMutation: MessageMutation.copy,
|
|
521
|
+
},
|
|
522
|
+
);
|
|
502
523
|
return;
|
|
503
524
|
}
|
|
504
525
|
|
|
@@ -506,9 +527,11 @@ export const handleMessageCopy = async (
|
|
|
506
527
|
// Transient copy failure — expected (connections drop). Queue
|
|
507
528
|
// redelivery retries, and the probe above keeps the retry from
|
|
508
529
|
// copying the message twice.
|
|
509
|
-
await messageService.
|
|
510
|
-
|
|
511
|
-
|
|
530
|
+
await messageService.transitionPlacement(
|
|
531
|
+
newMessageId,
|
|
532
|
+
{ status: MessageStatus.moving },
|
|
533
|
+
{ syncStatus: MessageSyncStatus.failed },
|
|
534
|
+
);
|
|
512
535
|
throw error;
|
|
513
536
|
}
|
|
514
537
|
|
|
@@ -2,14 +2,14 @@ import type {
|
|
|
2
2
|
IMessageRepository,
|
|
3
3
|
IThreadMessageRepository,
|
|
4
4
|
} from "@remit/data-ports";
|
|
5
|
-
import { MessageSyncStatus } from "@remit/domain-enums";
|
|
5
|
+
import { MessageMutation, MessageSyncStatus } from "@remit/domain-enums";
|
|
6
6
|
import {
|
|
7
7
|
type IImapConnection,
|
|
8
8
|
isMessageGoneFromOpenMailbox,
|
|
9
9
|
reconcileStaleMessage,
|
|
10
|
+
restoreSourcePlacement,
|
|
10
11
|
type StaleMessageReconcileDeps,
|
|
11
12
|
} from "@remit/mailbox-service";
|
|
12
|
-
import { restoreSourcePlacement } from "./restore-source-placement.js";
|
|
13
13
|
|
|
14
14
|
export interface MessageDeleteTerminalLogger {
|
|
15
15
|
info(obj: Record<string, unknown>, msg: string): void;
|
|
@@ -18,7 +18,7 @@ export interface MessageDeleteTerminalLogger {
|
|
|
18
18
|
|
|
19
19
|
export interface ResolveExhaustedMessageDeleteDeps
|
|
20
20
|
extends StaleMessageReconcileDeps {
|
|
21
|
-
messageService: Pick<IMessageRepository, "delete" | "
|
|
21
|
+
messageService: Pick<IMessageRepository, "delete" | "transitionPlacement">;
|
|
22
22
|
threadMessageService: Pick<
|
|
23
23
|
IThreadMessageRepository,
|
|
24
24
|
"findAllByMessageId" | "deleteMany" | "update"
|
|
@@ -60,7 +60,7 @@ export interface ResolveExhaustedMessageDeleteResult {
|
|
|
60
60
|
* effect, but it keeps failing: broken code or a broken account, not a
|
|
61
61
|
* transient blip. The row is put back where the server has just said the
|
|
62
62
|
* message is: `mailboxId` and `uid` return to the delete's source pair, and
|
|
63
|
-
* the thread rows lose the deletion mark the optimistic
|
|
63
|
+
* the thread rows lose the deletion mark the optimistic transition
|
|
64
64
|
* wrote (issue #1098).
|
|
65
65
|
*
|
|
66
66
|
* Settling `status` to `active` while leaving `mailboxId` on Trash is what
|
|
@@ -132,9 +132,13 @@ export const resolveExhaustedMessageDeleteFailure = async (
|
|
|
132
132
|
sourceMailboxId,
|
|
133
133
|
uid,
|
|
134
134
|
// The probe above just confirmed the pair, so the row is a faithful
|
|
135
|
-
// projection of the source again
|
|
136
|
-
// not
|
|
137
|
-
|
|
135
|
+
// projection of the source again — and the delete the user asked for did
|
|
136
|
+
// not happen, with nothing left to try it. `abandoned` is the value that
|
|
137
|
+
// says so without claiming a retry is coming (imap-mutations R3); the
|
|
138
|
+
// alert stays for the operator, and the mutation names itself so the
|
|
139
|
+
// reading pane offers the right way out (issue #1229).
|
|
140
|
+
syncStatus: MessageSyncStatus.abandoned,
|
|
141
|
+
abandonedMutation: MessageMutation.delete,
|
|
138
142
|
});
|
|
139
143
|
|
|
140
144
|
return { outcome: "broken" };
|