@bobfrankston/mailx-store 0.1.56 → 0.1.58
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.d.ts +17 -1
- package/db.js +101 -9
- package/package.json +5 -5
- package/store.d.ts +1 -1
- package/store.js +2 -2
package/db.d.ts
CHANGED
|
@@ -284,6 +284,9 @@ export declare class MailxDB {
|
|
|
284
284
|
inReplyTo: string;
|
|
285
285
|
references: string[];
|
|
286
286
|
date: number;
|
|
287
|
+
/** Date: header (send time). Defaults to `date` when the caller has no
|
|
288
|
+
* header date, so the column is never null. */
|
|
289
|
+
sentDate?: number;
|
|
287
290
|
subject: string;
|
|
288
291
|
from: EmailAddress;
|
|
289
292
|
to: EmailAddress[];
|
|
@@ -298,6 +301,13 @@ export declare class MailxDB {
|
|
|
298
301
|
* message_folders. Omitted/false for Gmail (label = folder, many per
|
|
299
302
|
* message). Set true by the IMAP storeMessages path. */
|
|
300
303
|
exclusive?: boolean;
|
|
304
|
+
/** UIDs the server currently lists for THIS folder, when the caller
|
|
305
|
+
* just enumerated them (the set-diff backfill path). Lets move-detect
|
|
306
|
+
* distinguish a real move (old uid gone) from a DUPLICATE DELIVERY
|
|
307
|
+
* (same Message-ID at two live uids in one folder — e.g. a message
|
|
308
|
+
* addressed to two aliases of the same mailbox). Without this the
|
|
309
|
+
* single row ping-pongs between the two uids every sync cycle. */
|
|
310
|
+
liveServerUids?: Set<number>;
|
|
301
311
|
}): number;
|
|
302
312
|
/** Backfill the FTS5 `body_text` column for a message after its body
|
|
303
313
|
* has been parsed. Capped at ~64 KB of text per row — FTS5 stores the
|
|
@@ -322,7 +332,7 @@ export declare class MailxDB {
|
|
|
322
332
|
/** Short-TTL cache for the unified-inbox survivor COUNT (the GROUP BY that
|
|
323
333
|
* scans the whole inbox). Keyed by flaggedOnly+folder-set. */
|
|
324
334
|
private _unifiedTotalCache;
|
|
325
|
-
getUnifiedInbox(page?: number, pageSize?: number, flaggedOnly?: boolean): PagedResult<MessageEnvelope>;
|
|
335
|
+
getUnifiedInbox(page?: number, pageSize?: number, flaggedOnly?: boolean, dateBasis?: "sent" | "received"): PagedResult<MessageEnvelope>;
|
|
326
336
|
/** Map a `messages` row to a MessageEnvelope. Exposes `uuid` (stable local
|
|
327
337
|
* identity) and `bodyPath` (authoritative on-disk location) in addition
|
|
328
338
|
* to the server-binding metadata. */
|
|
@@ -496,6 +506,12 @@ export declare class MailxDB {
|
|
|
496
506
|
preview: string;
|
|
497
507
|
body_path: string;
|
|
498
508
|
}[];
|
|
509
|
+
/** Count local rows in a folder sharing one Message-ID. The Sent-sweep
|
|
510
|
+
* uses this as a sanity cross-check before re-APPENDing a "missing"
|
|
511
|
+
* message: >1 local copies mean earlier duplicates synced IN from the
|
|
512
|
+
* server, so the server-side header search that just said "missing"
|
|
513
|
+
* was lying (the 3,704-duplicate loop, 2026-07-02). */
|
|
514
|
+
countMessagesByMessageId(accountId: string, folderId: number, messageId: string): number;
|
|
499
515
|
/** Rebind a local row to a different server UID without re-upserting.
|
|
500
516
|
* Used by the Sent-sweep to repair optimistic-insert mispredictions
|
|
501
517
|
* (local row was inserted at the predicted UIDNEXT but the server
|
package/db.js
CHANGED
|
@@ -556,6 +556,44 @@ export class MailxDB {
|
|
|
556
556
|
// parsed (existing IMAP-synced rows that predate the prefetch-reparse
|
|
557
557
|
// fix, where has_attachments stays 0 forever). NULL = never parsed.
|
|
558
558
|
this.addColumnIfMissing("messages", "body_parsed_at", "INTEGER");
|
|
559
|
+
// sent_date: the message's own Date: header (when it was SENT), distinct
|
|
560
|
+
// from `date` which is the per-account arrival time (IMAP INTERNALDATE /
|
|
561
|
+
// Gmail internalDate). Kept as its own column so the list can offer a
|
|
562
|
+
// Sent/Received date basis (Bob 2026-07-01) AND so identical messages
|
|
563
|
+
// delivered to multiple accounts cluster on send time. Backfilled to
|
|
564
|
+
// `date` for existing rows so it is ALWAYS non-null → a plain index sorts
|
|
565
|
+
// it as fast as the received-date path (no COALESCE, no hot-path
|
|
566
|
+
// regression of the getUnifiedInbox streaming walk). Real send times fill
|
|
567
|
+
// in as folders re-sync (or via `-repair`).
|
|
568
|
+
this.addColumnIfMissing("messages", "sent_date", "INTEGER");
|
|
569
|
+
// One-shot heal: strip header-fold whitespace from stored Message-IDs /
|
|
570
|
+
// In-Reply-To. A folded "Message-ID:\r\n\t<...>" header reached the DB
|
|
571
|
+
// as "\t<...>" (pre-trim iflow-direct envelope parser); the tab made
|
|
572
|
+
// every HEADER search for that id return 0 → the sent-sweep re-APPENDed
|
|
573
|
+
// "missing" messages forever (3,707 duplicates of one reply,
|
|
574
|
+
// 2026-07-02). Idempotent — after the first pass the WHERE matches
|
|
575
|
+
// nothing. char(9/10/13)+space covers all RFC 5322 fold whitespace.
|
|
576
|
+
try {
|
|
577
|
+
const ws = "char(9)||char(10)||char(13)||' '";
|
|
578
|
+
this.db.exec(`UPDATE messages SET message_id = TRIM(message_id, ${ws}) WHERE message_id <> TRIM(message_id, ${ws})`);
|
|
579
|
+
this.db.exec(`UPDATE messages SET in_reply_to = TRIM(in_reply_to, ${ws}) WHERE in_reply_to IS NOT NULL AND in_reply_to <> TRIM(in_reply_to, ${ws})`);
|
|
580
|
+
}
|
|
581
|
+
catch { /* non-fatal */ }
|
|
582
|
+
try {
|
|
583
|
+
this.db.exec("CREATE INDEX IF NOT EXISTS idx_messages_folder_sentdate ON messages(account_id, folder_id, sent_date DESC)");
|
|
584
|
+
}
|
|
585
|
+
catch { /* already exists */ }
|
|
586
|
+
try {
|
|
587
|
+
this.db.exec("CREATE INDEX IF NOT EXISTS idx_messages_sentdate ON messages(folder_id, sent_date DESC)");
|
|
588
|
+
}
|
|
589
|
+
catch { /* already exists */ }
|
|
590
|
+
try {
|
|
591
|
+
// One-shot backfill: seed sent_date from date wherever it's still
|
|
592
|
+
// null. Indexed IS NULL check keeps this near-instant after the first
|
|
593
|
+
// pass; new writes set sent_date directly so it converges to a no-op.
|
|
594
|
+
this.db.exec("UPDATE messages SET sent_date = date WHERE sent_date IS NULL");
|
|
595
|
+
}
|
|
596
|
+
catch { /* non-fatal */ }
|
|
559
597
|
// One-shot is_replied backfill on every boot. SQL-only, indexed
|
|
560
598
|
// both sides via idx_messages_message_id and idx_messages_in_reply_to,
|
|
561
599
|
// so a 100k-row mailbox finishes in milliseconds. Idempotent — after
|
|
@@ -1757,7 +1795,7 @@ export class MailxDB {
|
|
|
1757
1795
|
// subject HERE, but only when the existing mid is empty AND the
|
|
1758
1796
|
// incoming msg actually has one — never clobber a good value.
|
|
1759
1797
|
if (msg.messageId && (!existing.message_id || existing.message_id === "")) {
|
|
1760
|
-
this.db.prepare("UPDATE messages SET message_id = ?, date = ?, subject = ? WHERE id = ?").run(msg.messageId, msg.date || Date.now(), msg.subject || "", existing.id);
|
|
1798
|
+
this.db.prepare("UPDATE messages SET message_id = ?, date = ?, sent_date = ?, subject = ? WHERE id = ?").run(msg.messageId, msg.date || Date.now(), msg.sentDate ?? msg.date ?? Date.now(), msg.subject || "", existing.id);
|
|
1761
1799
|
// The row was indexed with an empty subject (it had no envelope),
|
|
1762
1800
|
// so search missed it. Re-index now that we have the real subject
|
|
1763
1801
|
// + from/to. Standalone FTS5 → delete + insert. body_text
|
|
@@ -1790,7 +1828,19 @@ export class MailxDB {
|
|
|
1790
1828
|
// surface transient move-dups. Keeping the collapse for now.
|
|
1791
1829
|
if (msg.messageId) {
|
|
1792
1830
|
const moved = this.db.prepare("SELECT id, folder_id, uid FROM messages WHERE account_id = ? AND message_id = ? LIMIT 1").get(msg.accountId, msg.messageId);
|
|
1793
|
-
|
|
1831
|
+
// Duplicate delivery, NOT a move: same folder, different uid, and
|
|
1832
|
+
// the caller's server UID list confirms the OLD uid is still in the
|
|
1833
|
+
// mailbox. Rebinding here would ping-pong the row between the two
|
|
1834
|
+
// uids on every sync (each cycle sees the unbound uid as
|
|
1835
|
+
// "server-only", refetches it, and rebinds). Fall through to a
|
|
1836
|
+
// fresh insert instead — both copies get their own row, matching
|
|
1837
|
+
// what the server (and every other client) actually shows, and the
|
|
1838
|
+
// set-diff converges.
|
|
1839
|
+
if (moved && moved.folder_id === msg.folderId && moved.uid !== msg.uid
|
|
1840
|
+
&& msg.liveServerUids?.has(moved.uid)) {
|
|
1841
|
+
console.log(` [move-detect] ${msg.accountId} ${msg.messageId}: uid ${moved.uid} still on server — duplicate copy at uid ${msg.uid}, inserting second row`);
|
|
1842
|
+
}
|
|
1843
|
+
else if (moved) {
|
|
1794
1844
|
console.log(` [move-detect] ${msg.accountId} ${msg.messageId}: rebinding row ${moved.id} (folder ${moved.folder_id}/uid ${moved.uid} → folder ${msg.folderId}/uid ${msg.uid})`);
|
|
1795
1845
|
this.db.prepare("UPDATE messages SET folder_id = ?, uid = ?, cached_at = ? WHERE id = ?").run(msg.folderId, msg.uid, Date.now(), moved.id);
|
|
1796
1846
|
this.upsertMessageFolder(moved.id, msg.folderId, msg.uid, msg.exclusive);
|
|
@@ -1832,10 +1882,10 @@ export class MailxDB {
|
|
|
1832
1882
|
const result = this.db.prepare(`
|
|
1833
1883
|
INSERT INTO messages (
|
|
1834
1884
|
account_id, folder_id, uid, uuid, message_id, in_reply_to, refs, thread_id,
|
|
1835
|
-
date, subject, from_address, from_name, to_json, cc_json,
|
|
1885
|
+
date, sent_date, subject, from_address, from_name, to_json, cc_json,
|
|
1836
1886
|
flags_json, size, has_attachments, preview, body_path, body_parsed_at, cached_at, provider_id, is_replied
|
|
1837
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1838
|
-
`).run(msg.accountId, msg.folderId, msg.uid, uuid, msg.messageId, msg.inReplyTo, JSON.stringify(msg.references), threadId, msg.date, msg.subject, msg.from.address, msg.from.name, JSON.stringify(msg.to), JSON.stringify(msg.cc), JSON.stringify(msg.flags), msg.size, msg.hasAttachments ? 1 : 0, msg.preview, msg.bodyPath, msg.bodyPath ? Date.now() : null, Date.now(), msg.providerId || null, alreadyReplied ? 1 : 0);
|
|
1887
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1888
|
+
`).run(msg.accountId, msg.folderId, msg.uid, uuid, msg.messageId, msg.inReplyTo, JSON.stringify(msg.references), threadId, msg.date, msg.sentDate ?? msg.date, msg.subject, msg.from.address, msg.from.name, JSON.stringify(msg.to), JSON.stringify(msg.cc), JSON.stringify(msg.flags), msg.size, msg.hasAttachments ? 1 : 0, msg.preview, msg.bodyPath, msg.bodyPath ? Date.now() : null, Date.now(), msg.providerId || null, alreadyReplied ? 1 : 0);
|
|
1839
1889
|
const rowId = Number(result.lastInsertRowid);
|
|
1840
1890
|
// Forward direction for ↩: if this message is a reply, mark the parent
|
|
1841
1891
|
// as replied-to. Cheap indexed UPDATE; no-op when the parent isn't in
|
|
@@ -1893,7 +1943,10 @@ export class MailxDB {
|
|
|
1893
1943
|
const offset = (page - 1) * pageSize;
|
|
1894
1944
|
const sort = query.sort || "date";
|
|
1895
1945
|
const sortDir = query.sortDir || "desc";
|
|
1896
|
-
|
|
1946
|
+
// Date basis: "sent" (Date: header, default — clusters identical mail
|
|
1947
|
+
// and matches other clients) or "received" (per-account arrival time).
|
|
1948
|
+
const dateCol = query.dateBasis === "received" ? "m.date" : "m.sent_date";
|
|
1949
|
+
const sortCol = sort === "from" ? "m.from_name" : sort === "subject" ? "m.subject" : dateCol;
|
|
1897
1950
|
let where = "m.account_id = ? AND mf.folder_id = ?";
|
|
1898
1951
|
const params = [query.accountId, query.folderId];
|
|
1899
1952
|
if (query.search) {
|
|
@@ -1958,6 +2011,7 @@ export class MailxDB {
|
|
|
1958
2011
|
references: JSON.parse(r.refs || "[]"),
|
|
1959
2012
|
threadId: r.thread_id || undefined,
|
|
1960
2013
|
date: r.date,
|
|
2014
|
+
sentDate: r.sent_date ?? r.date,
|
|
1961
2015
|
subject: r.subject,
|
|
1962
2016
|
from: { name: r.from_name, address: r.from_address },
|
|
1963
2017
|
to: JSON.parse(r.to_json),
|
|
@@ -1976,8 +2030,13 @@ export class MailxDB {
|
|
|
1976
2030
|
/** Short-TTL cache for the unified-inbox survivor COUNT (the GROUP BY that
|
|
1977
2031
|
* scans the whole inbox). Keyed by flaggedOnly+folder-set. */
|
|
1978
2032
|
_unifiedTotalCache = new Map();
|
|
1979
|
-
getUnifiedInbox(page = 1, pageSize = 50, flaggedOnly = false) {
|
|
2033
|
+
getUnifiedInbox(page = 1, pageSize = 50, flaggedOnly = false, dateBasis = "sent") {
|
|
1980
2034
|
const offset = (page - 1) * pageSize;
|
|
2035
|
+
// Date basis: sort/stream by the Date: header (sent, default) or the
|
|
2036
|
+
// per-account arrival time (received). sent_date is always non-null
|
|
2037
|
+
// (backfilled to date), and idx_messages_sentdate mirrors
|
|
2038
|
+
// idx_messages_folder_date, so the streaming walk stays O(page).
|
|
2039
|
+
const dateCol = dateBasis === "received" ? "m.date" : "m.sent_date";
|
|
1981
2040
|
// Find all inbox folder IDs
|
|
1982
2041
|
const inboxRows = this.db.prepare("SELECT id FROM folders WHERE special_use = 'inbox'").all();
|
|
1983
2042
|
if (inboxRows.length === 0)
|
|
@@ -2042,7 +2101,7 @@ export class MailxDB {
|
|
|
2042
2101
|
FROM messages m
|
|
2043
2102
|
JOIN message_folders mf ON mf.message_row_id = m.id
|
|
2044
2103
|
WHERE mf.folder_id IN (${placeholders})${flagFilter}
|
|
2045
|
-
ORDER BY
|
|
2104
|
+
ORDER BY ${dateCol} DESC, m.id DESC`);
|
|
2046
2105
|
// Per-survivor enrichment (runs only for the ≤pageSize kept rows, not the
|
|
2047
2106
|
// whole inbox). Same semantics as the old inline correlated subqueries.
|
|
2048
2107
|
const pendingStmt = this.db.prepare("SELECT 1 FROM sync_actions WHERE account_id = ? AND uid = ? LIMIT 1");
|
|
@@ -2083,6 +2142,7 @@ export class MailxDB {
|
|
|
2083
2142
|
references: JSON.parse(r.refs || "[]"),
|
|
2084
2143
|
threadId: r.thread_id || undefined,
|
|
2085
2144
|
date: r.date,
|
|
2145
|
+
sentDate: r.sent_date ?? r.date,
|
|
2086
2146
|
subject: r.subject,
|
|
2087
2147
|
from: { name: r.from_name, address: r.from_address },
|
|
2088
2148
|
to: JSON.parse(r.to_json),
|
|
@@ -2197,7 +2257,26 @@ export class MailxDB {
|
|
|
2197
2257
|
* for code paths that mean it; everyone else must pass folderId. */
|
|
2198
2258
|
updateMessageFlags(accountId, folderId, uid, flags) {
|
|
2199
2259
|
if (folderId != null) {
|
|
2200
|
-
|
|
2260
|
+
// Resolve the row via its folder MEMBERSHIP, not messages.folder_id.
|
|
2261
|
+
// The message-id collapse (upsertMessage move-detect) keeps ONE
|
|
2262
|
+
// `messages` row per message_id whose `folder_id` is an arbitrary
|
|
2263
|
+
// "primary" folder, while the message appears in OTHER folders via
|
|
2264
|
+
// `message_folders`. A `WHERE folder_id = ?` update therefore MISSED
|
|
2265
|
+
// any message viewed in a folder other than its stored primary —
|
|
2266
|
+
// 0 rows changed, so star/mark-read silently did nothing locally and
|
|
2267
|
+
// the change "came right back" on the next render (Bob 2026-06-27).
|
|
2268
|
+
// Match the membership (the same join getMessageByUid uses).
|
|
2269
|
+
const r = this.db.prepare(`
|
|
2270
|
+
UPDATE messages SET flags_json = ? WHERE id = (
|
|
2271
|
+
SELECT m.id FROM messages m
|
|
2272
|
+
JOIN message_folders mf ON mf.message_row_id = m.id
|
|
2273
|
+
WHERE m.account_id = ? AND mf.folder_id = ? AND mf.uid = ? LIMIT 1
|
|
2274
|
+
)
|
|
2275
|
+
`).run(JSON.stringify(flags), accountId, folderId, uid);
|
|
2276
|
+
// Fallback for any legacy row lacking a membership entry.
|
|
2277
|
+
if (r.changes === 0) {
|
|
2278
|
+
this.db.prepare("UPDATE messages SET flags_json = ? WHERE account_id = ? AND folder_id = ? AND uid = ?").run(JSON.stringify(flags), accountId, folderId, uid);
|
|
2279
|
+
}
|
|
2201
2280
|
}
|
|
2202
2281
|
else {
|
|
2203
2282
|
this.db.prepare("UPDATE messages SET flags_json = ? WHERE account_id = ? AND uid = ?").run(JSON.stringify(flags), accountId, uid);
|
|
@@ -2497,6 +2576,19 @@ export class MailxDB {
|
|
|
2497
2576
|
WHERE account_id = ? AND folder_id = ? AND cached_at >= ?
|
|
2498
2577
|
AND message_id IS NOT NULL AND message_id <> ''`).all(accountId, folderId, sinceMs);
|
|
2499
2578
|
}
|
|
2579
|
+
/** Count local rows in a folder sharing one Message-ID. The Sent-sweep
|
|
2580
|
+
* uses this as a sanity cross-check before re-APPENDing a "missing"
|
|
2581
|
+
* message: >1 local copies mean earlier duplicates synced IN from the
|
|
2582
|
+
* server, so the server-side header search that just said "missing"
|
|
2583
|
+
* was lying (the 3,704-duplicate loop, 2026-07-02). */
|
|
2584
|
+
countMessagesByMessageId(accountId, folderId, messageId) {
|
|
2585
|
+
if (!messageId)
|
|
2586
|
+
return 0;
|
|
2587
|
+
const r = this.db.prepare(`SELECT COUNT(*) AS c FROM messages m
|
|
2588
|
+
JOIN message_folders mf ON mf.message_row_id = m.id
|
|
2589
|
+
WHERE m.account_id = ? AND mf.folder_id = ? AND m.message_id = ?`).get(accountId, folderId, messageId);
|
|
2590
|
+
return r?.c | 0;
|
|
2591
|
+
}
|
|
2500
2592
|
/** Rebind a local row to a different server UID without re-upserting.
|
|
2501
2593
|
* Used by the Sent-sweep to repair optimistic-insert mispredictions
|
|
2502
2594
|
* (local row was inserted at the predicted UIDNEXT but the server
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-store",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.58",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
},
|
|
10
10
|
"license": "ISC",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@bobfrankston/mailx-types": "^0.1.
|
|
13
|
-
"@bobfrankston/mailx-settings": "^0.1.
|
|
12
|
+
"@bobfrankston/mailx-types": "^0.1.22",
|
|
13
|
+
"@bobfrankston/mailx-settings": "^0.1.33",
|
|
14
14
|
"@bobfrankston/mailx-bus": "^0.1.2",
|
|
15
15
|
"mailparser": "^3.7.2"
|
|
16
16
|
},
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
},
|
|
30
30
|
".transformedSnapshot": {
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@bobfrankston/mailx-types": "^0.1.
|
|
33
|
-
"@bobfrankston/mailx-settings": "^0.1.
|
|
32
|
+
"@bobfrankston/mailx-types": "^0.1.22",
|
|
33
|
+
"@bobfrankston/mailx-settings": "^0.1.33",
|
|
34
34
|
"@bobfrankston/mailx-bus": "^0.1.2",
|
|
35
35
|
"mailparser": "^3.7.2"
|
|
36
36
|
}
|
package/store.d.ts
CHANGED
|
@@ -130,7 +130,7 @@ export declare class Store {
|
|
|
130
130
|
/** Paginated message list for a (account, folder, ...) query. */
|
|
131
131
|
getMessages(query: MessageQuery): PagedResult<MessageEnvelope>;
|
|
132
132
|
/** All-Inboxes view: union of every account's INBOX, paginated. */
|
|
133
|
-
getUnifiedInbox(page?: number, pageSize?: number, flaggedOnly?: boolean): PagedResult<MessageEnvelope>;
|
|
133
|
+
getUnifiedInbox(page?: number, pageSize?: number, flaggedOnly?: boolean, dateBasis?: "sent" | "received"): PagedResult<MessageEnvelope>;
|
|
134
134
|
/** Local FTS5 search. Server-scope search is the reconciler's job. */
|
|
135
135
|
searchMessages(query: string, page?: number, pageSize?: number, accountId?: string, folderId?: number, includeTrashSpam?: boolean): PagedResult<MessageEnvelope>;
|
|
136
136
|
/** Read a fully-parsed message (envelope + body + attachments) entirely
|
package/store.js
CHANGED
|
@@ -182,8 +182,8 @@ export class Store {
|
|
|
182
182
|
return this.db.getMessages(query);
|
|
183
183
|
}
|
|
184
184
|
/** All-Inboxes view: union of every account's INBOX, paginated. */
|
|
185
|
-
getUnifiedInbox(page = 1, pageSize = 50, flaggedOnly = false) {
|
|
186
|
-
return this.db.getUnifiedInbox(page, pageSize, flaggedOnly);
|
|
185
|
+
getUnifiedInbox(page = 1, pageSize = 50, flaggedOnly = false, dateBasis = "sent") {
|
|
186
|
+
return this.db.getUnifiedInbox(page, pageSize, flaggedOnly, dateBasis);
|
|
187
187
|
}
|
|
188
188
|
/** Local FTS5 search. Server-scope search is the reconciler's job. */
|
|
189
189
|
searchMessages(query, page = 1, pageSize = 50, accountId, folderId, includeTrashSpam = false) {
|