js-dev-tool 1.2.4 → 1.2.6
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/basic-types.d.ts +1 -0
- package/extras/algorithms.d.ts +15 -0
- package/extras/algorithms.js +39 -0
- package/extras/jsonl.d.ts +116 -0
- package/extras/jsonl.js +204 -0
- package/package.json +2 -2
- package/tools.js +1 -1
package/basic-types.d.ts
CHANGED
|
@@ -107,6 +107,7 @@ type PickStringProperties<T> = PickProperties<string, T>;
|
|
|
107
107
|
type NonFunctionPropertyNames<T> = { [K in keyof T]-?: T[K] extends Function ? never : K }[keyof T];
|
|
108
108
|
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;
|
|
109
109
|
type ArrayToUnion<T extends readonly unknown[]> = T[number];
|
|
110
|
+
type ExpandUnion<T> = T extends any ? T : never;
|
|
110
111
|
/**
|
|
111
112
|
* for lazy assign class.
|
|
112
113
|
*/
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
3
|
+
// Copyright (C) 2026 jeffy-g <hirotom1107@gmail.com>
|
|
4
|
+
// Released under the MIT license
|
|
5
|
+
// https://opensource.org/licenses/mit-license.php
|
|
6
|
+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* @file extras/algorithms.d.ts
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Array.sort etc...
|
|
13
|
+
*/
|
|
14
|
+
export declare type TBnComparator<T, V> = (a: T, b: V) => number;
|
|
15
|
+
export declare const bnSearch: <T, V>(src: T[], v: V, comparator: TBnComparator<T, V>) => number;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
3
|
+
// Copyright (C) 2026 jeffy-g <hirotom1107@gmail.com>
|
|
4
|
+
// Released under the MIT license
|
|
5
|
+
// https://opensource.org/licenses/mit-license.php
|
|
6
|
+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* @file extras/algorithms.js
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* @import * as algzm from "./algorithms.js";
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* @type {algzm["bnSearch"]}
|
|
16
|
+
*/
|
|
17
|
+
const bnSearch = (src, v, comparator) => {
|
|
18
|
+
let low = 0;
|
|
19
|
+
let hi = src.length - 1;
|
|
20
|
+
while (low <= hi) {
|
|
21
|
+
/** to middle index */
|
|
22
|
+
const m = low + ((hi - low) >>> 1);
|
|
23
|
+
/** compare value */
|
|
24
|
+
const r = comparator(src[m], v);
|
|
25
|
+
if (r > 0) {
|
|
26
|
+
hi = m - 1;
|
|
27
|
+
}
|
|
28
|
+
else if (r < 0) {
|
|
29
|
+
low = m + 1;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
return m;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return -1;
|
|
36
|
+
};
|
|
37
|
+
module.exports = {
|
|
38
|
+
bnSearch,
|
|
39
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
3
|
+
Copyright (C) 2026 jeffy-g <hirotom1107@gmail.com>
|
|
4
|
+
Released under the MIT license
|
|
5
|
+
https://opensource.org/licenses/mit-license.php
|
|
6
|
+
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Resolve a file name to an absolute JSONL path.
|
|
10
|
+
*/
|
|
11
|
+
export type TJsonlPathResolver = (fileName: string) => string;
|
|
12
|
+
/**
|
|
13
|
+
* Handle JSON parse errors from {@link readJsonl}.
|
|
14
|
+
*
|
|
15
|
+
* `lineNo` is the physical line number in the source file.
|
|
16
|
+
*/
|
|
17
|
+
export type TJsonlParseErrorHandler = (error: unknown, line: string, lineNo: number) => void;
|
|
18
|
+
/**
|
|
19
|
+
* Common options for JSONL line reading.
|
|
20
|
+
*/
|
|
21
|
+
export type TJsonlReadBaseOption = {
|
|
22
|
+
/**
|
|
23
|
+
* @default undefined
|
|
24
|
+
*/
|
|
25
|
+
baseDir?: string;
|
|
26
|
+
/**
|
|
27
|
+
* @default undefined
|
|
28
|
+
*/
|
|
29
|
+
pathResolver?: TJsonlPathResolver;
|
|
30
|
+
/**
|
|
31
|
+
* @default "utf8"
|
|
32
|
+
*/
|
|
33
|
+
encoding?: BufferEncoding;
|
|
34
|
+
/**
|
|
35
|
+
* __Recommended minimum value is `128` (integer)__
|
|
36
|
+
*
|
|
37
|
+
* __Decimals are internally converted to integers__ `(| 0)`
|
|
38
|
+
*
|
|
39
|
+
* - The chunk size passed to `fs.createReadStream`.
|
|
40
|
+
* - Expected: A finite number greater than or equal to `128`.
|
|
41
|
+
* - Decimal values are truncated to signed 32-bit integer by `| 0`.
|
|
42
|
+
* - Very large values can overflow signed 32-bit range and may fail in Node.js stream validation.
|
|
43
|
+
*/
|
|
44
|
+
highWaterMark?: number;
|
|
45
|
+
/**
|
|
46
|
+
* @default true
|
|
47
|
+
*/
|
|
48
|
+
skipEmptyLine?: boolean;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Options for row-level JSONL parsing and transformation.
|
|
52
|
+
*/
|
|
53
|
+
export type TJsonlReadOption<TRow = any, TOut = TRow> = TJsonlReadBaseOption & {
|
|
54
|
+
/**
|
|
55
|
+
* @default undefined
|
|
56
|
+
*/
|
|
57
|
+
onParseError?: TJsonlParseErrorHandler;
|
|
58
|
+
/**
|
|
59
|
+
* @default undefined
|
|
60
|
+
*/
|
|
61
|
+
keyField?: string;
|
|
62
|
+
/**
|
|
63
|
+
* @default undefined
|
|
64
|
+
*/
|
|
65
|
+
filter?: (row: TRow) => boolean;
|
|
66
|
+
/**
|
|
67
|
+
* @default undefined
|
|
68
|
+
*/
|
|
69
|
+
map?: (row: TRow) => TOut;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Resolve file path from `fileName` and options.
|
|
73
|
+
*
|
|
74
|
+
* Priority:
|
|
75
|
+
* 1) `pathResolver`
|
|
76
|
+
* 2) `path.resolve(baseDir || process.cwd(), fileName)`
|
|
77
|
+
*/
|
|
78
|
+
export function resolveJsonlPath(fileName: string, options?: Pick<TJsonlReadBaseOption, "baseDir" | "pathResolver">): string;
|
|
79
|
+
/**
|
|
80
|
+
* Read JSONL file as raw lines.
|
|
81
|
+
*
|
|
82
|
+
* - Splits by LF (`\n`) and trims trailing CR (`\r`) per line.
|
|
83
|
+
* - By default, empty lines are skipped.
|
|
84
|
+
*/
|
|
85
|
+
export function readJsonlLines(fileName: string, onLine: (line: string, lineNo: number) => void, options?: TJsonlReadBaseOption): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Parse each JSONL line and emit typed rows.
|
|
88
|
+
*
|
|
89
|
+
* - Parse errors are reported via `onParseError` when provided.
|
|
90
|
+
* - Without `onParseError`, parse errors are logged to stderr.
|
|
91
|
+
*/
|
|
92
|
+
export function readJsonl<TRow = any>(fileName: string, onRow: (row: TRow, lineNo: number) => void, options?: TJsonlReadBaseOption & {
|
|
93
|
+
onParseError?: TJsonlParseErrorHandler;
|
|
94
|
+
}): Promise<void>;
|
|
95
|
+
/**
|
|
96
|
+
* Parse JSONL rows into an array.
|
|
97
|
+
*
|
|
98
|
+
* Supports row filtering and mapping via `filter` and `map`.
|
|
99
|
+
*/
|
|
100
|
+
export function readJsonlArray<TRow = any, TOut = TRow>(fileName: string, options?: TJsonlReadOption<TRow, TOut>): Promise<TOut[]>;
|
|
101
|
+
/**
|
|
102
|
+
* Parse JSONL rows into a key-value map.
|
|
103
|
+
*
|
|
104
|
+
* - Key field defaults to `"_key"`.
|
|
105
|
+
* - Rows with `null` / `undefined` key are ignored.
|
|
106
|
+
* - On duplicate keys, later rows overwrite earlier rows.
|
|
107
|
+
*/
|
|
108
|
+
export function readJsonlMapByKey<TRow = any, TOut = TRow>(fileName: string, options?: TJsonlReadOption<TRow, TOut> & {
|
|
109
|
+
keyField?: string;
|
|
110
|
+
}): Promise<Record<string, TOut>>;
|
|
111
|
+
/**
|
|
112
|
+
* Fast extraction for top-level integer field from a JSONL line.
|
|
113
|
+
*
|
|
114
|
+
* Intended for performance-sensitive hot paths where full `JSON.parse` is unnecessary.
|
|
115
|
+
*/
|
|
116
|
+
export function fastGetIntFieldCheap(line: string, key: string): number | undefined;
|
package/extras/jsonl.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
3
|
+
// Copyright (C) 2026 jeffy-g <hirotom1107@gmail.com>
|
|
4
|
+
// Released under the MIT license
|
|
5
|
+
// https://opensource.org/licenses/mit-license.php
|
|
6
|
+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
7
|
+
*/
|
|
8
|
+
"use strict";
|
|
9
|
+
/**
|
|
10
|
+
* @file extras/jsonl.js
|
|
11
|
+
*/
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
/**
|
|
15
|
+
* @import { TJsonlReadOption, TJsonlReadBaseOption } from "./jsonl.js";
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Resolve jsonl path from file name and options.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} fileName
|
|
21
|
+
* @param {Pick<TJsonlReadBaseOption, "baseDir" | "pathResolver">=} options
|
|
22
|
+
* @returns {string}
|
|
23
|
+
*/
|
|
24
|
+
function resolveJsonlPath(fileName, options = {}) {
|
|
25
|
+
if (options.pathResolver) {
|
|
26
|
+
return options.pathResolver(fileName);
|
|
27
|
+
}
|
|
28
|
+
return path.resolve(options.baseDir || process.cwd(), fileName);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Fast path: extracts a top-level integer field from a JSONL line by searching `"${key}":`.
|
|
32
|
+
*
|
|
33
|
+
* Notes:
|
|
34
|
+
* - Intended for top-level integer fields only.
|
|
35
|
+
* - Returns `undefined` when the key does not exist or the value is not an integer.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} line
|
|
38
|
+
* @param {string} key
|
|
39
|
+
* @returns {number | undefined}
|
|
40
|
+
*/
|
|
41
|
+
function fastGetIntFieldCheap(line, key) {
|
|
42
|
+
const pattern = `"${key}":`;
|
|
43
|
+
const keyStart = line.indexOf(pattern);
|
|
44
|
+
if (keyStart < 0) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
const lineLen = line.length;
|
|
48
|
+
let i = keyStart + pattern.length;
|
|
49
|
+
while (i < lineLen) {
|
|
50
|
+
const c = line[i];
|
|
51
|
+
if (c !== " " && c !== "\t") {
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
i++;
|
|
55
|
+
}
|
|
56
|
+
let sign = 1;
|
|
57
|
+
if (line[i] === "-") {
|
|
58
|
+
sign = -1;
|
|
59
|
+
i++;
|
|
60
|
+
}
|
|
61
|
+
let n = 0;
|
|
62
|
+
let ok = 0;
|
|
63
|
+
for (; i < lineLen; i++) {
|
|
64
|
+
const c = line.charCodeAt(i);
|
|
65
|
+
if (c >= 48 && c <= 57) {
|
|
66
|
+
n = n * 10 + (c - 48);
|
|
67
|
+
ok = 1;
|
|
68
|
+
} else {
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return ok ? sign * n : undefined;
|
|
73
|
+
}
|
|
74
|
+
const HIGHWATERMARK_MIN = 128;
|
|
75
|
+
/**
|
|
76
|
+
* @param {unknown} n
|
|
77
|
+
* @returns {n is number}
|
|
78
|
+
*/
|
|
79
|
+
const isNumber = (n) => Number.isFinite(n);
|
|
80
|
+
/**
|
|
81
|
+
* @param {string} fileName
|
|
82
|
+
* @param {(line: string, lineNo: number) => void} onLine
|
|
83
|
+
* @param {TJsonlReadBaseOption=} options
|
|
84
|
+
* @returns {Promise<void>}
|
|
85
|
+
*/
|
|
86
|
+
async function readJsonlLines(fileName, onLine, options = {}) {
|
|
87
|
+
const filePath = resolveJsonlPath(fileName, options);
|
|
88
|
+
/** @type {fs.ReadStreamOptions} */
|
|
89
|
+
const readOpt = {
|
|
90
|
+
encoding: options.encoding || "utf8",
|
|
91
|
+
};
|
|
92
|
+
if (isNumber(options.highWaterMark) && options.highWaterMark >= HIGHWATERMARK_MIN) {
|
|
93
|
+
readOpt.highWaterMark = options.highWaterMark | 0;
|
|
94
|
+
}
|
|
95
|
+
const skipEmptyLine = options.skipEmptyLine ?? true;
|
|
96
|
+
const stream = fs.createReadStream(filePath, readOpt);
|
|
97
|
+
let lineNo = 0;
|
|
98
|
+
let buf = "";
|
|
99
|
+
/** @param {string} rawLine */
|
|
100
|
+
const emitLine = (rawLine) => {
|
|
101
|
+
lineNo++;
|
|
102
|
+
if (rawLine[rawLine.length - 1] === "\r") {
|
|
103
|
+
rawLine = rawLine.slice(0, -1);
|
|
104
|
+
}
|
|
105
|
+
if (skipEmptyLine && !rawLine.length) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
onLine(rawLine, lineNo);
|
|
109
|
+
};
|
|
110
|
+
for await (const chunk of stream) {
|
|
111
|
+
buf += chunk;
|
|
112
|
+
let start = 0;
|
|
113
|
+
while (true) {
|
|
114
|
+
const nl = buf.indexOf("\n", start);
|
|
115
|
+
if (nl === -1) {
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
emitLine(buf.slice(start, nl));
|
|
119
|
+
start = nl + 1;
|
|
120
|
+
}
|
|
121
|
+
buf = buf.slice(start);
|
|
122
|
+
}
|
|
123
|
+
if (buf.length > 0) {
|
|
124
|
+
emitLine(buf);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* @template T
|
|
129
|
+
* @param {string} fileName
|
|
130
|
+
* @param {(row: T, lineNo: number) => void} onRow
|
|
131
|
+
* @param {TJsonlReadOption=} options
|
|
132
|
+
* @returns {Promise<void>}
|
|
133
|
+
*/
|
|
134
|
+
async function readJsonl(fileName, onRow, options = {}) {
|
|
135
|
+
const jsonParse = JSON.parse;
|
|
136
|
+
const onParseError = options.onParseError;
|
|
137
|
+
await readJsonlLines(
|
|
138
|
+
fileName,
|
|
139
|
+
(line, lineNo) => {
|
|
140
|
+
try {
|
|
141
|
+
const row = jsonParse(line);
|
|
142
|
+
onRow(row, lineNo);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (onParseError) {
|
|
145
|
+
onParseError(error, line, lineNo);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
console.error("jsonl parse error:", fileName, `line=${lineNo}`, error);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
options
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* @template T
|
|
156
|
+
* @param {string} fileName
|
|
157
|
+
* @param {TJsonlReadOption=} options
|
|
158
|
+
* @returns {Promise<T[]>}
|
|
159
|
+
*/
|
|
160
|
+
async function readJsonlArray(fileName, options = {}) {
|
|
161
|
+
/** @type {T[]} */
|
|
162
|
+
const out = [];
|
|
163
|
+
const filter = options.filter;
|
|
164
|
+
const mapper = options.map;
|
|
165
|
+
await readJsonl(fileName, (row) => {
|
|
166
|
+
if (filter && !filter(row)) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
out.push(mapper ? mapper(row) : row);
|
|
170
|
+
}, options);
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* @template T
|
|
175
|
+
* @param {string} fileName
|
|
176
|
+
* @param {TJsonlReadOption=} options
|
|
177
|
+
* @returns {Promise<Record<string, T>>}
|
|
178
|
+
*/
|
|
179
|
+
async function readJsonlMapByKey(fileName, options = {}) {
|
|
180
|
+
/** @type {Record<string, T>} */
|
|
181
|
+
const out = {};
|
|
182
|
+
const keyField = options.keyField || "_key";
|
|
183
|
+
const filter = options.filter;
|
|
184
|
+
const mapper = options.map;
|
|
185
|
+
await readJsonl(fileName, (row) => {
|
|
186
|
+
if (filter && !filter(row)) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const key = row[keyField];
|
|
190
|
+
if (key == null) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
out[String(key)] = mapper ? mapper(row) : row;
|
|
194
|
+
}, options);
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
module.exports = {
|
|
198
|
+
resolveJsonlPath,
|
|
199
|
+
readJsonlLines,
|
|
200
|
+
readJsonl,
|
|
201
|
+
readJsonlArray,
|
|
202
|
+
readJsonlMapByKey,
|
|
203
|
+
fastGetIntFieldCheap,
|
|
204
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "js-dev-tool",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"bin": {
|
|
5
5
|
"jstool": "tools.js"
|
|
6
6
|
},
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"!unzip.ts",
|
|
73
73
|
"!scripts/bin",
|
|
74
74
|
"!publish-version.json",
|
|
75
|
-
"!
|
|
75
|
+
"!scripts/*.mjs",
|
|
76
76
|
"!regex-test.ts",
|
|
77
77
|
"!extras/npm",
|
|
78
78
|
"!/**/*.png",
|
package/tools.js
CHANGED
|
@@ -141,7 +141,7 @@ const ToolFunctions = {
|
|
|
141
141
|
: [];
|
|
142
142
|
if (paths.length) {
|
|
143
143
|
utils.fireReplace(
|
|
144
|
-
/(v)?(\d+\.\d+\.\d+(?:[
|
|
144
|
+
/(v)?(\d+\.\d+\.\d+(?:[\w\-.+]+)?)/g,
|
|
145
145
|
/** @type {TStringReplacer} */($0, $prefix, $versionStr) => {
|
|
146
146
|
if ($versionStr === currentVersion) {
|
|
147
147
|
return ($prefix ? "v" : "") + nextVersion;
|