proton-mail-bridge-client 2.6.0 → 2.7.1

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.
@@ -137,7 +137,11 @@ export declare class LocalIndexService {
137
137
  private loadCheckpointsSync;
138
138
  private loadMessages;
139
139
  private loadThreadCandidateMessages;
140
+ private addThreadsFromMap;
141
+ private loadThreadByKey;
140
142
  private loadThreadMap;
143
+ private loadThreadStats;
144
+ private loadThreadIndex;
141
145
  private loadMessagesByEmailIds;
142
146
  private hasUnresolvedReferenceCandidate;
143
147
  private loadAllMessages;
@@ -1 +1 @@
1
- {"version":3,"file":"local-index-service.d.ts","sourceRoot":"","sources":["../../src/services/local-index-service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"local-index-service.d.ts","sourceRoot":"","sources":["../../src/services/local-index-service.ts"],"names":[],"mappings":"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"}
@@ -374,28 +374,19 @@ function isLikelyAutomatedSender(message) {
374
374
  }
375
375
  return message.from.some((address) => AUTOMATED_SENDER_PATTERN.test(address.address ?? ""));
376
376
  }
377
- function actionableThreadScore(thread, ownerEmail) {
378
- const latestMessage = thread.messages[thread.messages.length - 1];
379
- const latestIsOutgoing = latestMessage ? isOutgoingMessage(latestMessage, ownerEmail) : false;
380
- const pendingOn = latestMessage
381
- ? latestIsOutgoing
382
- ? "them"
383
- : isLikelyAutomatedSender(latestMessage)
384
- ? "unknown"
385
- : "you"
386
- : "unknown";
377
+ function actionableScore(stat) {
387
378
  let score = 0;
388
- score += thread.unreadCount * 10;
389
- if (pendingOn === "you") {
379
+ score += stat.unreadCount * 10;
380
+ if (stat.pendingOn === "you") {
390
381
  score += 8;
391
382
  }
392
- if (latestMessage?.isStarred) {
383
+ if (stat.isStarred) {
393
384
  score += 4;
394
385
  }
395
- if (latestMessage?.hasAttachments) {
386
+ if (stat.hasAttachments) {
396
387
  score += 2;
397
388
  }
398
- const latestTime = new Date(thread.latestDate || 0).getTime();
389
+ const latestTime = new Date(stat.latestDate || 0).getTime();
399
390
  if (latestTime > 0) {
400
391
  const ageHours = (Date.now() - latestTime) / (60 * 60 * 1000);
401
392
  if (ageHours > 24) {
@@ -405,8 +396,47 @@ function actionableThreadScore(thread, ownerEmail) {
405
396
  score += 1;
406
397
  }
407
398
  }
399
+ return score;
400
+ }
401
+ function pendingOnForThread(thread, ownerEmail) {
402
+ const latestMessage = thread.messages[thread.messages.length - 1];
403
+ const latestIsOutgoing = latestMessage ? isOutgoingMessage(latestMessage, ownerEmail) : false;
404
+ return latestMessage
405
+ ? latestIsOutgoing
406
+ ? "them"
407
+ : isLikelyAutomatedSender(latestMessage)
408
+ ? "unknown"
409
+ : "you"
410
+ : "unknown";
411
+ }
412
+ function actionableThreadScore(thread, ownerEmail) {
413
+ const latestMessage = thread.messages[thread.messages.length - 1];
414
+ const pendingOn = pendingOnForThread(thread, ownerEmail);
415
+ const score = actionableScore({
416
+ latestDate: thread.latestDate,
417
+ unreadCount: thread.unreadCount,
418
+ pendingOn,
419
+ isStarred: Boolean(latestMessage?.isStarred),
420
+ hasAttachments: Boolean(latestMessage?.hasAttachments),
421
+ });
408
422
  return { pendingOn, score };
409
423
  }
424
+ // A thread with the fields the actionable lists add on top of it.
425
+ function toActionableSummary(thread, ownerEmail) {
426
+ const latestMessage = thread.messages[thread.messages.length - 1];
427
+ const { pendingOn, score } = actionableThreadScore(thread, ownerEmail);
428
+ return {
429
+ ...thread,
430
+ latestEmailId: latestMessage?.primaryEmailId,
431
+ latestPreview: latestMessage?.preview,
432
+ latestFrom: latestMessage?.from ?? [],
433
+ latestIsRead: latestMessage?.isRead ?? true,
434
+ latestIsStarred: latestMessage?.isStarred ?? false,
435
+ latestHasAttachments: latestMessage?.hasAttachments ?? false,
436
+ pendingOn,
437
+ score,
438
+ };
439
+ }
410
440
  function safeJsonParse(value, fallback) {
411
441
  if (!value) {
412
442
  return fallback;
@@ -626,7 +656,23 @@ export class LocalIndexService {
626
656
  };
627
657
  const limit = normalizedFilters.limit ?? 50;
628
658
  const offset = 0;
629
- if (normalizedFilters.threadId?.trim()) {
659
+ const unrestrictedThreadLookup = !normalizedFilters.folder && normalizedFilters.isRead === undefined && !normalizedFilters.dateFrom;
660
+ if (normalizedFilters.threadId?.trim() && unrestrictedThreadLookup) {
661
+ // No folder/read/date restriction: the thread is built from the whole index, as getThreadById does.
662
+ const db = await this.ensureDb();
663
+ const thread = this.loadThreadByKey(db, normalizedFilters.threadId);
664
+ if (thread) {
665
+ const threadMessages = sortEmailsByNewest(thread.messages).filter((email) => matchesIndexedSearch(email, { ...normalizedFilters, threadId: undefined })).sort((left, right) => searchRelevanceScore(right, normalizedFilters) - searchRelevanceScore(left, normalizedFilters));
666
+ const totalCount = threadMessages.length;
667
+ return {
668
+ total: totalCount,
669
+ hasMore: totalCount > offset + limit,
670
+ emails: threadMessages.slice(offset, offset + limit),
671
+ ...this.indexFreshnessFields(this.readLastSyncAt(db)),
672
+ };
673
+ }
674
+ }
675
+ else if (normalizedFilters.threadId?.trim()) {
630
676
  const snapshot = await this.loadSnapshot({
631
677
  folder: normalizedFilters.folder,
632
678
  isRead: normalizedFilters.isRead,
@@ -928,11 +974,7 @@ export class LocalIndexService {
928
974
  // newest 5000 messages — or one whose id only comes out right when its
929
975
  // complete membership is considered — still resolves instead of throwing
930
976
  // "Thread not found".
931
- const { ownerEmail, updatedAt, folders, indexedFolders } = this.loadFoldersAndMetadata(db);
932
- const syncCheckpoints = this.loadCheckpointsSync(db);
933
- const messages = this.loadAllMessages(db);
934
- const snapshot = { ownerEmail, updatedAt, folders, indexedFolders, syncCheckpoints, messages };
935
- const thread = this.buildThreads(snapshot, true).find((entry) => entry.id === threadId);
977
+ const thread = this.loadThreadByKey(db, threadId);
936
978
  if (!thread) {
937
979
  throw new Error(`Thread not found for id ${threadId}`);
938
980
  }
@@ -951,6 +993,7 @@ export class LocalIndexService {
951
993
  // to narrow by.
952
994
  const unreadOnly = input.unreadOnly !== false;
953
995
  let messages;
996
+ let threadKeys;
954
997
  let messagesCapped = false;
955
998
  if (unreadOnly || input.label || input.query) {
956
999
  // Same SQL-prefilter-then-expand-by-thread_id pattern getThreads() already uses:
@@ -979,7 +1022,9 @@ export class LocalIndexService {
979
1022
  .prepare(sql)
980
1023
  .all(...params)
981
1024
  .map((row) => this.rowToEmailSummary(row));
982
- messages = this.expandCandidatesToFullThreads(db, candidateMessages);
1025
+ const expanded = this.expandCandidatesToFullThreads(db, candidateMessages);
1026
+ messages = expanded.messages;
1027
+ threadKeys = expanded.threadKeys;
983
1028
  }
984
1029
  else {
985
1030
  // unreadOnly explicitly false and no label/query: a genuinely unfiltered "every
@@ -989,7 +1034,7 @@ export class LocalIndexService {
989
1034
  messagesCapped = messages.length >= DEFAULT_SNAPSHOT_LIMIT;
990
1035
  }
991
1036
  const snapshot = { ownerEmail, updatedAt, folders, indexedFolders, syncCheckpoints, messages };
992
- const actionable = this.buildThreads(snapshot, true)
1037
+ const actionable = this.buildThreads(snapshot, true, threadKeys)
993
1038
  .map((thread) => {
994
1039
  const latestMessage = thread.messages[thread.messages.length - 1];
995
1040
  const { pendingOn, score } = actionableThreadScore(thread, snapshot.ownerEmail);
@@ -1098,40 +1143,19 @@ export class LocalIndexService {
1098
1143
  // entirely out of recentSnapshot above. SQL-prefilter by date (any qualifying
1099
1144
  // thread's latest message necessarily satisfies this condition itself), then expand
1100
1145
  // to full, uncapped thread membership — mirrors getFollowUpCandidates()' fix.
1101
- const cutoffIso = new Date(now - staleThresholdMs).toISOString();
1102
- const staleCandidateMessages = db
1103
- .prepare(`SELECT * FROM messages WHERE COALESCE(internal_date, date) < ?`)
1104
- .all(cutoffIso)
1105
- .map((row) => this.rowToEmailSummary(row));
1106
- const staleMessages = this.expandCandidatesToFullThreads(db, staleCandidateMessages);
1107
- const staleSnapshot = { ownerEmail, updatedAt, folders, indexedFolders, syncCheckpoints, messages: staleMessages };
1108
- const staleAwaitingYou = this.buildThreads(staleSnapshot, true)
1109
- .map((thread) => {
1110
- const latestMessage = thread.messages[thread.messages.length - 1];
1111
- const { pendingOn, score } = actionableThreadScore(thread, ownerEmail);
1112
- return {
1113
- ...thread,
1114
- latestEmailId: latestMessage?.primaryEmailId,
1115
- latestPreview: latestMessage?.preview,
1116
- latestFrom: latestMessage?.from ?? [],
1117
- latestIsRead: latestMessage?.isRead ?? true,
1118
- latestIsStarred: latestMessage?.isStarred ?? false,
1119
- latestHasAttachments: latestMessage?.hasAttachments ?? false,
1120
- pendingOn,
1121
- score,
1122
- };
1123
- })
1124
- .filter((thread) => {
1125
- if (thread.pendingOn !== "you" || !thread.latestDate) {
1146
+ const staleRanked = this.loadThreadStats(db, ownerEmail)
1147
+ .filter((stat) => {
1148
+ if (stat.pendingOn !== "you" || !stat.latestDate) {
1126
1149
  return false;
1127
1150
  }
1128
- return now - new Date(thread.latestDate).getTime() >= staleThresholdMs;
1151
+ return now - new Date(stat.latestDate).getTime() >= staleThresholdMs;
1129
1152
  })
1153
+ .map((stat) => ({ stat, score: actionableScore(stat) }))
1130
1154
  .sort((left, right) => {
1131
1155
  if (right.score !== left.score) {
1132
1156
  return right.score - left.score;
1133
1157
  }
1134
- return new Date(right.latestDate || 0).getTime() - new Date(left.latestDate || 0).getTime();
1158
+ return new Date(right.stat.latestDate || 0).getTime() - new Date(left.stat.latestDate || 0).getTime();
1135
1159
  });
1136
1160
  return {
1137
1161
  generatedAt: new Date().toISOString(),
@@ -1143,7 +1167,7 @@ export class LocalIndexService {
1143
1167
  pendingOnThem: allActionable.filter((thread) => thread.pendingOn === "them").length,
1144
1168
  starredThreads: allActionable.filter((thread) => thread.latestIsStarred).length,
1145
1169
  attachmentThreads: allActionable.filter((thread) => thread.latestHasAttachments).length,
1146
- staleAwaitingYou: staleAwaitingYou.length,
1170
+ staleAwaitingYou: staleRanked.length,
1147
1171
  },
1148
1172
  // counts/topThreads come from the newest DEFAULT_SNAPSHOT_LIMIT messages only.
1149
1173
  ...(recentMessages.length >= DEFAULT_SNAPSHOT_LIMIT
@@ -1151,13 +1175,17 @@ export class LocalIndexService {
1151
1175
  : {}),
1152
1176
  ...this.indexFreshnessFields(updatedAt),
1153
1177
  topThreads: allActionable.slice(0, input.limit ?? 10).map((thread) => shapeThreadForList(thread)),
1154
- staleAwaitingYou: staleAwaitingYou.slice(0, input.limit ?? 10).map((thread) => shapeThreadForList(thread)),
1178
+ staleAwaitingYou: staleRanked
1179
+ .slice(0, input.limit ?? 10)
1180
+ .flatMap(({ stat }) => {
1181
+ const thread = this.loadThreadByKey(db, stat.key);
1182
+ return thread ? [shapeThreadForList(toActionableSummary(thread, ownerEmail))] : [];
1183
+ }),
1155
1184
  };
1156
1185
  }
1157
1186
  async getFollowUpCandidates(input = {}) {
1158
1187
  const db = await this.ensureDb();
1159
- const { ownerEmail, updatedAt, folders, indexedFolders } = this.loadFoldersAndMetadata(db);
1160
- const syncCheckpoints = this.loadCheckpointsSync(db);
1188
+ const { ownerEmail, updatedAt } = this.loadFoldersAndMetadata(db);
1161
1189
  const minAgeHours = input.minAgeHours ?? 24;
1162
1190
  const pendingOn = input.pendingOn ?? "you";
1163
1191
  const thresholdMs = minAgeHours * 60 * 60 * 1000;
@@ -1172,39 +1200,20 @@ export class LocalIndexService {
1172
1200
  // qualify below necessarily has that message satisfy this condition itself, so
1173
1201
  // expanding every match to its full thread_id membership (expandCandidatesToFullThreads)
1174
1202
  // is guaranteed not to miss a qualifying thread, unbounded by the snapshot cap.
1175
- const cutoffIso = new Date(now - thresholdMs).toISOString();
1176
- const candidateMessages = db
1177
- .prepare(`SELECT * FROM messages WHERE COALESCE(internal_date, date) < ?`)
1178
- .all(cutoffIso)
1179
- .map((row) => this.rowToEmailSummary(row));
1180
- const messages = this.expandCandidatesToFullThreads(db, candidateMessages);
1181
- const snapshot = { ownerEmail, updatedAt, folders, indexedFolders, syncCheckpoints, messages };
1182
- const candidates = this.buildThreads(snapshot, true)
1183
- .map((thread) => {
1184
- const latestMessage = thread.messages[thread.messages.length - 1];
1185
- const { pendingOn: currentPendingOn, score } = actionableThreadScore(thread, snapshot.ownerEmail);
1186
- const ageHours = thread.latestDate
1187
- ? Math.max(0, Math.round((now - new Date(thread.latestDate).getTime()) / (60 * 60 * 1000)))
1188
- : undefined;
1189
- return {
1190
- ...thread,
1191
- latestEmailId: latestMessage?.primaryEmailId,
1192
- latestPreview: latestMessage?.preview,
1193
- latestFrom: latestMessage?.from ?? [],
1194
- latestIsRead: latestMessage?.isRead ?? true,
1195
- latestIsStarred: latestMessage?.isStarred ?? false,
1196
- latestHasAttachments: latestMessage?.hasAttachments ?? false,
1197
- pendingOn: currentPendingOn,
1198
- score,
1199
- ageHours,
1200
- suggestedAction: currentPendingOn === "you" ? "reply" : currentPendingOn === "them" ? "follow_up" : "review",
1201
- };
1202
- })
1203
- .filter((thread) => {
1204
- if (pendingOn !== "any" && thread.pendingOn !== pendingOn) {
1203
+ // Ranked from the per-thread stats of the whole index; only the page that is returned is built in full.
1204
+ const ranked = this.loadThreadStats(db, ownerEmail)
1205
+ .map((stat) => ({
1206
+ stat,
1207
+ score: actionableScore(stat),
1208
+ ageHours: stat.latestDate
1209
+ ? Math.max(0, Math.round((now - new Date(stat.latestDate).getTime()) / (60 * 60 * 1000)))
1210
+ : undefined,
1211
+ }))
1212
+ .filter(({ stat }) => {
1213
+ if (pendingOn !== "any" && stat.pendingOn !== pendingOn) {
1205
1214
  return false;
1206
1215
  }
1207
- if (thread.latestDate && now - new Date(thread.latestDate).getTime() < thresholdMs) {
1216
+ if (stat.latestDate && now - new Date(stat.latestDate).getTime() < thresholdMs) {
1208
1217
  return false;
1209
1218
  }
1210
1219
  return true;
@@ -1215,18 +1224,31 @@ export class LocalIndexService {
1215
1224
  }
1216
1225
  return right.score - left.score;
1217
1226
  });
1218
- const totalCount = candidates.length;
1227
+ const page = ranked.slice(offset, offset + limit).flatMap(({ stat, ageHours }) => {
1228
+ const thread = this.loadThreadByKey(db, stat.key);
1229
+ if (!thread)
1230
+ return [];
1231
+ const summary = toActionableSummary(thread, ownerEmail);
1232
+ return [
1233
+ {
1234
+ ...summary,
1235
+ ageHours,
1236
+ suggestedAction: summary.pendingOn === "you" ? "reply" : summary.pendingOn === "them" ? "follow_up" : "review",
1237
+ },
1238
+ ];
1239
+ });
1240
+ const totalCount = ranked.length;
1219
1241
  return {
1220
1242
  generatedAt: new Date().toISOString(),
1221
- indexUpdatedAt: snapshot.updatedAt,
1243
+ indexUpdatedAt: updatedAt,
1222
1244
  minAgeHours,
1223
1245
  pendingOn,
1224
1246
  total: totalCount,
1225
1247
  hasMore: totalCount > offset + limit,
1226
1248
  offset,
1227
1249
  ...(totalCount > offset + limit ? { nextOffset: offset + limit } : {}),
1228
- ...this.indexFreshnessFields(snapshot.updatedAt),
1229
- threads: candidates.slice(offset, offset + limit).map((thread) => shapeThreadForList(thread)),
1250
+ ...this.indexFreshnessFields(updatedAt),
1251
+ threads: page.map((thread) => shapeThreadForList(thread)),
1230
1252
  };
1231
1253
  }
1232
1254
  async findDocumentThreads(input = {}) {
@@ -1263,9 +1285,9 @@ export class LocalIndexService {
1263
1285
  .prepare(`SELECT * FROM messages WHERE has_attachments = 1 AND (${keywordConditions.join(" OR ")})`)
1264
1286
  .all(...keywordParams)
1265
1287
  .map((row) => this.rowToEmailSummary(row));
1266
- const messages = this.expandCandidatesToFullThreads(db, candidateMessages);
1267
- const snapshot = { ownerEmail, updatedAt, folders, indexedFolders, syncCheckpoints, messages };
1268
- const matches = this.buildThreads(snapshot, true)
1288
+ const expanded = this.expandCandidatesToFullThreads(db, candidateMessages);
1289
+ const snapshot = { ownerEmail, updatedAt, folders, indexedFolders, syncCheckpoints, messages: expanded.messages };
1290
+ const matches = this.buildThreads(snapshot, true, expanded.threadKeys)
1269
1291
  .map((thread) => {
1270
1292
  const documents = thread.messages.flatMap((message) => message.attachments.filter((attachment) => {
1271
1293
  const haystack = [
@@ -1341,6 +1363,7 @@ export class LocalIndexService {
1341
1363
  params.push(needle, needle, needle);
1342
1364
  }
1343
1365
  let messages;
1366
+ let threadKeys;
1344
1367
  let messagesCapped = false;
1345
1368
  if (conditions.length > 0) {
1346
1369
  // SQL-prefilter by participant text (a safe superset over the exact person/domain
@@ -1352,7 +1375,9 @@ export class LocalIndexService {
1352
1375
  .prepare(sql)
1353
1376
  .all(...params)
1354
1377
  .map((row) => this.rowToEmailSummary(row));
1355
- messages = this.expandCandidatesToFullThreads(db, candidateMessages);
1378
+ const expanded = this.expandCandidatesToFullThreads(db, candidateMessages);
1379
+ messages = expanded.messages;
1380
+ threadKeys = expanded.threadKeys;
1356
1381
  }
1357
1382
  else {
1358
1383
  // Neither person nor domain given: nothing SQL-expressible to narrow by — same
@@ -1361,7 +1386,7 @@ export class LocalIndexService {
1361
1386
  messagesCapped = messages.length >= DEFAULT_SNAPSHOT_LIMIT;
1362
1387
  }
1363
1388
  const snapshot = { ownerEmail, updatedAt, folders, indexedFolders, syncCheckpoints, messages };
1364
- const threads = this.buildThreads(snapshot, true)
1389
+ const threads = this.buildThreads(snapshot, true, threadKeys)
1365
1390
  .filter((thread) => thread.participants.some((participant) => {
1366
1391
  const participantText = `${participant.name ?? ""} ${participant.address ?? ""}`;
1367
1392
  const participantDomain = extractDomain(participant.address || "");
@@ -2035,6 +2060,8 @@ export class LocalIndexService {
2035
2060
  CREATE INDEX IF NOT EXISTS idx_messages_thread_id ON messages(thread_id);
2036
2061
  CREATE INDEX IF NOT EXISTS idx_messages_internal_date ON messages(internal_date);
2037
2062
  CREATE INDEX IF NOT EXISTS idx_messages_folder_date ON messages(folder, internal_date DESC);
2063
+ -- The newest-first order every capped read uses (see loadMessages); without it each call sorted the whole table.
2064
+ CREATE INDEX IF NOT EXISTS idx_messages_recent ON messages(COALESCE(internal_date, date) DESC, uid DESC);
2038
2065
 
2039
2066
  CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
2040
2067
  email_id UNINDEXED,
@@ -2426,34 +2453,66 @@ export class LocalIndexService {
2426
2453
  // getThreadById() now uses for these threads — so the thread is always built
2427
2454
  // from its complete membership, filtered query or not.
2428
2455
  if (this.hasUnresolvedReferenceCandidate(db, candidates)) {
2429
- // Pull in every message of the threads the candidates belong to, with the thread keys the whole
2430
- // index gives them (see loadThreadMap), instead of reading and grouping the entire index each time.
2431
- const map = this.loadThreadMap(db);
2432
- const wanted = new Set();
2433
- for (const email of byId.values()) {
2434
- const key = map.keyByMessage.get(canonicalMessageKey(email));
2435
- if (key !== undefined)
2436
- wanted.add(key);
2437
- }
2438
- const emailIds = [];
2439
- for (const key of wanted) {
2440
- for (const canonicalId of map.messagesByKey.get(key) ?? []) {
2441
- emailIds.push(...(map.emailIdsByMessage.get(canonicalId) ?? []));
2442
- }
2443
- }
2444
- for (const email of this.loadMessagesByEmailIds(db, emailIds)) {
2445
- byId.set(email.id, email);
2446
- }
2447
- return { messages: [...byId.values()], threadKeys: map.keyByMessage };
2456
+ const threadKeys = this.addThreadsFromMap(db, byId);
2457
+ return { messages: [...byId.values()], threadKeys };
2448
2458
  }
2449
2459
  return { messages: [...byId.values()] };
2450
2460
  }
2461
+ // Adds every message of the threads the given messages belong to (using the thread of every message in the
2462
+ // whole index, see loadThreadMap) instead of reading and grouping the entire index each time. Returns those
2463
+ // thread keys; buildThreads must use them, because on a partial set of messages it could land on another key.
2464
+ addThreadsFromMap(db, byId) {
2465
+ const map = this.loadThreadMap(db);
2466
+ const wanted = new Set();
2467
+ for (const email of byId.values()) {
2468
+ const key = map.keyByMessage.get(canonicalMessageKey(email));
2469
+ if (key !== undefined)
2470
+ wanted.add(key);
2471
+ }
2472
+ const emailIds = [];
2473
+ for (const key of wanted) {
2474
+ for (const canonicalId of map.messagesByKey.get(key) ?? []) {
2475
+ emailIds.push(...(map.emailIdsByMessage.get(canonicalId) ?? []));
2476
+ }
2477
+ }
2478
+ for (const email of this.loadMessagesByEmailIds(db, emailIds)) {
2479
+ byId.set(email.id, email);
2480
+ }
2481
+ return map.keyByMessage;
2482
+ }
2483
+ // One thread by its id (the thread key), built from its whole membership.
2484
+ loadThreadByKey(db, threadId) {
2485
+ const map = this.loadThreadMap(db);
2486
+ const members = map.messagesByKey.get(threadId);
2487
+ if (!members)
2488
+ return undefined;
2489
+ const emailIds = members.flatMap((canonicalId) => map.emailIdsByMessage.get(canonicalId) ?? []);
2490
+ const { ownerEmail, updatedAt, folders, indexedFolders } = this.loadFoldersAndMetadata(db);
2491
+ const snapshot = {
2492
+ ownerEmail,
2493
+ updatedAt,
2494
+ folders,
2495
+ indexedFolders,
2496
+ syncCheckpoints: this.loadCheckpointsSync(db),
2497
+ messages: this.loadMessagesByEmailIds(db, emailIds),
2498
+ };
2499
+ return this.buildThreads(snapshot, true, map.keyByMessage).find((entry) => entry.id === threadId);
2500
+ }
2451
2501
  // The thread of every message in the index, worked out exactly as buildThreads does for a full read, but
2452
2502
  // only the keys are kept. Valid until the index is written to.
2453
2503
  loadThreadMap(db) {
2504
+ return this.loadThreadIndex(db, false).map;
2505
+ }
2506
+ // For every thread, what the follow-up and digest rankings need, in buildThreads order. Costs one full
2507
+ // read per index version, like the map it is built beside.
2508
+ loadThreadStats(db, ownerEmail) {
2509
+ return this.loadThreadIndex(db, true, ownerEmail).stats;
2510
+ }
2511
+ loadThreadIndex(db, withStats, ownerArg) {
2454
2512
  const version = `${this.writeVersion}:${String(db.pragma("data_version", { simple: true }))}`;
2455
- if (this.threadMapCache?.version === version) {
2456
- return this.threadMapCache.map;
2513
+ const cached = this.threadMapCache;
2514
+ if (cached?.version === version && (!withStats || cached.stats)) {
2515
+ return { map: cached.map, stats: cached.stats };
2457
2516
  }
2458
2517
  const { ownerEmail, updatedAt, folders, indexedFolders } = this.loadFoldersAndMetadata(db);
2459
2518
  const snapshot = {
@@ -2478,8 +2537,22 @@ export class LocalIndexService {
2478
2537
  messagesByKey.set(message.threadKey, [message.canonicalId]);
2479
2538
  }
2480
2539
  const map = { keyByMessage, messagesByKey, emailIdsByMessage };
2481
- this.threadMapCache = { version, map };
2482
- return map;
2540
+ let stats;
2541
+ if (withStats) {
2542
+ stats = this.buildThreads(snapshot, true, keyByMessage).map((thread) => {
2543
+ const latestMessage = thread.messages[thread.messages.length - 1];
2544
+ return {
2545
+ key: thread.id,
2546
+ latestDate: thread.latestDate,
2547
+ unreadCount: thread.unreadCount,
2548
+ pendingOn: pendingOnForThread(thread, ownerEmail ?? ownerArg),
2549
+ isStarred: Boolean(latestMessage?.isStarred),
2550
+ hasAttachments: Boolean(latestMessage?.hasAttachments),
2551
+ };
2552
+ });
2553
+ }
2554
+ this.threadMapCache = { version, map, stats };
2555
+ return { map, stats };
2483
2556
  }
2484
2557
  loadMessagesByEmailIds(db, emailIds) {
2485
2558
  const result = [];
@@ -2562,11 +2635,10 @@ export class LocalIndexService {
2562
2635
  // why this must also check whether some OTHER message's In-Reply-To points at a
2563
2636
  // header-less candidate, not just the candidate's own inReplyTo/references.
2564
2637
  if (this.hasUnresolvedReferenceCandidate(db, candidates)) {
2565
- for (const email of this.loadAllMessages(db)) {
2566
- byId.set(email.id, email);
2567
- }
2638
+ const threadKeys = this.addThreadsFromMap(db, byId);
2639
+ return { messages: [...byId.values()], threadKeys };
2568
2640
  }
2569
- return [...byId.values()];
2641
+ return { messages: [...byId.values()] };
2570
2642
  }
2571
2643
  async loadSnapshot(options = {}) {
2572
2644
  const db = await this.ensureDb();