readline-pager 0.2.6 → 0.3.0

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/dist/main.cjs CHANGED
@@ -1,63 +1,189 @@
1
- Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ let node_fs = require("node:fs");
2
6
  let node_fs_promises = require("node:fs/promises");
3
7
  let node_worker_threads = require("node:worker_threads");
4
-
5
8
  //#region src/queue.ts
6
- function createPageQueue() {
7
- const queue = [];
8
- let resolver = null;
9
+ function createRingBuffer(capacity) {
10
+ if (!Number.isFinite(capacity) || capacity <= 0) throw new RangeError("capacity must be a positive number");
11
+ let buf = new Array(capacity);
12
+ let head = 0;
13
+ let tail = 0;
14
+ let count = 0;
15
+ let consumerWaiter = null;
16
+ let producerWaiter = null;
17
+ function push(item) {
18
+ if (count === buf.length) {
19
+ const newCap = buf.length * 2;
20
+ const newBuf = new Array(newCap);
21
+ for (let i = 0; i < count; i++) newBuf[i] = buf[(head + i) % buf.length];
22
+ buf = newBuf;
23
+ head = 0;
24
+ tail = count;
25
+ }
26
+ buf[tail] = item;
27
+ tail++;
28
+ if (tail === buf.length) tail = 0;
29
+ count++;
30
+ if (consumerWaiter) {
31
+ const w = consumerWaiter;
32
+ consumerWaiter = null;
33
+ w();
34
+ }
35
+ }
36
+ function shiftSync() {
37
+ if (count === 0) return null;
38
+ const v = buf[head];
39
+ buf[head] = void 0;
40
+ head++;
41
+ if (head === buf.length) head = 0;
42
+ count--;
43
+ if (producerWaiter) {
44
+ const w = producerWaiter;
45
+ producerWaiter = null;
46
+ w();
47
+ }
48
+ return v;
49
+ }
50
+ async function shift(done = false) {
51
+ if (count) return shiftSync();
52
+ if (done) return null;
53
+ await new Promise((r) => {
54
+ consumerWaiter = r;
55
+ });
56
+ if (count) return shiftSync();
57
+ return null;
58
+ }
59
+ function wake() {
60
+ if (consumerWaiter) {
61
+ const w = consumerWaiter;
62
+ consumerWaiter = null;
63
+ w();
64
+ }
65
+ if (producerWaiter) {
66
+ const w = producerWaiter;
67
+ producerWaiter = null;
68
+ w();
69
+ }
70
+ }
71
+ function clear() {
72
+ for (let i = 0; i < buf.length; i++) buf[i] = void 0;
73
+ head = 0;
74
+ tail = 0;
75
+ count = 0;
76
+ wake();
77
+ }
9
78
  return {
10
- queue,
11
- push(page) {
12
- queue.push(page);
13
- resolver?.();
14
- resolver = null;
15
- },
16
- wake() {
17
- resolver?.();
18
- resolver = null;
79
+ push,
80
+ shift,
81
+ shiftSync,
82
+ wake,
83
+ clear,
84
+ get count() {
85
+ return count;
19
86
  },
20
- async shift(done) {
21
- if (queue.length) return queue.shift();
22
- if (done()) return null;
23
- await new Promise((r) => resolver = r);
24
- if (queue.length) return queue.shift();
25
- if (done()) return null;
26
- return null;
87
+ get capacity() {
88
+ return buf.length;
27
89
  }
28
90
  };
29
91
  }
30
-
31
92
  //#endregion
32
93
  //#region src/reader/backward.reader.ts
33
94
  function createBackwardReader(filepath, options) {
34
95
  const { chunkSize, pageSize, delimiter, prefetch } = options;
35
- const pageQueue = createPageQueue();
96
+ const pageQueue = createRingBuffer(Math.max(2, prefetch + 1));
97
+ const local = [];
36
98
  let fd = null;
99
+ let fdSync = null;
37
100
  let pos = 0;
38
101
  let buffer = "";
39
102
  let done = false;
40
103
  let closed = false;
41
- let emittedCount = 0;
42
- let firstLine = null;
43
- let lastLine = null;
44
- const local = [];
45
- async function init() {
46
- if (fd) return;
47
- fd = await (0, node_fs_promises.open)(filepath, "r");
48
- pos = (await fd.stat()).size;
49
- if (pos === 0) done = true;
104
+ let startsWithDelimiter = false;
105
+ fdSync = (0, node_fs.openSync)(filepath, "r");
106
+ pos = (0, node_fs.statSync)(filepath).size;
107
+ if (pos === 0) {
108
+ pageQueue.push([buffer]);
109
+ done = true;
110
+ pageQueue.wake();
50
111
  }
51
- async function fill() {
112
+ (async () => {
113
+ try {
114
+ fd = await (0, node_fs_promises.open)(filepath, "r");
115
+ pos = (await fd.stat()).size;
116
+ if (pos === 0) {
117
+ if (!done) {
118
+ pageQueue.push([buffer]);
119
+ done = true;
120
+ }
121
+ if (fd) {
122
+ await fd.close();
123
+ fd = null;
124
+ }
125
+ pageQueue.wake();
126
+ return;
127
+ }
128
+ while (!done && !closed) {
129
+ while (pageQueue.count < prefetch && pos > 0 && !closed) {
130
+ const readSize = Math.min(chunkSize, pos);
131
+ pos -= readSize;
132
+ const buf = Buffer.allocUnsafe(readSize);
133
+ await fd.read(buf, 0, readSize, pos);
134
+ buffer = buf.toString("utf8") + buffer;
135
+ if (pos === 0 && buffer.startsWith(delimiter)) startsWithDelimiter = true;
136
+ let idx;
137
+ while ((idx = buffer.lastIndexOf(delimiter)) !== -1) {
138
+ const line = buffer.slice(idx + delimiter.length);
139
+ buffer = buffer.slice(0, idx);
140
+ local.push(line);
141
+ while (local.length >= pageSize) {
142
+ const page = local.splice(0, pageSize);
143
+ pageQueue.push(page);
144
+ }
145
+ }
146
+ }
147
+ if (pos === 0 && !done) {
148
+ if (buffer.length > 0) local.push(buffer);
149
+ else if (startsWithDelimiter) local.push("");
150
+ buffer = "";
151
+ while (local.length > 0 && !closed) {
152
+ const page = local.slice(local.length - Math.min(pageSize, local.length));
153
+ local.length -= page.length;
154
+ pageQueue.push(page);
155
+ }
156
+ done = true;
157
+ if (fd) {
158
+ await fd.close();
159
+ fd = null;
160
+ }
161
+ pageQueue.wake();
162
+ break;
163
+ }
164
+ if (!done && !closed) await new Promise((r) => setImmediate(r));
165
+ }
166
+ } catch {
167
+ done = true;
168
+ pageQueue.wake();
169
+ try {
170
+ if (fd) {
171
+ await fd.close();
172
+ fd = null;
173
+ }
174
+ } catch {}
175
+ }
176
+ })();
177
+ function fillSync() {
52
178
  if (done || closed) return;
53
- await init();
54
- if (!fd) return;
55
- while (pageQueue.queue.length < prefetch && pos > 0) {
179
+ if (fdSync === null) return;
180
+ while (pageQueue.count < prefetch && pos > 0 && !closed) {
56
181
  const readSize = Math.min(chunkSize, pos);
57
182
  pos -= readSize;
58
183
  const buf = Buffer.allocUnsafe(readSize);
59
- await fd.read(buf, 0, readSize, pos);
60
- buffer += buf.toString("utf8");
184
+ (0, node_fs.readSync)(fdSync, buf, 0, readSize, pos);
185
+ buffer = buf.toString("utf8") + buffer;
186
+ if (pos === 0 && buffer.startsWith(delimiter)) startsWithDelimiter = true;
61
187
  let idx;
62
188
  while ((idx = buffer.lastIndexOf(delimiter)) !== -1) {
63
189
  const line = buffer.slice(idx + delimiter.length);
@@ -69,90 +195,178 @@ function createBackwardReader(filepath, options) {
69
195
  }
70
196
  }
71
197
  }
72
- if (pos === 0) {
73
- local.push(buffer);
198
+ if (pos === 0 && !done) {
199
+ if (buffer.length > 0) local.push(buffer);
200
+ else if (startsWithDelimiter) local.push("");
74
201
  buffer = "";
75
202
  while (local.length > 0) {
76
- const sliceSize = Math.min(pageSize, local.length);
77
- const page = local.splice(local.length - sliceSize, sliceSize);
203
+ const page = local.slice(local.length - Math.min(pageSize, local.length));
204
+ local.length -= page.length;
78
205
  pageQueue.push(page);
79
206
  }
80
207
  done = true;
81
- if (fd) {
82
- await fd.close();
83
- fd = null;
208
+ if (fdSync !== null) {
209
+ (0, node_fs.closeSync)(fdSync);
210
+ fdSync = null;
84
211
  }
212
+ pageQueue.wake();
85
213
  }
86
214
  }
87
215
  async function next() {
88
216
  if (closed) return null;
89
- await fill();
90
- const page = await pageQueue.shift(() => done);
91
- if (!page) return null;
92
- emittedCount += page.length;
93
- firstLine ??= page[0];
94
- lastLine = page[page.length - 1];
95
- return page;
217
+ return await pageQueue.shift(done);
218
+ }
219
+ function nextSync() {
220
+ if (closed) return null;
221
+ fillSync();
222
+ return pageQueue.shiftSync();
96
223
  }
97
224
  async function close() {
98
225
  closed = true;
99
226
  done = true;
100
- pageQueue.queue.length = 0;
101
- if (fd) await fd.close();
227
+ pageQueue.clear();
228
+ if (fd) {
229
+ try {
230
+ await fd.close();
231
+ } catch {}
232
+ fd = null;
233
+ }
234
+ if (fdSync !== null) {
235
+ try {
236
+ (0, node_fs.closeSync)(fdSync);
237
+ } catch {}
238
+ fdSync = null;
239
+ }
102
240
  }
103
241
  return {
104
242
  next,
243
+ nextSync,
105
244
  close,
106
- get lineCount() {
107
- return emittedCount;
108
- },
109
- get firstLine() {
110
- return firstLine;
111
- },
112
- get lastLine() {
113
- return lastLine;
114
- },
115
245
  async *[Symbol.asyncIterator]() {
116
- while (true) {
117
- const p = await next();
118
- if (!p) break;
119
- yield p;
246
+ try {
247
+ while (true) {
248
+ const p = await next();
249
+ if (!p) break;
250
+ yield p;
251
+ }
252
+ } finally {
253
+ await close();
254
+ }
255
+ },
256
+ *[Symbol.iterator]() {
257
+ try {
258
+ while (true) {
259
+ const p = nextSync();
260
+ if (!p) break;
261
+ yield p;
262
+ }
263
+ } finally {
264
+ closed = true;
265
+ done = true;
266
+ pageQueue.clear();
267
+ try {
268
+ if (fdSync) (0, node_fs.closeSync)(fdSync);
269
+ } catch {}
270
+ fdSync = null;
271
+ try {
272
+ if (fd?.fd) (0, node_fs.closeSync)(fd.fd);
273
+ } catch {}
274
+ fd = null;
120
275
  }
121
276
  }
122
277
  };
123
278
  }
124
-
125
279
  //#endregion
126
280
  //#region src/reader/forward.reader.ts
127
281
  function createForwardReader(filepath, options) {
128
282
  const { chunkSize, pageSize, delimiter, prefetch } = options;
129
- const pageQueue = createPageQueue();
283
+ const pageQueue = createRingBuffer(Math.max(2, prefetch + 1));
284
+ const local = [];
130
285
  let fd = null;
286
+ let fdSync = null;
131
287
  let pos = 0;
132
288
  let size = 0;
133
289
  let buffer = "";
134
290
  let done = false;
135
291
  let closed = false;
136
- let emittedCount = 0;
137
- let firstLine = null;
138
- let lastLine = null;
139
- const local = [];
140
- async function init() {
141
- if (fd) return;
142
- fd = await (0, node_fs_promises.open)(filepath, "r");
143
- size = (await fd.stat()).size;
144
- if (size === 0) done = true;
292
+ let flushed = false;
293
+ fdSync = (0, node_fs.openSync)(filepath, "r");
294
+ size = (0, node_fs.statSync)(filepath).size;
295
+ if (size === 0) {
296
+ pageQueue.push([buffer]);
297
+ done = true;
298
+ pageQueue.wake();
145
299
  }
146
- async function fill() {
300
+ (async () => {
301
+ try {
302
+ fd = await (0, node_fs_promises.open)(filepath, "r");
303
+ size = (await fd.stat()).size;
304
+ if (size === 0) {
305
+ if (!done) {
306
+ pageQueue.push([buffer]);
307
+ done = true;
308
+ }
309
+ if (fd) {
310
+ await fd.close();
311
+ fd = null;
312
+ }
313
+ pageQueue.wake();
314
+ return;
315
+ }
316
+ while (!done && !closed) {
317
+ while (pageQueue.count < prefetch && pos < size && !closed) {
318
+ const readSize = Math.min(chunkSize, size - pos);
319
+ const buf = Buffer.allocUnsafe(readSize);
320
+ const { bytesRead } = await fd.read(buf, 0, readSize, pos);
321
+ pos += bytesRead;
322
+ buffer = buffer + buf.toString("utf8", 0, bytesRead);
323
+ let idx;
324
+ while ((idx = buffer.indexOf(delimiter)) !== -1) {
325
+ const line = buffer.slice(0, idx);
326
+ buffer = buffer.slice(idx + delimiter.length);
327
+ local.push(line);
328
+ while (local.length >= pageSize) pageQueue.push(local.splice(0, pageSize));
329
+ }
330
+ }
331
+ if (pos >= size && !flushed) {
332
+ flushed = true;
333
+ local.push(buffer.length > 0 ? buffer : "");
334
+ buffer = "";
335
+ while (local.length > 0 && !closed) {
336
+ const page = local.slice(0, pageSize);
337
+ local.length -= page.length;
338
+ pageQueue.push(page);
339
+ }
340
+ done = true;
341
+ if (fd) {
342
+ await fd.close();
343
+ fd = null;
344
+ }
345
+ pageQueue.wake();
346
+ break;
347
+ }
348
+ if (!done && !closed) await new Promise((r) => setImmediate(r));
349
+ }
350
+ } catch {
351
+ done = true;
352
+ pageQueue.wake();
353
+ try {
354
+ if (fd) {
355
+ await fd.close();
356
+ fd = null;
357
+ }
358
+ } catch {}
359
+ }
360
+ })();
361
+ function fillSync() {
147
362
  if (done || closed) return;
148
- await init();
149
- if (!fd) return;
150
- while (pageQueue.queue.length < prefetch && pos < size) {
363
+ if (fdSync === null) return;
364
+ while (pageQueue.count < prefetch && pos < size && !closed) {
151
365
  const readSize = Math.min(chunkSize, size - pos);
152
366
  const buf = Buffer.allocUnsafe(readSize);
153
- const { bytesRead } = await fd.read(buf, 0, readSize, pos);
367
+ const bytesRead = (0, node_fs.readSync)(fdSync, buf, 0, readSize, pos);
154
368
  pos += bytesRead;
155
- buffer += buf.toString("utf8", 0, bytesRead);
369
+ buffer = buffer + buf.toString("utf8", 0, bytesRead);
156
370
  let idx;
157
371
  while ((idx = buffer.indexOf(delimiter)) !== -1) {
158
372
  const line = buffer.slice(0, idx);
@@ -161,73 +375,102 @@ function createForwardReader(filepath, options) {
161
375
  while (local.length >= pageSize) pageQueue.push(local.splice(0, pageSize));
162
376
  }
163
377
  }
164
- if (pos >= size) {
165
- const parts = buffer.length > 0 ? buffer.split(delimiter) : [""];
166
- for (const line of parts) local.push(line);
378
+ if (pos >= size && !flushed) {
379
+ flushed = true;
380
+ local.push(buffer.length > 0 ? buffer : "");
167
381
  buffer = "";
168
- while (local.length > 0) pageQueue.push(local.splice(0, pageSize));
382
+ while (local.length > 0) {
383
+ const page = local.slice(0, pageSize);
384
+ local.length -= page.length;
385
+ pageQueue.push(page);
386
+ }
169
387
  done = true;
170
- if (fd) {
171
- await fd.close();
172
- fd = null;
388
+ if (fdSync !== null) {
389
+ (0, node_fs.closeSync)(fdSync);
390
+ fdSync = null;
173
391
  }
392
+ pageQueue.wake();
174
393
  }
175
394
  }
176
395
  async function next() {
177
396
  if (closed) return null;
178
- await fill();
179
- const page = await pageQueue.shift(() => done);
180
- if (!page) return null;
181
- emittedCount += page.length;
182
- firstLine ??= page[0];
183
- lastLine = page[page.length - 1];
184
- return page;
397
+ return await pageQueue.shift(done);
398
+ }
399
+ function nextSync() {
400
+ if (closed) return null;
401
+ fillSync();
402
+ return pageQueue.shiftSync();
185
403
  }
186
404
  async function close() {
187
405
  closed = true;
188
406
  done = true;
189
- pageQueue.queue.length = 0;
190
- if (fd) await fd.close();
407
+ pageQueue.clear();
408
+ if (fd) {
409
+ try {
410
+ await fd.close();
411
+ } catch {}
412
+ fd = null;
413
+ }
414
+ if (fdSync !== null) {
415
+ try {
416
+ (0, node_fs.closeSync)(fdSync);
417
+ } catch {}
418
+ fdSync = null;
419
+ }
191
420
  }
192
421
  return {
193
422
  next,
423
+ nextSync,
194
424
  close,
195
- get lineCount() {
196
- return emittedCount;
197
- },
198
- get firstLine() {
199
- return firstLine;
200
- },
201
- get lastLine() {
202
- return lastLine;
203
- },
204
425
  async *[Symbol.asyncIterator]() {
205
- while (true) {
206
- const p = await next();
207
- if (!p) break;
208
- yield p;
426
+ try {
427
+ while (true) {
428
+ const p = await next();
429
+ if (!p) break;
430
+ yield p;
431
+ }
432
+ } finally {
433
+ await close();
434
+ }
435
+ },
436
+ *[Symbol.iterator]() {
437
+ try {
438
+ while (true) {
439
+ const p = nextSync();
440
+ if (!p) break;
441
+ yield p;
442
+ }
443
+ } finally {
444
+ closed = true;
445
+ done = true;
446
+ pageQueue.clear();
447
+ try {
448
+ if (fdSync !== null) (0, node_fs.closeSync)(fdSync);
449
+ } catch {}
450
+ fdSync = null;
451
+ try {
452
+ if (fd?.fd) (0, node_fs.closeSync)(fd.fd);
453
+ } catch {}
454
+ fd = null;
209
455
  }
210
456
  }
211
457
  };
212
458
  }
213
-
214
459
  //#endregion
215
460
  //#region src/reader/worker.reader.ts
216
- const workerFile = typeof {} !== "undefined" ? new URL("./worker.mjs", require("url").pathToFileURL(__filename).href) : require.resolve("./worker.cjs");
461
+ const workerFile = new URL("./worker.mjs", require("url").pathToFileURL(__filename).href);
217
462
  function createWorkerReader(filepath, options) {
218
463
  const { chunkSize, pageSize, delimiter, prefetch } = options;
464
+ const pageQueue = createRingBuffer(Math.max(2, prefetch + 1));
465
+ let done = false;
466
+ let closed = false;
219
467
  const worker = new node_worker_threads.Worker(new URL(workerFile, require("url").pathToFileURL(__filename).href), { workerData: {
220
468
  filepath,
221
469
  chunkSize,
222
470
  pageSize,
223
- delimiter
471
+ delimiter,
472
+ prefetch
224
473
  } });
225
- const pageQueue = createPageQueue();
226
- let done = false;
227
- let closed = false;
228
- let emittedCount = 0;
229
- let firstLine = null;
230
- let lastLine = null;
231
474
  worker.on("message", (msg) => {
232
475
  if (msg.type === "page") pageQueue.push(msg.data);
233
476
  if (msg.type === "done") {
@@ -235,49 +478,66 @@ function createWorkerReader(filepath, options) {
235
478
  pageQueue.wake();
236
479
  }
237
480
  });
481
+ worker.on("error", () => {
482
+ done = true;
483
+ pageQueue.wake();
484
+ });
485
+ worker.on("exit", () => {
486
+ done = true;
487
+ pageQueue.wake();
488
+ });
238
489
  async function next() {
239
490
  if (closed) return null;
240
- const page = await pageQueue.shift(() => done);
241
- if (!page) return null;
242
- emittedCount += page.length;
243
- firstLine ??= page[0];
244
- lastLine = page[page.length - 1];
245
- return page;
491
+ return await pageQueue.shift(done);
492
+ }
493
+ function nextSync() {
494
+ if (closed) return null;
495
+ return pageQueue.shiftSync();
246
496
  }
247
497
  async function close() {
248
498
  closed = true;
249
499
  done = true;
250
- worker.terminate();
500
+ pageQueue.clear();
501
+ await worker.terminate();
502
+ }
503
+ function tryClose() {
504
+ close().catch(() => {});
251
505
  }
252
506
  return {
253
507
  next,
508
+ nextSync,
254
509
  close,
255
- get lineCount() {
256
- return emittedCount;
257
- },
258
- get firstLine() {
259
- return firstLine;
260
- },
261
- get lastLine() {
262
- return lastLine;
263
- },
264
510
  async *[Symbol.asyncIterator]() {
265
- while (true) {
266
- const p = await next();
267
- if (!p) break;
268
- yield p;
511
+ try {
512
+ while (true) {
513
+ const p = await next();
514
+ if (!p) break;
515
+ yield p;
516
+ }
517
+ } finally {
518
+ tryClose();
519
+ }
520
+ },
521
+ *[Symbol.iterator]() {
522
+ try {
523
+ while (true) {
524
+ const p = nextSync();
525
+ if (!p) break;
526
+ yield p;
527
+ }
528
+ } finally {
529
+ tryClose();
269
530
  }
270
531
  }
271
532
  };
272
533
  }
273
-
274
534
  //#endregion
275
535
  //#region src/main.ts
276
536
  function createPager(filepath, options = {}) {
277
- const { chunkSize = 64 * 1024, pageSize = 1e3, delimiter = "\n", prefetch = 1, backward = false, useWorker = false } = options;
537
+ const { chunkSize = 64 * 1024, pageSize = 1e3, delimiter = "\n", prefetch = 8, backward = false, useWorker = false } = options;
278
538
  if (!filepath) throw new Error("filepath required");
279
- if (pageSize <= 0) throw new RangeError("pageSize must be > 0");
280
- if (prefetch <= 0) throw new RangeError("prefetch must be >= 1");
539
+ if (pageSize < 1) throw new RangeError("pageSize must be >= 1");
540
+ if (prefetch < 1) throw new RangeError("prefetch must be >= 1");
281
541
  if (backward && useWorker) throw new Error("backward not supported with useWorker");
282
542
  return useWorker ? createWorkerReader(filepath, {
283
543
  chunkSize,
@@ -296,7 +556,6 @@ function createPager(filepath, options = {}) {
296
556
  delimiter
297
557
  });
298
558
  }
299
-
300
559
  //#endregion
301
560
  exports.createPager = createPager;
302
- exports.default = createPager;
561
+ exports.default = createPager;
package/dist/main.d.cts CHANGED
@@ -9,12 +9,10 @@ interface PagerOptions extends Partial<ReaderOptions> {
9
9
  backward?: boolean;
10
10
  useWorker?: boolean;
11
11
  }
12
- interface Pager extends AsyncIterable<string[]> {
12
+ interface Pager extends AsyncIterable<string[]>, Iterable<string[]> {
13
13
  next(): Promise<string[] | null>;
14
- close(): void;
15
- readonly lineCount: number;
16
- readonly firstLine: string | null;
17
- readonly lastLine: string | null;
14
+ nextSync(): string[] | null;
15
+ close(): Promise<void>;
18
16
  }
19
17
  //#endregion
20
18
  //#region src/main.d.ts