@remit/mailbox-service 0.0.35 → 0.0.36

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/mailbox-service",
3
- "version": "0.0.35",
3
+ "version": "0.0.36",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -11,7 +11,10 @@ import {
11
11
  MailboxSyncStatus,
12
12
  } from "@remit/domain-enums";
13
13
  import { parseImapAttributes } from "./attribute-mapper.js";
14
- import { MailboxSyncService } from "./mailbox-sync.js";
14
+ import { type MailboxSyncLogger, MailboxSyncService } from "./mailbox-sync.js";
15
+
16
+ const silentLogger: MailboxSyncLogger = { info: () => {}, debug: () => {} };
17
+
15
18
  import type { IImapConnection, ImapNamespaces } from "./types.js";
16
19
 
17
20
  describe("parseImapAttributes – locale invariance (#194)", () => {
@@ -121,7 +124,11 @@ describe("MailboxSyncService.syncMailboxes — UIDVALIDITY cursor detection (#12
121
124
 
122
125
  it("trips cursor_invalid when the STATUS sweep observes a changed UIDVALIDITY", async () => {
123
126
  const { mailboxService, specialUseService, updateCalls } = buildServices(1);
124
- const service = new MailboxSyncService(mailboxService, specialUseService);
127
+ const service = new MailboxSyncService(
128
+ mailboxService,
129
+ specialUseService,
130
+ silentLogger,
131
+ );
125
132
  const connection = buildConnection(2);
126
133
 
127
134
  await service.syncMailboxes({ accountId: "acc-1" }, connection);
@@ -136,7 +143,11 @@ describe("MailboxSyncService.syncMailboxes — UIDVALIDITY cursor detection (#12
136
143
 
137
144
  it("does not write anything when UIDVALIDITY (and everything else) is unchanged", async () => {
138
145
  const { mailboxService, specialUseService, updateCalls } = buildServices(1);
139
- const service = new MailboxSyncService(mailboxService, specialUseService);
146
+ const service = new MailboxSyncService(
147
+ mailboxService,
148
+ specialUseService,
149
+ silentLogger,
150
+ );
140
151
  const connection = buildConnection(1);
141
152
 
142
153
  await service.syncMailboxes({ accountId: "acc-1" }, connection);
@@ -150,7 +161,11 @@ describe("MailboxSyncService.syncMailboxes — UIDVALIDITY cursor detection (#12
150
161
  // the server's current value would step it over every change message sync
151
162
  // had not yet applied.
152
163
  const { mailboxService, specialUseService, updateCalls } = buildServices(1);
153
- const service = new MailboxSyncService(mailboxService, specialUseService);
164
+ const service = new MailboxSyncService(
165
+ mailboxService,
166
+ specialUseService,
167
+ silentLogger,
168
+ );
154
169
  const connection = buildConnection(2, "99999");
155
170
 
156
171
  await service.syncMailboxes({ accountId: "acc-1" }, connection);
@@ -162,7 +177,11 @@ describe("MailboxSyncService.syncMailboxes — UIDVALIDITY cursor detection (#12
162
177
 
163
178
  it("does not sweep-write a mailbox whose only difference is the server mod-sequence", async () => {
164
179
  const { mailboxService, specialUseService, updateCalls } = buildServices(1);
165
- const service = new MailboxSyncService(mailboxService, specialUseService);
180
+ const service = new MailboxSyncService(
181
+ mailboxService,
182
+ specialUseService,
183
+ silentLogger,
184
+ );
166
185
  const connection = buildConnection(1, "4242");
167
186
 
168
187
  await service.syncMailboxes({ accountId: "acc-1" }, connection);
@@ -175,7 +194,11 @@ describe("MailboxSyncService.syncMailboxes — UIDVALIDITY cursor detection (#12
175
194
  1,
176
195
  MailboxCursorState.cursor_invalid,
177
196
  );
178
- const service = new MailboxSyncService(mailboxService, specialUseService);
197
+ const service = new MailboxSyncService(
198
+ mailboxService,
199
+ specialUseService,
200
+ silentLogger,
201
+ );
179
202
  const connection = buildConnection(2);
180
203
 
181
204
  await service.syncMailboxes({ accountId: "acc-1" }, connection);
@@ -273,7 +296,11 @@ describe("MailboxSyncService.syncMailboxes — reconcile does not delete pending
273
296
  syncStatus: MailboxSyncStatus.pending,
274
297
  },
275
298
  ]);
276
- const service = new MailboxSyncService(mailboxService, specialUseService);
299
+ const service = new MailboxSyncService(
300
+ mailboxService,
301
+ specialUseService,
302
+ silentLogger,
303
+ );
277
304
 
278
305
  const result = await service.syncMailboxes(
279
306
  { accountId: "acc-1" },
@@ -299,7 +326,11 @@ describe("MailboxSyncService.syncMailboxes — reconcile does not delete pending
299
326
  syncStatus: MailboxSyncStatus.synced,
300
327
  },
301
328
  ]);
302
- const service = new MailboxSyncService(mailboxService, specialUseService);
329
+ const service = new MailboxSyncService(
330
+ mailboxService,
331
+ specialUseService,
332
+ silentLogger,
333
+ );
303
334
 
304
335
  const result = await service.syncMailboxes(
305
336
  { accountId: "acc-1" },
@@ -419,7 +450,11 @@ describe("MailboxSyncService.syncMailboxes — a folder leaving mid-sweep (#339)
419
450
  doomedSyncStatus: syncStatus,
420
451
  });
421
452
  const { connection, statusPaths } = buildConnection(async () => status());
422
- const service = new MailboxSyncService(mailboxService, specialUseService);
453
+ const service = new MailboxSyncService(
454
+ mailboxService,
455
+ specialUseService,
456
+ silentLogger,
457
+ );
423
458
 
424
459
  await service.syncMailboxes({ accountId: "acc-1" }, connection);
425
460
 
@@ -439,7 +474,11 @@ describe("MailboxSyncService.syncMailboxes — a folder leaving mid-sweep (#339)
439
474
  if (path === "Doomed") throw new Error("Mailbox doesn't exist: Doomed");
440
475
  return status();
441
476
  });
442
- const service = new MailboxSyncService(mailboxService, specialUseService);
477
+ const service = new MailboxSyncService(
478
+ mailboxService,
479
+ specialUseService,
480
+ silentLogger,
481
+ );
443
482
 
444
483
  await assert.doesNotReject(
445
484
  service.syncMailboxes({ accountId: "acc-1" }, connection),
@@ -460,7 +499,11 @@ describe("MailboxSyncService.syncMailboxes — a folder leaving mid-sweep (#339)
460
499
  if (path === "Doomed") throw new Error("connection reset by peer");
461
500
  return status();
462
501
  });
463
- const service = new MailboxSyncService(mailboxService, specialUseService);
502
+ const service = new MailboxSyncService(
503
+ mailboxService,
504
+ specialUseService,
505
+ silentLogger,
506
+ );
464
507
 
465
508
  await assert.rejects(
466
509
  service.syncMailboxes({ accountId: "acc-1" }, connection),
@@ -77,19 +77,45 @@ export interface SyncAccountInfo {
77
77
  accountId: string;
78
78
  }
79
79
 
80
+ /**
81
+ * Logger interface for MailboxSyncService.
82
+ *
83
+ * `info` is reserved here for the three mailbox lifecycle events — created,
84
+ * updated, deleted — and is the argued exception to `plugins/no-logger-info.grit`
85
+ * that the rest of this repo writes as a suppression. (The plugin matches the
86
+ * bare `logger` identifier, so it does not see an injected logger; the reasoning
87
+ * is written out instead of being silently absent.) A folder disappearing from
88
+ * someone's mail is destructive and irreversible from this service's side, and
89
+ * the only record that this process did it is the line it writes. That must not
90
+ * sit below the default threshold.
91
+ *
92
+ * Everything else this service has to say — a special-use flag it synced, a
93
+ * duplicate folder it skipped — is a routine trace and goes to `debug`.
94
+ */
95
+ export interface MailboxSyncLogger {
96
+ info(obj: Record<string, unknown>, msg: string): void;
97
+ debug(obj: Record<string, unknown>, msg: string): void;
98
+ }
99
+
80
100
  /**
81
101
  * Service for synchronizing mailbox metadata between IMAP and the data backend.
82
102
  */
83
103
  export class MailboxSyncService {
84
104
  private mailboxService: IMailboxRepository;
85
105
  private specialUseService: IMailboxSpecialUseRepository;
106
+ private log: MailboxSyncLogger;
86
107
 
108
+ // Required, with no no-op default: the lines below are the only record that a
109
+ // mailbox was created or destroyed, and a default would let the next call site
110
+ // lose them with no error and no failing test.
87
111
  constructor(
88
112
  mailboxService: IMailboxRepository,
89
113
  specialUseService: IMailboxSpecialUseRepository,
114
+ logger: MailboxSyncLogger,
90
115
  ) {
91
116
  this.mailboxService = mailboxService;
92
117
  this.specialUseService = specialUseService;
118
+ this.log = logger;
93
119
  }
94
120
 
95
121
  /**
@@ -146,8 +172,13 @@ export class MailboxSyncService {
146
172
  account.accountId,
147
173
  existing.mailboxId,
148
174
  );
149
- console.info(
150
- `Deleted non-selectable mailbox: ${existing.mailboxId} (${existing.fullPath})`,
175
+ this.log.info(
176
+ {
177
+ mailboxId: existing.mailboxId,
178
+ fullPath: existing.fullPath,
179
+ reason: "non-selectable",
180
+ },
181
+ "Deleted mailbox",
151
182
  );
152
183
  result.deleted++;
153
184
  }
@@ -164,8 +195,13 @@ export class MailboxSyncService {
164
195
  account.accountId,
165
196
  existing.mailboxId,
166
197
  );
167
- console.info(
168
- `Deleted duplicate special-use mailbox: ${existing.mailboxId} (${existing.fullPath})`,
198
+ this.log.info(
199
+ {
200
+ mailboxId: existing.mailboxId,
201
+ fullPath: existing.fullPath,
202
+ reason: "duplicate-special-use",
203
+ },
204
+ "Deleted mailbox",
169
205
  );
170
206
  result.deleted++;
171
207
  }
@@ -238,8 +274,13 @@ export class MailboxSyncService {
238
274
  // pending rows to the create/rename flow that owns them.
239
275
  if (existing.syncStatus === MailboxSyncStatus.pending) continue;
240
276
  await this.mailboxService.delete(account.accountId, existing.mailboxId);
241
- console.info(
242
- `Deleted mailbox: ${existing.mailboxId} (${existing.fullPath})`,
277
+ this.log.info(
278
+ {
279
+ mailboxId: existing.mailboxId,
280
+ fullPath: existing.fullPath,
281
+ reason: "not-on-server",
282
+ },
283
+ "Deleted mailbox",
243
284
  );
244
285
  result.deleted++;
245
286
  }
@@ -419,12 +460,18 @@ export class MailboxSyncService {
419
460
  mailbox.mailboxId,
420
461
  parsed.specialUse,
421
462
  );
422
- console.info(
423
- `Created mailbox: ${mailbox.mailboxId} (${mailboxInfo.fullPath}) [special-use: ${parsed.specialUse.join(", ")}]`,
463
+ this.log.info(
464
+ {
465
+ mailboxId: mailbox.mailboxId,
466
+ fullPath: mailboxInfo.fullPath,
467
+ specialUse: parsed.specialUse,
468
+ },
469
+ "Created mailbox",
424
470
  );
425
471
  } else {
426
- console.info(
427
- `Created mailbox: ${mailbox.mailboxId} (${mailboxInfo.fullPath})`,
472
+ this.log.info(
473
+ { mailboxId: mailbox.mailboxId, fullPath: mailboxInfo.fullPath },
474
+ "Created mailbox",
428
475
  );
429
476
  }
430
477
 
@@ -504,8 +551,13 @@ export class MailboxSyncService {
504
551
  `specialUse: [${(existing.specialUse ?? []).join(",")}] -> [${parsed.specialUse.join(",")}]`,
505
552
  );
506
553
 
507
- console.info(
508
- `Updating mailbox: ${existing.mailboxId} (${mailboxInfo.fullPath}) [${changes.join(", ")}]`,
554
+ this.log.info(
555
+ {
556
+ mailboxId: existing.mailboxId,
557
+ fullPath: mailboxInfo.fullPath,
558
+ changes,
559
+ },
560
+ "Updating mailbox",
509
561
  );
510
562
 
511
563
  // Update mailbox with fresh status. ElectroDB rejects empty sets, so we
@@ -557,8 +609,9 @@ export class MailboxSyncService {
557
609
 
558
610
  if (parsed.specialUse.length > 0) {
559
611
  await this.specialUseService.createMany(mailboxId, parsed.specialUse);
560
- console.info(
561
- `Synced special-use for ${mailboxInfo.fullPath}: ${parsed.specialUse.join(", ")}`,
612
+ this.log.debug(
613
+ { fullPath: mailboxInfo.fullPath, specialUse: parsed.specialUse },
614
+ "Synced special-use",
562
615
  );
563
616
  }
564
617
  };
@@ -619,8 +672,13 @@ export class MailboxSyncService {
619
672
  }
620
673
 
621
674
  // This is a duplicate - another folder has the attribute
622
- console.info(
623
- `Skipping duplicate folder "${mailbox.fullPath}" - "${claimedPath}" has \\${expectedSpecialUse} attribute`,
675
+ this.log.debug(
676
+ {
677
+ fullPath: mailbox.fullPath,
678
+ claimedPath,
679
+ specialUse: expectedSpecialUse,
680
+ },
681
+ "Skipping duplicate folder",
624
682
  );
625
683
  return true;
626
684
  };