@remit/mailbox-service 0.0.111 → 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} +19 -77
- 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} +39 -63
- 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} +19 -50
- 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 -101
- 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 -73
- package/src/account-credentials.ts +0 -210
- package/src/adversarial-mime.e2e.test.ts +0 -307
- 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 -360
- package/src/body-sync-auto-read-writeonce.test.ts +0 -219
- package/src/body-sync-calendar-suggestion.test.ts +0 -433
- package/src/body-sync-category-denormalize.test.ts +0 -352
- package/src/body-sync-category-override.test.ts +0 -501
- package/src/body-sync-category-writeonce.test.ts +0 -234
- package/src/body-sync-filter.test.ts +0 -237
- package/src/body-sync-message-gone.test.ts +0 -139
- package/src/body-sync-placement-flags.test.ts +0 -347
- package/src/body-sync-placement-writeonce.test.ts +0 -337
- package/src/body-sync-quarantine.test.ts +0 -276
- package/src/body-sync-queue.test.ts +0 -124
- package/src/body-sync-queue.ts +0 -131
- package/src/body-sync-resync.test.ts +0 -605
- package/src/body-sync-terminal.test.ts +0 -310
- package/src/body-sync-terminal.ts +0 -176
- package/src/body-sync-unclassifiable-once.test.ts +0 -260
- package/src/body-sync-unsubscribed-auto-read.test.ts +0 -176
- package/src/body-sync.ts +0 -1970
- package/src/calendar-parts.ts +0 -41
- package/src/classification-backfill.test.ts +0 -615
- package/src/classification-backfill.ts +0 -363
- package/src/classify-message.test.ts +0 -205
- 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 -369
- package/src/external-move.ts +0 -272
- package/src/filters/anchor-drift.ts +0 -55
- package/src/filters/filter-config.test.ts +0 -229
- package/src/filters/filter-config.ts +0 -69
- package/src/filters/list-id.test.ts +0 -50
- package/src/filters/match.test.ts +0 -429
- package/src/filters/match.ts +0 -274
- package/src/filters/message-embedder.test.ts +0 -39
- package/src/filters/message-embedder.ts +0 -33
- package/src/filters/pipeline.test.ts +0 -330
- package/src/filters/pipeline.ts +0 -284
- package/src/flag-push-terminal.test.ts +0 -209
- package/src/flag-push.test.ts +0 -408
- package/src/flag-push.ts +0 -198
- package/src/flag-queue-stars.test.ts +0 -171
- package/src/flag-queue.ts +0 -509
- package/src/folder-placement.ts +0 -56
- 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 -134
- package/src/imapflow-connection.e2e.test.ts +0 -335
- package/src/imapflow-connection.integ.test.ts +0 -316
- package/src/imapflow-connection.test.ts +0 -469
- package/src/imapflow-connection.ts +0 -1475
- package/src/index.ts +0 -350
- package/src/list-id-backfill.test.ts +0 -573
- 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 -249
- package/src/mailbox-delete-intent.test.ts +0 -246
- package/src/mailbox-management.test.ts +0 -356
- package/src/mailbox-management.ts +0 -839
- package/src/mailbox-presence.test.ts +0 -72
- package/src/mailbox-queue.ts +0 -415
- package/src/mailbox-rename-intent.test.ts +0 -389
- package/src/mailbox-rename-subtree.test.ts +0 -664
- package/src/mailbox-sync.test.ts +0 -848
- package/src/mailbox-sync.ts +0 -724
- package/src/message-delete-junk-reconcile.test.ts +0 -117
- package/src/message-delete-no-trash.test.ts +0 -245
- package/src/message-delete-unsettled-placement.test.ts +0 -396
- package/src/message-move-copy.test.ts +0 -562
- package/src/message-move-empty-trash.test.ts +0 -326
- package/src/message-move-same-mailbox.test.ts +0 -139
- package/src/message-move-unsettled-placement.test.ts +0 -378
- package/src/message-move.ts +0 -1348
- package/src/message-parser.ts +0 -26
- package/src/message-presence.test.ts +0 -50
- package/src/message-sync-address-spoof.test.ts +0 -293
- package/src/message-sync-category.test.ts +0 -142
- package/src/message-sync-changedsince.test.ts +0 -840
- package/src/message-sync-flags.test.ts +0 -158
- package/src/message-sync-junk-harvest.test.ts +0 -244
- package/src/message-sync-quarantine.test.ts +0 -416
- package/src/message-sync-repoint.test.ts +0 -419
- package/src/message-sync.ts +0 -1963
- package/src/mime-walker.test.ts +0 -311
- package/src/mime-walker.ts +0 -305
- 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 -738
- package/src/outbox-attachment.ts +0 -536
- package/src/outbox-failed-recovery.test.ts +0 -370
- package/src/outbox-queue-settled-row.test.ts +0 -140
- package/src/outbox-queue.test.ts +0 -291
- package/src/outbox-queue.ts +0 -508
- package/src/pass-through-unit-of-work.ts +0 -17
- package/src/placement-move-terminal.test.ts +0 -300
- package/src/placement-move-terminal.ts +0 -148
- package/src/placement-move-unsettled.test.ts +0 -275
- package/src/placement-move.ts +0 -336
- package/src/placement-settled.test.ts +0 -147
- package/src/placement-transition-race.test.ts +0 -482
- package/src/quarantine.test.ts +0 -191
- package/src/quarantine.ts +0 -228
- package/src/snippet.test.ts +0 -213
- package/src/snippet.ts +0 -152
- package/src/spam-report.test.ts +0 -801
- package/src/spam-report.ts +0 -357
- package/src/stale-message-reconcile.test.ts +0 -82
- package/src/stale-message-reconcile.ts +0 -55
- 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/folder-roles.ts +0 -66
- package/src/test-helpers/isolated-mailbox.ts +0 -44
- package/src/test-helpers/mailfuzz-connection.ts +0 -27
- package/src/test-helpers/mime-tree-generator.ts +0 -461
- 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/types/mailparser-augment.d.ts +0 -15
- 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/test/fixtures/mime-adversarial-e2e/01-octet-stream-pdf.eml +0 -21
- package/test/fixtures/mime-adversarial-e2e/02-nested-related-cid-image.eml +0 -31
- package/test/fixtures/mime-adversarial-e2e/03-calendar-invite.eml +0 -33
- package/test/fixtures/mime-adversarial-e2e/04-forwarded-rfc822-with-attachments.eml +0 -42
- package/test/fixtures/mime-adversarial-e2e/05-tnef-winmail.eml +0 -25
- package/test/fixtures/mime-adversarial-e2e/06-8bit-non-ascii.eml +0 -15
- package/test/fixtures/mime-adversarial-e2e/07-quoted-printable-soft-breaks.eml +0 -17
- package/test/fixtures/mime-adversarial-e2e/08-alternative-empty-text.eml +0 -18
- package/tsconfig.json +0 -8
package/src/flag-push.ts
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { SendMessageCommand, type SQSClient } from "@aws-sdk/client-sqs";
|
|
3
|
-
import type { IMessageFlagPushRepository } from "@remit/data-ports";
|
|
4
|
-
import { createQueueProducer } from "@remit/sqs-client/producer";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Event the reconciler (imap-worker `handleFlagPush`) drains. Carries ONLY
|
|
8
|
-
* our own message id + the flag field — never a UID and never the desired
|
|
9
|
-
* operation (that lives on the marker, resolved fresh at push time, epic
|
|
10
|
-
* #1281 invariant 1). The event stays valid across any amount of queue delay
|
|
11
|
-
* or a later local flip of the SAME field (which replaces the marker before
|
|
12
|
-
* this event is ever processed).
|
|
13
|
-
*/
|
|
14
|
-
export interface FlagPushEvent {
|
|
15
|
-
type: "FLAG_PUSH";
|
|
16
|
-
eventId: string;
|
|
17
|
-
timestamp: number;
|
|
18
|
-
accountId: string;
|
|
19
|
-
accountConfigId: string;
|
|
20
|
-
messageId: string;
|
|
21
|
-
flagName: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface FlagPushLogger {
|
|
25
|
-
info(obj: Record<string, unknown>, msg: string): void;
|
|
26
|
-
error(obj: Record<string, unknown>, msg: string): void;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const noopLogger: FlagPushLogger = {
|
|
30
|
-
info: () => {},
|
|
31
|
-
error: () => {},
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export interface FlagPushConfig {
|
|
35
|
-
markerService: IMessageFlagPushRepository;
|
|
36
|
-
sqsQueueUrl: string;
|
|
37
|
-
sqsEndpoint?: string;
|
|
38
|
-
logger?: FlagPushLogger;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export type FlagPushOperationValue = "add" | "remove";
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Writes a pending flag-push marker and best-effort enqueues a wake-up hint
|
|
45
|
-
* (issue #1273, epic #1281). Called AFTER the caller (`FlagQueueService`) has
|
|
46
|
-
* already durably written the local `MessageFlag`/`ThreadMessage` state in
|
|
47
|
-
* the SAME request — the user's intent is durable the moment `flip` writes
|
|
48
|
-
* the marker, before the enqueue even runs.
|
|
49
|
-
*
|
|
50
|
-
* Mirrors `PlacementMoveService` (#1289)'s marker lifecycle, scoped per
|
|
51
|
-
* (`messageId`, `flagName`) instead of per `messageId` — a message can carry
|
|
52
|
-
* an independent pending read-state marker and pending star marker at once
|
|
53
|
-
* (epic FAQ: "a flag flip replaces a pending flag flip", scoped per field).
|
|
54
|
-
* `put` (not `create`) always resets the marker to `pending`, so a later flip
|
|
55
|
-
* of the SAME field simply replaces the earlier marker — "later intent wins
|
|
56
|
-
* locally".
|
|
57
|
-
*
|
|
58
|
-
* Unlike #1289's placement move (where an enqueue failure must propagate,
|
|
59
|
-
* because nothing besides the marker+enqueue sequence has committed the
|
|
60
|
-
* user's intent), the flag flip here is durable the instant the marker write
|
|
61
|
-
* lands — the enqueue is purely a latency optimization so the push does not
|
|
62
|
-
* have to wait for the next periodic sync tick. An enqueue failure is
|
|
63
|
-
* therefore swallowed on purpose: it must never fail the caller's request.
|
|
64
|
-
* It must also never be silent — logged with an `alert`-shaped entry so a
|
|
65
|
-
* string of failures (SQS itself unhealthy) is operationally visible, same
|
|
66
|
-
* convention as the enqueue this replaces (`flag-queue.ts`'s prior
|
|
67
|
-
* `flag_sync_enqueue_failed`).
|
|
68
|
-
*/
|
|
69
|
-
export class FlagPushService {
|
|
70
|
-
private markerService: IMessageFlagPushRepository;
|
|
71
|
-
private sqs: SQSClient;
|
|
72
|
-
private queueUrl: string;
|
|
73
|
-
private log: FlagPushLogger;
|
|
74
|
-
|
|
75
|
-
constructor(config: FlagPushConfig) {
|
|
76
|
-
this.markerService = config.markerService;
|
|
77
|
-
this.queueUrl = config.sqsQueueUrl;
|
|
78
|
-
this.log = config.logger ?? noopLogger;
|
|
79
|
-
this.sqs = createQueueProducer({
|
|
80
|
-
queueUrl: config.sqsQueueUrl,
|
|
81
|
-
endpoint: config.sqsEndpoint,
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
flip = async (params: {
|
|
86
|
-
accountId: string;
|
|
87
|
-
accountConfigId: string;
|
|
88
|
-
messageId: string;
|
|
89
|
-
mailboxId: string;
|
|
90
|
-
flagName: string;
|
|
91
|
-
operation: FlagPushOperationValue;
|
|
92
|
-
}): Promise<void> => {
|
|
93
|
-
const {
|
|
94
|
-
accountId,
|
|
95
|
-
accountConfigId,
|
|
96
|
-
messageId,
|
|
97
|
-
mailboxId,
|
|
98
|
-
flagName,
|
|
99
|
-
operation,
|
|
100
|
-
} = params;
|
|
101
|
-
|
|
102
|
-
await this.markerService.put({
|
|
103
|
-
messageId,
|
|
104
|
-
flagName,
|
|
105
|
-
accountId,
|
|
106
|
-
accountConfigId,
|
|
107
|
-
mailboxId,
|
|
108
|
-
operation,
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
await this.enqueueHint({ accountId, accountConfigId, messageId, flagName });
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
private enqueueHint = async (params: {
|
|
115
|
-
accountId: string;
|
|
116
|
-
accountConfigId: string;
|
|
117
|
-
messageId: string;
|
|
118
|
-
flagName: string;
|
|
119
|
-
}): Promise<void> => {
|
|
120
|
-
const event: FlagPushEvent = {
|
|
121
|
-
type: "FLAG_PUSH",
|
|
122
|
-
eventId: randomUUID(),
|
|
123
|
-
timestamp: Date.now(),
|
|
124
|
-
...params,
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const useFifo = this.queueUrl.endsWith(".fifo");
|
|
128
|
-
|
|
129
|
-
// The marker persisted in `flip` is the durable record — the queue,
|
|
130
|
-
// per the issue's own words. This send is only a wake-up hint: a
|
|
131
|
-
// queue-down failure must NOT fail the caller's flip (intent is
|
|
132
|
-
// already durable) and must NOT be silent either (standing rule: our
|
|
133
|
-
// infra failing is never a quiet blip). The periodic per-mailbox
|
|
134
|
-
// drain (issue #1273) still finds and pushes the marker regardless.
|
|
135
|
-
await this.sqs
|
|
136
|
-
.send(
|
|
137
|
-
new SendMessageCommand({
|
|
138
|
-
QueueUrl: this.queueUrl,
|
|
139
|
-
MessageBody: JSON.stringify(event),
|
|
140
|
-
...(useFifo && {
|
|
141
|
-
MessageGroupId: params.accountId,
|
|
142
|
-
MessageDeduplicationId: event.eventId,
|
|
143
|
-
}),
|
|
144
|
-
}),
|
|
145
|
-
)
|
|
146
|
-
.then(async () => {
|
|
147
|
-
// Only advance past `pending` once the hint is CONFIRMED sent — a
|
|
148
|
-
// marker still `pending` is exactly the periodic drain's query
|
|
149
|
-
// target (`sync-messages.ts`'s `drainPendingFlagPushes`), so a
|
|
150
|
-
// failed send below must leave it there, never here.
|
|
151
|
-
await this.markerService
|
|
152
|
-
.updateState(params.messageId, params.flagName, "queued")
|
|
153
|
-
.catch(async (error: unknown) => {
|
|
154
|
-
// Fast-path race (review finding on #1292): the worker can
|
|
155
|
-
// drain AND delete the marker before this transition runs —
|
|
156
|
-
// SQS delivery + processing can outrun this .then(). The
|
|
157
|
-
// enqueue itself already succeeded, so re-check whether the
|
|
158
|
-
// marker still exists before treating this as an infra
|
|
159
|
-
// failure: if it's already gone, the push already completed
|
|
160
|
-
// and this is expected, routine — never the
|
|
161
|
-
// flag_push_hint_enqueue_failed alarm shape. Any OTHER
|
|
162
|
-
// failure (e.g. the re-check itself fails because the
|
|
163
|
-
// backend is genuinely down) still propagates to the outer
|
|
164
|
-
// catch below.
|
|
165
|
-
const stillPending = await this.markerService.find(
|
|
166
|
-
params.messageId,
|
|
167
|
-
params.flagName,
|
|
168
|
-
);
|
|
169
|
-
if (!stillPending) {
|
|
170
|
-
this.log.info(
|
|
171
|
-
{ eventId: event.eventId, ...params },
|
|
172
|
-
"Marker already drained before the queued-state transition (fast-path race, harmless)",
|
|
173
|
-
);
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
throw error;
|
|
177
|
-
});
|
|
178
|
-
this.log.info(
|
|
179
|
-
{ eventId: event.eventId, ...params },
|
|
180
|
-
"Enqueued FLAG_PUSH wake-up hint",
|
|
181
|
-
);
|
|
182
|
-
})
|
|
183
|
-
.catch((error: unknown) => {
|
|
184
|
-
this.log.error(
|
|
185
|
-
{
|
|
186
|
-
alert: "flag_push_hint_enqueue_failed",
|
|
187
|
-
eventId: event.eventId,
|
|
188
|
-
...params,
|
|
189
|
-
errorName: (error as { name?: string })?.name,
|
|
190
|
-
errorCode:
|
|
191
|
-
(error as { Code?: string })?.Code ??
|
|
192
|
-
(error as { code?: string })?.code,
|
|
193
|
-
},
|
|
194
|
-
"Failed to enqueue FLAG_PUSH wake-up hint (marker persisted; the periodic sync tick will still drain it)",
|
|
195
|
-
);
|
|
196
|
-
});
|
|
197
|
-
};
|
|
198
|
-
}
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { describe, test } from "node:test";
|
|
3
|
-
import type {
|
|
4
|
-
IMessageFlagRepository,
|
|
5
|
-
IMessageRepository,
|
|
6
|
-
IThreadMessageRepository,
|
|
7
|
-
MessageItem,
|
|
8
|
-
ThreadMessageItem,
|
|
9
|
-
UpdateThreadMessageInput,
|
|
10
|
-
} from "@remit/data-ports";
|
|
11
|
-
import { MessageSystemFlag } from "@remit/domain-enums";
|
|
12
|
-
import type { FlagPushService } from "./flag-push.js";
|
|
13
|
-
import { FlagQueueService } from "./flag-queue.js";
|
|
14
|
-
|
|
15
|
-
// #44: `hasStars` is the boolean of record and `star` its presentation colour.
|
|
16
|
-
// The UI's star toggle sends only `isStarred`, so a star that left `star` at
|
|
17
|
-
// the `none` sentinel disagreed with `hasStars` and every read site that
|
|
18
|
-
// consulted the colour rejected the row. The colour now follows the boolean.
|
|
19
|
-
|
|
20
|
-
const ACCOUNT_CONFIG_ID = "cfg-1";
|
|
21
|
-
const ACCOUNT_ID = "acct-1";
|
|
22
|
-
const MESSAGE_ID = "msg-1";
|
|
23
|
-
const MAILBOX_ID = "mbx-1";
|
|
24
|
-
const THREAD_MESSAGE_ID = "tm-1";
|
|
25
|
-
|
|
26
|
-
const threadMessage = {
|
|
27
|
-
threadMessageId: THREAD_MESSAGE_ID,
|
|
28
|
-
accountConfigId: ACCOUNT_CONFIG_ID,
|
|
29
|
-
mailboxId: MAILBOX_ID,
|
|
30
|
-
sentDate: 1,
|
|
31
|
-
isRead: false,
|
|
32
|
-
isDeleted: false,
|
|
33
|
-
hasStars: false,
|
|
34
|
-
hasAttachment: false,
|
|
35
|
-
} as unknown as ThreadMessageItem;
|
|
36
|
-
|
|
37
|
-
const buildService = ({
|
|
38
|
-
alreadyFlagged = false,
|
|
39
|
-
}: {
|
|
40
|
-
alreadyFlagged?: boolean;
|
|
41
|
-
} = {}): {
|
|
42
|
-
service: FlagQueueService;
|
|
43
|
-
updates: UpdateThreadMessageInput[];
|
|
44
|
-
flips: Array<{ flagName: string; operation: string }>;
|
|
45
|
-
} => {
|
|
46
|
-
const updates: UpdateThreadMessageInput[] = [];
|
|
47
|
-
const flips: Array<{ flagName: string; operation: string }> = [];
|
|
48
|
-
|
|
49
|
-
const messageService = {
|
|
50
|
-
get: async () => ({ mailboxId: MAILBOX_ID }) as unknown as MessageItem,
|
|
51
|
-
} as unknown as IMessageRepository;
|
|
52
|
-
|
|
53
|
-
const messageFlagService = {
|
|
54
|
-
hasFlag: async (_messageId: string, flagName: string) =>
|
|
55
|
-
flagName === MessageSystemFlag.Flagged ? alreadyFlagged : false,
|
|
56
|
-
addFlag: async () => {},
|
|
57
|
-
removeFlag: async () => {},
|
|
58
|
-
} as unknown as IMessageFlagRepository;
|
|
59
|
-
|
|
60
|
-
const threadMessageService = {
|
|
61
|
-
findAllByMessageId: async () => [threadMessage],
|
|
62
|
-
update: async (
|
|
63
|
-
_accountConfigId: string,
|
|
64
|
-
_threadMessageId: string,
|
|
65
|
-
input: UpdateThreadMessageInput,
|
|
66
|
-
) => {
|
|
67
|
-
updates.push(input);
|
|
68
|
-
return threadMessage;
|
|
69
|
-
},
|
|
70
|
-
} as unknown as IThreadMessageRepository;
|
|
71
|
-
|
|
72
|
-
const flagPushService = {
|
|
73
|
-
flip: async (event: { flagName: string; operation: string }) => {
|
|
74
|
-
flips.push({ flagName: event.flagName, operation: event.operation });
|
|
75
|
-
},
|
|
76
|
-
} as unknown as FlagPushService;
|
|
77
|
-
|
|
78
|
-
const service = new FlagQueueService({
|
|
79
|
-
messageFlagService,
|
|
80
|
-
messageService,
|
|
81
|
-
threadMessageService,
|
|
82
|
-
flagPushService,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
return { service, updates, flips };
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
describe("updateFlags keeps hasStars and the star colour in step", () => {
|
|
89
|
-
test("starring without a colour sets both the boolean and a visible colour", async () => {
|
|
90
|
-
const { service, updates } = buildService();
|
|
91
|
-
|
|
92
|
-
await service.updateFlags(ACCOUNT_CONFIG_ID, MESSAGE_ID, ACCOUNT_ID, {
|
|
93
|
-
isStarred: true,
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
assert.deepEqual(updates, [{ hasStars: true, star: "yellow" }]);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("unstarring clears the colour back to the none sentinel", async () => {
|
|
100
|
-
const { service, updates } = buildService({ alreadyFlagged: true });
|
|
101
|
-
|
|
102
|
-
await service.updateFlags(ACCOUNT_CONFIG_ID, MESSAGE_ID, ACCOUNT_ID, {
|
|
103
|
-
isStarred: false,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
assert.deepEqual(updates, [{ hasStars: false, star: "none" }]);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("an explicit colour wins over the default", async () => {
|
|
110
|
-
const { service, updates } = buildService();
|
|
111
|
-
|
|
112
|
-
await service.updateFlags(ACCOUNT_CONFIG_ID, MESSAGE_ID, ACCOUNT_ID, {
|
|
113
|
-
isStarred: true,
|
|
114
|
-
starColor: "red",
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
assert.deepEqual(updates, [{ hasStars: true, star: "red" }]);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
// A colour-only request is legal on the wire. Before, it wrote the colour
|
|
121
|
-
// and left `hasStars` alone, so a coloured-but-unstarred row rendered
|
|
122
|
-
// unstarred, stayed out of Starred, and never pushed \Flagged.
|
|
123
|
-
test("a colour alone stars the message and pushes the flag", async () => {
|
|
124
|
-
const { service, updates, flips } = buildService();
|
|
125
|
-
|
|
126
|
-
await service.updateFlags(ACCOUNT_CONFIG_ID, MESSAGE_ID, ACCOUNT_ID, {
|
|
127
|
-
starColor: "blue",
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
assert.deepEqual(updates, [{ hasStars: true, star: "blue" }]);
|
|
131
|
-
assert.deepEqual(flips, [
|
|
132
|
-
{ flagName: MessageSystemFlag.Flagged, operation: "add" },
|
|
133
|
-
]);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
test("the none colour unstars the message", async () => {
|
|
137
|
-
const { service, updates, flips } = buildService({ alreadyFlagged: true });
|
|
138
|
-
|
|
139
|
-
await service.updateFlags(ACCOUNT_CONFIG_ID, MESSAGE_ID, ACCOUNT_ID, {
|
|
140
|
-
starColor: "none",
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
assert.deepEqual(updates, [{ hasStars: false, star: "none" }]);
|
|
144
|
-
assert.deepEqual(flips, [
|
|
145
|
-
{ flagName: MessageSystemFlag.Flagged, operation: "remove" },
|
|
146
|
-
]);
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test("isStarred decides the boolean when both fields are sent", async () => {
|
|
150
|
-
const { service, updates } = buildService();
|
|
151
|
-
|
|
152
|
-
await service.updateFlags(ACCOUNT_CONFIG_ID, MESSAGE_ID, ACCOUNT_ID, {
|
|
153
|
-
isStarred: true,
|
|
154
|
-
starColor: "none",
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
assert.deepEqual(updates, [{ hasStars: true, star: "none" }]);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
test("starring pushes the Flagged keyword", async () => {
|
|
161
|
-
const { service, flips } = buildService();
|
|
162
|
-
|
|
163
|
-
await service.updateFlags(ACCOUNT_CONFIG_ID, MESSAGE_ID, ACCOUNT_ID, {
|
|
164
|
-
isStarred: true,
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
assert.deepEqual(flips, [
|
|
168
|
-
{ flagName: MessageSystemFlag.Flagged, operation: "add" },
|
|
169
|
-
]);
|
|
170
|
-
});
|
|
171
|
-
});
|