@bobfrankston/mailx-store 0.1.9 → 0.1.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/db.js CHANGED
@@ -1188,7 +1188,13 @@ export class MailxDB {
1188
1188
  }
1189
1189
  /** Get messages without cached bodies (for background prefetch) */
1190
1190
  getMessagesWithoutBody(accountId, limit = 50) {
1191
- return this.db.prepare("SELECT uid, folder_id as folderId FROM messages WHERE account_id = ? AND (body_path IS NULL OR body_path = '') ORDER BY date DESC LIMIT ?").all(accountId, limit);
1191
+ // Prefetch order: smallest first, NULLs last, recent within tiebreak.
1192
+ // Reasoning: on slow / metered Android networks, the user feels the
1193
+ // cache "fill up" much faster when the queue is dominated by short
1194
+ // notification mails (a handful of KB each) instead of a single
1195
+ // multi-megabyte attachment that monopolizes bandwidth for minutes.
1196
+ // Size 0 / NULL fall to the end so they don't masquerade as small.
1197
+ return this.db.prepare("SELECT uid, folder_id as folderId FROM messages WHERE account_id = ? AND (body_path IS NULL OR body_path = '') ORDER BY (size IS NULL OR size = 0), size ASC, date DESC LIMIT ?").all(accountId, limit);
1192
1198
  }
1193
1199
  getHighestUid(accountId, folderId) {
1194
1200
  const r = this.db.prepare("SELECT MAX(uid) as maxUid FROM messages WHERE account_id = ? AND folder_id = ?").get(accountId, folderId);
@@ -1613,6 +1619,23 @@ export class MailxDB {
1613
1619
  const score = (r) => (r.match_rank || 0) * 10_000
1614
1620
  + (r.use_count || 0) * Math.pow(0.5, Math.max(0, now - (r.last_used || 0)) / HALF_LIFE_MS);
1615
1621
  rows.sort((a, b) => score(b) - score(a));
1622
+ // Dedup by lowercased email — same address often appears as both
1623
+ // source='google' (synced from Google Contacts) and source='discovered'
1624
+ // (auto-collected from sent mail). The user only wants to see the
1625
+ // best entry; we keep the higher-ranked source (which the sort above
1626
+ // has already put first), and silently drop the duplicate. Without
1627
+ // this, the autocomplete dropdown showed two identical Kevin Healy
1628
+ // rows just labeled GOOGLE and DISCOVERED.
1629
+ const seenEmails = new Set();
1630
+ rows = rows.filter(r => {
1631
+ const k = (r.email || "").toLowerCase();
1632
+ if (!k)
1633
+ return true;
1634
+ if (seenEmails.has(k))
1635
+ return false;
1636
+ seenEmails.add(k);
1637
+ return true;
1638
+ });
1616
1639
  rows = rows.slice(0, limit);
1617
1640
  return rows.map(r => ({ name: r.name, email: r.email, source: r.source, useCount: r.use_count }));
1618
1641
  }
package/file-store.d.ts CHANGED
@@ -28,6 +28,12 @@ export declare class FileMessageStore implements MessageStore {
28
28
  * it in `body_path`. The (folderId, uid) args are kept for interface
29
29
  * compatibility; they do NOT affect the filename. */
30
30
  putMessage(accountId: string, _folderId: number, _uid: number, raw: Buffer): Promise<string>;
31
+ /** Resolve a stored body_path (relative or absolute) to an absolute
32
+ * filesystem path. Returns "" if the input doesn't resolve to a file
33
+ * inside the store. UI / "Source" actions need the absolute path so
34
+ * they can open / pass it to OS file pickers without re-introducing
35
+ * the basePath context every time. */
36
+ absolutePath(stored: string): string;
31
37
  /** Read by stored path (relative or absolute). */
32
38
  readByPath(stored: string): Promise<Buffer>;
33
39
  hasByPath(stored: string): Promise<boolean>;
package/file-store.js CHANGED
@@ -51,6 +51,14 @@ export class FileMessageStore {
51
51
  fs.writeFileSync(abs, raw);
52
52
  return rel;
53
53
  }
54
+ /** Resolve a stored body_path (relative or absolute) to an absolute
55
+ * filesystem path. Returns "" if the input doesn't resolve to a file
56
+ * inside the store. UI / "Source" actions need the absolute path so
57
+ * they can open / pass it to OS file pickers without re-introducing
58
+ * the basePath context every time. */
59
+ absolutePath(stored) {
60
+ return this.resolveStored(stored);
61
+ }
54
62
  /** Read by stored path (relative or absolute). */
55
63
  async readByPath(stored) {
56
64
  const abs = this.resolveStored(stored);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-store",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -10,7 +10,7 @@
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
12
  "@bobfrankston/mailx-types": "^0.1.10",
13
- "@bobfrankston/mailx-settings": "^0.1.11"
13
+ "@bobfrankston/mailx-settings": "^0.1.12"
14
14
  },
15
15
  "repository": {
16
16
  "type": "git",
@@ -26,7 +26,7 @@
26
26
  ".transformedSnapshot": {
27
27
  "dependencies": {
28
28
  "@bobfrankston/mailx-types": "^0.1.10",
29
- "@bobfrankston/mailx-settings": "^0.1.11"
29
+ "@bobfrankston/mailx-settings": "^0.1.12"
30
30
  }
31
31
  }
32
32
  }