@bobfrankston/mailx 1.0.124 → 1.0.126

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/blowaway.cmd CHANGED
@@ -1,2 +1,3 @@
1
1
  del C:\Users\Bob\.mailx\mailx.db C:\Users\Bob\.mailx\mailx.db-shm C:\Users\Bob\.mailx\mailx.db-wal
2
- : rmdir /s /q C:\Users\Bob\.mailx\mailxstore
2
+ if .%1==.all rmdir /s /q C:\Users\Bob\.mailx\mailxstore
3
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx",
3
- "version": "1.0.124",
3
+ "version": "1.0.126",
4
4
  "description": "Local-first email client with IMAP sync and standalone native app",
5
5
  "type": "module",
6
6
  "main": "bin/mailx.js",
@@ -323,6 +323,34 @@ export class ImapManager extends EventEmitter {
323
323
  const fetched = await client.fetchMessagesSinceUid(folder.path, highestUid, { source: false });
324
324
  // Filter out the last known message (IMAP * always returns at least one)
325
325
  messages = fetched.filter(m => m.uid > highestUid);
326
+ // Gap detection: compare DB count vs IMAP count to catch interrupted syncs
327
+ const dbCount = this.db.getMessageCount(accountId, folderId);
328
+ try {
329
+ const imapCount = await client.getMessagesCount(folder.path);
330
+ const gap = imapCount - dbCount - messages.length;
331
+ if (gap > 0) {
332
+ console.log(` ${folder.path}: gap detected — IMAP has ${imapCount}, DB has ${dbCount}, ${messages.length} new → ${gap} missing`);
333
+ // Full UID reconciliation: get all IMAP UIDs, find missing ones
334
+ const existingUids = new Set(this.db.getUidsForFolder(accountId, folderId));
335
+ const newUids = new Set(messages.map(m => m.uid));
336
+ const allImapUids = await client.getUids(folder.path);
337
+ const missingUids = allImapUids.filter(uid => !existingUids.has(uid) && !newUids.has(uid));
338
+ if (missingUids.length > 0) {
339
+ console.log(` ${folder.path}: reconciling ${missingUids.length} missing messages`);
340
+ // Fetch in chunks to avoid huge commands
341
+ const chunkSize = 500;
342
+ for (let i = 0; i < missingUids.length; i += chunkSize) {
343
+ const chunk = missingUids.slice(i, i + chunkSize);
344
+ const range = chunk.join(",");
345
+ const recovered = await client.fetchMessages(folder.path, range, { source: false });
346
+ messages.push(...recovered);
347
+ }
348
+ }
349
+ }
350
+ }
351
+ catch (e) {
352
+ console.error(` ${folder.path}: gap detection failed: ${e.message}`);
353
+ }
326
354
  // Backfill: if historyDays extends further back than our oldest message, fetch the gap
327
355
  const oldestDate = this.db.getOldestDate(accountId, folderId);
328
356
  if (oldestDate > 0 && startDate.getTime() < oldestDate) {
@@ -497,7 +525,6 @@ export class ImapManager extends EventEmitter {
497
525
  // Legacy fallback removed — was doubling connections.
498
526
  // If native client has issues, set useNativeClient=false or use --legacy-imap flag.
499
527
  await client.logout();
500
- this.trackLogout(accountId);
501
528
  client = null;
502
529
  accountFolders.set(accountId, folders);
503
530
  // Sync inbox immediately
@@ -510,7 +537,6 @@ export class ImapManager extends EventEmitter {
510
537
  new Promise((_, reject) => setTimeout(() => reject(new Error("Sync timeout (60s)")), 60000))
511
538
  ]);
512
539
  await client.logout();
513
- this.trackLogout(accountId);
514
540
  client = null;
515
541
  }
516
542
  catch (e) {
@@ -626,7 +652,6 @@ export class ImapManager extends EventEmitter {
626
652
  await client.logout();
627
653
  }
628
654
  catch { /* */ }
629
- this.trackLogout(accountId);
630
655
  client = this.createClient(accountId);
631
656
  }
632
657
  }
@@ -714,7 +739,6 @@ export class ImapManager extends EventEmitter {
714
739
  client = this.createClient(accountId);
715
740
  const count = await client.getMessagesCount("INBOX");
716
741
  await client.logout();
717
- this.trackLogout(accountId);
718
742
  client = null;
719
743
  const prev = this.lastInboxCounts.get(accountId) ?? count;
720
744
  this.lastInboxCounts.set(accountId, count);
@@ -723,7 +747,6 @@ export class ImapManager extends EventEmitter {
723
747
  client = this.createClient(accountId);
724
748
  await this.syncFolder(accountId, inbox.id, client);
725
749
  await client.logout();
726
- this.trackLogout(accountId);
727
750
  client = null;
728
751
  }
729
752
  }
@@ -1267,7 +1290,6 @@ export class ImapManager extends EventEmitter {
1267
1290
  await client.logout();
1268
1291
  }
1269
1292
  catch { }
1270
- this.trackLogout(accountId);
1271
1293
  return;
1272
1294
  }
1273
1295
  const sendingFlag = `$Sending-${this.hostname}`;
@@ -1373,7 +1395,6 @@ export class ImapManager extends EventEmitter {
1373
1395
  await client.logout();
1374
1396
  }
1375
1397
  catch { /* ignore */ }
1376
- this.trackLogout(accountId);
1377
1398
  }
1378
1399
  }
1379
1400
  /** Start background Outbox worker — runs immediately then every 10 seconds */
@@ -60,6 +60,7 @@ export declare class MailxDB {
60
60
  updateBodyPath(accountId: string, uid: number, bodyPath: string): void;
61
61
  getHighestUid(accountId: string, folderId: number): number;
62
62
  getOldestDate(accountId: string, folderId: number): number;
63
+ getMessageCount(accountId: string, folderId: number): number;
63
64
  /** Get all UIDs for a folder */
64
65
  getUidsForFolder(accountId: string, folderId: number): number[];
65
66
  /** Delete a message by account + UID */
@@ -328,6 +328,10 @@ export class MailxDB {
328
328
  const r = this.db.prepare("SELECT MIN(date) as minDate FROM messages WHERE account_id = ? AND folder_id = ?").get(accountId, folderId);
329
329
  return r?.minDate || 0;
330
330
  }
331
+ getMessageCount(accountId, folderId) {
332
+ const r = this.db.prepare("SELECT count(*) as cnt FROM messages WHERE account_id = ? AND folder_id = ?").get(accountId, folderId);
333
+ return r?.cnt || 0;
334
+ }
331
335
  /** Get all UIDs for a folder */
332
336
  getUidsForFolder(accountId, folderId) {
333
337
  const rows = this.db.prepare("SELECT uid FROM messages WHERE account_id = ? AND folder_id = ?").all(accountId, folderId);