@bobfrankston/mailx 1.0.234 → 1.0.235

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": "@bobfrankston/mailx",
3
- "version": "1.0.234",
3
+ "version": "1.0.235",
4
4
  "description": "Local-first email client with IMAP sync and standalone native app",
5
5
  "type": "module",
6
6
  "main": "bin/mailx.js",
@@ -24,7 +24,7 @@
24
24
  "@bobfrankston/iflow-node": "^0.1.2",
25
25
  "@bobfrankston/miscinfo": "^1.0.8",
26
26
  "@bobfrankston/oauthsupport": "^1.0.22",
27
- "@bobfrankston/msger": "^0.1.296",
27
+ "@bobfrankston/msger": "^0.1.297",
28
28
  "@capacitor/android": "^8.3.0",
29
29
  "@capacitor/cli": "^8.3.0",
30
30
  "@capacitor/core": "^8.3.0",
@@ -78,7 +78,7 @@
78
78
  "@bobfrankston/iflow-node": "^0.1.2",
79
79
  "@bobfrankston/miscinfo": "^1.0.8",
80
80
  "@bobfrankston/oauthsupport": "^1.0.22",
81
- "@bobfrankston/msger": "^0.1.296",
81
+ "@bobfrankston/msger": "^0.1.297",
82
82
  "@capacitor/android": "^8.3.0",
83
83
  "@capacitor/cli": "^8.3.0",
84
84
  "@capacitor/core": "^8.3.0",
@@ -1009,9 +1009,15 @@ export class ImapManager extends EventEmitter {
1009
1009
  // the previous high. upsertMessage's primary-key dedup handles it.
1010
1010
  void highestUid;
1011
1011
  let stored = 0;
1012
- this.db.beginTransaction();
1013
- try {
1014
- for (const msg of msgs) {
1012
+ let errors = 0;
1013
+ // Don't wrap the whole batch in one transaction: a single bad row
1014
+ // would roll back the entire batch. E.g. a message with a malformed
1015
+ // Date header gave `new Date(rawStr).getTime() === NaN`, SQLite
1016
+ // coerced that to NULL, the NOT NULL constraint failed, and the
1017
+ // whole Gmail sync lost 200 messages per tick. Now each row runs
1018
+ // standalone — bad rows are logged and skipped.
1019
+ for (const msg of msgs) {
1020
+ try {
1015
1021
  const flags = [];
1016
1022
  if (msg.seen)
1017
1023
  flags.push("\\Seen");
@@ -1021,12 +1027,20 @@ export class ImapManager extends EventEmitter {
1021
1027
  flags.push("\\Answered");
1022
1028
  if (msg.draft)
1023
1029
  flags.push("\\Draft");
1030
+ // Sanitize date: reject NaN (from malformed RFC 822 Date headers)
1031
+ // and fall back to "now" so the message still lands in the DB.
1032
+ let dateMs = Date.now();
1033
+ if (msg.date instanceof Date) {
1034
+ const t = msg.date.getTime();
1035
+ if (Number.isFinite(t))
1036
+ dateMs = t;
1037
+ }
1024
1038
  this.db.upsertMessage({
1025
1039
  accountId, folderId, uid: msg.uid,
1026
1040
  messageId: msg.messageId || "",
1027
1041
  inReplyTo: msg.inReplyTo || "",
1028
1042
  references: msg.references || [],
1029
- date: msg.date instanceof Date ? msg.date.getTime() : Date.now(),
1043
+ date: dateMs,
1030
1044
  subject: msg.subject || "",
1031
1045
  from: toEmailAddress(msg.from?.[0] || {}),
1032
1046
  to: toEmailAddresses(msg.to || []),
@@ -1035,12 +1049,15 @@ export class ImapManager extends EventEmitter {
1035
1049
  });
1036
1050
  stored++;
1037
1051
  }
1038
- this.db.commitTransaction();
1039
- }
1040
- catch (e) {
1041
- this.db.rollbackTransaction();
1042
- console.error(` [api] storeApiMessages error: ${e.message}`);
1052
+ catch (e) {
1053
+ errors++;
1054
+ if (errors <= 3) {
1055
+ console.error(` [api] upsert ${accountId}/${folderId}/${msg.uid} (${msg.messageId}): ${e.message}`);
1056
+ }
1057
+ }
1043
1058
  }
1059
+ if (errors > 0)
1060
+ console.error(` [api] storeApiMessages: ${errors} of ${msgs.length} rows failed (${stored} stored)`);
1044
1061
  return stored;
1045
1062
  }
1046
1063
  /** Kill and recreate the persistent ops connection */