@remit/mailbox-service 0.0.112 → 0.0.114
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 +129 -0
- package/dist/mailbox-queue.js +216 -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 +280 -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,351 @@
|
|
|
1
|
+
import { type IAddressRepository, type ICalendarStore, type ICalendarSuggestionRepository, type IEnvelopeRepository, type IFilterRepository, type IMailboxSpecialUseRepository, type IMessageRepository, type IThreadMessageRepository } from "@remit/data-ports";
|
|
2
|
+
import { type ParsedBody, type StorageService } from "@remit/storage-service";
|
|
3
|
+
import { type ParsedMail } from "mailparser";
|
|
4
|
+
import { type FilterConfig } from "./filters/pipeline.js";
|
|
5
|
+
import type { FlagQueueService } from "./flag-queue.js";
|
|
6
|
+
import { type FolderPlacement } from "./heuristics/classifyPlacement.js";
|
|
7
|
+
import type { PlacementMoveService } from "./placement-move.js";
|
|
8
|
+
import { type QuarantineService } from "./quarantine.js";
|
|
9
|
+
import type { SyncWarningService } from "./sync-warning.js";
|
|
10
|
+
import { type IImapConnection } from "./types.js";
|
|
11
|
+
/**
|
|
12
|
+
* Whether the body-sync hot path defers per-part S3 objects. Each MIME leaf is
|
|
13
|
+
* its own PutObject, so during bulk sync these per-part writes dominate S3 write
|
|
14
|
+
* count and cost. With deferral ON a synced message writes only `body.eml` +
|
|
15
|
+
* `parsed.json.gz` (2 writes); the per-part objects are materialized lazily on
|
|
16
|
+
* the first `contentUrl` read via {@link BodySyncService.ensureBodyPartsStored}.
|
|
17
|
+
*
|
|
18
|
+
* Default ON (cost savings). Set `DEFER_BODY_PARTS=false`/`0`/`off` to restore
|
|
19
|
+
* the original eager per-part writes — a safe, reversible escape hatch.
|
|
20
|
+
*/
|
|
21
|
+
export declare const isBodyPartDeferralEnabled: () => boolean;
|
|
22
|
+
/**
|
|
23
|
+
* The snippet the list row shows, from whichever body part carries text.
|
|
24
|
+
* Exported for the classification backfill (issue #1197), which denormalizes
|
|
25
|
+
* the same fields the body-store path does, from the same bytes.
|
|
26
|
+
*/
|
|
27
|
+
export declare const extractSnippet: (parsed: ParsedMail) => string;
|
|
28
|
+
export declare const toParsedBody: (parsed: ParsedMail) => ParsedBody;
|
|
29
|
+
export interface BodySyncLogger {
|
|
30
|
+
info(obj: Record<string, unknown>, msg: string): void;
|
|
31
|
+
debug?(obj: Record<string, unknown>, msg: string): void;
|
|
32
|
+
warn?(obj: Record<string, unknown>, msg: string): void;
|
|
33
|
+
error?(obj: Record<string, unknown>, msg: string): void;
|
|
34
|
+
}
|
|
35
|
+
export interface SyncBodiesResult {
|
|
36
|
+
syncedCount: number;
|
|
37
|
+
syncedMessageIds: string[];
|
|
38
|
+
skippedCount: number;
|
|
39
|
+
failedCount: number;
|
|
40
|
+
failedMessageIds: string[];
|
|
41
|
+
}
|
|
42
|
+
export interface FetchBodyResult {
|
|
43
|
+
text: string | null;
|
|
44
|
+
html: string | null;
|
|
45
|
+
storedAt: string;
|
|
46
|
+
}
|
|
47
|
+
export interface StoreBodyPartContentsResult {
|
|
48
|
+
stored: number;
|
|
49
|
+
}
|
|
50
|
+
export type ConnectionGetter = () => Promise<IImapConnection>;
|
|
51
|
+
export interface BodySyncFolder {
|
|
52
|
+
mailboxId: string;
|
|
53
|
+
fullPath: string;
|
|
54
|
+
placement: FolderPlacement;
|
|
55
|
+
}
|
|
56
|
+
export interface BodySyncRoundFolder extends BodySyncFolder {
|
|
57
|
+
uidValidity: number;
|
|
58
|
+
}
|
|
59
|
+
export interface PlacementConfig {
|
|
60
|
+
mailboxSpecialUseService: IMailboxSpecialUseRepository;
|
|
61
|
+
/**
|
|
62
|
+
* Local-first mover for a confident placement verdict (issue #1271). Same
|
|
63
|
+
* `moveMessage(accountConfigId, messageId, destinationMailboxId, accountId)`
|
|
64
|
+
* signature as the general-purpose `MessageMoveService` it replaced here,
|
|
65
|
+
* but backed by a pending-move marker + its own reconciler queue instead of
|
|
66
|
+
* the general MESSAGE_MOVE event (see `placement-move.ts`).
|
|
67
|
+
*/
|
|
68
|
+
placementMoveService: PlacementMoveService;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* What body sync needs to set a message aside (issue #72). The mailbox facts
|
|
72
|
+
* are supplied by the caller because they are properties of the round, not of
|
|
73
|
+
* any one message.
|
|
74
|
+
*/
|
|
75
|
+
export interface QuarantineConfig {
|
|
76
|
+
quarantineService: QuarantineService;
|
|
77
|
+
mailboxId: string;
|
|
78
|
+
/**
|
|
79
|
+
* The mailbox's stored UIDVALIDITY. Taken from the mailbox row rather than
|
|
80
|
+
* from the open box because a round whose two disagree is paused by the
|
|
81
|
+
* cursor guard before it reaches any UID.
|
|
82
|
+
*/
|
|
83
|
+
uidValidity: number;
|
|
84
|
+
/** Redeliveries of this batch so far, so the record says how hard we tried. */
|
|
85
|
+
attempts: number;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* What body sync needs to auto-mark-read a message from an unsubscribed
|
|
89
|
+
* sender (issue #302, RFC 039 Decision 3). Reuses the same
|
|
90
|
+
* `FlagQueueService.markAsRead` a manual mark-as-read already goes through —
|
|
91
|
+
* local `\Seen` + `ThreadMessage.isRead` + a durable pending IMAP flag-push
|
|
92
|
+
* marker — so this fires the same round-trip, not a second primitive.
|
|
93
|
+
*/
|
|
94
|
+
export interface UnsubscribeConfig {
|
|
95
|
+
flagQueueService: FlagQueueService;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* What body sync needs to offer a mail's invitation as a card (issue #1033).
|
|
99
|
+
*
|
|
100
|
+
* The calendar store is here to provision the account's default collection on
|
|
101
|
+
* first use, through the one function every other caller provisions with. That
|
|
102
|
+
* is not bookkeeping: an invitation whose DTSTART names no zone is RFC 5545
|
|
103
|
+
* floating time, and reading it anywhere but where the user's calendar lives
|
|
104
|
+
* shows the meeting at the wrong hour. Without a collection there is no zone
|
|
105
|
+
* to read it in, and every such event silently lands in UTC.
|
|
106
|
+
*
|
|
107
|
+
* `filterService` is read to honour `dismiss{muteSender:true}`: a sender the
|
|
108
|
+
* user muted gets no card at all.
|
|
109
|
+
*/
|
|
110
|
+
export interface CalendarSuggestionConfig {
|
|
111
|
+
calendarSuggestionService: ICalendarSuggestionRepository;
|
|
112
|
+
calendarStore: ICalendarStore;
|
|
113
|
+
filterService: Pick<IFilterRepository, "listByAccountAndState">;
|
|
114
|
+
}
|
|
115
|
+
export declare class BodySyncService {
|
|
116
|
+
private messageService;
|
|
117
|
+
private storageService;
|
|
118
|
+
private threadMessageService;
|
|
119
|
+
private addressService;
|
|
120
|
+
private envelopeService;
|
|
121
|
+
private readonly syncWarningService;
|
|
122
|
+
private readonly placementConfig?;
|
|
123
|
+
private readonly filterConfig?;
|
|
124
|
+
private readonly quarantineConfig?;
|
|
125
|
+
private readonly unsubscribeConfig?;
|
|
126
|
+
private readonly calendarConfig?;
|
|
127
|
+
private log;
|
|
128
|
+
private readonly filterPipeline?;
|
|
129
|
+
constructor(messageService: IMessageRepository, storageService: StorageService, threadMessageService: IThreadMessageRepository, addressService: IAddressRepository, envelopeService: IEnvelopeRepository, syncWarningService: SyncWarningService, logger?: BodySyncLogger, placementConfig?: PlacementConfig | undefined, filterConfig?: FilterConfig | undefined, quarantineConfig?: QuarantineConfig | undefined, unsubscribeConfig?: UnsubscribeConfig | undefined, calendarConfig?: CalendarSuggestionConfig | undefined);
|
|
130
|
+
syncBodies(messageIds: string[], accountId: string, accountConfigId: string, folder: BodySyncRoundFolder, getConnection: ConnectionGetter, force?: boolean): Promise<SyncBodiesResult>;
|
|
131
|
+
/**
|
|
132
|
+
* Set a message aside because its body will not parse (issue #72). The
|
|
133
|
+
* fingerprint comes from the BodyPart rows metadata sync already wrote: the
|
|
134
|
+
* body path streams raw bytes and has no FETCH result to read.
|
|
135
|
+
*/
|
|
136
|
+
private recordQuarantine;
|
|
137
|
+
private buildResult;
|
|
138
|
+
/**
|
|
139
|
+
* Stream one message body straight to storage while teeing the bytes into a
|
|
140
|
+
* buffer for the parse-dependent steps (snippet, classification, parsed-body
|
|
141
|
+
* cache, per-part objects). The S3 upload never sees a whole-body concat —
|
|
142
|
+
* the storage service streams it — but mailparser still needs the full bytes,
|
|
143
|
+
* so we collect them in parallel. Later issues move parsing off the hot path.
|
|
144
|
+
*
|
|
145
|
+
* `isReStore` marks a re-fetch of a message whose row already carried
|
|
146
|
+
* `bodyStorageKey` (the `force` re-arm in {@link syncBodies}): the
|
|
147
|
+
* post-store steps then re-materialize the body artifacts without
|
|
148
|
+
* re-running any decision (issue #1011).
|
|
149
|
+
*/
|
|
150
|
+
private storeStreamedBody;
|
|
151
|
+
/**
|
|
152
|
+
* The steps every synced body must go through regardless of which path
|
|
153
|
+
* fetched it first — the sync path ({@link storeStreamedBody}) and the
|
|
154
|
+
* read-path backfill ({@link fetchAndGetBody}) both call this instead of
|
|
155
|
+
* duplicating (and drifting on) their own subset. Before issue #1271,
|
|
156
|
+
* `fetchAndGetBody` skipped classification/placement entirely — a message
|
|
157
|
+
* materialized by a read got different treatment than one synced in bulk.
|
|
158
|
+
*
|
|
159
|
+
* Order matters for two independent reasons, both load-bearing:
|
|
160
|
+
* 1. The parsed-body cache must be durable before `bodyStorageKey` (the
|
|
161
|
+
* skip-guard signal) — see the comment on the write below.
|
|
162
|
+
* 2. A placement move — local mailboxId change, pending marker, IMAP-push
|
|
163
|
+
* enqueue (`PlacementMoveService.moveMessage`, issue #1271) — must
|
|
164
|
+
* complete, or fully fail, BEFORE `bodyStorageKey` is written. That
|
|
165
|
+
* failure is never swallowed: if it throws, `bodyStorageKey` is not yet
|
|
166
|
+
* durable, so a retry reprocesses the message from scratch (marker
|
|
167
|
+
* included) instead of the skip-guard stranding a `movedByRemit` flag
|
|
168
|
+
* with no marker behind it — the defect this issue fixes.
|
|
169
|
+
*
|
|
170
|
+
* Returns the parsed mail so callers that already need it (both do) don't
|
|
171
|
+
* pay for mailparser twice.
|
|
172
|
+
*/
|
|
173
|
+
private applyPostStoreSteps;
|
|
174
|
+
/**
|
|
175
|
+
* Fetch a single message body, store it, and return the parsed content.
|
|
176
|
+
*
|
|
177
|
+
* If the body is already stored, retrieves it from storage.
|
|
178
|
+
* Otherwise fetches from IMAP, stores it, and returns the parsed content.
|
|
179
|
+
*
|
|
180
|
+
* @param messageId - The message ID to fetch
|
|
181
|
+
* @param accountId - The account ID (for storage path)
|
|
182
|
+
* @param accountConfigId - The account config ID (for thread updates)
|
|
183
|
+
* @param folder - The IMAP mailbox path and its folder placement
|
|
184
|
+
* @param getConnection - Function to get a (lazy) IMAP connection
|
|
185
|
+
* @returns Parsed text and HTML content
|
|
186
|
+
*/
|
|
187
|
+
fetchAndGetBody(messageId: string, accountId: string, accountConfigId: string, folder: BodySyncFolder, getConnection: ConnectionGetter): Promise<FetchBodyResult>;
|
|
188
|
+
private fetchFromImap;
|
|
189
|
+
/**
|
|
190
|
+
* The server no longer holds the message at the row's UID. A row with our
|
|
191
|
+
* own move or delete in flight belongs to that mutation (409). A settled row
|
|
192
|
+
* is removed with its thread entries, but only while its placement still
|
|
193
|
+
* matches what was read (imap-mutations R3); losing that predicate is a 409
|
|
194
|
+
* like any other lost placement transition.
|
|
195
|
+
*/
|
|
196
|
+
private reconcileGoneMessage;
|
|
197
|
+
/**
|
|
198
|
+
* Header classification, with the sender's `Address.flags.category`
|
|
199
|
+
* override (issue #299, RFC 039 Decision 3) substituted for the
|
|
200
|
+
* header-derived category when one is set. Shared with the classification
|
|
201
|
+
* backfill (issue #1197) as {@link classifyParsedMessage}, so the one-time
|
|
202
|
+
* pass and the body-store path can never classify the same bytes
|
|
203
|
+
* differently.
|
|
204
|
+
*/
|
|
205
|
+
private classifyMessage;
|
|
206
|
+
private incrementInboundCount;
|
|
207
|
+
/**
|
|
208
|
+
* The per-sender signals {@link computePlacement} needs, from ONE `Address`
|
|
209
|
+
* fetch (RFC 039 Decision 3/3a, issue #300): the trust reads exactly as
|
|
210
|
+
* `deriveSenderTrust` always did — `vip → wellknown → unknown`, untouched by
|
|
211
|
+
* `blocked` — plus `blocked`/`autoArchive` off the same row. `trustSetAt` is
|
|
212
|
+
* the `setAt` of whichever flag produced the trust value, needed by
|
|
213
|
+
* {@link resolveSenderPlacement}'s tie-break; it stays local to placement and
|
|
214
|
+
* never reaches `deriveSenderTrust`'s own contract (the trust badge).
|
|
215
|
+
*/
|
|
216
|
+
private deriveSenderPlacementSignals;
|
|
217
|
+
/**
|
|
218
|
+
* `flags.unsubscribed` (issue #302, RFC 039 Decision 3): "auto-mark-read
|
|
219
|
+
* until sender stops" — fires on every new message from that sender for as
|
|
220
|
+
* long as the flag stays set, per the flag's own doc comment; there is no
|
|
221
|
+
* separate expiry mechanism, and no caching of the decision beyond this
|
|
222
|
+
* per-message `Address` read. A no-op when body sync was built without an
|
|
223
|
+
* {@link UnsubscribeConfig} or the message carries no `From` address.
|
|
224
|
+
*
|
|
225
|
+
* Write-once per message (issue #499), like the `category` and placement
|
|
226
|
+
* derivations alongside it: read state is applied on the pass that first
|
|
227
|
+
* classifies a message and never re-decided. A user who marks such a message
|
|
228
|
+
* unread afterwards owns it — a re-entrant pass over an already-classified
|
|
229
|
+
* body would otherwise silently undo that.
|
|
230
|
+
*/
|
|
231
|
+
private applyUnsubscribedAutoRead;
|
|
232
|
+
/**
|
|
233
|
+
* Offer what a message's `text/calendar` parts propose, as cards beside the
|
|
234
|
+
* message (issue #1033). Nothing here reaches a calendar: a suggestion is
|
|
235
|
+
* written `Pending` and waits for a person, and a `METHOD:CANCEL` is a card
|
|
236
|
+
* of its own rather than a withdrawal of an event the user added.
|
|
237
|
+
*
|
|
238
|
+
* Write-once per message, like the `category`, placement and read-state
|
|
239
|
+
* derivations alongside it (issues #499, #1011): a card is offered on the
|
|
240
|
+
* pass that first classifies a message — the same moment the account's
|
|
241
|
+
* filters run — and never again. The two re-entrant paths a forced body
|
|
242
|
+
* re-sync goes through (`fetchAndGetBody`'s `NoSuchKey` fallback,
|
|
243
|
+
* `syncBodies(..., force: true)`) reach this with `bodyStorageKey` already
|
|
244
|
+
* set, and re-offering there would resurrect a card the user dismissed.
|
|
245
|
+
*
|
|
246
|
+
* Isolated the same way placement and filters are: a card is auxiliary to
|
|
247
|
+
* storing the mail, which is already durable by the time this runs. A
|
|
248
|
+
* repository failure here is logged loudly with the messageId rather than
|
|
249
|
+
* failing a body store that otherwise succeeded.
|
|
250
|
+
*/
|
|
251
|
+
private deriveCalendarSuggestions;
|
|
252
|
+
private deriveSenderUnsubscribed;
|
|
253
|
+
/**
|
|
254
|
+
* Evaluate the account's active filters against a synced message (RFC 034),
|
|
255
|
+
* BEFORE the single Message update — so a filter's `movedByRemit` flag joins
|
|
256
|
+
* that same UpdateItem. Returns the actions to apply (labels + at most one
|
|
257
|
+
* move); the caller applies them. A no-op returning no actions when no
|
|
258
|
+
* {@link FilterConfig} is wired.
|
|
259
|
+
*
|
|
260
|
+
* Isolation lives in {@link FilterPipeline.evaluate}: this read/decision phase
|
|
261
|
+
* never fails the body store (the #1246 placement precedent). The applies of
|
|
262
|
+
* the returned decision are the caller's and are deliberately unswallowed.
|
|
263
|
+
*/
|
|
264
|
+
private evaluateFilters;
|
|
265
|
+
/**
|
|
266
|
+
* Resolve the placement decision for a message, BEFORE the single Message
|
|
267
|
+
* update — so both the `movedByRemit` flag and the audit verdict join that
|
|
268
|
+
* one UpdateItem instead of a second mutation. Drives both directions (junk → inbox rescue,
|
|
269
|
+
* inbox → junk demote) off the pure {@link classifyPlacement} verdict.
|
|
270
|
+
*
|
|
271
|
+
* Returns a {@link PlacementOutcome}: a `verdict` to persist whenever Remit
|
|
272
|
+
* decided to act (action != leave), confident and unsure alike, so the
|
|
273
|
+
* distribution is queryable on the message; a `move` to enqueue only for a
|
|
274
|
+
* confident verdict; and `placementDecidedAt` whenever a verdict — including
|
|
275
|
+
* "leave" — was genuinely computed (issue #383), so a re-entrant call short-
|
|
276
|
+
* circuits instead of re-deciding a placement the user may have since
|
|
277
|
+
* overridden by hand.
|
|
278
|
+
*
|
|
279
|
+
* Always logs a structured verdict line for confident, actionable verdicts so
|
|
280
|
+
* the real distribution is observable on a live mailbox.
|
|
281
|
+
*
|
|
282
|
+
* The verdict reads the just-computed classification rather than a persisted
|
|
283
|
+
* row, since those fields are not written until the single update. Placement
|
|
284
|
+
* is auxiliary to the body store — the primary artifact (body.eml) is already
|
|
285
|
+
* durable by the time this runs — so a failure here (e.g. a placement-repo
|
|
286
|
+
* query erroring on a schema-drifted DB) is caught and logged loudly with an
|
|
287
|
+
* alertable field instead of failing the surrounding message store. The empty
|
|
288
|
+
* {@link PlacementOutcome} means "no action", whether Remit genuinely decided
|
|
289
|
+
* to leave the message alone or placement itself failed; the alert log is
|
|
290
|
+
* what distinguishes the latter, and neither case marks the message decided.
|
|
291
|
+
*/
|
|
292
|
+
private resolvePlacement;
|
|
293
|
+
private computePlacement;
|
|
294
|
+
/**
|
|
295
|
+
* `flags.autoArchive` (issue #300, RFC 039 Decision 3): file a message
|
|
296
|
+
* straight to Archive, skipping Inbox. Only reached from
|
|
297
|
+
* {@link computePlacement} when {@link classifyPlacement} had no confident
|
|
298
|
+
* junk/inbox verdict of its own — `blocked`/DKIM/DMARC always take priority
|
|
299
|
+
* over this filing preference — and only for the `leave` verdicts
|
|
300
|
+
* {@link autoArchiveApplies} admits.
|
|
301
|
+
*
|
|
302
|
+
* No {@link MessagePlacementVerdict} is recorded: `PlacementAction` (the
|
|
303
|
+
* audit enum) has only `MoveToInbox`/`MoveToJunk` — issue #300 is scoped to
|
|
304
|
+
* no TypeSpec change, so an archive move carries no audit verdict, same as
|
|
305
|
+
* a matched filter's move. The move itself reuses the same
|
|
306
|
+
* `placementMoveService.moveMessage` path as every other confident move.
|
|
307
|
+
*
|
|
308
|
+
* Idempotent the same way {@link classifyPlacement}'s own branches are: a
|
|
309
|
+
* message already sitting in Archive is left alone, not moved again.
|
|
310
|
+
*/
|
|
311
|
+
private resolveAutoArchive;
|
|
312
|
+
/**
|
|
313
|
+
* Persist one S3 object per non-multipart leaf so the SPA can resolve
|
|
314
|
+
* `BodyPartResponse.contentUrl` (#298). Keys follow the layout
|
|
315
|
+
* `accounts/{accountConfigId}/{accountId}/messages/{messageId}/parts/{partPath}`
|
|
316
|
+
* so they line up with the URL shape `derive/contentUrl.ts` emits.
|
|
317
|
+
*
|
|
318
|
+
* The mapper is total (#395 PR B): every leaf gets a `BodyPartContentPair`,
|
|
319
|
+
* possibly with a zero-byte content for leaves that have no source bytes
|
|
320
|
+
* (genuinely empty parts, or pathological inputs the positional fallback
|
|
321
|
+
* couldn't pair). No try/catch is needed; the only failure surface here is
|
|
322
|
+
* an S3 write itself, which `pMap` surfaces directly.
|
|
323
|
+
*
|
|
324
|
+
* If `listBodyParts` returns an empty list (e.g. a legacy message synced
|
|
325
|
+
* before #133 populated BodyPart rows), this is a no-op.
|
|
326
|
+
*/
|
|
327
|
+
private storeBodyPartContents;
|
|
328
|
+
/**
|
|
329
|
+
* Lazily materialize the per-part S3 objects for an already-synced message
|
|
330
|
+
* whose parts were deferred during bulk sync (DEFER_BODY_PARTS).
|
|
331
|
+
*
|
|
332
|
+
* Called from the read path (the API `describeMessage` handler) before the
|
|
333
|
+
* SPA fetches any `contentUrl`: the part bytes are served directly from S3 by
|
|
334
|
+
* CloudFront with no Lambda in the request path, so a missing object would
|
|
335
|
+
* surface to the SPA as a hard "body-missing" failure. Generating the parts
|
|
336
|
+
* here — re-parsing the stored `body.eml` with the same `mapBodyPartsToContent`
|
|
337
|
+
* logic body-sync uses — guarantees every `contentUrl` resolves, while bulk
|
|
338
|
+
* sync stays at 2 writes/message.
|
|
339
|
+
*
|
|
340
|
+
* Idempotent: skips any leaf already on S3, so repeat reads cost only HEAD
|
|
341
|
+
* checks, and a re-read after a partial failure fills the gaps. Requires the
|
|
342
|
+
* message body to be stored (`bodyStorageKey`); callers gate on that.
|
|
343
|
+
*
|
|
344
|
+
* Warm-open fast path: a single HEAD on the `.materialized` sentinel
|
|
345
|
+
* short-circuits the whole pass — no body.eml GET, no MIME re-parse, no
|
|
346
|
+
* per-leaf HEADs. The sentinel is written only after every leaf is confirmed
|
|
347
|
+
* stored, so its presence guarantees all `contentUrl`s resolve.
|
|
348
|
+
*/
|
|
349
|
+
ensureBodyPartsStored(accountConfigId: string, accountId: string, messageId: string, bodyStorageKey: string): Promise<StoreBodyPartContentsResult>;
|
|
350
|
+
private storeParsedBodyCache;
|
|
351
|
+
}
|