@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 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
@@ -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.
@@ -261,7 +261,7 @@ export class MessageMoveService {
261
261
  continue;
262
262
  }
263
263
  // Update ThreadMessage
264
- await this.updateThreadMessageForMove(accountConfigId, messageId, trashMailbox.mailboxId, true);
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, isMovingToTrash);
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.upsertWithStatus({
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
  /**
@@ -1,11 +1,11 @@
1
1
  import { storedDisplayName } from "@remit/data-ports/display-name";
2
- import { deriveAddressId, deriveBodyPartId, deriveCopyMessageId, deriveEnvelopeId, deriveMessageId, deriveMessageIdFromSource, deriveThreadId, isValidMessageId, wireMessageId, } from "@remit/data-ports/id";
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 { ROOT_PART_PATH, walkMimeStructure } from "./mime-walker.js";
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 pMap(existingRows.filter((row) => stale.has(row.messageId)), (row) => this.messageService.deleteIfPlacement(row.messageId, {
499
- mailboxId,
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: sighting === "discarded" }));
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,
@@ -16,7 +16,6 @@ export declare const buildThreadMessageTrashUpdate: (threadMessage: ThreadMessag
16
16
  set: {
17
17
  uid: number;
18
18
  mailboxId: string;
19
- isDeleted: boolean;
20
19
  };
21
20
  composites: {
22
21
  sentDate: number;
@@ -10,7 +10,6 @@ export const buildThreadMessageTrashUpdate = (threadMessage, newUid, destination
10
10
  set: {
11
11
  uid: newUid,
12
12
  mailboxId: destinationMailboxId,
13
- isDeleted: true,
14
13
  },
15
14
  composites: currentComposites(threadMessage),
16
15
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/mailbox-service",
3
- "version": "0.0.120",
3
+ "version": "0.0.122",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",