readline-pager 0.7.3 → 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
@@ -76,7 +76,12 @@ while ((page = pager.nextSync()) !== null) {}
76
76
  // Native C++
77
77
  for (const page of createNativePager("./bigfile.txt")) {
78
78
  }
79
- ```
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
80
85
 
81
86
  ---
82
87
 
@@ -148,10 +153,9 @@ Stops reading and releases resources asynchronously. Safe to call at any time.
148
153
  Run the benchmark locally:
149
154
 
150
155
  ```bash
151
- npm run benchmark:node
152
-
153
- # or customize with args
154
- 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
155
159
  ```
156
160
 
157
161
  > Test setup: generated text files (UUID lines), NVMe SSD, Node.js runtime.
@@ -170,7 +174,7 @@ node test/benchmark.ts --lines=20000 --page-size=500 --backward
170
174
  ## 🛠 Development & Contributing
171
175
 
172
176
  - Minimum supported Node.js: **v18.12**
173
- - Development/test environment: **Node v25.8** and **TypeScript v6.0**
177
+ - Development/test environment: **Node v26.3** and **TypeScript v6.0**
174
178
 
175
179
  Run tests:
176
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 { i as PagerOptions, n as Output, r as Pager, t as createNativePager } from "./native-7FzZtBp1.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 { type Pager, type PagerOptions, 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 { i as PagerOptions, n as Output, r as Pager, t as createNativePager } from "./native-B0PXEEYw.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 { type Pager, type PagerOptions, 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 };
@@ -23,6 +23,8 @@ interface Pager<T extends Output = "string"> {
23
23
  close(): Promise<void>;
24
24
  [Symbol.asyncIterator](): AsyncIterator<ResolvePageOutput<T>>;
25
25
  [Symbol.iterator](): Iterator<ResolvePageOutput<T>>;
26
+ [Symbol.asyncDispose](): Promise<void>;
27
+ [Symbol.dispose](): void;
26
28
  }
27
29
  //#endregion
28
30
  //#region src/native.d.ts
@@ -23,6 +23,8 @@ interface Pager<T extends Output = "string"> {
23
23
  close(): Promise<void>;
24
24
  [Symbol.asyncIterator](): AsyncIterator<ResolvePageOutput<T>>;
25
25
  [Symbol.iterator](): Iterator<ResolvePageOutput<T>>;
26
+ [Symbol.asyncDispose](): Promise<void>;
27
+ [Symbol.dispose](): void;
26
28
  }
27
29
  //#endregion
28
30
  //#region src/native.d.ts
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-7FzZtBp1.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-B0PXEEYw.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,35 +1,32 @@
1
1
  {
2
2
  "name": "readline-pager",
3
- "version": "0.7.3",
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 c8 --reporter=lcov --reporter=text node --test test/**/*.test.ts",
8
+ "test": "./scripts/test.sh",
10
9
  "test:coverage": "npx lcov-badge2 coverage/lcov.info -o coverage.svg",
11
- "prepublishOnly": "npm run build && npm run test",
12
- "benchmark:node": "node test/benchmark.ts",
13
- "benchmark:deno": "deno --allow-write --allow-read --allow-env test/benchmark.ts",
14
- "benchmark:bun": "bun test/benchmark.ts"
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}'"
15
13
  },
16
14
  "devDependencies": {
17
- "@types/bun": "~1.3.13",
18
- "@types/deno": "~2.5.0",
19
- "@types/node": "~25.6.2",
20
- "c8": "~11.0.0",
15
+ "@types/bun": "~1.3.14",
16
+ "@types/deno": "~2.7.0",
17
+ "@types/node": "~26.0.0",
21
18
  "lcov-badge2": "~1.1.4",
22
- "node-gyp": "~12.3.0",
23
- "prettier": "~3.8.3",
19
+ "node-gyp": "~13.0.0",
20
+ "prettier": "~3.8.4",
24
21
  "prettier-plugin-organize-imports": "~4.3.0",
25
- "tsdown": "~0.22.0",
22
+ "tsdown": "~0.22.3",
26
23
  "typescript": "~6.0.3"
27
24
  },
28
25
  "optionalDependencies": {
29
- "@devmor-j/readline-pager-linux-arm64": "0.7.3",
30
- "@devmor-j/readline-pager-linux-musl-arm64": "0.7.3",
31
- "@devmor-j/readline-pager-linux-musl-x64": "0.7.3",
32
- "@devmor-j/readline-pager-linux-x64": "0.7.3"
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"
33
30
  },
34
31
  "gypfile": false,
35
32
  "type": "module",