@remit/imap-worker 0.0.8 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/imap-worker",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "exports": {
@@ -147,6 +147,45 @@ describe("drainPendingFlagPushes — periodic per-mailbox re-arm (issue #1273)",
147
147
  assert.equal(emitted.length, 2);
148
148
  });
149
149
 
150
+ /**
151
+ * Markers persisted before the system-flag wire-format fix carry the
152
+ * unprefixed spelling (`Seen`, not `\Seen`) the enum emitter used to
153
+ * produce. The drain re-arms from the stored `marker.flagName` and never
154
+ * from the enum, and `handleFlagPush` threads that same value through
155
+ * `find`/`updateState`/`delete` — so a marker written under the old
156
+ * spelling still matches itself and drains to completion. No migration,
157
+ * no orphans.
158
+ */
159
+ it("re-arms a marker persisted under the pre-fix unprefixed spelling verbatim", async () => {
160
+ const markerService = {
161
+ listByMailboxId: async () => [
162
+ marker({ messageId: "legacy-msg", flagName: "Seen" }),
163
+ marker({ messageId: "legacy-star", flagName: "Flagged" }),
164
+ ],
165
+ } as unknown as IMessageFlagPushRepository;
166
+
167
+ const emitted: Array<{ messageId: string; flagName: string }> = [];
168
+ const { log } = buildLogger();
169
+
170
+ await drainPendingFlagPushes(
171
+ markerService,
172
+ account,
173
+ "mbx-1",
174
+ log,
175
+ async (event) => {
176
+ emitted.push(event as unknown as (typeof emitted)[number]);
177
+ },
178
+ );
179
+
180
+ assert.deepEqual(
181
+ emitted.map((e) => [e.messageId, e.flagName]),
182
+ [
183
+ ["legacy-msg", "Seen"],
184
+ ["legacy-star", "Flagged"],
185
+ ],
186
+ );
187
+ });
188
+
150
189
  it("a re-arm (SQS) failure is caught per-marker and logged loudly — never thrown", async () => {
151
190
  const markerService = {
152
191
  listByMailboxId: async () => [marker()],
@@ -6,6 +6,7 @@ import type {
6
6
  IEnvelopeRepository,
7
7
  IMailboxRepository,
8
8
  IMessageFlagPushRepository,
9
+ IMessageFlagRepository,
9
10
  IMessageRepository,
10
11
  IThreadMessageRepository,
11
12
  IUnitOfWork,
@@ -70,6 +71,7 @@ export const syncMessages = async (
70
71
  threadMessage: threadMessageService,
71
72
  mailboxLock: mailboxLockService,
72
73
  flagPush: flagPushMarkerService,
74
+ messageFlag: messageFlagService,
73
75
  unitOfWork,
74
76
  secrets,
75
77
  } = await getClient();
@@ -137,6 +139,7 @@ export const syncMessages = async (
137
139
  addressService,
138
140
  threadMessageService,
139
141
  flagPushMarkerService,
142
+ messageFlagService,
140
143
  unitOfWork,
141
144
  },
142
145
  log,
@@ -179,6 +182,7 @@ interface SyncDeps {
179
182
  addressService: IAddressRepository;
180
183
  threadMessageService: IThreadMessageRepository;
181
184
  flagPushMarkerService: IMessageFlagPushRepository;
185
+ messageFlagService: IMessageFlagRepository;
182
186
  unitOfWork?: IUnitOfWork;
183
187
  }
184
188
 
@@ -269,6 +273,7 @@ const syncMailboxMessages = async (
269
273
  addressService,
270
274
  threadMessageService,
271
275
  flagPushMarkerService,
276
+ messageFlagService,
272
277
  unitOfWork,
273
278
  } = deps;
274
279
 
@@ -300,6 +305,8 @@ const syncMailboxMessages = async (
300
305
  threadMessageService,
301
306
  log,
302
307
  unitOfWork,
308
+ flagPushMarkerService,
309
+ messageFlagService,
303
310
  );
304
311
 
305
312
  // Connect once, reuse for the entire sync operation
@@ -331,6 +338,16 @@ const syncMailboxMessages = async (
331
338
  );
332
339
  }
333
340
 
341
+ // A stalled cursor is the one failure on this path that produces no error:
342
+ // unapplicable messages are caught and held back, so the round returns
343
+ // normally, the SQS record is deleted, and neither redrive nor the DLQ ever
344
+ // engages. Without this metric the mailbox stops syncing behind a worker
345
+ // that looks healthy. It is emitted as a count so a sustained non-zero
346
+ // value alarms, per-mailbox detail being in the accompanying ERROR log.
347
+ if (result.cursorStalled) {
348
+ metrics.addMetric("imapSyncCursorStalled", MetricUnit.Count, 1);
349
+ }
350
+
334
351
  // Emit body sync events for the messages we just synced. Each event carries
335
352
  // messageId+uid pairs so the consumer issues one ranged FETCH per batch
336
353
  // without re-resolving UIDs. messageIds stays populated for backward compat.