readline-pager 0.7.2 → 0.8.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/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # 📄 readline-pager
2
2
 
3
+ <!-- rumdl-disable MD033 -->
3
4
  <p align="center">
4
5
  <img src="https://raw.githubusercontent.com/devmor-j/readline-pager/main/logo.webp" alt="logo" width="349">
5
6
  </p>
@@ -10,9 +11,11 @@
10
11
  </a>
11
12
  <img src="https://img.shields.io/npm/dw/readline-pager" alt="downloads">
12
13
  <img src="https://img.shields.io/github/stars/devmor-j/readline-pager" alt="stars">
14
+ <img src="coverage.svg" alt="coverage">
13
15
  </p>
14
16
 
15
- ⚡ High-performance paginated file reader for Node.js. Process large text files efficiently without loading them into memory.
17
+ ⚡ High-performance paginated file reader for Node.js. Process large text files
18
+ efficiently without loading them into memory.
16
19
 
17
20
  - 📦 Zero dependencies
18
21
  - ⚡ Up to ~3× faster than Node.js `readline`
@@ -20,10 +23,12 @@
20
23
  - 🔁 Async (`for await...of`) and sync (`for...of`) iteration
21
24
  - 📄 Page-based reading with manual control (`next`, `nextSync`)
22
25
  - 🔀 Forward and backward reading support
23
- - 🧪 Fully typed with over 90% test coverage
26
+ - 🧪 Fully typed with high test coverage
24
27
 
25
28
  > **Important:**
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.
29
+ > Performance depends heavily on the `chunkSize` option. Tune it for your
30
+ > storage device. A value of **64 KiB** is usually a good starting point.
31
+ > Increasing it may improve throughput until you reach the best value for your hardware.
27
32
 
28
33
  ---
29
34
 
@@ -71,7 +76,12 @@ while ((page = pager.nextSync()) !== null) {}
71
76
  // Native C++
72
77
  for (const page of createNativePager("./bigfile.txt")) {
73
78
  }
74
- ```
79
+
80
+ // Automatic cleanup on exit (optional, requires Node.js v22+)
81
+ await using pager = createPager("./bigfile.txt");
82
+ for await (const page of pager) {
83
+ }
84
+ // pager will be disposed automatically when it goes out of scope
75
85
 
76
86
  ---
77
87
 
@@ -103,7 +113,9 @@ createNativePager(filepath, {
103
113
  - `output` — Controls the page data type.
104
114
 
105
115
  > **Note:**
106
- > `createNativePager` requires x86 AVX2 or ARM NEON CPU instruction set extensions and will throw if they are not available. It also does **not** support multi-character delimiters because it uses fast SIMD-based scanning.
116
+ > `createNativePager` requires x86 AVX2 or ARM NEON CPU instruction set extensions
117
+ > and will throw if they are not available. It also does **not** support
118
+ > multi-character delimiters because it uses fast SIMD-based scanning.
107
119
 
108
120
  ---
109
121
 
@@ -118,7 +130,8 @@ Returns `null` when the end of the file is reached.
118
130
  Empty lines are preserved.
119
131
 
120
132
  > **Note:**
121
- > Unlike Node.js `readline`, which may skip empty files or leading empty lines, `readline-pager` always returns all lines.
133
+ > Unlike Node.js `readline`, which may skip empty files or leading empty lines,
134
+ > `readline-pager` always returns all lines.
122
135
  >
123
136
  > - A completely empty file (`0` bytes) produces `[""]` on the first read.
124
137
  > - A file containing multiple empty lines returns each line as an empty string.
@@ -140,17 +153,14 @@ Stops reading and releases resources asynchronously. Safe to call at any time.
140
153
  Run the benchmark locally:
141
154
 
142
155
  ```bash
143
- npm run benchmark:node
144
-
145
- # or customize with args
146
- node test/benchmark.ts --lines=20000 --page-size=500 --backward
156
+ npm run benchmark # default: Node.js
157
+ npm run benchmark -- deno # Deno runtime
158
+ npm run benchmark -- bun --backward # Bun runtime with custom args
147
159
  ```
148
160
 
149
161
  > Test setup: generated text files (UUID lines), NVMe SSD, Node.js runtime.
150
162
  > Results are averaged across multiple runs. Actual performance depends on hardware.
151
163
 
152
- ---
153
-
154
164
  ### ⚡ Throughput (MB/s)
155
165
 
156
166
  | Method | 1M lines (35 MB) | 10M lines (353 MB) | 100M lines (3.5 GB) | 1B lines (35.3 GB) |
@@ -164,7 +174,7 @@ node test/benchmark.ts --lines=20000 --page-size=500 --backward
164
174
  ## 🛠 Development & Contributing
165
175
 
166
176
  - Minimum supported Node.js: **v18.12**
167
- - Development/test environment: **Node v25.8** and **TypeScript v6.0**
177
+ - Development/test environment: **Node v26.3** and **TypeScript v6.0**
168
178
 
169
179
  Run tests:
170
180
 
package/dist/main.cjs CHANGED
@@ -1,7 +1,4 @@
1
- Object.defineProperties(exports, {
2
- __esModule: { value: true },
3
- [Symbol.toStringTag]: { value: "Module" }
4
- });
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
5
2
  const require_native = require("./native.cjs");
6
3
  let node_fs = require("node:fs");
7
4
  let node_fs_promises = require("node:fs/promises");
@@ -156,30 +153,33 @@ function createBackwardReader(filepath, options) {
156
153
  pageQueue.wake();
157
154
  return;
158
155
  }
159
- while (!done && !closed) {
160
- while (pageQueue.count < prefetch && pos > 0 && !closed) {
161
- const readSize = Math.min(chunkSize, pos);
162
- pos -= readSize;
163
- const buf = Buffer.allocUnsafe(readSize);
164
- const { bytesRead } = await fd.read(buf, 0, readSize, pos);
165
- if (isBufferOutput) pageQueue.push(buf.subarray(0, bytesRead));
166
- else {
167
- buffer = buf.toString("utf8", 0, bytesRead) + buffer;
168
- if (pos === 0 && buffer.startsWith(delimiter)) startsWithDelimiter = true;
169
- consumeBuffer();
156
+ try {
157
+ while (!done && !closed) {
158
+ while (pageQueue.count < prefetch && pos > 0 && !closed) {
159
+ const readSize = Math.min(chunkSize, pos);
160
+ pos -= readSize;
161
+ const buf = Buffer.allocUnsafe(readSize);
162
+ const { bytesRead } = await fd.read(buf, 0, readSize, pos);
163
+ if (isBufferOutput) pageQueue.push(buf.subarray(0, bytesRead));
164
+ else {
165
+ buffer = buf.toString("utf8", 0, bytesRead) + buffer;
166
+ if (pos === 0 && buffer.startsWith(delimiter)) startsWithDelimiter = true;
167
+ consumeBuffer();
168
+ }
170
169
  }
171
- }
172
- if (pos === 0 && !flushed) {
173
- flushTail();
174
- if (fd) {
175
- try {
176
- await fd.close();
177
- } catch {}
178
- fd = null;
170
+ if (pos === 0 && !flushed) {
171
+ flushTail();
172
+ break;
179
173
  }
180
- break;
174
+ if (!done && !closed) await new Promise((r) => setImmediate(r));
175
+ }
176
+ } finally {
177
+ if (fd) {
178
+ try {
179
+ await fd.close();
180
+ } catch {}
181
+ fd = null;
181
182
  }
182
- if (!done && !closed) await new Promise((r) => setImmediate(r));
183
183
  }
184
184
  })();
185
185
  function fillSync() {
@@ -265,13 +265,29 @@ function createBackwardReader(filepath, options) {
265
265
  } catch {}
266
266
  fdSync = null;
267
267
  }
268
- if (fd?.fd) {
269
- try {
270
- (0, node_fs.closeSync)(fd.fd);
271
- } catch {}
268
+ if (fd) {
269
+ fd.close().catch(() => {});
272
270
  fd = null;
273
271
  }
274
272
  }
273
+ },
274
+ [Symbol.asyncDispose]() {
275
+ return close();
276
+ },
277
+ [Symbol.dispose]() {
278
+ closed = true;
279
+ done = true;
280
+ pageQueue.clear();
281
+ if (fdSync) {
282
+ try {
283
+ (0, node_fs.closeSync)(fdSync);
284
+ } catch {}
285
+ fdSync = null;
286
+ }
287
+ if (fd) {
288
+ fd.close().catch(() => {});
289
+ fd = null;
290
+ }
275
291
  }
276
292
  };
277
293
  }
@@ -337,29 +353,32 @@ function createForwardReader(filepath, options) {
337
353
  pageQueue.wake();
338
354
  return;
339
355
  }
340
- while (!done && !closed) {
341
- while (pageQueue.count < prefetch && pos < size && !closed) {
342
- const readSize = Math.min(chunkSize, size - pos);
343
- const buf = Buffer.allocUnsafe(readSize);
344
- const { bytesRead } = await fd.read(buf, 0, readSize, pos);
345
- pos += bytesRead;
346
- if (isBufferOutput) pageQueue.push(buf.subarray(0, bytesRead));
347
- else {
348
- buffer = buffer + buf.toString("utf8", 0, bytesRead);
349
- consumeBuffer();
356
+ try {
357
+ while (!done && !closed) {
358
+ while (pageQueue.count < prefetch && pos < size && !closed) {
359
+ const readSize = Math.min(chunkSize, size - pos);
360
+ const buf = Buffer.allocUnsafe(readSize);
361
+ const { bytesRead } = await fd.read(buf, 0, readSize, pos);
362
+ pos += bytesRead;
363
+ if (isBufferOutput) pageQueue.push(buf.subarray(0, bytesRead));
364
+ else {
365
+ buffer = buffer + buf.toString("utf8", 0, bytesRead);
366
+ consumeBuffer();
367
+ }
350
368
  }
351
- }
352
- if (pos >= size && !flushed) {
353
- flushTail();
354
- if (fd) {
355
- try {
356
- await fd.close();
357
- } catch {}
358
- fd = null;
369
+ if (pos >= size && !flushed) {
370
+ flushTail();
371
+ break;
359
372
  }
360
- break;
373
+ if (!done && !closed) await new Promise((r) => setImmediate(r));
374
+ }
375
+ } finally {
376
+ if (fd) {
377
+ try {
378
+ await fd.close();
379
+ } catch {}
380
+ fd = null;
361
381
  }
362
- if (!done && !closed) await new Promise((r) => setImmediate(r));
363
382
  }
364
383
  })();
365
384
  function fillSync() {
@@ -444,13 +463,29 @@ function createForwardReader(filepath, options) {
444
463
  } catch {}
445
464
  fdSync = null;
446
465
  }
447
- if (fd?.fd) {
448
- try {
449
- (0, node_fs.closeSync)(fd.fd);
450
- } catch {}
466
+ if (fd) {
467
+ fd.close().catch(() => {});
451
468
  fd = null;
452
469
  }
453
470
  }
471
+ },
472
+ [Symbol.asyncDispose]() {
473
+ return close();
474
+ },
475
+ [Symbol.dispose]() {
476
+ closed = true;
477
+ done = true;
478
+ pageQueue.clear();
479
+ if (fdSync) {
480
+ try {
481
+ (0, node_fs.closeSync)(fdSync);
482
+ } catch {}
483
+ fdSync = null;
484
+ }
485
+ if (fd) {
486
+ fd.close().catch(() => {});
487
+ fd = null;
488
+ }
454
489
  }
455
490
  };
456
491
  }
@@ -468,14 +503,8 @@ function createPager(filepath, options = {}) {
468
503
  delimiter,
469
504
  output
470
505
  };
471
- const reader = backward ? createBackwardReader(filepath, readerOptions) : createForwardReader(filepath, readerOptions);
472
- if (process.env.PAGER_TEST_CLEANUPS) {
473
- globalThis.__pager_test_cleanups__ ??= [];
474
- globalThis.__pager_test_cleanups__.push(reader.close);
475
- }
476
- return reader;
506
+ return backward ? createBackwardReader(filepath, readerOptions) : createForwardReader(filepath, readerOptions);
477
507
  }
478
508
  //#endregion
479
509
  exports.createNativePager = require_native.createNativePager;
480
510
  exports.createPager = createPager;
481
- exports.default = createPager;
package/dist/main.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as Output, c as PagerOptions, i as NativeReaderOptions, l as ReaderOptions, n as AsyncFunction, o as PageOutput, r as NativeAddon, s as Pager, t as createNativePager, u as ResolvePageOutput } from "./native-7x1Vhdaw.cjs";
1
+ import { i as PagerOptions, n as Output, r as Pager, t as createNativePager } from "./native-BJ3yhPgn.cjs";
2
2
 
3
3
  //#region src/main.d.ts
4
4
  declare function createPager<T extends Output>(filepath: string, options: PagerOptions & {
@@ -6,4 +6,4 @@ declare function createPager<T extends Output>(filepath: string, options: PagerO
6
6
  }): Pager<T>;
7
7
  declare function createPager(filepath: string, options?: PagerOptions): Pager<"string">;
8
8
  //#endregion
9
- export { AsyncFunction, NativeAddon, NativeReaderOptions, Output, PageOutput, Pager, PagerOptions, ReaderOptions, ResolvePageOutput, createNativePager, createPager, createPager as default };
9
+ export { type Pager, type PagerOptions, createNativePager, createPager };
package/dist/main.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as Output, c as PagerOptions, i as NativeReaderOptions, l as ReaderOptions, n as AsyncFunction, o as PageOutput, r as NativeAddon, s as Pager, t as createNativePager, u as ResolvePageOutput } from "./native-D6SBIgoE.mjs";
1
+ import { i as PagerOptions, n as Output, r as Pager, t as createNativePager } from "./native-BJ3yhPgn.mjs";
2
2
 
3
3
  //#region src/main.d.ts
4
4
  declare function createPager<T extends Output>(filepath: string, options: PagerOptions & {
@@ -6,4 +6,4 @@ declare function createPager<T extends Output>(filepath: string, options: PagerO
6
6
  }): Pager<T>;
7
7
  declare function createPager(filepath: string, options?: PagerOptions): Pager<"string">;
8
8
  //#endregion
9
- export { AsyncFunction, NativeAddon, NativeReaderOptions, Output, PageOutput, Pager, PagerOptions, ReaderOptions, ResolvePageOutput, createNativePager, createPager, createPager as default };
9
+ export { type Pager, type PagerOptions, createNativePager, createPager };
package/dist/main.mjs CHANGED
@@ -152,30 +152,33 @@ function createBackwardReader(filepath, options) {
152
152
  pageQueue.wake();
153
153
  return;
154
154
  }
155
- while (!done && !closed) {
156
- while (pageQueue.count < prefetch && pos > 0 && !closed) {
157
- const readSize = Math.min(chunkSize, pos);
158
- pos -= readSize;
159
- const buf = Buffer.allocUnsafe(readSize);
160
- const { bytesRead } = await fd.read(buf, 0, readSize, pos);
161
- if (isBufferOutput) pageQueue.push(buf.subarray(0, bytesRead));
162
- else {
163
- buffer = buf.toString("utf8", 0, bytesRead) + buffer;
164
- if (pos === 0 && buffer.startsWith(delimiter)) startsWithDelimiter = true;
165
- consumeBuffer();
155
+ try {
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
+ if (isBufferOutput) pageQueue.push(buf.subarray(0, bytesRead));
163
+ else {
164
+ buffer = buf.toString("utf8", 0, bytesRead) + buffer;
165
+ if (pos === 0 && buffer.startsWith(delimiter)) startsWithDelimiter = true;
166
+ consumeBuffer();
167
+ }
166
168
  }
167
- }
168
- if (pos === 0 && !flushed) {
169
- flushTail();
170
- if (fd) {
171
- try {
172
- await fd.close();
173
- } catch {}
174
- fd = null;
169
+ if (pos === 0 && !flushed) {
170
+ flushTail();
171
+ break;
175
172
  }
176
- break;
173
+ if (!done && !closed) await new Promise((r) => setImmediate(r));
174
+ }
175
+ } finally {
176
+ if (fd) {
177
+ try {
178
+ await fd.close();
179
+ } catch {}
180
+ fd = null;
177
181
  }
178
- if (!done && !closed) await new Promise((r) => setImmediate(r));
179
182
  }
180
183
  })();
181
184
  function fillSync() {
@@ -261,13 +264,29 @@ function createBackwardReader(filepath, options) {
261
264
  } catch {}
262
265
  fdSync = null;
263
266
  }
264
- if (fd?.fd) {
265
- try {
266
- closeSync(fd.fd);
267
- } catch {}
267
+ if (fd) {
268
+ fd.close().catch(() => {});
268
269
  fd = null;
269
270
  }
270
271
  }
272
+ },
273
+ [Symbol.asyncDispose]() {
274
+ return close();
275
+ },
276
+ [Symbol.dispose]() {
277
+ closed = true;
278
+ done = true;
279
+ pageQueue.clear();
280
+ if (fdSync) {
281
+ try {
282
+ closeSync(fdSync);
283
+ } catch {}
284
+ fdSync = null;
285
+ }
286
+ if (fd) {
287
+ fd.close().catch(() => {});
288
+ fd = null;
289
+ }
271
290
  }
272
291
  };
273
292
  }
@@ -333,29 +352,32 @@ function createForwardReader(filepath, options) {
333
352
  pageQueue.wake();
334
353
  return;
335
354
  }
336
- while (!done && !closed) {
337
- while (pageQueue.count < prefetch && pos < size && !closed) {
338
- const readSize = Math.min(chunkSize, size - pos);
339
- const buf = Buffer.allocUnsafe(readSize);
340
- const { bytesRead } = await fd.read(buf, 0, readSize, pos);
341
- pos += bytesRead;
342
- if (isBufferOutput) pageQueue.push(buf.subarray(0, bytesRead));
343
- else {
344
- buffer = buffer + buf.toString("utf8", 0, bytesRead);
345
- consumeBuffer();
355
+ try {
356
+ while (!done && !closed) {
357
+ while (pageQueue.count < prefetch && pos < size && !closed) {
358
+ const readSize = Math.min(chunkSize, size - pos);
359
+ const buf = Buffer.allocUnsafe(readSize);
360
+ const { bytesRead } = await fd.read(buf, 0, readSize, pos);
361
+ pos += bytesRead;
362
+ if (isBufferOutput) pageQueue.push(buf.subarray(0, bytesRead));
363
+ else {
364
+ buffer = buffer + buf.toString("utf8", 0, bytesRead);
365
+ consumeBuffer();
366
+ }
346
367
  }
347
- }
348
- if (pos >= size && !flushed) {
349
- flushTail();
350
- if (fd) {
351
- try {
352
- await fd.close();
353
- } catch {}
354
- fd = null;
368
+ if (pos >= size && !flushed) {
369
+ flushTail();
370
+ break;
355
371
  }
356
- break;
372
+ if (!done && !closed) await new Promise((r) => setImmediate(r));
373
+ }
374
+ } finally {
375
+ if (fd) {
376
+ try {
377
+ await fd.close();
378
+ } catch {}
379
+ fd = null;
357
380
  }
358
- if (!done && !closed) await new Promise((r) => setImmediate(r));
359
381
  }
360
382
  })();
361
383
  function fillSync() {
@@ -440,13 +462,29 @@ function createForwardReader(filepath, options) {
440
462
  } catch {}
441
463
  fdSync = null;
442
464
  }
443
- if (fd?.fd) {
444
- try {
445
- closeSync(fd.fd);
446
- } catch {}
465
+ if (fd) {
466
+ fd.close().catch(() => {});
447
467
  fd = null;
448
468
  }
449
469
  }
470
+ },
471
+ [Symbol.asyncDispose]() {
472
+ return close();
473
+ },
474
+ [Symbol.dispose]() {
475
+ closed = true;
476
+ done = true;
477
+ pageQueue.clear();
478
+ if (fdSync) {
479
+ try {
480
+ closeSync(fdSync);
481
+ } catch {}
482
+ fdSync = null;
483
+ }
484
+ if (fd) {
485
+ fd.close().catch(() => {});
486
+ fd = null;
487
+ }
450
488
  }
451
489
  };
452
490
  }
@@ -464,12 +502,7 @@ function createPager(filepath, options = {}) {
464
502
  delimiter,
465
503
  output
466
504
  };
467
- const reader = backward ? createBackwardReader(filepath, readerOptions) : createForwardReader(filepath, readerOptions);
468
- if (process.env.PAGER_TEST_CLEANUPS) {
469
- globalThis.__pager_test_cleanups__ ??= [];
470
- globalThis.__pager_test_cleanups__.push(reader.close);
471
- }
472
- return reader;
505
+ return backward ? createBackwardReader(filepath, readerOptions) : createForwardReader(filepath, readerOptions);
473
506
  }
474
507
  //#endregion
475
- export { createNativePager, createPager, createPager as default };
508
+ export { createNativePager, createPager };
@@ -1,7 +1,5 @@
1
1
  //#region src/types.d.ts
2
- type AsyncFunction = () => Promise<void>;
3
2
  type Output = "string" | "buffer";
4
- type PageOutput = string[] | Buffer;
5
3
  type ResolvePageOutput<T extends Output> = T extends "buffer" ? Buffer : string[];
6
4
  interface ReaderOptions {
7
5
  chunkSize: number;
@@ -19,20 +17,14 @@ interface NativeReaderOptions {
19
17
  type PagerOptions = Partial<ReaderOptions> & {
20
18
  backward?: boolean;
21
19
  };
22
- type AddonFD = object | null;
23
- type AddonData = Buffer | null;
24
- interface NativeAddon {
25
- open: (filepath: string, pageSize: number, delimiter: string, backward: boolean) => AddonFD;
26
- next: (fd: AddonFD) => Promise<AddonData>;
27
- nextSync: (fd: AddonFD) => AddonData;
28
- close: (fd: AddonFD) => Promise<void>;
29
- }
30
20
  interface Pager<T extends Output = "string"> {
31
21
  next(): Promise<ResolvePageOutput<T> | null>;
32
22
  nextSync(): ResolvePageOutput<T> | null;
33
23
  close(): Promise<void>;
34
24
  [Symbol.asyncIterator](): AsyncIterator<ResolvePageOutput<T>>;
35
25
  [Symbol.iterator](): Iterator<ResolvePageOutput<T>>;
26
+ [Symbol.asyncDispose](): Promise<void>;
27
+ [Symbol.dispose](): void;
36
28
  }
37
29
  //#endregion
38
30
  //#region src/native.d.ts
@@ -41,4 +33,4 @@ declare function createNativePager<T extends Output>(filepath: string, options:
41
33
  }): Pager<T>;
42
34
  declare function createNativePager(filepath: string, options?: Partial<NativeReaderOptions>): Pager<"string">;
43
35
  //#endregion
44
- export { Output as a, PagerOptions as c, NativeReaderOptions as i, ReaderOptions as l, AsyncFunction as n, PageOutput as o, NativeAddon as r, Pager as s, createNativePager as t, ResolvePageOutput as u };
36
+ export { PagerOptions as i, Output as n, Pager as r, createNativePager as t };
@@ -1,7 +1,5 @@
1
1
  //#region src/types.d.ts
2
- type AsyncFunction = () => Promise<void>;
3
2
  type Output = "string" | "buffer";
4
- type PageOutput = string[] | Buffer;
5
3
  type ResolvePageOutput<T extends Output> = T extends "buffer" ? Buffer : string[];
6
4
  interface ReaderOptions {
7
5
  chunkSize: number;
@@ -19,20 +17,14 @@ interface NativeReaderOptions {
19
17
  type PagerOptions = Partial<ReaderOptions> & {
20
18
  backward?: boolean;
21
19
  };
22
- type AddonFD = object | null;
23
- type AddonData = Buffer | null;
24
- interface NativeAddon {
25
- open: (filepath: string, pageSize: number, delimiter: string, backward: boolean) => AddonFD;
26
- next: (fd: AddonFD) => Promise<AddonData>;
27
- nextSync: (fd: AddonFD) => AddonData;
28
- close: (fd: AddonFD) => Promise<void>;
29
- }
30
20
  interface Pager<T extends Output = "string"> {
31
21
  next(): Promise<ResolvePageOutput<T> | null>;
32
22
  nextSync(): ResolvePageOutput<T> | null;
33
23
  close(): Promise<void>;
34
24
  [Symbol.asyncIterator](): AsyncIterator<ResolvePageOutput<T>>;
35
25
  [Symbol.iterator](): Iterator<ResolvePageOutput<T>>;
26
+ [Symbol.asyncDispose](): Promise<void>;
27
+ [Symbol.dispose](): void;
36
28
  }
37
29
  //#endregion
38
30
  //#region src/native.d.ts
@@ -41,4 +33,4 @@ declare function createNativePager<T extends Output>(filepath: string, options:
41
33
  }): Pager<T>;
42
34
  declare function createNativePager(filepath: string, options?: Partial<NativeReaderOptions>): Pager<"string">;
43
35
  //#endregion
44
- export { Output as a, PagerOptions as c, NativeReaderOptions as i, ReaderOptions as l, AsyncFunction as n, PageOutput as o, NativeAddon as r, Pager as s, createNativePager as t, ResolvePageOutput as u };
36
+ export { PagerOptions as i, Output as n, Pager as r, createNativePager as t };
package/dist/native.cjs CHANGED
@@ -34,10 +34,6 @@ function createNativePager(filepath, options = {}) {
34
34
  if (delimiter?.length > 1) throw new RangeError("native reader only supports single-character delimiters");
35
35
  const nativeReader = loadNativeAddon();
36
36
  const isBufferOutput = output === "buffer";
37
- if (process.env.PAGER_TEST_CLEANUPS) {
38
- globalThis.__pager_test_cleanups__ ??= [];
39
- globalThis.__pager_test_cleanups__.push(nativeReader.close);
40
- }
41
37
  let fd = nativeReader.open(filepath, pageSize, delimiter, backward);
42
38
  let closed = false;
43
39
  async function next() {
@@ -90,6 +86,12 @@ function createNativePager(filepath, options = {}) {
90
86
  } finally {
91
87
  tryClose();
92
88
  }
89
+ },
90
+ [Symbol.asyncDispose]() {
91
+ return close();
92
+ },
93
+ [Symbol.dispose]() {
94
+ tryClose();
93
95
  }
94
96
  };
95
97
  }
package/dist/native.d.cts CHANGED
@@ -1,2 +1,2 @@
1
- import { t as createNativePager } from "./native-7x1Vhdaw.cjs";
1
+ import { t as createNativePager } from "./native-BJ3yhPgn.cjs";
2
2
  export { createNativePager };
package/dist/native.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { t as createNativePager } from "./native-D6SBIgoE.mjs";
1
+ import { t as createNativePager } from "./native-BJ3yhPgn.mjs";
2
2
  export { createNativePager };
package/dist/native.mjs CHANGED
@@ -33,10 +33,6 @@ function createNativePager(filepath, options = {}) {
33
33
  if (delimiter?.length > 1) throw new RangeError("native reader only supports single-character delimiters");
34
34
  const nativeReader = loadNativeAddon();
35
35
  const isBufferOutput = output === "buffer";
36
- if (process.env.PAGER_TEST_CLEANUPS) {
37
- globalThis.__pager_test_cleanups__ ??= [];
38
- globalThis.__pager_test_cleanups__.push(nativeReader.close);
39
- }
40
36
  let fd = nativeReader.open(filepath, pageSize, delimiter, backward);
41
37
  let closed = false;
42
38
  async function next() {
@@ -89,6 +85,12 @@ function createNativePager(filepath, options = {}) {
89
85
  } finally {
90
86
  tryClose();
91
87
  }
88
+ },
89
+ [Symbol.asyncDispose]() {
90
+ return close();
91
+ },
92
+ [Symbol.dispose]() {
93
+ tryClose();
92
94
  }
93
95
  };
94
96
  }
package/package.json CHANGED
@@ -1,32 +1,32 @@
1
1
  {
2
2
  "name": "readline-pager",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "scripts": {
5
5
  "build:js": "tsdown",
6
6
  "build:native": "node-gyp rebuild",
7
7
  "build": "npm run build:native && npm run build:js",
8
- "pretest": "npm run build",
9
- "test": "PAGER_TEST_CLEANUPS=1 node --test --experimental-test-coverage test/**/*.test.ts",
10
- "prepublishOnly": "npm run build && npm run test",
11
- "benchmark:node": "node test/benchmark.ts",
12
- "benchmark:deno": "deno --allow-write --allow-read --allow-env test/benchmark.ts",
13
- "benchmark:bun": "bun test/benchmark.ts"
8
+ "test": "./scripts/test.sh",
9
+ "test:coverage": "npx lcov-badge2 coverage/lcov.info -o coverage.svg",
10
+ "prepublishOnly": "npm run test",
11
+ "benchmark": "./scripts/benchmark.sh",
12
+ "prettier": "npx prettier -wu './(src|test)/**/*.{js,cjs,mjs,ts,cts,mts,json,yml,md}'"
14
13
  },
15
14
  "devDependencies": {
16
- "@types/bun": "~1.3.12",
17
- "@types/deno": "~2.5.0",
18
- "@types/node": "~25.6.0",
19
- "node-gyp": "~12.2.0",
20
- "prettier": "~3.8.2",
15
+ "@types/bun": "~1.3.14",
16
+ "@types/deno": "~2.7.0",
17
+ "@types/node": "~26.0.0",
18
+ "lcov-badge2": "~1.1.4",
19
+ "node-gyp": "~13.0.0",
20
+ "prettier": "~3.8.4",
21
21
  "prettier-plugin-organize-imports": "~4.3.0",
22
- "tsdown": "~0.21.7",
23
- "typescript": "~6.0.2"
22
+ "tsdown": "~0.22.3",
23
+ "typescript": "~6.0.3"
24
24
  },
25
25
  "optionalDependencies": {
26
- "@devmor-j/readline-pager-linux-arm64": "0.7.2",
27
- "@devmor-j/readline-pager-linux-musl-arm64": "0.7.2",
28
- "@devmor-j/readline-pager-linux-musl-x64": "0.7.2",
29
- "@devmor-j/readline-pager-linux-x64": "0.7.2"
26
+ "@devmor-j/readline-pager-linux-arm64": "0.8.0",
27
+ "@devmor-j/readline-pager-linux-musl-arm64": "0.8.0",
28
+ "@devmor-j/readline-pager-linux-musl-x64": "0.8.0",
29
+ "@devmor-j/readline-pager-linux-x64": "0.8.0"
30
30
  },
31
31
  "gypfile": false,
32
32
  "type": "module",