@remit/mailbox-service 0.0.120 → 0.0.122
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/body-sync.js +1 -19
- package/dist/message-move.d.ts +0 -4
- package/dist/message-move.js +5 -22
- package/dist/message-sync.js +6 -10
- package/dist/test-helpers/test-store.js +0 -2
- package/dist/thread-message-rows.d.ts +0 -1
- package/dist/thread-message-rows.js +0 -1
- package/package.json +1 -1
package/dist/body-sync.js
CHANGED
|
@@ -6,7 +6,7 @@ import { deriveAddressId, deriveBodyPartId } from "@remit/data-ports/id";
|
|
|
6
6
|
import { isEncapsulatedMessage } from "@remit/data-ports/message-parts";
|
|
7
7
|
import { isBulkSender } from "@remit/data-ports/wellknown";
|
|
8
8
|
import { FilterState, MessageClassificationState, PlacementAction, PlacementConfidence, QuarantineFailureStage, SenderTrust, SyncWarningStage, } from "@remit/domain-enums";
|
|
9
|
-
import { isStorageNotFoundError, } from "@remit/storage-service";
|
|
9
|
+
import { isStorageNotFoundError, MATERIALIZED_SENTINEL_PART_PATH, MATERIALIZED_SENTINEL_V2_PART_PATH, } from "@remit/storage-service";
|
|
10
10
|
import { Semaphore } from "async-mutex";
|
|
11
11
|
import { simpleParser } from "mailparser";
|
|
12
12
|
import pMap from "p-map";
|
|
@@ -42,24 +42,6 @@ export const isBodyPartDeferralEnabled = () => {
|
|
|
42
42
|
const normalized = raw.trim().toLowerCase();
|
|
43
43
|
return normalized !== "false" && normalized !== "0" && normalized !== "off";
|
|
44
44
|
};
|
|
45
|
-
/**
|
|
46
|
-
* Sentinel object written under a message's parts prefix once lazy
|
|
47
|
-
* materialization has stored EVERY leaf. The read path checks it first with a
|
|
48
|
-
* single HEAD and early-returns, skipping the body.eml GET, the MIME re-parse,
|
|
49
|
-
* and the per-leaf HEADs on every warm re-open.
|
|
50
|
-
*
|
|
51
|
-
* Deliberately an S3-only marker (no Message-row flag). A leading dot keeps it
|
|
52
|
-
* out of any real IMAP section path (those are dot-separated digits).
|
|
53
|
-
*/
|
|
54
|
-
const MATERIALIZED_SENTINEL_PART_PATH = ".materialized";
|
|
55
|
-
/**
|
|
56
|
-
* The sentinel for a message carrying a forwarded message. Those parts were
|
|
57
|
-
* once stored under a layout in which the forwarded message's root overwrote
|
|
58
|
-
* its own row, so a `.materialized` written then vouches for objects at the
|
|
59
|
-
* wrong paths. Its own name makes every such message materialize once more,
|
|
60
|
-
* overwriting what is there; no other message is touched.
|
|
61
|
-
*/
|
|
62
|
-
const MATERIALIZED_SENTINEL_V2_PART_PATH = ".materialized.2";
|
|
63
45
|
/**
|
|
64
46
|
* A mid-stream socket drop during the ranged body fetch surfaces as a typed
|
|
65
47
|
* `MailConnectionError` (the connection layer classifies imapflow's
|
package/dist/message-move.d.ts
CHANGED
|
@@ -249,10 +249,6 @@ export declare class MessageMoveService {
|
|
|
249
249
|
* batch's total wait is one row's ceiling rather than the sum across it.
|
|
250
250
|
*/
|
|
251
251
|
private settledPlacements;
|
|
252
|
-
/**
|
|
253
|
-
* Update ThreadMessage for move operations.
|
|
254
|
-
* Updates mailboxId and optionally isDeleted.
|
|
255
|
-
*/
|
|
256
252
|
private updateThreadMessageForMove;
|
|
257
253
|
/**
|
|
258
254
|
* Delete every ThreadMessage row that points at this messageId.
|
package/dist/message-move.js
CHANGED
|
@@ -261,7 +261,7 @@ export class MessageMoveService {
|
|
|
261
261
|
continue;
|
|
262
262
|
}
|
|
263
263
|
// Update ThreadMessage
|
|
264
|
-
await this.updateThreadMessageForMove(accountConfigId, messageId, trashMailbox.mailboxId
|
|
264
|
+
await this.updateThreadMessageForMove(accountConfigId, messageId, trashMailbox.mailboxId);
|
|
265
265
|
await this.addressService.reconcileJunkOnlyForMessage(messageId, configJunkRoles);
|
|
266
266
|
events.push({
|
|
267
267
|
event: {
|
|
@@ -380,10 +380,6 @@ export class MessageMoveService {
|
|
|
380
380
|
}
|
|
381
381
|
const sourceMailbox = await this.mailboxService.get(accountId, message.mailboxId);
|
|
382
382
|
const destinationMailbox = await this.mailboxService.get(accountId, destinationMailboxId);
|
|
383
|
-
// Check if moving to/from Trash
|
|
384
|
-
const trashMailbox = await this.mailboxSpecialUseService.findTrashMailbox(accountId);
|
|
385
|
-
const isMovingToTrash = Boolean(trashMailbox && destinationMailboxId === trashMailbox.mailboxId);
|
|
386
|
-
const isMovingFromTrash = Boolean(trashMailbox && message.mailboxId === trashMailbox.mailboxId);
|
|
387
383
|
// The optimistic write is a transition off the row this call read
|
|
388
384
|
// (imap-mutations R3). Three mailbox lookups stand between the read and
|
|
389
385
|
// here, and PLACEMENT_MOVE_PUSH runs on a queue this account's FIFO group
|
|
@@ -408,12 +404,8 @@ export class MessageMoveService {
|
|
|
408
404
|
throw new MessagePlacementUnsettledError(`Message ${messageId} was not moved: something else changed where it is while this move was being prepared`, accountId, messageId, "in_flight");
|
|
409
405
|
}
|
|
410
406
|
// Update ThreadMessage
|
|
411
|
-
await this.updateThreadMessageForMove(accountConfigId, messageId, destinationMailboxId
|
|
407
|
+
await this.updateThreadMessageForMove(accountConfigId, messageId, destinationMailboxId);
|
|
412
408
|
await this.addressService.reconcileJunkOnlyForMessage(messageId, await this.mailboxSpecialUseService.resolveJunkRolesForConfig(accountConfigId));
|
|
413
|
-
// If moving FROM Trash, clear isDeleted
|
|
414
|
-
if (isMovingFromTrash) {
|
|
415
|
-
await this.updateThreadMessageDeleted(accountConfigId, messageId, false);
|
|
416
|
-
}
|
|
417
409
|
this.log.info({
|
|
418
410
|
messageId,
|
|
419
411
|
from: sourceMailbox.fullPath,
|
|
@@ -478,15 +470,13 @@ export class MessageMoveService {
|
|
|
478
470
|
// the copy at uid=0 with an undelivered event; upsert no-ops on the row
|
|
479
471
|
// and lets the event be re-enqueued below, keeping the operation
|
|
480
472
|
// idempotent and the copy re-drivable (issue #75).
|
|
481
|
-
const copyRow = await this.messageService.
|
|
473
|
+
const copyRow = await this.messageService.copyMessage(messageId, {
|
|
482
474
|
messageId: newMessageId,
|
|
483
475
|
mailboxId: destinationMailboxId,
|
|
484
476
|
uid: 0, // Will be updated by worker after IMAP COPY
|
|
485
477
|
sequenceNumber: 0, // Will be updated by worker
|
|
486
478
|
rfc822Size: sourceMessage.rfc822Size,
|
|
487
479
|
internalDate: sourceMessage.internalDate,
|
|
488
|
-
envelopeId: sourceMessage.envelopeId, // Share envelope with source
|
|
489
|
-
rootBodyPartId: sourceMessage.rootBodyPartId, // Share body parts with source
|
|
490
480
|
status: MessageStatus.moving,
|
|
491
481
|
syncStatus: MessageSyncStatus.pending,
|
|
492
482
|
bodyStorageKey: sourceMessage.bodyStorageKey,
|
|
@@ -502,7 +492,6 @@ export class MessageMoveService {
|
|
|
502
492
|
classificationState: sourceMessage.classificationState,
|
|
503
493
|
hasListUnsubscribe: sourceMessage.hasListUnsubscribe,
|
|
504
494
|
});
|
|
505
|
-
await this.messageService.copySearchText(messageId, newMessageId);
|
|
506
495
|
// Copy ThreadMessage entry
|
|
507
496
|
const sourceThreadMessage = await this.threadMessageService.getByMessageId(accountConfigId, messageId);
|
|
508
497
|
const copyListing = await this.threadMessageService.create({
|
|
@@ -719,11 +708,7 @@ export class MessageMoveService {
|
|
|
719
708
|
* batch's total wait is one row's ceiling rather than the sum across it.
|
|
720
709
|
*/
|
|
721
710
|
settledPlacements = async (messages, accountId) => Promise.all(messages.map((message) => this.settledPlacement(message, accountId)));
|
|
722
|
-
|
|
723
|
-
* Update ThreadMessage for move operations.
|
|
724
|
-
* Updates mailboxId and optionally isDeleted.
|
|
725
|
-
*/
|
|
726
|
-
updateThreadMessageForMove = async (accountConfigId, messageId, newMailboxId, isDeleted) => {
|
|
711
|
+
updateThreadMessageForMove = async (accountConfigId, messageId, newMailboxId) => {
|
|
727
712
|
const threadMessage = await this.threadMessageService.getByMessageId(accountConfigId, messageId);
|
|
728
713
|
this.log.info({
|
|
729
714
|
messageId,
|
|
@@ -731,9 +716,8 @@ export class MessageMoveService {
|
|
|
731
716
|
threadMessageId: threadMessage.threadMessageId,
|
|
732
717
|
sentDate: threadMessage.sentDate,
|
|
733
718
|
newMailboxId,
|
|
734
|
-
isDeleted,
|
|
735
719
|
}, "Updating ThreadMessage for move");
|
|
736
|
-
await this.threadMessageService.update(threadMessage.accountConfigId, threadMessage.threadMessageId, { mailboxId: newMailboxId, isDeleted }, {
|
|
720
|
+
await this.threadMessageService.update(threadMessage.accountConfigId, threadMessage.threadMessageId, { mailboxId: newMailboxId, isDeleted: false }, {
|
|
737
721
|
// Composites contain CURRENT values for condition checking
|
|
738
722
|
// ElectroDB uses these to verify the item state before updating
|
|
739
723
|
composites: {
|
|
@@ -749,7 +733,6 @@ export class MessageMoveService {
|
|
|
749
733
|
messageId,
|
|
750
734
|
threadMessageId: threadMessage.threadMessageId,
|
|
751
735
|
newMailboxId,
|
|
752
|
-
isDeleted,
|
|
753
736
|
}, "Updated ThreadMessage for move");
|
|
754
737
|
};
|
|
755
738
|
/**
|
package/dist/message-sync.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { storedDisplayName } from "@remit/data-ports/display-name";
|
|
2
|
-
import { deriveAddressId,
|
|
2
|
+
import { deriveAddressId, deriveCopyMessageId, deriveEnvelopeId, deriveMessageId, deriveMessageIdFromSource, deriveThreadId, isValidMessageId, wireMessageId, } from "@remit/data-ports/id";
|
|
3
3
|
import { AddressRole, MailboxCursorState, MessageKeywordFlag, MessageSystemFlag, StarColor, SyncWarningStage, } from "@remit/domain-enums";
|
|
4
4
|
import pMap from "p-map";
|
|
5
5
|
import { confirmDepartures, placementKey, sightingContestsPlacement, } from "./external-move.js";
|
|
6
6
|
import { guardMailboxCursor, isCursorRebuildNeeded } from "./mailbox-cursor.js";
|
|
7
7
|
import { matchCursorRebuild, } from "./mailbox-cursor-rebuild.js";
|
|
8
|
-
import {
|
|
8
|
+
import { walkMimeStructure } from "./mime-walker.js";
|
|
9
9
|
import { advanceChangeCursor, advanceUidWatermarks, dropAppliedPrefix, formatChangeCursor, hasChangeCursor, orderByModseq, parseChangeCursor, parseModseq, } from "./sync-watermarks.js";
|
|
10
10
|
import { messageReferencesOf, parentOf, threadRootOf } from "./thread-root.js";
|
|
11
11
|
const MESSAGE_SAVE_CONCURRENCY = 10;
|
|
@@ -495,10 +495,9 @@ export class MessageSyncService {
|
|
|
495
495
|
}
|
|
496
496
|
}, { concurrency: MESSAGE_SAVE_CONCURRENCY });
|
|
497
497
|
const stale = new Set(staleMessageIds);
|
|
498
|
-
await
|
|
499
|
-
|
|
500
|
-
uid: row.uid
|
|
501
|
-
}), { concurrency: MESSAGE_SAVE_CONCURRENCY });
|
|
498
|
+
await this.messageService.deleteManyIfPlacement(mailboxId, existingRows
|
|
499
|
+
.filter((row) => stale.has(row.messageId))
|
|
500
|
+
.map((row) => ({ messageId: row.messageId, uid: row.uid })));
|
|
502
501
|
const newMessages = newUids.length > 0 ? await this.fetchMessageBatch(newUids) : [];
|
|
503
502
|
const applicable = newMessages.filter(hasEnvelope);
|
|
504
503
|
const roles = await this.folderRolesFor(accountId, accountConfigId);
|
|
@@ -1033,7 +1032,6 @@ export class MessageSyncService {
|
|
|
1033
1032
|
const envelope = msg.envelope;
|
|
1034
1033
|
const messageId = messageIdForSighting(accountId, mailboxId, msg);
|
|
1035
1034
|
const envelopeId = deriveEnvelopeId(messageId);
|
|
1036
|
-
const rootBodyPartId = deriveBodyPartId(messageId, ROOT_PART_PATH);
|
|
1037
1035
|
const internalDateMs = msg.internalDate.getTime();
|
|
1038
1036
|
const { value: sentDate, usedFallback: dateFellBack } = parseHeaderDate(envelope.date, internalDateMs);
|
|
1039
1037
|
if (dateFellBack) {
|
|
@@ -1095,8 +1093,6 @@ export class MessageSyncService {
|
|
|
1095
1093
|
sequenceNumber: msg.seq,
|
|
1096
1094
|
rfc822Size: msg.size ?? 0,
|
|
1097
1095
|
internalDate: msg.internalDate.getTime(),
|
|
1098
|
-
envelopeId,
|
|
1099
|
-
rootBodyPartId,
|
|
1100
1096
|
});
|
|
1101
1097
|
// The server has just named this folder and this UID for a message the
|
|
1102
1098
|
// database holds under another folder. `upsertWithStatus` left the row
|
|
@@ -1122,7 +1118,7 @@ export class MessageSyncService {
|
|
|
1122
1118
|
// re-point names the placement it read, writes nothing when the row has
|
|
1123
1119
|
// moved since, and the UID waits for the next round.
|
|
1124
1120
|
const repointed = repointable &&
|
|
1125
|
-
(await this.messageService.repointIfPlacement(messageId, { status: item.status, mailboxId: item.mailboxId, uid: item.uid }, { mailboxId, uid: msg.uid, isDeleted:
|
|
1121
|
+
(await this.messageService.repointIfPlacement(messageId, { status: item.status, mailboxId: item.mailboxId, uid: item.uid }, { mailboxId, uid: msg.uid, isDeleted: false }));
|
|
1126
1122
|
if (repointable && !repointed)
|
|
1127
1123
|
unresolvedSighting = true;
|
|
1128
1124
|
owned = created || item.mailboxId === mailboxId || repointed;
|
|
@@ -115,8 +115,6 @@ export const seedMessage = async (store, account, mailbox, uid, overrides = {})
|
|
|
115
115
|
sequenceNumber: uid,
|
|
116
116
|
rfc822Size: 100,
|
|
117
117
|
internalDate: 1_700_000_000_000 + uid,
|
|
118
|
-
envelopeId: randomUUID(),
|
|
119
|
-
rootBodyPartId: randomUUID(),
|
|
120
118
|
status: "active",
|
|
121
119
|
syncStatus: "synced",
|
|
122
120
|
...overrides.message,
|