@bobfrankston/mailx-store 0.1.19 → 0.1.21
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 +7 -0
- package/db.js +46 -5
- package/package.json +3 -3
package/db.d.ts
CHANGED
|
@@ -83,6 +83,7 @@ export declare class MailxDB {
|
|
|
83
83
|
recurringEventId?: string;
|
|
84
84
|
htmlLink?: string;
|
|
85
85
|
isHoliday?: boolean;
|
|
86
|
+
reminderMinutes?: number[];
|
|
86
87
|
}): string;
|
|
87
88
|
getCalendarEvents(accountId: string, fromMs: number, toMs: number): any[];
|
|
88
89
|
/** Lookup by uuid only — used by patch/delete paths that don't have an
|
|
@@ -224,6 +225,12 @@ export declare class MailxDB {
|
|
|
224
225
|
bodyPath: string;
|
|
225
226
|
providerId?: string;
|
|
226
227
|
}): number;
|
|
228
|
+
/** Backfill the FTS5 `body_text` column for a message after its body
|
|
229
|
+
* has been parsed. Capped at ~64 KB of text per row — FTS5 stores the
|
|
230
|
+
* raw text and indexes tokens; we don't need every byte of a 1 MB
|
|
231
|
+
* marketing email to find a word in the first paragraph. Called from
|
|
232
|
+
* LocalStore.getMessage right after simpleParser succeeds. */
|
|
233
|
+
updateFtsBodyByUid(accountId: string, folderId: number, uid: number, bodyText: string): void;
|
|
227
234
|
/** List view: messages currently in (account, folder).
|
|
228
235
|
* Joins through `message_folders` so the UID + folder location come
|
|
229
236
|
* from membership rows, not from the legacy messages.folder_id /
|
package/db.js
CHANGED
|
@@ -411,6 +411,15 @@ export class MailxDB {
|
|
|
411
411
|
// calendar_events table; the flag distinguishes them so the UI can
|
|
412
412
|
// style them differently and the alarm scheduler can skip them.
|
|
413
413
|
this.addColumnIfMissing("calendar_events", "is_holiday", "INTEGER NOT NULL DEFAULT 0");
|
|
414
|
+
// Per-event reminder offsets (Bob 2026-05-12: "use the reminder
|
|
415
|
+
// times in the event (there can be multiple)"). Google's API
|
|
416
|
+
// returns `reminders.overrides` as an array of {method, minutes}
|
|
417
|
+
// — we store the minute offsets as JSON so alarms.ts can fire ONE
|
|
418
|
+
// popup per offset (10-min, 30-min, 1-day, …) instead of the
|
|
419
|
+
// hard-coded 10-min default. `useDefault: true` events get the
|
|
420
|
+
// calendar's defaultReminders pre-resolved server-side and pulled
|
|
421
|
+
// through here too.
|
|
422
|
+
this.addColumnIfMissing("calendar_events", "reminder_minutes_json", "TEXT DEFAULT '[]'");
|
|
414
423
|
// Backfill UUIDs for any pre-existing rows that were inserted before
|
|
415
424
|
// this column landed. One UPDATE + an id roundtrip per row — cheap
|
|
416
425
|
// at our row counts, runs once per DB upgrade.
|
|
@@ -733,12 +742,13 @@ export class MailxDB {
|
|
|
733
742
|
// ── Calendar events (two-way cache) ──
|
|
734
743
|
upsertCalendarEvent(ev) {
|
|
735
744
|
const uuid = ev.uuid || randomUUID().replace(/-/g, "");
|
|
745
|
+
const remindersJson = JSON.stringify(Array.isArray(ev.reminderMinutes) ? ev.reminderMinutes : []);
|
|
736
746
|
this.db.prepare(`
|
|
737
747
|
INSERT INTO calendar_events
|
|
738
748
|
(uuid, account_id, provider_id, calendar_id, title, start_ms, end_ms,
|
|
739
749
|
all_day, location, notes, etag, last_synced, dirty, deleted, updated_at,
|
|
740
|
-
recurring_event_id, html_link, is_holiday)
|
|
741
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?)
|
|
750
|
+
recurring_event_id, html_link, is_holiday, reminder_minutes_json)
|
|
751
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?)
|
|
742
752
|
ON CONFLICT(uuid) DO UPDATE SET
|
|
743
753
|
account_id=excluded.account_id, provider_id=excluded.provider_id,
|
|
744
754
|
calendar_id=excluded.calendar_id, title=excluded.title,
|
|
@@ -749,8 +759,9 @@ export class MailxDB {
|
|
|
749
759
|
updated_at=excluded.updated_at,
|
|
750
760
|
recurring_event_id=excluded.recurring_event_id,
|
|
751
761
|
html_link=excluded.html_link,
|
|
752
|
-
is_holiday=excluded.is_holiday
|
|
753
|
-
|
|
762
|
+
is_holiday=excluded.is_holiday,
|
|
763
|
+
reminder_minutes_json=excluded.reminder_minutes_json
|
|
764
|
+
`).run(uuid, ev.accountId, ev.providerId || null, ev.calendarId || "primary", ev.title, ev.startMs, ev.endMs, ev.allDay ? 1 : 0, ev.location || "", ev.notes || "", ev.etag || null, ev.dirty ? 0 : Date.now(), ev.dirty ? 1 : 0, Date.now(), ev.recurringEventId || null, ev.htmlLink || null, ev.isHoliday ? 1 : 0, remindersJson);
|
|
754
765
|
return uuid;
|
|
755
766
|
}
|
|
756
767
|
getCalendarEvents(accountId, fromMs, toMs) {
|
|
@@ -778,7 +789,15 @@ export class MailxDB {
|
|
|
778
789
|
return rows.map(this.calendarRowToObject);
|
|
779
790
|
}
|
|
780
791
|
calendarRowToObject(r) {
|
|
792
|
+
let reminderMinutes = [];
|
|
793
|
+
try {
|
|
794
|
+
const parsed = JSON.parse(r.reminder_minutes_json || "[]");
|
|
795
|
+
if (Array.isArray(parsed))
|
|
796
|
+
reminderMinutes = parsed.filter((n) => Number.isFinite(n)).map((n) => Number(n));
|
|
797
|
+
}
|
|
798
|
+
catch { /* invalid JSON → empty */ }
|
|
781
799
|
return {
|
|
800
|
+
reminderMinutes,
|
|
782
801
|
uuid: r.uuid, accountId: r.account_id, providerId: r.provider_id,
|
|
783
802
|
calendarId: r.calendar_id, title: r.title, startMs: r.start_ms,
|
|
784
803
|
endMs: r.end_ms, allDay: !!r.all_day, location: r.location, notes: r.notes,
|
|
@@ -1372,13 +1391,35 @@ export class MailxDB {
|
|
|
1372
1391
|
// sync during the additive migration but reads will move to JOIN
|
|
1373
1392
|
// with message_folders.
|
|
1374
1393
|
this.upsertMessageFolder(rowId, msg.folderId, msg.uid);
|
|
1375
|
-
// Index for full-text search
|
|
1394
|
+
// Index for full-text search. body_text seeded from `msg.preview`
|
|
1395
|
+
// here — the full parsed body isn't available at upsert time (we
|
|
1396
|
+
// store .eml on disk; parsing is on-demand). LocalStore.getMessage
|
|
1397
|
+
// calls `updateFtsBodyByUid` after simpleParser runs to swap the
|
|
1398
|
+
// preview-only seed for the full body text, so subsequent searches
|
|
1399
|
+
// hit body content too. Without that backfill, words that only
|
|
1400
|
+
// appear deep in the body (Bob 2026-05-12: "I searched for ksink
|
|
1401
|
+
// which is in <...>.eml but it was not found") never match.
|
|
1376
1402
|
try {
|
|
1377
1403
|
this.db.prepare("INSERT INTO messages_fts (rowid, subject, from_name, from_address, to_text, cc_text, body_text) VALUES (?, ?, ?, ?, ?, ?, ?)").run(rowId, msg.subject, msg.from.name, msg.from.address, toText, ccText, msg.preview);
|
|
1378
1404
|
}
|
|
1379
1405
|
catch { /* FTS insert may fail on rebuild, non-fatal */ }
|
|
1380
1406
|
return rowId;
|
|
1381
1407
|
}
|
|
1408
|
+
/** Backfill the FTS5 `body_text` column for a message after its body
|
|
1409
|
+
* has been parsed. Capped at ~64 KB of text per row — FTS5 stores the
|
|
1410
|
+
* raw text and indexes tokens; we don't need every byte of a 1 MB
|
|
1411
|
+
* marketing email to find a word in the first paragraph. Called from
|
|
1412
|
+
* LocalStore.getMessage right after simpleParser succeeds. */
|
|
1413
|
+
updateFtsBodyByUid(accountId, folderId, uid, bodyText) {
|
|
1414
|
+
try {
|
|
1415
|
+
const row = this.db.prepare("SELECT id FROM messages WHERE account_id = ? AND folder_id = ? AND uid = ?").get(accountId, folderId, uid);
|
|
1416
|
+
if (!row)
|
|
1417
|
+
return;
|
|
1418
|
+
const capped = bodyText.length > 64_000 ? bodyText.slice(0, 64_000) : bodyText;
|
|
1419
|
+
this.db.prepare("UPDATE messages_fts SET body_text = ? WHERE rowid = ?").run(capped, row.id);
|
|
1420
|
+
}
|
|
1421
|
+
catch { /* FTS update is best-effort */ }
|
|
1422
|
+
}
|
|
1382
1423
|
/** List view: messages currently in (account, folder).
|
|
1383
1424
|
* Joins through `message_folders` so the UID + folder location come
|
|
1384
1425
|
* from membership rows, not from the legacy messages.folder_id /
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-store",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
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.11",
|
|
13
|
-
"@bobfrankston/mailx-settings": "^0.1.
|
|
13
|
+
"@bobfrankston/mailx-settings": "^0.1.16"
|
|
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.11",
|
|
29
|
-
"@bobfrankston/mailx-settings": "^0.1.
|
|
29
|
+
"@bobfrankston/mailx-settings": "^0.1.16"
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
}
|