readline-pager 0.2.7 → 0.3.1

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.mjs CHANGED
@@ -1,67 +1,189 @@
1
1
  import { createRequire } from "node:module";
2
+ import { closeSync, openSync, readSync, statSync } from "node:fs";
2
3
  import { open } from "node:fs/promises";
3
4
  import { Worker } from "node:worker_threads";
4
-
5
5
  //#region \0rolldown/runtime.js
6
6
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
7
-
8
7
  //#endregion
9
8
  //#region src/queue.ts
10
- function createPageQueue() {
11
- const queue = [];
12
- 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
+ }
13
78
  return {
14
- queue,
15
- push(page) {
16
- queue.push(page);
17
- resolver?.();
18
- resolver = null;
19
- },
20
- wake() {
21
- resolver?.();
22
- resolver = null;
79
+ push,
80
+ shift,
81
+ shiftSync,
82
+ wake,
83
+ clear,
84
+ get count() {
85
+ return count;
23
86
  },
24
- async shift(done) {
25
- if (queue.length) return queue.shift();
26
- if (done()) return null;
27
- await new Promise((r) => resolver = r);
28
- if (queue.length) return queue.shift();
29
- if (done()) return null;
30
- return null;
87
+ get capacity() {
88
+ return buf.length;
31
89
  }
32
90
  };
33
91
  }
34
-
35
92
  //#endregion
36
93
  //#region src/reader/backward.reader.ts
37
94
  function createBackwardReader(filepath, options) {
38
95
  const { chunkSize, pageSize, delimiter, prefetch } = options;
39
- const pageQueue = createPageQueue();
96
+ const pageQueue = createRingBuffer(Math.max(2, prefetch + 1));
97
+ const local = [];
40
98
  let fd = null;
99
+ let fdSync = null;
41
100
  let pos = 0;
42
101
  let buffer = "";
43
102
  let done = false;
44
103
  let closed = false;
45
- let emittedCount = 0;
46
- let firstLine = null;
47
- let lastLine = null;
48
- const local = [];
49
- async function init() {
50
- if (fd) return;
51
- fd = await open(filepath, "r");
52
- pos = (await fd.stat()).size;
53
- if (pos === 0) done = true;
104
+ let startsWithDelimiter = false;
105
+ fdSync = openSync(filepath, "r");
106
+ pos = statSync(filepath).size;
107
+ if (pos === 0) {
108
+ pageQueue.push([buffer]);
109
+ done = true;
110
+ pageQueue.wake();
54
111
  }
55
- async function fill() {
112
+ (async () => {
113
+ try {
114
+ fd = await 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() {
56
178
  if (done || closed) return;
57
- await init();
58
- if (!fd) return;
59
- while (pageQueue.queue.length < prefetch && pos > 0) {
179
+ if (fdSync === null) return;
180
+ while (pageQueue.count < prefetch && pos > 0 && !closed) {
60
181
  const readSize = Math.min(chunkSize, pos);
61
182
  pos -= readSize;
62
183
  const buf = Buffer.allocUnsafe(readSize);
63
- await fd.read(buf, 0, readSize, pos);
64
- buffer += buf.toString("utf8");
184
+ readSync(fdSync, buf, 0, readSize, pos);
185
+ buffer = buf.toString("utf8") + buffer;
186
+ if (pos === 0 && buffer.startsWith(delimiter)) startsWithDelimiter = true;
65
187
  let idx;
66
188
  while ((idx = buffer.lastIndexOf(delimiter)) !== -1) {
67
189
  const line = buffer.slice(idx + delimiter.length);
@@ -73,52 +195,53 @@ function createBackwardReader(filepath, options) {
73
195
  }
74
196
  }
75
197
  }
76
- if (pos === 0) {
77
- local.push(buffer);
198
+ if (pos === 0 && !done) {
199
+ if (buffer.length > 0) local.push(buffer);
200
+ else if (startsWithDelimiter) local.push("");
78
201
  buffer = "";
79
202
  while (local.length > 0) {
80
- const sliceSize = Math.min(pageSize, local.length);
81
- 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;
82
205
  pageQueue.push(page);
83
206
  }
84
207
  done = true;
85
- if (fd) {
86
- await fd.close();
87
- fd = null;
208
+ if (fdSync !== null) {
209
+ closeSync(fdSync);
210
+ fdSync = null;
88
211
  }
212
+ pageQueue.wake();
89
213
  }
90
214
  }
91
215
  async function next() {
92
216
  if (closed) return null;
93
- await fill();
94
- const page = await pageQueue.shift(() => done);
95
- if (!page) return null;
96
- emittedCount += page.length;
97
- firstLine ??= page[0];
98
- lastLine = page[page.length - 1];
99
- return page;
217
+ return await pageQueue.shift(done);
218
+ }
219
+ function nextSync() {
220
+ if (closed) return null;
221
+ fillSync();
222
+ return pageQueue.shiftSync();
100
223
  }
101
224
  async function close() {
102
225
  closed = true;
103
226
  done = true;
104
- pageQueue.queue.length = 0;
227
+ pageQueue.clear();
105
228
  if (fd) {
106
- await fd.close();
229
+ try {
230
+ await fd.close();
231
+ } catch {}
107
232
  fd = null;
108
233
  }
234
+ if (fdSync !== null) {
235
+ try {
236
+ closeSync(fdSync);
237
+ } catch {}
238
+ fdSync = null;
239
+ }
109
240
  }
110
241
  return {
111
242
  next,
243
+ nextSync,
112
244
  close,
113
- get lineCount() {
114
- return emittedCount;
115
- },
116
- get firstLine() {
117
- return firstLine;
118
- },
119
- get lastLine() {
120
- return lastLine;
121
- },
122
245
  async *[Symbol.asyncIterator]() {
123
246
  try {
124
247
  while (true) {
@@ -127,43 +250,123 @@ function createBackwardReader(filepath, options) {
127
250
  yield p;
128
251
  }
129
252
  } finally {
130
- await close().catch(() => {});
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) closeSync(fdSync);
269
+ } catch {}
270
+ fdSync = null;
271
+ try {
272
+ if (fd?.fd) closeSync(fd.fd);
273
+ } catch {}
274
+ fd = null;
131
275
  }
132
276
  }
133
277
  };
134
278
  }
135
-
136
279
  //#endregion
137
280
  //#region src/reader/forward.reader.ts
138
281
  function createForwardReader(filepath, options) {
139
282
  const { chunkSize, pageSize, delimiter, prefetch } = options;
140
- const pageQueue = createPageQueue();
283
+ const pageQueue = createRingBuffer(Math.max(2, prefetch + 1));
284
+ const local = [];
141
285
  let fd = null;
286
+ let fdSync = null;
142
287
  let pos = 0;
143
288
  let size = 0;
144
289
  let buffer = "";
145
290
  let done = false;
146
291
  let closed = false;
147
- let emittedCount = 0;
148
- let firstLine = null;
149
- let lastLine = null;
150
- const local = [];
151
- async function init() {
152
- if (fd) return;
153
- fd = await open(filepath, "r");
154
- size = (await fd.stat()).size;
155
- if (size === 0) done = true;
292
+ let flushed = false;
293
+ fdSync = openSync(filepath, "r");
294
+ size = statSync(filepath).size;
295
+ if (size === 0) {
296
+ pageQueue.push([buffer]);
297
+ done = true;
298
+ pageQueue.wake();
156
299
  }
157
- async function fill() {
300
+ (async () => {
301
+ try {
302
+ fd = await 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() {
158
362
  if (done || closed) return;
159
- await init();
160
- if (!fd) return;
161
- while (pageQueue.queue.length < prefetch && pos < size) {
363
+ if (fdSync === null) return;
364
+ while (pageQueue.count < prefetch && pos < size && !closed) {
162
365
  const readSize = Math.min(chunkSize, size - pos);
163
366
  const buf = Buffer.allocUnsafe(readSize);
164
- const { bytesRead } = await fd.read(buf, 0, readSize, pos);
367
+ const bytesRead = readSync(fdSync, buf, 0, readSize, pos);
165
368
  pos += bytesRead;
166
- buffer += buf.toString("utf8", 0, bytesRead);
369
+ buffer = buffer + buf.toString("utf8", 0, bytesRead);
167
370
  let idx;
168
371
  while ((idx = buffer.indexOf(delimiter)) !== -1) {
169
372
  const line = buffer.slice(0, idx);
@@ -172,49 +375,53 @@ function createForwardReader(filepath, options) {
172
375
  while (local.length >= pageSize) pageQueue.push(local.splice(0, pageSize));
173
376
  }
174
377
  }
175
- if (pos >= size) {
176
- const parts = buffer.length > 0 ? buffer.split(delimiter) : [""];
177
- for (const line of parts) local.push(line);
378
+ if (pos >= size && !flushed) {
379
+ flushed = true;
380
+ local.push(buffer.length > 0 ? buffer : "");
178
381
  buffer = "";
179
- 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
+ }
180
387
  done = true;
181
- if (fd) {
182
- await fd.close();
183
- fd = null;
388
+ if (fdSync !== null) {
389
+ closeSync(fdSync);
390
+ fdSync = null;
184
391
  }
392
+ pageQueue.wake();
185
393
  }
186
394
  }
187
395
  async function next() {
188
396
  if (closed) return null;
189
- await fill();
190
- const page = await pageQueue.shift(() => done);
191
- if (!page) return null;
192
- emittedCount += page.length;
193
- firstLine ??= page[0];
194
- lastLine = page[page.length - 1];
195
- return page;
397
+ return await pageQueue.shift(done);
398
+ }
399
+ function nextSync() {
400
+ if (closed) return null;
401
+ fillSync();
402
+ return pageQueue.shiftSync();
196
403
  }
197
404
  async function close() {
198
405
  closed = true;
199
406
  done = true;
200
- pageQueue.queue.length = 0;
407
+ pageQueue.clear();
201
408
  if (fd) {
202
- await fd.close();
409
+ try {
410
+ await fd.close();
411
+ } catch {}
203
412
  fd = null;
204
413
  }
414
+ if (fdSync !== null) {
415
+ try {
416
+ closeSync(fdSync);
417
+ } catch {}
418
+ fdSync = null;
419
+ }
205
420
  }
206
421
  return {
207
422
  next,
423
+ nextSync,
208
424
  close,
209
- get lineCount() {
210
- return emittedCount;
211
- },
212
- get firstLine() {
213
- return firstLine;
214
- },
215
- get lastLine() {
216
- return lastLine;
217
- },
218
425
  async *[Symbol.asyncIterator]() {
219
426
  try {
220
427
  while (true) {
@@ -225,27 +432,45 @@ function createForwardReader(filepath, options) {
225
432
  } finally {
226
433
  await close();
227
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) closeSync(fdSync);
449
+ } catch {}
450
+ fdSync = null;
451
+ try {
452
+ if (fd?.fd) closeSync(fd.fd);
453
+ } catch {}
454
+ fd = null;
455
+ }
228
456
  }
229
457
  };
230
458
  }
231
-
232
459
  //#endregion
233
460
  //#region src/reader/worker.reader.ts
234
461
  const workerFile = typeof import.meta !== "undefined" ? new URL("./worker.mjs", import.meta.url) : __require.resolve("./worker.cjs");
235
462
  function createWorkerReader(filepath, options) {
236
463
  const { chunkSize, pageSize, delimiter, prefetch } = options;
464
+ const pageQueue = createRingBuffer(Math.max(2, prefetch + 1));
465
+ let done = false;
466
+ let closed = false;
237
467
  const worker = new Worker(new URL(workerFile, import.meta.url), { workerData: {
238
468
  filepath,
239
469
  chunkSize,
240
470
  pageSize,
241
- delimiter
471
+ delimiter,
472
+ prefetch
242
473
  } });
243
- const pageQueue = createPageQueue();
244
- let done = false;
245
- let closed = false;
246
- let emittedCount = 0;
247
- let firstLine = null;
248
- let lastLine = null;
249
474
  worker.on("message", (msg) => {
250
475
  if (msg.type === "page") pageQueue.push(msg.data);
251
476
  if (msg.type === "done") {
@@ -253,32 +478,35 @@ function createWorkerReader(filepath, options) {
253
478
  pageQueue.wake();
254
479
  }
255
480
  });
481
+ worker.on("error", () => {
482
+ done = true;
483
+ pageQueue.wake();
484
+ });
485
+ worker.on("exit", () => {
486
+ done = true;
487
+ pageQueue.wake();
488
+ });
256
489
  async function next() {
257
490
  if (closed) return null;
258
- const page = await pageQueue.shift(() => done);
259
- if (!page) return null;
260
- emittedCount += page.length;
261
- firstLine ??= page[0];
262
- lastLine = page[page.length - 1];
263
- return page;
491
+ return await pageQueue.shift(done);
492
+ }
493
+ function nextSync() {
494
+ if (closed) return null;
495
+ return pageQueue.shiftSync();
264
496
  }
265
497
  async function close() {
266
498
  closed = true;
267
499
  done = true;
500
+ pageQueue.clear();
268
501
  await worker.terminate();
269
502
  }
503
+ function tryClose() {
504
+ close().catch(() => {});
505
+ }
270
506
  return {
271
507
  next,
508
+ nextSync,
272
509
  close,
273
- get lineCount() {
274
- return emittedCount;
275
- },
276
- get firstLine() {
277
- return firstLine;
278
- },
279
- get lastLine() {
280
- return lastLine;
281
- },
282
510
  async *[Symbol.asyncIterator]() {
283
511
  try {
284
512
  while (true) {
@@ -287,19 +515,29 @@ function createWorkerReader(filepath, options) {
287
515
  yield p;
288
516
  }
289
517
  } finally {
290
- await close();
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();
291
530
  }
292
531
  }
293
532
  };
294
533
  }
295
-
296
534
  //#endregion
297
535
  //#region src/main.ts
298
536
  function createPager(filepath, options = {}) {
299
- 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;
300
538
  if (!filepath) throw new Error("filepath required");
301
- if (pageSize <= 0) throw new RangeError("pageSize must be > 0");
302
- 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");
303
541
  if (backward && useWorker) throw new Error("backward not supported with useWorker");
304
542
  return useWorker ? createWorkerReader(filepath, {
305
543
  chunkSize,
@@ -318,6 +556,5 @@ function createPager(filepath, options = {}) {
318
556
  delimiter
319
557
  });
320
558
  }
321
-
322
559
  //#endregion
323
- export { createPager, createPager as default };
560
+ export { createPager, createPager as default };