readline-pager 0.5.1 → 0.6.2

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/README.md CHANGED
@@ -20,7 +20,7 @@
20
20
  - 🔁 Async (`for await...of`) and sync (`for...of`) iteration
21
21
  - 📄 Page-based reading with manual control (`next`, `nextSync`)
22
22
  - 🔀 Forward and backward reading support
23
- - 🧪 Fully typed with high test coverage (~95%)
23
+ - 🧪 Fully typed with high test coverage (>90%)
24
24
 
25
25
  > **Important:**
26
26
  > Performance depends heavily on the `chunkSize` option. Tune it for your storage device. A value of **64 KiB** is usually a good starting point. Increasing it may improve throughput until you reach the best value for your hardware.
@@ -164,7 +164,7 @@ node test/_benchmark.ts --lines=20000 --page-size=500 --backward
164
164
  Run tests:
165
165
 
166
166
  ```bash
167
- npm ci
167
+ npm i
168
168
  npm test
169
169
  ```
170
170
 
package/dist/main.cjs CHANGED
@@ -6,7 +6,7 @@ const require_native = require("./native.cjs");
6
6
  let node_fs = require("node:fs");
7
7
  let node_fs_promises = require("node:fs/promises");
8
8
  let node_worker_threads = require("node:worker_threads");
9
- //#region src/queue.ts
9
+ //#region src/helper.ts
10
10
  function createRingBuffer(capacity) {
11
11
  if (!Number.isFinite(capacity) || capacity <= 0) throw new RangeError("capacity must be a positive number");
12
12
  let buf = new Array(capacity);
@@ -102,115 +102,99 @@ function createBackwardReader(filepath, options) {
102
102
  let buffer = "";
103
103
  let done = false;
104
104
  let closed = false;
105
+ let flushed = false;
105
106
  let startsWithDelimiter = false;
107
+ function consumeBuffer() {
108
+ let idx;
109
+ while ((idx = buffer.lastIndexOf(delimiter)) !== -1) {
110
+ const line = buffer.slice(idx + delimiter.length);
111
+ buffer = buffer.slice(0, idx);
112
+ local.push(line);
113
+ while (local.length >= pageSize) pageQueue.push(local.splice(0, pageSize));
114
+ }
115
+ }
116
+ function flushTail() {
117
+ if (flushed) return;
118
+ flushed = true;
119
+ if (buffer.length > 0) local.push(buffer);
120
+ else if (startsWithDelimiter) local.push("");
121
+ buffer = "";
122
+ while (local.length > 0) {
123
+ const page = local.slice(local.length - Math.min(pageSize, local.length));
124
+ local.length -= page.length;
125
+ pageQueue.push(page);
126
+ }
127
+ done = true;
128
+ pageQueue.wake();
129
+ }
106
130
  fdSync = (0, node_fs.openSync)(filepath, "r");
107
131
  pos = (0, node_fs.statSync)(filepath).size;
108
132
  if (pos === 0) {
109
- pageQueue.push([buffer]);
133
+ pageQueue.push([""]);
110
134
  done = true;
135
+ flushed = true;
111
136
  pageQueue.wake();
112
137
  }
113
138
  (async () => {
114
- try {
115
- fd = await (0, node_fs_promises.open)(filepath, "r");
116
- pos = (await fd.stat()).size;
117
- if (pos === 0) {
118
- if (!done) {
119
- pageQueue.push([buffer]);
120
- done = true;
121
- }
122
- if (fd) {
123
- await fd.close();
124
- fd = null;
125
- }
126
- pageQueue.wake();
127
- return;
139
+ fd = await (0, node_fs_promises.open)(filepath, "r");
140
+ pos = (await fd.stat()).size;
141
+ if (pos === 0) {
142
+ if (!done) {
143
+ pageQueue.push([""]);
144
+ done = true;
145
+ flushed = true;
128
146
  }
129
- while (!done && !closed) {
130
- while (pageQueue.count < prefetch && pos > 0 && !closed) {
131
- const readSize = Math.min(chunkSize, pos);
132
- pos -= readSize;
133
- const buf = Buffer.allocUnsafe(readSize);
134
- await fd.read(buf, 0, readSize, pos);
135
- buffer = buf.toString("utf8") + buffer;
136
- if (pos === 0 && buffer.startsWith(delimiter)) startsWithDelimiter = true;
137
- let idx;
138
- while ((idx = buffer.lastIndexOf(delimiter)) !== -1) {
139
- const line = buffer.slice(idx + delimiter.length);
140
- buffer = buffer.slice(0, idx);
141
- local.push(line);
142
- while (local.length >= pageSize) {
143
- const page = local.splice(0, pageSize);
144
- pageQueue.push(page);
145
- }
146
- }
147
- }
148
- if (pos === 0 && !done) {
149
- if (buffer.length > 0) local.push(buffer);
150
- else if (startsWithDelimiter) local.push("");
151
- buffer = "";
152
- while (local.length > 0 && !closed) {
153
- const page = local.slice(local.length - Math.min(pageSize, local.length));
154
- local.length -= page.length;
155
- pageQueue.push(page);
156
- }
157
- done = true;
158
- if (fd) {
159
- await fd.close();
160
- fd = null;
161
- }
162
- pageQueue.wake();
163
- break;
164
- }
165
- if (!done && !closed) await new Promise((r) => setImmediate(r));
147
+ if (fd) {
148
+ try {
149
+ await fd.close();
150
+ } catch {}
151
+ fd = null;
166
152
  }
167
- } catch {
168
- done = true;
169
153
  pageQueue.wake();
170
- try {
154
+ return;
155
+ }
156
+ while (!done && !closed) {
157
+ while (pageQueue.count < prefetch && pos > 0 && !closed) {
158
+ const readSize = Math.min(chunkSize, pos);
159
+ pos -= readSize;
160
+ const buf = Buffer.allocUnsafe(readSize);
161
+ const { bytesRead } = await fd.read(buf, 0, readSize, pos);
162
+ buffer = buf.toString("utf8", 0, bytesRead) + buffer;
163
+ if (pos === 0 && buffer.startsWith(delimiter)) startsWithDelimiter = true;
164
+ consumeBuffer();
165
+ }
166
+ if (pos === 0 && !flushed) {
167
+ flushTail();
171
168
  if (fd) {
172
- await fd.close();
169
+ try {
170
+ await fd.close();
171
+ } catch {}
173
172
  fd = null;
174
173
  }
175
- } catch {}
174
+ break;
175
+ }
176
+ if (!done && !closed) await new Promise((r) => setImmediate(r));
176
177
  }
177
178
  })();
178
179
  function fillSync() {
179
- if (done || closed) return;
180
- if (fdSync === null) return;
180
+ if (done || closed || !fdSync) return;
181
181
  while (pageQueue.count < prefetch && pos > 0 && !closed) {
182
182
  const readSize = Math.min(chunkSize, pos);
183
183
  pos -= readSize;
184
184
  const buf = Buffer.allocUnsafe(readSize);
185
- (0, node_fs.readSync)(fdSync, buf, 0, readSize, pos);
186
- buffer = buf.toString("utf8") + buffer;
185
+ const bytesRead = (0, node_fs.readSync)(fdSync, buf, 0, readSize, pos);
186
+ buffer = buf.toString("utf8", 0, bytesRead) + buffer;
187
187
  if (pos === 0 && buffer.startsWith(delimiter)) startsWithDelimiter = true;
188
- let idx;
189
- while ((idx = buffer.lastIndexOf(delimiter)) !== -1) {
190
- const line = buffer.slice(idx + delimiter.length);
191
- buffer = buffer.slice(0, idx);
192
- local.push(line);
193
- while (local.length >= pageSize) {
194
- const page = local.splice(0, pageSize);
195
- pageQueue.push(page);
196
- }
197
- }
188
+ consumeBuffer();
198
189
  }
199
- if (pos === 0 && !done) {
200
- if (buffer.length > 0) local.push(buffer);
201
- else if (startsWithDelimiter) local.push("");
202
- buffer = "";
203
- while (local.length > 0) {
204
- const page = local.slice(local.length - Math.min(pageSize, local.length));
205
- local.length -= page.length;
206
- pageQueue.push(page);
207
- }
208
- done = true;
209
- if (fdSync !== null) {
210
- (0, node_fs.closeSync)(fdSync);
190
+ if (pos === 0 && !flushed) {
191
+ flushTail();
192
+ if (fdSync) {
193
+ try {
194
+ (0, node_fs.closeSync)(fdSync);
195
+ } catch {}
211
196
  fdSync = null;
212
197
  }
213
- pageQueue.wake();
214
198
  }
215
199
  }
216
200
  async function next() {
@@ -223,20 +207,21 @@ function createBackwardReader(filepath, options) {
223
207
  return pageQueue.shiftSync();
224
208
  }
225
209
  async function close() {
210
+ if (closed) return;
226
211
  closed = true;
227
212
  done = true;
228
213
  pageQueue.clear();
229
- if (fd) {
214
+ if (fdSync) {
230
215
  try {
231
- await fd.close();
216
+ (0, node_fs.closeSync)(fdSync);
232
217
  } catch {}
233
- fd = null;
218
+ fdSync = null;
234
219
  }
235
- if (fdSync !== null) {
220
+ if (fd) {
236
221
  try {
237
- (0, node_fs.closeSync)(fdSync);
222
+ await fd.close();
238
223
  } catch {}
239
- fdSync = null;
224
+ fd = null;
240
225
  }
241
226
  }
242
227
  return {
@@ -265,14 +250,18 @@ function createBackwardReader(filepath, options) {
265
250
  closed = true;
266
251
  done = true;
267
252
  pageQueue.clear();
268
- try {
269
- if (fdSync) (0, node_fs.closeSync)(fdSync);
270
- } catch {}
271
- fdSync = null;
272
- try {
273
- if (fd?.fd) (0, node_fs.closeSync)(fd.fd);
274
- } catch {}
275
- fd = null;
253
+ if (fdSync) {
254
+ try {
255
+ (0, node_fs.closeSync)(fdSync);
256
+ } catch {}
257
+ fdSync = null;
258
+ }
259
+ if (fd?.fd) {
260
+ try {
261
+ (0, node_fs.closeSync)(fd.fd);
262
+ } catch {}
263
+ fd = null;
264
+ }
276
265
  }
277
266
  }
278
267
  };
@@ -291,106 +280,90 @@ function createForwardReader(filepath, options) {
291
280
  let done = false;
292
281
  let closed = false;
293
282
  let flushed = false;
283
+ function consumeBuffer() {
284
+ let idx;
285
+ while ((idx = buffer.indexOf(delimiter)) !== -1) {
286
+ const line = buffer.slice(0, idx);
287
+ buffer = buffer.slice(idx + delimiter.length);
288
+ local.push(line);
289
+ while (local.length >= pageSize) pageQueue.push(local.splice(0, pageSize));
290
+ }
291
+ }
292
+ function flushTail() {
293
+ if (flushed) return;
294
+ flushed = true;
295
+ local.push(buffer.length > 0 ? buffer : "");
296
+ buffer = "";
297
+ while (local.length > 0) pageQueue.push(local.splice(0, pageSize));
298
+ done = true;
299
+ pageQueue.wake();
300
+ }
294
301
  fdSync = (0, node_fs.openSync)(filepath, "r");
295
302
  size = (0, node_fs.statSync)(filepath).size;
296
303
  if (size === 0) {
297
- pageQueue.push([buffer]);
304
+ pageQueue.push([""]);
298
305
  done = true;
306
+ flushed = true;
299
307
  pageQueue.wake();
300
308
  }
301
309
  (async () => {
302
- try {
303
- fd = await (0, node_fs_promises.open)(filepath, "r");
304
- size = (await fd.stat()).size;
305
- if (size === 0) {
306
- if (!done) {
307
- pageQueue.push([buffer]);
308
- done = true;
309
- }
310
- if (fd) {
311
- await fd.close();
312
- fd = null;
313
- }
314
- pageQueue.wake();
315
- return;
310
+ fd = await (0, node_fs_promises.open)(filepath, "r");
311
+ size = (await fd.stat()).size;
312
+ if (size === 0) {
313
+ if (!done) {
314
+ pageQueue.push([""]);
315
+ done = true;
316
+ flushed = true;
316
317
  }
317
- while (!done && !closed) {
318
- while (pageQueue.count < prefetch && pos < size && !closed) {
319
- const readSize = Math.min(chunkSize, size - pos);
320
- const buf = Buffer.allocUnsafe(readSize);
321
- const { bytesRead } = await fd.read(buf, 0, readSize, pos);
322
- pos += bytesRead;
323
- buffer = buffer + buf.toString("utf8", 0, bytesRead);
324
- let idx;
325
- while ((idx = buffer.indexOf(delimiter)) !== -1) {
326
- const line = buffer.slice(0, idx);
327
- buffer = buffer.slice(idx + delimiter.length);
328
- local.push(line);
329
- while (local.length >= pageSize) pageQueue.push(local.splice(0, pageSize));
330
- }
331
- }
332
- if (pos >= size && !flushed) {
333
- flushed = true;
334
- local.push(buffer.length > 0 ? buffer : "");
335
- buffer = "";
336
- while (local.length > 0 && !closed) {
337
- const page = local.slice(0, pageSize);
338
- local.length -= page.length;
339
- pageQueue.push(page);
340
- }
341
- done = true;
342
- if (fd) {
343
- await fd.close();
344
- fd = null;
345
- }
346
- pageQueue.wake();
347
- break;
348
- }
349
- if (!done && !closed) await new Promise((r) => setImmediate(r));
318
+ if (fd) {
319
+ try {
320
+ await fd.close();
321
+ } catch {}
322
+ fd = null;
350
323
  }
351
- } catch {
352
- done = true;
353
324
  pageQueue.wake();
354
- try {
325
+ return;
326
+ }
327
+ while (!done && !closed) {
328
+ while (pageQueue.count < prefetch && pos < size && !closed) {
329
+ const readSize = Math.min(chunkSize, size - pos);
330
+ const buf = Buffer.allocUnsafe(readSize);
331
+ const { bytesRead } = await fd.read(buf, 0, readSize, pos);
332
+ pos += bytesRead;
333
+ buffer = buffer + buf.toString("utf8", 0, bytesRead);
334
+ consumeBuffer();
335
+ }
336
+ if (pos >= size && !flushed) {
337
+ flushTail();
355
338
  if (fd) {
356
- await fd.close();
339
+ try {
340
+ await fd.close();
341
+ } catch {}
357
342
  fd = null;
358
343
  }
359
- } catch {}
344
+ break;
345
+ }
346
+ if (!done && !closed) await new Promise((r) => setImmediate(r));
360
347
  }
361
348
  })();
362
349
  function fillSync() {
363
- if (done || closed) return;
364
- if (fdSync === null) return;
350
+ if (done || closed || !fdSync) return;
365
351
  while (pageQueue.count < prefetch && pos < size && !closed) {
366
352
  const readSize = Math.min(chunkSize, size - pos);
367
353
  const buf = Buffer.allocUnsafe(readSize);
368
354
  const bytesRead = (0, node_fs.readSync)(fdSync, buf, 0, readSize, pos);
369
355
  pos += bytesRead;
370
356
  buffer = buffer + buf.toString("utf8", 0, bytesRead);
371
- let idx;
372
- while ((idx = buffer.indexOf(delimiter)) !== -1) {
373
- const line = buffer.slice(0, idx);
374
- buffer = buffer.slice(idx + delimiter.length);
375
- local.push(line);
376
- while (local.length >= pageSize) pageQueue.push(local.splice(0, pageSize));
377
- }
357
+ consumeBuffer();
378
358
  }
379
359
  if (pos >= size && !flushed) {
380
- flushed = true;
381
- local.push(buffer.length > 0 ? buffer : "");
382
- buffer = "";
383
- while (local.length > 0) {
384
- const page = local.slice(0, pageSize);
385
- local.length -= page.length;
386
- pageQueue.push(page);
387
- }
388
- done = true;
389
- if (fdSync !== null) {
390
- (0, node_fs.closeSync)(fdSync);
360
+ flushTail();
361
+ if (fdSync) {
362
+ try {
363
+ (0, node_fs.closeSync)(fdSync);
364
+ } catch {}
391
365
  fdSync = null;
392
366
  }
393
- pageQueue.wake();
394
367
  }
395
368
  }
396
369
  async function next() {
@@ -403,20 +376,21 @@ function createForwardReader(filepath, options) {
403
376
  return pageQueue.shiftSync();
404
377
  }
405
378
  async function close() {
379
+ if (closed) return;
406
380
  closed = true;
407
381
  done = true;
408
382
  pageQueue.clear();
409
- if (fd) {
383
+ if (fdSync) {
410
384
  try {
411
- await fd.close();
385
+ (0, node_fs.closeSync)(fdSync);
412
386
  } catch {}
413
- fd = null;
387
+ fdSync = null;
414
388
  }
415
- if (fdSync !== null) {
389
+ if (fd) {
416
390
  try {
417
- (0, node_fs.closeSync)(fdSync);
391
+ await fd.close();
418
392
  } catch {}
419
- fdSync = null;
393
+ fd = null;
420
394
  }
421
395
  }
422
396
  return {
@@ -445,14 +419,18 @@ function createForwardReader(filepath, options) {
445
419
  closed = true;
446
420
  done = true;
447
421
  pageQueue.clear();
448
- try {
449
- if (fdSync !== null) (0, node_fs.closeSync)(fdSync);
450
- } catch {}
451
- fdSync = null;
452
- try {
453
- if (fd?.fd) (0, node_fs.closeSync)(fd.fd);
454
- } catch {}
455
- fd = null;
422
+ if (fdSync) {
423
+ try {
424
+ (0, node_fs.closeSync)(fdSync);
425
+ } catch {}
426
+ fdSync = null;
427
+ }
428
+ if (fd?.fd) {
429
+ try {
430
+ (0, node_fs.closeSync)(fd.fd);
431
+ } catch {}
432
+ fd = null;
433
+ }
456
434
  }
457
435
  }
458
436
  };
@@ -462,18 +440,23 @@ function createForwardReader(filepath, options) {
462
440
  const workerFile = new URL("./worker.mjs", require("url").pathToFileURL(__filename).href);
463
441
  function createWorkerReader(filepath, options) {
464
442
  const { prefetch } = options;
465
- const pageQueue = createRingBuffer(Math.max(2, prefetch + 1));
466
443
  let done = false;
467
444
  let closed = false;
445
+ const pageQueue = createRingBuffer(Math.max(2, prefetch + 1));
468
446
  const worker = new node_worker_threads.Worker(new URL(workerFile, require("url").pathToFileURL(__filename).href), { workerData: {
469
447
  filepath,
470
448
  options
471
449
  } });
472
450
  worker.on("message", (msg) => {
473
- if (msg.type === "page") pageQueue.push(msg.data);
474
- if (msg.type === "done") {
475
- done = true;
476
- pageQueue.wake();
451
+ switch (msg.type) {
452
+ case "page":
453
+ pageQueue.push(msg.data);
454
+ break;
455
+ case "done":
456
+ case "error":
457
+ done = true;
458
+ pageQueue.wake();
459
+ break;
477
460
  }
478
461
  });
479
462
  worker.on("error", () => {
@@ -493,10 +476,13 @@ function createWorkerReader(filepath, options) {
493
476
  return pageQueue.shiftSync();
494
477
  }
495
478
  async function close() {
479
+ if (closed) return;
496
480
  closed = true;
497
481
  done = true;
498
482
  pageQueue.clear();
499
- await worker.terminate();
483
+ try {
484
+ await worker.terminate();
485
+ } catch {}
500
486
  }
501
487
  function tryClose() {
502
488
  close().catch(() => {});
package/dist/main.d.cts CHANGED
@@ -1,6 +1,6 @@
1
- import { a as NativeReaderOptions, c as ReaderOptions, i as NativeAddon, n as AddonData, o as Pager, r as AddonFD, s as PagerOptions, t as createNativePager } from "./native-46pCT8Rc.cjs";
1
+ import { a as PagerOptions, i as Pager, n as NativeAddon, o as ReaderOptions, r as NativeReaderOptions, s as WorkerMessage, t as createNativePager } from "./native-_NmVYcF6.cjs";
2
2
 
3
3
  //#region src/main.d.ts
4
4
  declare function createPager(filepath: string, options?: PagerOptions): Pager;
5
5
  //#endregion
6
- export { AddonData, AddonFD, NativeAddon, NativeReaderOptions, Pager, PagerOptions, ReaderOptions, createNativePager, createPager, createPager as default };
6
+ export { NativeAddon, NativeReaderOptions, Pager, PagerOptions, ReaderOptions, WorkerMessage, createNativePager, createPager, createPager as default };
package/dist/main.d.mts CHANGED
@@ -1,6 +1,6 @@
1
- import { a as NativeReaderOptions, c as ReaderOptions, i as NativeAddon, n as AddonData, o as Pager, r as AddonFD, s as PagerOptions, t as createNativePager } from "./native-DeBXdY3U.mjs";
1
+ import { a as PagerOptions, i as Pager, n as NativeAddon, o as ReaderOptions, r as NativeReaderOptions, s as WorkerMessage, t as createNativePager } from "./native-DGzYrMHK.mjs";
2
2
 
3
3
  //#region src/main.d.ts
4
4
  declare function createPager(filepath: string, options?: PagerOptions): Pager;
5
5
  //#endregion
6
- export { AddonData, AddonFD, NativeAddon, NativeReaderOptions, Pager, PagerOptions, ReaderOptions, createNativePager, createPager, createPager as default };
6
+ export { NativeAddon, NativeReaderOptions, Pager, PagerOptions, ReaderOptions, WorkerMessage, createNativePager, createPager, createPager as default };