@bobfrankston/iflow 1.0.54 → 1.0.56
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/imaplib/imap-compat.d.ts +2 -2
- package/imaplib/imap-compat.js +4 -3
- package/imaplib/imap-native.d.ts +6 -3
- package/imaplib/imap-native.js +40 -19
- package/package.json +1 -1
package/imaplib/imap-compat.d.ts
CHANGED
|
@@ -37,10 +37,10 @@ export declare class CompatImapClient {
|
|
|
37
37
|
fetchMessagesSinceUid(mailbox: string, sinceUid: number, options?: {
|
|
38
38
|
source?: boolean;
|
|
39
39
|
}): Promise<any[]>;
|
|
40
|
-
/** Fetch messages by date range */
|
|
40
|
+
/** Fetch messages by date range. Optional onChunk callback for incremental processing. */
|
|
41
41
|
fetchMessageByDate(mailbox: string, start: Date, end?: Date, options?: {
|
|
42
42
|
source?: boolean;
|
|
43
|
-
}): Promise<any[]>;
|
|
43
|
+
}, onChunk?: (msgs: any[]) => void): Promise<any[]>;
|
|
44
44
|
/** Fetch a single message by UID */
|
|
45
45
|
fetchMessageByUid(mailbox: string, uid: number, options?: {
|
|
46
46
|
source?: boolean;
|
package/imaplib/imap-compat.js
CHANGED
|
@@ -102,11 +102,12 @@ export class CompatImapClient {
|
|
|
102
102
|
await this.native.closeMailbox();
|
|
103
103
|
return msgs.map(toCompatMessage);
|
|
104
104
|
}
|
|
105
|
-
/** Fetch messages by date range */
|
|
106
|
-
async fetchMessageByDate(mailbox, start, end, options) {
|
|
105
|
+
/** Fetch messages by date range. Optional onChunk callback for incremental processing. */
|
|
106
|
+
async fetchMessageByDate(mailbox, start, end, options, onChunk) {
|
|
107
107
|
await this.ensureConnected();
|
|
108
108
|
await this.native.select(mailbox);
|
|
109
|
-
const
|
|
109
|
+
const chunkCb = onChunk ? (raw) => onChunk(raw.map(toCompatMessage)) : undefined;
|
|
110
|
+
const msgs = await this.native.fetchByDate(start, end, options, chunkCb);
|
|
110
111
|
await this.native.closeMailbox();
|
|
111
112
|
return msgs.map(toCompatMessage);
|
|
112
113
|
}
|
package/imaplib/imap-native.d.ts
CHANGED
|
@@ -86,11 +86,11 @@ export declare class NativeImapClient {
|
|
|
86
86
|
/** Fetch messages since a UID */
|
|
87
87
|
fetchSinceUid(sinceUid: number, options?: {
|
|
88
88
|
source?: boolean;
|
|
89
|
-
}): Promise<NativeFetchedMessage[]>;
|
|
90
|
-
/** Fetch messages by date range */
|
|
89
|
+
}, onChunk?: (msgs: NativeFetchedMessage[]) => void): Promise<NativeFetchedMessage[]>;
|
|
90
|
+
/** Fetch messages by date range. Optional onChunk callback receives each batch as it arrives. */
|
|
91
91
|
fetchByDate(since: Date, before?: Date, options?: {
|
|
92
92
|
source?: boolean;
|
|
93
|
-
}): Promise<NativeFetchedMessage[]>;
|
|
93
|
+
}, onChunk?: (msgs: NativeFetchedMessage[]) => void): Promise<NativeFetchedMessage[]>;
|
|
94
94
|
/** Fetch a single message by UID */
|
|
95
95
|
fetchMessage(uid: number, options?: {
|
|
96
96
|
source?: boolean;
|
|
@@ -119,6 +119,9 @@ export declare class NativeImapClient {
|
|
|
119
119
|
* This is NOT a wall-clock timeout. Timer resets every time data arrives from the server.
|
|
120
120
|
* A large FETCH returning data continuously will never timeout. */
|
|
121
121
|
private inactivityTimeout;
|
|
122
|
+
/** Fetch chunk sizes — start small for quick first paint, ramp up for throughput */
|
|
123
|
+
private static INITIAL_CHUNK_SIZE;
|
|
124
|
+
private static MAX_CHUNK_SIZE;
|
|
122
125
|
/** Active command timer — reset by handleData on every data arrival */
|
|
123
126
|
private commandTimer;
|
|
124
127
|
private sendCommand;
|
package/imaplib/imap-native.js
CHANGED
|
@@ -289,26 +289,33 @@ export class NativeImapClient {
|
|
|
289
289
|
return this.parseFetchResponses(responses);
|
|
290
290
|
}
|
|
291
291
|
/** Fetch messages since a UID */
|
|
292
|
-
async fetchSinceUid(sinceUid, options = {}) {
|
|
293
|
-
// Search for UIDs first, then fetch in chunks (avoids unbounded single command)
|
|
292
|
+
async fetchSinceUid(sinceUid, options = {}, onChunk) {
|
|
294
293
|
const uids = await this.search(`UID ${sinceUid + 1}:*`);
|
|
295
294
|
if (uids.length === 0)
|
|
296
295
|
return [];
|
|
297
|
-
|
|
298
|
-
|
|
296
|
+
console.log(` [fetch] ${uids.length} UIDs since ${sinceUid}`);
|
|
297
|
+
if (uids.length <= NativeImapClient.INITIAL_CHUNK_SIZE) {
|
|
298
|
+
const msgs = await this.fetchMessages(uids.join(","), options);
|
|
299
|
+
if (onChunk)
|
|
300
|
+
onChunk(msgs);
|
|
301
|
+
return msgs;
|
|
299
302
|
}
|
|
300
|
-
// Chunk large fetches
|
|
301
303
|
const allMessages = [];
|
|
302
|
-
|
|
303
|
-
|
|
304
|
+
let chunkSize = NativeImapClient.INITIAL_CHUNK_SIZE;
|
|
305
|
+
for (let i = 0; i < uids.length; i += chunkSize) {
|
|
306
|
+
const chunk = uids.slice(i, i + chunkSize);
|
|
304
307
|
const msgs = await this.fetchMessages(chunk.join(","), options);
|
|
305
308
|
allMessages.push(...msgs);
|
|
309
|
+
console.log(` [fetch] ${allMessages.length}/${uids.length} (chunk of ${chunk.length})`);
|
|
310
|
+
if (onChunk)
|
|
311
|
+
onChunk(msgs);
|
|
312
|
+
if (chunkSize < NativeImapClient.MAX_CHUNK_SIZE)
|
|
313
|
+
chunkSize = Math.min(chunkSize * 4, NativeImapClient.MAX_CHUNK_SIZE);
|
|
306
314
|
}
|
|
307
315
|
return allMessages;
|
|
308
316
|
}
|
|
309
|
-
/** Fetch messages by date range */
|
|
310
|
-
async fetchByDate(since, before, options = {}) {
|
|
311
|
-
// First search for UIDs in date range, then fetch
|
|
317
|
+
/** Fetch messages by date range. Optional onChunk callback receives each batch as it arrives. */
|
|
318
|
+
async fetchByDate(since, before, options = {}, onChunk) {
|
|
312
319
|
const criteria = proto.buildSearchCriteria({
|
|
313
320
|
since,
|
|
314
321
|
before: before || undefined,
|
|
@@ -316,14 +323,18 @@ export class NativeImapClient {
|
|
|
316
323
|
const uids = await this.search(criteria);
|
|
317
324
|
if (uids.length === 0)
|
|
318
325
|
return [];
|
|
319
|
-
|
|
320
|
-
const chunkSize = 500;
|
|
326
|
+
console.log(` [fetch] ${uids.length} UIDs to fetch`);
|
|
321
327
|
const allMessages = [];
|
|
328
|
+
let chunkSize = NativeImapClient.INITIAL_CHUNK_SIZE;
|
|
322
329
|
for (let i = 0; i < uids.length; i += chunkSize) {
|
|
323
330
|
const chunk = uids.slice(i, i + chunkSize);
|
|
324
|
-
const
|
|
325
|
-
const msgs = await this.fetchMessages(range, options);
|
|
331
|
+
const msgs = await this.fetchMessages(chunk.join(","), options);
|
|
326
332
|
allMessages.push(...msgs);
|
|
333
|
+
console.log(` [fetch] ${allMessages.length}/${uids.length} (chunk of ${chunk.length})`);
|
|
334
|
+
if (onChunk)
|
|
335
|
+
onChunk(msgs);
|
|
336
|
+
if (chunkSize < NativeImapClient.MAX_CHUNK_SIZE)
|
|
337
|
+
chunkSize = Math.min(chunkSize * 4, NativeImapClient.MAX_CHUNK_SIZE);
|
|
327
338
|
}
|
|
328
339
|
return allMessages;
|
|
329
340
|
}
|
|
@@ -444,6 +455,9 @@ export class NativeImapClient {
|
|
|
444
455
|
* This is NOT a wall-clock timeout. Timer resets every time data arrives from the server.
|
|
445
456
|
* A large FETCH returning data continuously will never timeout. */
|
|
446
457
|
inactivityTimeout = 30000;
|
|
458
|
+
/** Fetch chunk sizes — start small for quick first paint, ramp up for throughput */
|
|
459
|
+
static INITIAL_CHUNK_SIZE = 25;
|
|
460
|
+
static MAX_CHUNK_SIZE = 500;
|
|
447
461
|
/** Active command timer — reset by handleData on every data arrival */
|
|
448
462
|
commandTimer = null;
|
|
449
463
|
sendCommand(tag, command) {
|
|
@@ -507,11 +521,18 @@ export class NativeImapClient {
|
|
|
507
521
|
}
|
|
508
522
|
processBuffer() {
|
|
509
523
|
while (true) {
|
|
510
|
-
// Check for literal {size}\r\n — reading exact
|
|
524
|
+
// Check for literal {size}\r\n — reading exact BYTE count of literal data
|
|
525
|
+
// CRITICAL: literalBytes is in octets (from IMAP {N}), but this.buffer is a
|
|
526
|
+
// JavaScript string where multi-byte UTF-8 characters count as 1 character.
|
|
527
|
+
// We must use Buffer.byteLength to find the correct character boundary.
|
|
511
528
|
if (this.pendingCommand?.literalBytes != null) {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
529
|
+
const neededBytes = this.pendingCommand.literalBytes;
|
|
530
|
+
const bufferBytes = Buffer.byteLength(this.buffer, "utf-8");
|
|
531
|
+
if (bufferBytes >= neededBytes) {
|
|
532
|
+
// Find the character position that corresponds to the byte boundary
|
|
533
|
+
const buf = Buffer.from(this.buffer, "utf-8");
|
|
534
|
+
let literal = buf.subarray(0, neededBytes).toString("utf-8");
|
|
535
|
+
this.buffer = buf.subarray(neededBytes).toString("utf-8");
|
|
515
536
|
// For non-BODY literals (e.g. display names in ENVELOPE), wrap in quotes
|
|
516
537
|
// so tokenizeParenList treats them as a single token
|
|
517
538
|
if (!this.pendingCommand.currentLiteralKey) {
|
|
@@ -535,7 +556,7 @@ export class NativeImapClient {
|
|
|
535
556
|
continue;
|
|
536
557
|
}
|
|
537
558
|
if (this.verbose && this.pendingCommand.literalBytes > 0) {
|
|
538
|
-
console.log(` [imap] waiting for literal: need ${
|
|
559
|
+
console.log(` [imap] waiting for literal: need ${neededBytes} bytes, have ${bufferBytes} bytes`);
|
|
539
560
|
}
|
|
540
561
|
break; // Wait for more data
|
|
541
562
|
}
|