@bobfrankston/iflow-direct 0.1.51 → 0.1.53

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/imap-native.js CHANGED
@@ -7,6 +7,13 @@
7
7
  * Existing callers are not affected.
8
8
  */
9
9
  import * as proto from "./imap-protocol.js";
10
+ // Yield the event loop between buffer slices. Node has setImmediate (drains
11
+ // I/O + timers + IPC before resuming); browser / Android (BridgeTransport)
12
+ // runtimes don't, so fall back to a 0 ms timer. Reached via globalThis so this
13
+ // transport-agnostic package needs no node type dependency.
14
+ const yieldToEventLoop = typeof globalThis.setImmediate === "function"
15
+ ? globalThis.setImmediate
16
+ : (cb) => { setTimeout(cb, 0); };
10
17
  export class NativeImapClient {
11
18
  transport;
12
19
  transportFactory;
@@ -356,8 +363,15 @@ export class NativeImapClient {
356
363
  // is the QRESYNC case; without it, the message just vanished
357
364
  // mid-session (post-EXPUNGE on another client).
358
365
  const text = r.text.replace(/^\(EARLIER\)\s*/i, "").trim();
359
- for (const uid of proto.parseUidSet(text))
366
+ // Cap expansion (see parseUidSet): a stale-modseq VANISHED range
367
+ // can be millions of UIDs. Stop at 100k — the caller's bounded
368
+ // reconcile takes over for an over-cap set rather than trusting
369
+ // it (Bob 2026-06-04). 100k > any real single-cycle vanish.
370
+ const VANISHED_EXPAND_CAP = 100_000;
371
+ for (const uid of proto.parseUidSet(text, VANISHED_EXPAND_CAP))
360
372
  vanishedUids.push(uid);
373
+ if (vanishedUids.length >= VANISHED_EXPAND_CAP)
374
+ break;
361
375
  }
362
376
  }
363
377
  this.selectedMailbox = mailbox;
@@ -1201,7 +1215,7 @@ export class NativeImapClient {
1201
1215
  const TIME_BUDGET_MS = 10;
1202
1216
  while (true) {
1203
1217
  if (performance.now() - t0 > TIME_BUDGET_MS) {
1204
- setImmediate(() => this.processBuffer());
1218
+ yieldToEventLoop(() => this.processBuffer());
1205
1219
  return;
1206
1220
  }
1207
1221
  // Check for literal — `{N}` announces N octets of arbitrary data
@@ -90,7 +90,7 @@ export declare function enableCommand(tag: string, extensions: string[]): string
90
90
  * when it has one; here `*` is treated as a sentinel and skipped so the
91
91
  * call site can decide what to do (typically: ignore — VANISHED never
92
92
  * emits `*` because that would be open-ended). */
93
- export declare function parseUidSet(set: string): number[];
93
+ export declare function parseUidSet(set: string, maxExpand?: number): number[];
94
94
  /** Build EXAMINE command (read-only SELECT) */
95
95
  export declare function examineCommand(tag: string, mailbox: string): string;
96
96
  /** Build STATUS command */
package/imap-protocol.js CHANGED
@@ -56,9 +56,17 @@ export function enableCommand(tag, extensions) {
56
56
  * when it has one; here `*` is treated as a sentinel and skipped so the
57
57
  * call site can decide what to do (typically: ignore — VANISHED never
58
58
  * emits `*` because that would be open-ended). */
59
- export function parseUidSet(set) {
59
+ export function parseUidSet(set, maxExpand = Infinity) {
60
+ // maxExpand caps the expanded output. A VANISHED *range* like
61
+ // `(EARLIER) 1:4828883` (sent when the client's modseq is very stale)
62
+ // would otherwise expand to MILLIONS of integers — burning CPU/memory
63
+ // here and wedging the consumer downstream. The caller passes a cap and
64
+ // treats an at-cap result as "too stale, reconcile some other way"
65
+ // (Bob 2026-06-04). Default Infinity keeps every existing caller unchanged.
60
66
  const out = [];
61
67
  for (const part of set.split(",")) {
68
+ if (out.length >= maxExpand)
69
+ break;
62
70
  const trimmed = part.trim();
63
71
  if (!trimmed)
64
72
  continue;
@@ -73,7 +81,7 @@ export function parseUidSet(set) {
73
81
  const hi = range[1] === "*" ? lo : parseInt(range[1], 10);
74
82
  if (!Number.isFinite(lo) || !Number.isFinite(hi) || hi < lo)
75
83
  continue;
76
- for (let u = lo; u <= hi; u++)
84
+ for (let u = lo; u <= hi && out.length < maxExpand; u++)
77
85
  out.push(u);
78
86
  }
79
87
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/iflow-direct",
3
- "version": "0.1.51",
3
+ "version": "0.1.53",
4
4
  "description": "Direct IMAP client — transport-agnostic, no Node.js dependencies, browser-ready",
5
5
  "main": "index.js",
6
6
  "types": "index.ts",