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 +23 -13
- package/dist/main.cjs +89 -60
- package/dist/main.d.cts +2 -2
- package/dist/main.d.mts +2 -2
- package/dist/main.mjs +89 -56
- package/dist/{native-7x1Vhdaw.d.cts → native-BJ3yhPgn.d.cts} +3 -11
- package/dist/{native-D6SBIgoE.d.mts → native-BJ3yhPgn.d.mts} +3 -11
- package/dist/native.cjs +6 -4
- package/dist/native.d.cts +1 -1
- package/dist/native.d.mts +1 -1
- package/dist/native.mjs +6 -4
- package/package.json +18 -18
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
|
|
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
|
|
26
|
+
- 🧪 Fully typed with high test coverage
|
|
24
27
|
|
|
25
28
|
> **Important:**
|
|
26
|
-
> Performance depends heavily on the `chunkSize` option. Tune it for your
|
|
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
|
|
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,
|
|
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:
|
|
144
|
-
|
|
145
|
-
#
|
|
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
|
|
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.
|
|
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
|
-
|
|
160
|
-
while (
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
|
269
|
-
|
|
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
|
-
|
|
341
|
-
while (
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
353
|
-
|
|
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
|
-
|
|
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
|
|
448
|
-
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
9
|
+
export { type Pager, type PagerOptions, createNativePager, createPager };
|
package/dist/main.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
156
|
-
while (
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
169
|
-
|
|
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
|
-
|
|
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
|
|
265
|
-
|
|
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
|
-
|
|
337
|
-
while (
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
-
|
|
349
|
-
|
|
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
|
-
|
|
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
|
|
444
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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 {
|
|
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-
|
|
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-
|
|
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.
|
|
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
|
-
"
|
|
9
|
-
"test": "
|
|
10
|
-
"prepublishOnly": "npm run
|
|
11
|
-
"benchmark
|
|
12
|
-
"
|
|
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.
|
|
17
|
-
"@types/deno": "~2.
|
|
18
|
-
"@types/node": "~
|
|
19
|
-
"
|
|
20
|
-
"
|
|
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.
|
|
23
|
-
"typescript": "~6.0.
|
|
22
|
+
"tsdown": "~0.22.3",
|
|
23
|
+
"typescript": "~6.0.3"
|
|
24
24
|
},
|
|
25
25
|
"optionalDependencies": {
|
|
26
|
-
"@devmor-j/readline-pager-linux-arm64": "0.
|
|
27
|
-
"@devmor-j/readline-pager-linux-musl-arm64": "0.
|
|
28
|
-
"@devmor-j/readline-pager-linux-musl-x64": "0.
|
|
29
|
-
"@devmor-j/readline-pager-linux-x64": "0.
|
|
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",
|