@bobfrankston/mailx-store 0.1.47 → 0.1.48

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.
Files changed (3) hide show
  1. package/db.d.ts +7 -1
  2. package/db.js +35 -1
  3. package/package.json +1 -1
package/db.d.ts CHANGED
@@ -9,7 +9,13 @@ import type { MessageEnvelope, Folder, EmailAddress, PagedResult, MessageQuery }
9
9
  export declare function setContactsDenyPatterns(patterns: string[]): void;
10
10
  export declare class MailxDB {
11
11
  private db;
12
- constructor(dbDir: string);
12
+ /** True for the DB read-worker's connection. Read-only handles never run
13
+ * DDL / migrations / backfills (the main writer already did, before this
14
+ * handle opened) and reject any write at the SQLite level. */
15
+ readonly readOnly: boolean;
16
+ constructor(dbDir: string, opts?: {
17
+ readOnly?: boolean;
18
+ });
13
19
  /** Fail loud + early if expected columns are missing. Cheap (PRAGMA only
14
20
  * runs at startup). The user-facing message names the recovery command. */
15
21
  private verifySchema;
package/db.js CHANGED
@@ -403,10 +403,35 @@ const SCHEMA = `
403
403
  `;
404
404
  export class MailxDB {
405
405
  db;
406
- constructor(dbDir) {
406
+ /** True for the DB read-worker's connection. Read-only handles never run
407
+ * DDL / migrations / backfills (the main writer already did, before this
408
+ * handle opened) and reject any write at the SQLite level. */
409
+ readOnly;
410
+ constructor(dbDir, opts = {}) {
407
411
  fs.mkdirSync(dbDir, { recursive: true });
408
412
  const dbPath = path.join(dbDir, "mailx.db");
413
+ this.readOnly = !!opts.readOnly;
409
414
  this.db = new DatabaseSync(dbPath);
415
+ if (this.readOnly) {
416
+ // Read-worker connection (Phase 0 read isolation). The main-thread
417
+ // MailxDB has ALREADY created the schema and run every migration /
418
+ // backfill before this worker is spawned, so we must NOT touch any
419
+ // of that here — two threads racing writes on one WAL file means
420
+ // SQLITE_BUSY and corruption risk. Strategy:
421
+ // • Do NOT pass {readOnly:true} to DatabaseSync. An OS-read-only
422
+ // handle on a WAL database can't maintain the -shm wal-index
423
+ // and fails to read at all. We open read-WRITE at the OS level
424
+ // so WAL reads work, then forbid writes LOGICALLY below.
425
+ // • PRAGMA query_only = ON makes every statement on this handle
426
+ // read-only — INSERT/UPDATE/DELETE/DDL all throw. That is the
427
+ // guard that lets us reuse the full MailxDB read surface here
428
+ // without any chance of a stray write.
429
+ // WAL mode is a persisted property of the file (the writer set it),
430
+ // so readers see committed snapshots with no journal_mode call.
431
+ this.db.exec("PRAGMA foreign_keys = ON");
432
+ this.db.exec("PRAGMA query_only = ON");
433
+ return;
434
+ }
410
435
  this.db.exec("PRAGMA journal_mode = WAL");
411
436
  this.db.exec("PRAGMA foreign_keys = ON");
412
437
  this.db.exec(SCHEMA);
@@ -1180,6 +1205,7 @@ export class MailxDB {
1180
1205
  accountId: r.account_id,
1181
1206
  folderId: r.folder_id,
1182
1207
  uid: r.uid,
1208
+ uuid: r.uuid || "", // stable identity for row data-uuid / selection restore
1183
1209
  messageId: r.message_id || "",
1184
1210
  inReplyTo: r.in_reply_to || "",
1185
1211
  references: JSON.parse(r.refs || "[]"),
@@ -1873,6 +1899,13 @@ export class MailxDB {
1873
1899
  // Bcc). The unified-inbox UI shows a small ⇆ badge on these
1874
1900
  // rows so the user knows "this is a copy of the same message".
1875
1901
  dupeCount: r.dupeCount | 0,
1902
+ // uuid is the stable local identity each message-list row carries as
1903
+ // data-uuid. Without it, restoreSelection() / rememberPosition() /
1904
+ // the set-diff scroll anchor all match on empty string and fail, so
1905
+ // the selected-row highlight vanishes on every reload (Bob
1906
+ // 2026-06-13: "no highlighting in the summary"). getMessages already
1907
+ // maps uuid; this unified-inbox map dropped it. SELECT m.* exposes it.
1908
+ uuid: r.uuid || "",
1876
1909
  }));
1877
1910
  return { items, total, page, pageSize };
1878
1911
  }
@@ -3132,6 +3165,7 @@ export class MailxDB {
3132
3165
  folderId: r.folder_id,
3133
3166
  folderName: r.folder_name || "",
3134
3167
  uid: r.uid,
3168
+ uuid: r.uuid || "", // stable identity for row data-uuid / selection restore
3135
3169
  messageId: r.message_id || "",
3136
3170
  inReplyTo: r.in_reply_to || "",
3137
3171
  references: JSON.parse(r.refs || "[]"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-store",
3
- "version": "0.1.47",
3
+ "version": "0.1.48",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",