@bobfrankston/rmfmail 1.0.696 → 1.0.698

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.
@@ -166,7 +166,7 @@ function decodeEntities(text: string): string {
166
166
  /** Extract a plain-text preview from message source */
167
167
  async function extractPreview(source: string): Promise<{ bodyHtml: string; bodyText: string; preview: string; hasAttachments: boolean }> {
168
168
  try {
169
- const parsed = await parseSerial(source);
169
+ const parsed = await parseSerial(source, "background");
170
170
  const bodyText = parsed.text || "";
171
171
  const bodyHtml = parsed.html || "";
172
172
  // Use text part; fall back to stripping HTML tags if text is empty
@@ -1007,7 +1007,11 @@ export class ImapManager extends EventEmitter {
1007
1007
  source: string,
1008
1008
  flags: string[],
1009
1009
  ): Promise<void> {
1010
- const parsed = await parseSerial(source);
1010
+ // insertLocalRowFromSource runs right after sendMessage — that's a
1011
+ // user-initiated path but the parse cost is on the post-send
1012
+ // background work, not the click-through. Tag as background so a
1013
+ // concurrent user-click foreground parse jumps ahead.
1014
+ const parsed = await parseSerial(source, "background");
1011
1015
  // Coerce mailparser AddressObject(s) into the flat `{name, address}[]`
1012
1016
  // shape storeMessages's downstream toEmailAddresses expects.
1013
1017
  // RFC 2047 encoded-word decoding (incl. inside quoted-strings) is
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-imap",
3
- "version": "0.1.41",
3
+ "version": "0.1.42",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bobfrankston/mailx-imap",
9
- "version": "0.1.41",
9
+ "version": "0.1.42",
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
12
  "@bobfrankston/iflow-direct": "^0.1.27",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-imap",
3
- "version": "0.1.41",
3
+ "version": "0.1.42",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-store",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -27,7 +27,8 @@
27
27
  * pool is built.
28
28
  */
29
29
  import { type ParsedMail, type Source } from "mailparser";
30
- /** Serialized wrapper around `mailparser.simpleParser`. Drop-in replacement —
31
- * same signature, same return value. */
32
- export declare function parseSerial(source: Source): Promise<ParsedMail>;
30
+ /** Serialized wrapper around `mailparser.simpleParser`. `priority` defaults
31
+ * to "foreground" only sync/background callers should pass "background"
32
+ * so user clicks aren't stuck behind a back-fill. */
33
+ export declare function parseSerial(source: Source, priority?: "foreground" | "background"): Promise<ParsedMail>;
33
34
  //# sourceMappingURL=parse-serial.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"parse-serial.d.ts","sourceRoot":"","sources":["parse-serial.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAgB,KAAK,UAAU,EAAE,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;AAIxE;yCACyC;AACzC,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAIrE"}
1
+ {"version":3,"file":"parse-serial.d.ts","sourceRoot":"","sources":["parse-serial.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAgB,KAAK,UAAU,EAAE,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;AAsCxE;;sDAEsD;AACtD,wBAAsB,WAAW,CAC7B,MAAM,EAAE,MAAM,EACd,QAAQ,GAAE,YAAY,GAAG,YAA2B,GACrD,OAAO,CAAC,UAAU,CAAC,CAOrB"}
@@ -27,12 +27,42 @@
27
27
  * pool is built.
28
28
  */
29
29
  import { simpleParser } from "mailparser";
30
- let _chain = Promise.resolve();
31
- /** Serialized wrapper around `mailparser.simpleParser`. Drop-in replacement
32
- * same signature, same return value. */
33
- export async function parseSerial(source) {
34
- const queued = _chain.then(() => simpleParser(source));
35
- _chain = queued.catch(() => undefined);
36
- return queued;
30
+ const _head = []; // UI / foreground — drained first
31
+ const _tail = []; // sync / background
32
+ let _pumping = false;
33
+ async function pump() {
34
+ if (_pumping)
35
+ return;
36
+ _pumping = true;
37
+ try {
38
+ for (;;) {
39
+ const next = _head.shift() ?? _tail.shift();
40
+ if (!next)
41
+ break;
42
+ try {
43
+ const parsed = await simpleParser(next.source);
44
+ next.resolve(parsed);
45
+ }
46
+ catch (e) {
47
+ next.reject(e);
48
+ }
49
+ }
50
+ }
51
+ finally {
52
+ _pumping = false;
53
+ }
54
+ }
55
+ /** Serialized wrapper around `mailparser.simpleParser`. `priority` defaults
56
+ * to "foreground" — only sync/background callers should pass "background"
57
+ * so user clicks aren't stuck behind a back-fill. */
58
+ export async function parseSerial(source, priority = "foreground") {
59
+ return new Promise((resolve, reject) => {
60
+ const entry = { source, resolve, reject };
61
+ if (priority === "foreground")
62
+ _head.push(entry);
63
+ else
64
+ _tail.push(entry);
65
+ pump();
66
+ });
37
67
  }
38
68
  //# sourceMappingURL=parse-serial.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"parse-serial.js","sourceRoot":"","sources":["parse-serial.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,YAAY,EAAgC,MAAM,YAAY,CAAC;AAExE,IAAI,MAAM,GAAqB,OAAO,CAAC,OAAO,EAAE,CAAC;AAEjD;yCACyC;AACzC,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAc;IAC5C,MAAM,MAAM,GAAwB,MAAM,CAAC,IAAI,CAAC,GAAwB,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;IACjG,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,GAAc,EAAE,CAAC,SAAS,CAAC,CAAC;IAClD,OAAO,MAAM,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"parse-serial.js","sourceRoot":"","sources":["parse-serial.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,YAAY,EAAgC,MAAM,YAAY,CAAC;AAexE,MAAM,KAAK,GAAc,EAAE,CAAC,CAAE,kCAAkC;AAChE,MAAM,KAAK,GAAc,EAAE,CAAC,CAAE,oBAAoB;AAClD,IAAI,QAAQ,GAAG,KAAK,CAAC;AAErB,KAAK,UAAU,IAAI;IACf,IAAI,QAAQ;QAAE,OAAO;IACrB,QAAQ,GAAG,IAAI,CAAC;IAChB,IAAI,CAAC;QACD,SAAS,CAAC;YACN,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,EAAE,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAC5C,IAAI,CAAC,IAAI;gBAAE,MAAM;YACjB,IAAI,CAAC;gBACD,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBAC/C,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YACzB,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACT,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YACnB,CAAC;QACL,CAAC;IACL,CAAC;YAAS,CAAC;QACP,QAAQ,GAAG,KAAK,CAAC;IACrB,CAAC;AACL,CAAC;AAED;;sDAEsD;AACtD,MAAM,CAAC,KAAK,UAAU,WAAW,CAC7B,MAAc,EACd,WAAwC,YAAY;IAEpD,OAAO,IAAI,OAAO,CAAa,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC/C,MAAM,KAAK,GAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QACnD,IAAI,QAAQ,KAAK,YAAY;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;;YAC5C,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvB,IAAI,EAAE,CAAC;IACX,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -29,12 +29,53 @@
29
29
 
30
30
  import { simpleParser, type ParsedMail, type Source } from "mailparser";
31
31
 
32
- let _chain: Promise<unknown> = Promise.resolve();
32
+ /**
33
+ * Priority queue. A background pump runs the next pending parse; UI calls
34
+ * insert at the head of the line, sync calls go to the tail. Without this,
35
+ * a first-sync of 96 folders × thousands of messages each can queue
36
+ * thousands of `extractPreview` parses ahead of a single user click — the
37
+ * preview pane sits on "Loading body…" for minutes while the back-fill
38
+ * grinds through. Same parses, different order.
39
+ */
40
+ type Pending = {
41
+ source: Source;
42
+ resolve: (m: ParsedMail) => void;
43
+ reject: (e: unknown) => void;
44
+ };
45
+ const _head: Pending[] = []; // UI / foreground — drained first
46
+ const _tail: Pending[] = []; // sync / background
47
+ let _pumping = false;
48
+
49
+ async function pump(): Promise<void> {
50
+ if (_pumping) return;
51
+ _pumping = true;
52
+ try {
53
+ for (;;) {
54
+ const next = _head.shift() ?? _tail.shift();
55
+ if (!next) break;
56
+ try {
57
+ const parsed = await simpleParser(next.source);
58
+ next.resolve(parsed);
59
+ } catch (e) {
60
+ next.reject(e);
61
+ }
62
+ }
63
+ } finally {
64
+ _pumping = false;
65
+ }
66
+ }
33
67
 
34
- /** Serialized wrapper around `mailparser.simpleParser`. Drop-in replacement —
35
- * same signature, same return value. */
36
- export async function parseSerial(source: Source): Promise<ParsedMail> {
37
- const queued: Promise<ParsedMail> = _chain.then((): Promise<ParsedMail> => simpleParser(source));
38
- _chain = queued.catch((): undefined => undefined);
39
- return queued;
68
+ /** Serialized wrapper around `mailparser.simpleParser`. `priority` defaults
69
+ * to "foreground" only sync/background callers should pass "background"
70
+ * so user clicks aren't stuck behind a back-fill. */
71
+ export async function parseSerial(
72
+ source: Source,
73
+ priority: "foreground" | "background" = "foreground",
74
+ ): Promise<ParsedMail> {
75
+ return new Promise<ParsedMail>((resolve, reject) => {
76
+ const entry: Pending = { source, resolve, reject };
77
+ if (priority === "foreground") _head.push(entry);
78
+ else _tail.push(entry);
79
+ pump();
80
+ });
40
81
  }
@@ -1,116 +0,0 @@
1
- {
2
- "name": "@bobfrankston/mailx-imap",
3
- "version": "0.1.12",
4
- "lockfileVersion": 3,
5
- "requires": true,
6
- "packages": {
7
- "../../../../projects/oauth/oauthsupport": {
8
- "name": "@bobfrankston/oauthsupport",
9
- "version": "1.0.25",
10
- "license": "MIT",
11
- "devDependencies": {
12
- "@types/node": "^25.2.1"
13
- },
14
- "engines": {
15
- "node": ">=18.0.0"
16
- }
17
- },
18
- "../../../MailApps/iflow-direct": {
19
- "name": "@bobfrankston/iflow-direct",
20
- "version": "0.1.27",
21
- "license": "ISC",
22
- "dependencies": {
23
- "@bobfrankston/tcp-transport": "file:../tcp-transport"
24
- },
25
- "devDependencies": {
26
- "@types/node": "^25.6.0"
27
- }
28
- },
29
- "../../../MailApps/mailx-sync": {
30
- "name": "@bobfrankston/mailx-sync",
31
- "version": "0.1.10",
32
- "license": "ISC",
33
- "dependencies": {
34
- "@bobfrankston/iflow-direct": "file:../iflow-direct",
35
- "@bobfrankston/tcp-transport": "file:../tcp-transport"
36
- },
37
- "devDependencies": {
38
- "@types/node": "^25.6.0"
39
- }
40
- },
41
- "../../../MailApps/smtp-direct": {
42
- "name": "@bobfrankston/smtp-direct",
43
- "version": "0.1.5",
44
- "license": "ISC",
45
- "dependencies": {
46
- "@bobfrankston/tcp-transport": "file:../tcp-transport"
47
- },
48
- "devDependencies": {
49
- "@types/node": "^25.6.0"
50
- }
51
- },
52
- "../../../MailApps/tcp-transport": {
53
- "name": "@bobfrankston/tcp-transport",
54
- "version": "0.1.5",
55
- "license": "ISC",
56
- "devDependencies": {
57
- "@types/node": "^25.6.0"
58
- }
59
- },
60
- "../../app/packages/mailx-settings": {
61
- "name": "@bobfrankston/mailx-settings",
62
- "version": "0.1.6",
63
- "license": "ISC",
64
- "dependencies": {
65
- "@bobfrankston/mailx-types": "file:../mailx-types",
66
- "jsonc-parser": "^3.3.1"
67
- }
68
- },
69
- "../../app/packages/mailx-store": {
70
- "name": "@bobfrankston/mailx-store",
71
- "version": "0.1.5",
72
- "license": "ISC",
73
- "dependencies": {
74
- "@bobfrankston/mailx-settings": "file:../mailx-settings",
75
- "@bobfrankston/mailx-types": "file:../mailx-types"
76
- }
77
- },
78
- "../../app/packages/mailx-types": {
79
- "name": "@bobfrankston/mailx-types",
80
- "version": "0.1.5",
81
- "license": "ISC"
82
- },
83
- "node_modules/@bobfrankston/iflow-direct": {
84
- "resolved": "../../../MailApps/iflow-direct",
85
- "link": true
86
- },
87
- "node_modules/@bobfrankston/mailx-settings": {
88
- "resolved": "../../app/packages/mailx-settings",
89
- "link": true
90
- },
91
- "node_modules/@bobfrankston/mailx-store": {
92
- "resolved": "../../app/packages/mailx-store",
93
- "link": true
94
- },
95
- "node_modules/@bobfrankston/mailx-sync": {
96
- "resolved": "../../../MailApps/mailx-sync",
97
- "link": true
98
- },
99
- "node_modules/@bobfrankston/mailx-types": {
100
- "resolved": "../../app/packages/mailx-types",
101
- "link": true
102
- },
103
- "node_modules/@bobfrankston/oauthsupport": {
104
- "resolved": "../../../../projects/oauth/oauthsupport",
105
- "link": true
106
- },
107
- "node_modules/@bobfrankston/smtp-direct": {
108
- "resolved": "../../../MailApps/smtp-direct",
109
- "link": true
110
- },
111
- "node_modules/@bobfrankston/tcp-transport": {
112
- "resolved": "../../../MailApps/tcp-transport",
113
- "link": true
114
- }
115
- }
116
- }