readline-pager 0.2.5 → 0.2.7

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
@@ -38,6 +38,7 @@ npm install readline-pager
38
38
 
39
39
  ```ts
40
40
  import { createPager } from "readline-pager";
41
+ // const { createPager } = require("readline-pager");
41
42
 
42
43
  const pager = createPager("./bigfile.txt");
43
44
 
@@ -51,8 +52,6 @@ for await (const page of pager) {
51
52
  **Recommended for highest throughput:**
52
53
 
53
54
  ```ts
54
- const pager = createPager("./bigfile.txt");
55
-
56
55
  while (true) {
57
56
  const page = await pager.next();
58
57
  if (!page) break;
@@ -60,9 +59,7 @@ while (true) {
60
59
 
61
60
  // or
62
61
  let page;
63
- while ((page = await pager.next()) !== null) {
64
- // process page
65
- }
62
+ while ((page = await pager.next()) !== null) {}
66
63
  ```
67
64
 
68
65
  - `while + next()` is the fastest iteration method (avoids extra async-iterator overhead).
@@ -76,14 +73,14 @@ while ((page = await pager.next()) !== null) {
76
73
  createPager(filepath, {
77
74
  chunkSize?: number, // default: 64 * 1024 (64 KiB)
78
75
  pageSize?: number, // default: 1_000
79
- delimiter?: string, // default: "\n"
76
+ delimiter?: string, // default: "\n"
80
77
  prefetch?: number, // default: 1
81
78
  backward?: boolean, // default: false
82
79
  useWorker?: boolean, // default: false (forward only)
83
80
  });
84
81
  ```
85
82
 
86
- - `chunkSize`: number of bytes read per I/O operation. **Tune this** — default is `64 * 1024`.
83
+ - `chunkSize` number of bytes read per I/O operation.
87
84
  - `pageSize` — number of lines per page.
88
85
  - `delimiter` — line separator.
89
86
  - `prefetch` — max number of pages buffered internally. Not required for typical use; tuning has little effect once the engine is optimized.
@@ -103,7 +100,7 @@ Returns the next page or `null` when finished. Empty lines are preserved.
103
100
  - A completely empty file (`0` bytes) produces `[""]` on the first read.
104
101
  - A file with multiple empty lines returns each line as an empty string (e.g., `["", ""]` for two empty lines). Node.js `readline` may emit fewer or no `line` events in these cases.
105
102
 
106
- ### `pager.close(): void`
103
+ ### `pager.close(): Promise<void>`
107
104
 
108
105
  Stops reading and releases resources immediately. Safe to call at any time.
109
106
 
@@ -121,28 +118,46 @@ Run the included benchmark:
121
118
 
122
119
  ```bash
123
120
  # default run
124
- node test/_benchmark.ts
121
+ npm run benchmark
125
122
 
126
123
  # or customize with args
127
124
  node test/_benchmark.ts --lines=20000 --page-size=500 --backward
128
125
  ```
129
126
 
130
127
  > Test setup: generated text files with uuid, run on a fast NVMe machine with default options; values are averages from multiple runs. Results are machine-dependent.
128
+ >
129
+ > The **Average Throughput (MB/s)** is computed for two strategies: reading files line by line and page by page.
130
+ >
131
+ > In addition to _Node_, the two other popular JavaScript runtimes were also tested with `readline-pager`.
132
+
133
+ ### Line by line
134
+
135
+ | Runtime / Method | 1M lines (35 MB) | 10M lines (353 MB) | 100M lines (3,529 MB) | 1,000M lines (35,286 MB) |
136
+ | ---------------- | ---------------: | -----------------: | --------------------: | -----------------------: |
137
+ | Node — node:line | 369 | 435 | 455 | 455 |
138
+ | Deno — node:line | 203 | 230 | 230 | 229 |
139
+ | Deno — deno:line | 738 | 901 | 915 | 809 |
140
+ | Bun — node:line | 246 | 279 | 283 | 280 |
141
+ | Bun — bun:line | 938 | 1,540 | 1,668 | 1,315 |
142
+
143
+ ### Page by page
131
144
 
132
- | Lines | File MB | Node `readline` (MB/s) | Bun streaming (MB/s) | `readline-pager` (Node) (MB/s) |
133
- | -----: | -------: | ---------------------: | -------------------: | -----------------------------: |
134
- | 10M | 352.86 | ~423 | ~296 | **~1,327** |
135
- | 100M | 3528.59 | ~441 | ~298 | **~1,378** |
136
- | 1,000M | 35285.95 | ~426 | ~294 | **~1,168** |
145
+ | Runtime / Method | 1M lines (35 MB) | 10M lines (353 MB) | 100M lines (3,529 MB) | 1,000M lines (35,286 MB) |
146
+ | --------------------- | ---------------: | -----------------: | --------------------: | -----------------------: |
147
+ | Node — readline-pager | 1,053 | 1,311 | 1,278 | 936 |
148
+ | Deno — deno:page | 852 | 909 | 908 | 783 |
149
+ | Deno readline-pager | 1,131 | 1,268 | 1,271 | 911 |
150
+ | Bun — bun:page | 411 | 440 | 449 | 428 |
151
+ | Bun — readline-pager | 827 | 1,021 | 1,040 | 804 |
137
152
 
138
- **Takeaway:** `readline-pager` delivers multi-GB/s memory-to-memory throughput on large files on typical NVMe hardware; results vary with `chunkSize`, runtime (Node vs Bun), and CPU/OS.
153
+ **Runtime Environment:** Node.js v25.6.1 & Bun v1.3.9 & Deno 2.6.10
139
154
 
140
155
  ---
141
156
 
142
157
  ## 🛠 Development & Contributing
143
158
 
144
- - Minimum supported Node.js: **v18.12** (lts/hydrogen).
145
- - Development/test environment: **Node v25.6**, **TypeScript v5.9**.
159
+ - Minimum supported Node.js: **v18.12 (lts/hydrogen)**.
160
+ - Development/test environment: **Node v25.6 & TypeScript v5.9**.
146
161
 
147
162
  Run tests:
148
163
 
package/dist/main.cjs CHANGED
@@ -1,4 +1,4 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1
+ Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });
2
2
  let node_fs_promises = require("node:fs/promises");
3
3
  let node_worker_threads = require("node:worker_threads");
4
4
 
@@ -98,7 +98,10 @@ function createBackwardReader(filepath, options) {
98
98
  closed = true;
99
99
  done = true;
100
100
  pageQueue.queue.length = 0;
101
- if (fd) await fd.close();
101
+ if (fd) {
102
+ await fd.close();
103
+ fd = null;
104
+ }
102
105
  }
103
106
  return {
104
107
  next,
@@ -113,10 +116,14 @@ function createBackwardReader(filepath, options) {
113
116
  return lastLine;
114
117
  },
115
118
  async *[Symbol.asyncIterator]() {
116
- while (true) {
117
- const p = await next();
118
- if (!p) break;
119
- yield p;
119
+ try {
120
+ while (true) {
121
+ const p = await next();
122
+ if (!p) break;
123
+ yield p;
124
+ }
125
+ } finally {
126
+ await close().catch(() => {});
120
127
  }
121
128
  }
122
129
  };
@@ -187,7 +194,10 @@ function createForwardReader(filepath, options) {
187
194
  closed = true;
188
195
  done = true;
189
196
  pageQueue.queue.length = 0;
190
- if (fd) await fd.close();
197
+ if (fd) {
198
+ await fd.close();
199
+ fd = null;
200
+ }
191
201
  }
192
202
  return {
193
203
  next,
@@ -202,10 +212,14 @@ function createForwardReader(filepath, options) {
202
212
  return lastLine;
203
213
  },
204
214
  async *[Symbol.asyncIterator]() {
205
- while (true) {
206
- const p = await next();
207
- if (!p) break;
208
- yield p;
215
+ try {
216
+ while (true) {
217
+ const p = await next();
218
+ if (!p) break;
219
+ yield p;
220
+ }
221
+ } finally {
222
+ await close();
209
223
  }
210
224
  }
211
225
  };
@@ -247,7 +261,7 @@ function createWorkerReader(filepath, options) {
247
261
  async function close() {
248
262
  closed = true;
249
263
  done = true;
250
- worker.terminate();
264
+ await worker.terminate();
251
265
  }
252
266
  return {
253
267
  next,
@@ -262,10 +276,14 @@ function createWorkerReader(filepath, options) {
262
276
  return lastLine;
263
277
  },
264
278
  async *[Symbol.asyncIterator]() {
265
- while (true) {
266
- const p = await next();
267
- if (!p) break;
268
- yield p;
279
+ try {
280
+ while (true) {
281
+ const p = await next();
282
+ if (!p) break;
283
+ yield p;
284
+ }
285
+ } finally {
286
+ await close();
269
287
  }
270
288
  }
271
289
  };
@@ -298,4 +316,5 @@ function createPager(filepath, options = {}) {
298
316
  }
299
317
 
300
318
  //#endregion
301
- exports.createPager = createPager;
319
+ exports.createPager = createPager;
320
+ exports.default = createPager;
package/dist/main.d.cts CHANGED
@@ -11,7 +11,7 @@ interface PagerOptions extends Partial<ReaderOptions> {
11
11
  }
12
12
  interface Pager extends AsyncIterable<string[]> {
13
13
  next(): Promise<string[] | null>;
14
- close(): void;
14
+ close(): Promise<void>;
15
15
  readonly lineCount: number;
16
16
  readonly firstLine: string | null;
17
17
  readonly lastLine: string | null;
@@ -20,4 +20,4 @@ interface Pager extends AsyncIterable<string[]> {
20
20
  //#region src/main.d.ts
21
21
  declare function createPager(filepath: string, options?: PagerOptions): Pager;
22
22
  //#endregion
23
- export { Pager, PagerOptions, ReaderOptions, createPager };
23
+ export { Pager, PagerOptions, ReaderOptions, createPager, createPager as default };
package/dist/main.d.mts CHANGED
@@ -11,7 +11,7 @@ interface PagerOptions extends Partial<ReaderOptions> {
11
11
  }
12
12
  interface Pager extends AsyncIterable<string[]> {
13
13
  next(): Promise<string[] | null>;
14
- close(): void;
14
+ close(): Promise<void>;
15
15
  readonly lineCount: number;
16
16
  readonly firstLine: string | null;
17
17
  readonly lastLine: string | null;
@@ -20,4 +20,4 @@ interface Pager extends AsyncIterable<string[]> {
20
20
  //#region src/main.d.ts
21
21
  declare function createPager(filepath: string, options?: PagerOptions): Pager;
22
22
  //#endregion
23
- export { Pager, PagerOptions, ReaderOptions, createPager };
23
+ export { Pager, PagerOptions, ReaderOptions, createPager, createPager as default };
package/dist/main.mjs CHANGED
@@ -102,7 +102,10 @@ function createBackwardReader(filepath, options) {
102
102
  closed = true;
103
103
  done = true;
104
104
  pageQueue.queue.length = 0;
105
- if (fd) await fd.close();
105
+ if (fd) {
106
+ await fd.close();
107
+ fd = null;
108
+ }
106
109
  }
107
110
  return {
108
111
  next,
@@ -117,10 +120,14 @@ function createBackwardReader(filepath, options) {
117
120
  return lastLine;
118
121
  },
119
122
  async *[Symbol.asyncIterator]() {
120
- while (true) {
121
- const p = await next();
122
- if (!p) break;
123
- yield p;
123
+ try {
124
+ while (true) {
125
+ const p = await next();
126
+ if (!p) break;
127
+ yield p;
128
+ }
129
+ } finally {
130
+ await close().catch(() => {});
124
131
  }
125
132
  }
126
133
  };
@@ -191,7 +198,10 @@ function createForwardReader(filepath, options) {
191
198
  closed = true;
192
199
  done = true;
193
200
  pageQueue.queue.length = 0;
194
- if (fd) await fd.close();
201
+ if (fd) {
202
+ await fd.close();
203
+ fd = null;
204
+ }
195
205
  }
196
206
  return {
197
207
  next,
@@ -206,10 +216,14 @@ function createForwardReader(filepath, options) {
206
216
  return lastLine;
207
217
  },
208
218
  async *[Symbol.asyncIterator]() {
209
- while (true) {
210
- const p = await next();
211
- if (!p) break;
212
- yield p;
219
+ try {
220
+ while (true) {
221
+ const p = await next();
222
+ if (!p) break;
223
+ yield p;
224
+ }
225
+ } finally {
226
+ await close();
213
227
  }
214
228
  }
215
229
  };
@@ -251,7 +265,7 @@ function createWorkerReader(filepath, options) {
251
265
  async function close() {
252
266
  closed = true;
253
267
  done = true;
254
- worker.terminate();
268
+ await worker.terminate();
255
269
  }
256
270
  return {
257
271
  next,
@@ -266,10 +280,14 @@ function createWorkerReader(filepath, options) {
266
280
  return lastLine;
267
281
  },
268
282
  async *[Symbol.asyncIterator]() {
269
- while (true) {
270
- const p = await next();
271
- if (!p) break;
272
- yield p;
283
+ try {
284
+ while (true) {
285
+ const p = await next();
286
+ if (!p) break;
287
+ yield p;
288
+ }
289
+ } finally {
290
+ await close();
273
291
  }
274
292
  }
275
293
  };
@@ -302,4 +320,4 @@ function createPager(filepath, options = {}) {
302
320
  }
303
321
 
304
322
  //#endregion
305
- export { createPager };
323
+ export { createPager, createPager as default };
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "readline-pager",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "scripts": {
5
5
  "build": "tsdown",
6
6
  "pretest": "npm run build",
7
7
  "test": "node --test --experimental-test-coverage test/**/*.test.ts",
8
- "prepublishOnly": "npm run build && npm run test"
8
+ "prepublishOnly": "npm run build && npm run test",
9
+ "benchmark": "node test/_benchmark.ts; bun test/_benchmark.ts; deno --allow-write --allow-read test/_benchmark.ts"
9
10
  },
10
11
  "devDependencies": {
11
12
  "@types/bun": "~1.3.9",
13
+ "@types/deno": "~2.5.0",
12
14
  "@types/node": "~25.3.0",
13
15
  "prettier": "~3.8.1",
14
16
  "prettier-plugin-organize-imports": "~4.3.0",