inibase 1.0.0-rc.25 → 1.0.0-rc.27
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 +59 -40
- package/dist/config.d.ts +4 -0
- package/dist/config.js +4 -0
- package/dist/file.d.ts +149 -13
- package/dist/file.js +574 -223
- package/dist/index.d.ts +11 -12
- package/dist/index.js +221 -136
- package/dist/utils.d.ts +155 -0
- package/dist/utils.js +155 -0
- package/dist/utils.server.d.ts +44 -0
- package/dist/utils.server.js +72 -0
- package/package.json +3 -5
package/dist/file.js
CHANGED
|
@@ -1,16 +1,51 @@
|
|
|
1
|
-
import { open,
|
|
1
|
+
import { open, access, writeFile, readFile, constants as fsConstants, } from "node:fs/promises";
|
|
2
2
|
import { createInterface } from "node:readline";
|
|
3
|
+
import { Transform } from "node:stream";
|
|
4
|
+
import { pipeline } from "node:stream/promises";
|
|
5
|
+
import { createGzip, createGunzip, gzip as gzipAsync, gunzip as gunzipAsync, } from "node:zlib";
|
|
6
|
+
import { promisify } from "node:util";
|
|
3
7
|
import { detectFieldType, isArrayOfArrays, isNumber, isObject, } from "./utils.js";
|
|
4
8
|
import { encodeID, comparePassword } from "./utils.server.js";
|
|
9
|
+
import Config from "./config.js";
|
|
10
|
+
const gzip = promisify(gzipAsync);
|
|
11
|
+
const gunzip = promisify(gunzipAsync);
|
|
12
|
+
export const write = async (filePath, data, disableCompression = false) => {
|
|
13
|
+
await writeFile(filePath, Config.isCompressionEnabled && !disableCompression ? await gzip(data) : data);
|
|
14
|
+
};
|
|
15
|
+
export const read = async (filePath, disableCompression = false) => {
|
|
16
|
+
return Config.isCompressionEnabled && !disableCompression
|
|
17
|
+
? (await gunzip(await readFile(filePath))).toString()
|
|
18
|
+
: (await readFile(filePath)).toString();
|
|
19
|
+
};
|
|
20
|
+
const _pipeline = async (rl, writeStream, transform) => {
|
|
21
|
+
if (Config.isCompressionEnabled)
|
|
22
|
+
await pipeline(rl, transform, createGzip(), writeStream);
|
|
23
|
+
else
|
|
24
|
+
await pipeline(rl, transform, writeStream);
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Creates a readline interface for a given file handle.
|
|
28
|
+
*
|
|
29
|
+
* @param fileHandle - The file handle from which to create a read stream.
|
|
30
|
+
* @returns A readline.Interface instance configured with the provided file stream.
|
|
31
|
+
*/
|
|
5
32
|
const readLineInternface = (fileHandle) => {
|
|
6
33
|
return createInterface({
|
|
7
|
-
input:
|
|
34
|
+
input: Config.isCompressionEnabled
|
|
35
|
+
? fileHandle.createReadStream().pipe(createGunzip())
|
|
36
|
+
: fileHandle.createReadStream(),
|
|
8
37
|
crlfDelay: Infinity,
|
|
9
38
|
});
|
|
10
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* Checks if a file or directory exists at the specified path.
|
|
42
|
+
*
|
|
43
|
+
* @param path - The path to the file or directory.
|
|
44
|
+
* @returns A Promise that resolves to true if the file/directory exists, false otherwise.
|
|
45
|
+
*/
|
|
11
46
|
export const isExists = async (path) => {
|
|
12
47
|
try {
|
|
13
|
-
await
|
|
48
|
+
await access(path, fsConstants.R_OK | fsConstants.W_OK);
|
|
14
49
|
return true;
|
|
15
50
|
}
|
|
16
51
|
catch {
|
|
@@ -18,28 +53,57 @@ export const isExists = async (path) => {
|
|
|
18
53
|
}
|
|
19
54
|
};
|
|
20
55
|
const delimiters = [",", "|", "&", "$", "#", "@", "^", ":", "!", ";"];
|
|
56
|
+
/**
|
|
57
|
+
* Secures input by encoding/escaping characters.
|
|
58
|
+
*
|
|
59
|
+
* @param input - String, number, boolean, or null.
|
|
60
|
+
* @returns Encoded string for true/false, special characters in strings, or original input.
|
|
61
|
+
*/
|
|
21
62
|
const secureString = (input) => {
|
|
22
63
|
if (["true", "false"].includes(String(input)))
|
|
23
64
|
return input ? 1 : 0;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
:
|
|
65
|
+
if (typeof input !== "string")
|
|
66
|
+
return input;
|
|
67
|
+
let decodedInput = null;
|
|
68
|
+
try {
|
|
69
|
+
decodedInput = decodeURIComponent(input);
|
|
70
|
+
}
|
|
71
|
+
catch (_error) {
|
|
72
|
+
decodedInput = decodeURIComponent(input.replace(/%(?![0-9][0-9a-fA-F]+)/g, ""));
|
|
73
|
+
}
|
|
74
|
+
const replacements = {
|
|
75
|
+
"<": "<",
|
|
76
|
+
">": ">",
|
|
77
|
+
",": "%2C",
|
|
78
|
+
"|": "%7C",
|
|
79
|
+
"&": "%26",
|
|
80
|
+
$: "%24",
|
|
81
|
+
"#": "%23",
|
|
82
|
+
"@": "%40",
|
|
83
|
+
"^": "%5E",
|
|
84
|
+
":": "%3A",
|
|
85
|
+
"!": "%21",
|
|
86
|
+
";": "%3B",
|
|
87
|
+
"\n": "\\n",
|
|
88
|
+
"\r": "\\r",
|
|
89
|
+
};
|
|
90
|
+
// Replace characters using a single regular expression.
|
|
91
|
+
return decodedInput.replace(/[<>,|&$#@^:!\n\r]/g, (match) => replacements[match]);
|
|
41
92
|
};
|
|
93
|
+
/**
|
|
94
|
+
* Secures each element in an array or a single value using secureString.
|
|
95
|
+
*
|
|
96
|
+
* @param arr_str - An array or a single value of any type.
|
|
97
|
+
* @returns An array with each element secured, or a single secured value.
|
|
98
|
+
*/
|
|
42
99
|
const secureArray = (arr_str) => Array.isArray(arr_str) ? arr_str.map(secureArray) : secureString(arr_str);
|
|
100
|
+
/**
|
|
101
|
+
* Joins elements of a multidimensional array into a string.
|
|
102
|
+
*
|
|
103
|
+
* @param arr - A multidimensional array or a single level array.
|
|
104
|
+
* @param delimiter_index - Index for selecting delimiter, defaults to 0.
|
|
105
|
+
* @returns Joined string of array elements with appropriate delimiters.
|
|
106
|
+
*/
|
|
43
107
|
const joinMultidimensionalArray = (arr, delimiter_index = 0) => {
|
|
44
108
|
delimiter_index++;
|
|
45
109
|
if (isArrayOfArrays(arr))
|
|
@@ -47,283 +111,539 @@ const joinMultidimensionalArray = (arr, delimiter_index = 0) => {
|
|
|
47
111
|
delimiter_index--;
|
|
48
112
|
return arr.join(delimiters[delimiter_index]);
|
|
49
113
|
};
|
|
114
|
+
/**
|
|
115
|
+
* Encodes the input using 'secureString' and 'joinMultidimensionalArray' functions.
|
|
116
|
+
* If the input is an array, it is first secured and then joined into a string.
|
|
117
|
+
* If the input is a single value, it is directly secured.
|
|
118
|
+
*
|
|
119
|
+
* @param input - A value or array of values (string, number, boolean, null).
|
|
120
|
+
* @param secretKey - Optional secret key for encoding, can be a string or Buffer.
|
|
121
|
+
* @returns The secured and/or joined string.
|
|
122
|
+
*/
|
|
50
123
|
export const encode = (input, secretKey) => {
|
|
124
|
+
// Use the optimized secureArray and joinMultidimensionalArray functions.
|
|
51
125
|
return Array.isArray(input)
|
|
52
126
|
? joinMultidimensionalArray(secureArray(input))
|
|
53
127
|
: secureString(input);
|
|
54
128
|
};
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
.replaceAll("%24", "$")
|
|
62
|
-
.replaceAll("%23", "#")
|
|
63
|
-
.replaceAll("%40", "@")
|
|
64
|
-
.replaceAll("%5E", "^")
|
|
65
|
-
.replaceAll("%3A", ":")
|
|
66
|
-
.replaceAll("%21", "!")
|
|
67
|
-
.replaceAll("%3B", ";")
|
|
68
|
-
.replaceAll("\\n", "\n")
|
|
69
|
-
.replaceAll("\\r", "\r") || null;
|
|
129
|
+
/**
|
|
130
|
+
* Decodes each element in an array or a single value using unSecureString.
|
|
131
|
+
*
|
|
132
|
+
* @param arr_str - An array or a single value of any type.
|
|
133
|
+
* @returns An array with each element decoded, or a single decoded value.
|
|
134
|
+
*/
|
|
70
135
|
const unSecureArray = (arr_str) => Array.isArray(arr_str) ? arr_str.map(unSecureArray) : unSecureString(arr_str);
|
|
136
|
+
/**
|
|
137
|
+
* Reverses the encoding done by 'secureString'. Replaces encoded characters with their original symbols.
|
|
138
|
+
*
|
|
139
|
+
* @param input - Encoded string.
|
|
140
|
+
* @returns Decoded string or null if input is empty.
|
|
141
|
+
*/
|
|
142
|
+
const unSecureString = (input) => {
|
|
143
|
+
// Define a mapping of encoded characters to their original symbols.
|
|
144
|
+
const replacements = {
|
|
145
|
+
"<": "<",
|
|
146
|
+
"%2C": ",",
|
|
147
|
+
"%7C": "|",
|
|
148
|
+
"%26": "&",
|
|
149
|
+
"%24": "$",
|
|
150
|
+
"%23": "#",
|
|
151
|
+
"%40": "@",
|
|
152
|
+
"%5E": "^",
|
|
153
|
+
"%3A": ":",
|
|
154
|
+
"%21": "!",
|
|
155
|
+
"%3B": ";",
|
|
156
|
+
"\\n": "\n",
|
|
157
|
+
"\\r": "\r",
|
|
158
|
+
};
|
|
159
|
+
// Replace encoded characters with their original symbols using the defined mapping.
|
|
160
|
+
return (input.replace(/%(2C|7C|26|24|23|40|5E|3A|21|3B|\\n|\\r)/g, (match) => replacements[match]) || null);
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Reverses the process of 'joinMultidimensionalArray', splitting a string back into a multidimensional array.
|
|
164
|
+
* It identifies delimiters used in the joined string and applies them recursively to reconstruct the original array structure.
|
|
165
|
+
*
|
|
166
|
+
* @param joinedString - A string, array, or multidimensional array.
|
|
167
|
+
* @returns Original array structure before joining, or the input if no delimiters are found.
|
|
168
|
+
*/
|
|
71
169
|
const reverseJoinMultidimensionalArray = (joinedString) => {
|
|
170
|
+
// Helper function for recursive array splitting based on delimiters.
|
|
72
171
|
const reverseJoinMultidimensionalArrayHelper = (arr, delimiter) => Array.isArray(arr)
|
|
73
172
|
? arr.map((ar) => reverseJoinMultidimensionalArrayHelper(ar, delimiter))
|
|
74
173
|
: arr.split(delimiter);
|
|
174
|
+
// Identify available delimiters in the input.
|
|
75
175
|
const availableDelimiters = delimiters.filter((delimiter) => joinedString.includes(delimiter));
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
? reverseJoinMultidimensionalArrayHelper(joinedString, delimiter)
|
|
79
|
-
: joinedString.split(delimiter);
|
|
80
|
-
}
|
|
81
|
-
return joinedString;
|
|
176
|
+
// Apply delimiters recursively to reconstruct the original array structure.
|
|
177
|
+
return availableDelimiters.reduce((acc, delimiter) => reverseJoinMultidimensionalArrayHelper(acc, delimiter), joinedString);
|
|
82
178
|
};
|
|
179
|
+
/**
|
|
180
|
+
* Decodes a value based on specified field types and optional secret key.
|
|
181
|
+
* Handles different data types and structures, including nested arrays.
|
|
182
|
+
*
|
|
183
|
+
* @param value - The value to be decoded, can be string, number, or array.
|
|
184
|
+
* @param fieldType - Optional type of the field to guide decoding (e.g., 'number', 'boolean').
|
|
185
|
+
* @param fieldChildrenType - Optional type for children elements, used for arrays.
|
|
186
|
+
* @param secretKey - Optional secret key for decoding, can be string or Buffer.
|
|
187
|
+
* @returns Decoded value, transformed according to the specified field type(s).
|
|
188
|
+
*/
|
|
83
189
|
const decodeHelper = (value, fieldType, fieldChildrenType, secretKey) => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
:
|
|
105
|
-
|
|
106
|
-
|
|
190
|
+
// Use a stack-based approach for efficient processing without recursion.
|
|
191
|
+
const stack = [{ value }];
|
|
192
|
+
while (stack.length > 0) {
|
|
193
|
+
// Explicitly check if stack.pop() is not undefined.
|
|
194
|
+
const stackItem = stack.pop();
|
|
195
|
+
if (!stackItem) {
|
|
196
|
+
// Skip the rest of the loop if the stack item is undefined.
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const { value } = stackItem;
|
|
200
|
+
if (Array.isArray(value) && fieldType !== "array") {
|
|
201
|
+
// If the value is an array and the fieldType is not 'array', process each element.
|
|
202
|
+
stack.push(...value.map((v) => ({ value: v })));
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
switch (fieldType) {
|
|
206
|
+
// Handle different field types with appropriate decoding logic.
|
|
207
|
+
case "table":
|
|
208
|
+
case "number":
|
|
209
|
+
return isNumber(value) ? Number(value) : null;
|
|
210
|
+
case "boolean":
|
|
211
|
+
return typeof value === "string" ? value === "true" : Boolean(value);
|
|
212
|
+
case "array":
|
|
213
|
+
if (!Array.isArray(value))
|
|
214
|
+
return [value];
|
|
215
|
+
if (fieldChildrenType)
|
|
216
|
+
// Decode each element in the array based on the specified fieldChildrenType.
|
|
217
|
+
return fieldChildrenType
|
|
218
|
+
? value.map((v) => decode(v, Array.isArray(fieldChildrenType)
|
|
219
|
+
? detectFieldType(v, fieldChildrenType)
|
|
220
|
+
: fieldChildrenType, undefined, secretKey))
|
|
221
|
+
: value;
|
|
222
|
+
case "id":
|
|
223
|
+
return isNumber(value) && secretKey
|
|
224
|
+
? encodeID(value, secretKey)
|
|
225
|
+
: value;
|
|
226
|
+
default:
|
|
227
|
+
return value;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
107
230
|
}
|
|
108
231
|
};
|
|
232
|
+
/**
|
|
233
|
+
* Decodes the input based on the specified field type(s) and an optional secret key.
|
|
234
|
+
* Handles different formats of input, including strings, numbers, and their array representations.
|
|
235
|
+
*
|
|
236
|
+
* @param input - The input to be decoded, can be a string, number, or null.
|
|
237
|
+
* @param fieldType - Optional type of the field to guide decoding (e.g., 'number', 'boolean').
|
|
238
|
+
* @param fieldChildrenType - Optional type for child elements in array inputs.
|
|
239
|
+
* @param secretKey - Optional secret key for decoding, can be a string or Buffer.
|
|
240
|
+
* @returns Decoded value as a string, number, boolean, or array of these, or null if no fieldType or input is null/empty.
|
|
241
|
+
*/
|
|
109
242
|
export const decode = (input, fieldType, fieldChildrenType, secretKey) => {
|
|
110
243
|
if (!fieldType)
|
|
111
244
|
return null;
|
|
112
245
|
if (input === null || input === "")
|
|
113
246
|
return null;
|
|
114
247
|
if (Array.isArray(fieldType))
|
|
248
|
+
// Detect the fieldType based on the input and the provided array of possible types.
|
|
115
249
|
fieldType = detectFieldType(String(input), fieldType);
|
|
250
|
+
// Decode the input using the decodeHelper function.
|
|
116
251
|
return decodeHelper(typeof input === "string"
|
|
117
252
|
? input.includes(",")
|
|
118
253
|
? unSecureArray(reverseJoinMultidimensionalArray(input))
|
|
119
254
|
: unSecureString(input)
|
|
120
255
|
: input, fieldType, fieldChildrenType, secretKey);
|
|
121
256
|
};
|
|
122
|
-
export
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
for await (const line of rl)
|
|
133
|
-
lineCount++, (lastLine = line);
|
|
134
|
-
if (lastLine)
|
|
135
|
-
lines.set(lineCount, decode(lastLine, fieldType, fieldChildrenType, secretKey));
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
let lineNumbersArray = new Set(Array.isArray(lineNumbers) ? lineNumbers : [lineNumbers]);
|
|
139
|
-
for await (const line of rl) {
|
|
140
|
-
lineCount++;
|
|
141
|
-
if (!lineNumbersArray.has(lineCount))
|
|
142
|
-
continue;
|
|
143
|
-
lines.set(lineCount, decode(line, fieldType, fieldChildrenType, secretKey));
|
|
144
|
-
lineNumbersArray.delete(lineCount);
|
|
145
|
-
if (!lineNumbersArray.size)
|
|
146
|
-
break;
|
|
257
|
+
export async function get(filePath, lineNumbers, fieldType, fieldChildrenType, secretKey, readWholeFile) {
|
|
258
|
+
let fileHandle, rl;
|
|
259
|
+
try {
|
|
260
|
+
fileHandle = await open(filePath, "r");
|
|
261
|
+
rl = readLineInternface(fileHandle);
|
|
262
|
+
let lines = {}, linesCount = 0;
|
|
263
|
+
if (!lineNumbers) {
|
|
264
|
+
for await (const line of rl)
|
|
265
|
+
linesCount++,
|
|
266
|
+
(lines[linesCount] = decode(line, fieldType, fieldChildrenType, secretKey));
|
|
147
267
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (!(replacements instanceof Map))
|
|
158
|
-
replacements = new Map(Object.entries(replacements));
|
|
268
|
+
else if (lineNumbers === -1) {
|
|
269
|
+
let lastLine = null;
|
|
270
|
+
for await (const line of rl)
|
|
271
|
+
linesCount++, (lastLine = line);
|
|
272
|
+
if (lastLine)
|
|
273
|
+
lines[linesCount] = decode(lastLine, fieldType, fieldChildrenType, secretKey);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
let lineNumbersArray = new Set(Array.isArray(lineNumbers) ? lineNumbers : [lineNumbers]);
|
|
159
277
|
for await (const line of rl) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (Math.min(...newLinesNumbers) - lineCount - 1 > 1)
|
|
168
|
-
writeStream.write("\n".repeat(Math.min(...newLinesNumbers) - lineCount - 1));
|
|
169
|
-
for await (const newLineNumber of newLinesNumbers)
|
|
170
|
-
writeStream.write(replacements.get(newLineNumber.toString()) + "\n");
|
|
278
|
+
linesCount++;
|
|
279
|
+
if (!lineNumbersArray.has(linesCount))
|
|
280
|
+
continue;
|
|
281
|
+
lines[linesCount] = decode(line, fieldType, fieldChildrenType, secretKey);
|
|
282
|
+
lineNumbersArray.delete(linesCount);
|
|
283
|
+
if (!lineNumbersArray.size && !readWholeFile)
|
|
284
|
+
break;
|
|
171
285
|
}
|
|
172
286
|
}
|
|
173
|
-
|
|
174
|
-
for await (const _line of rl)
|
|
175
|
-
writeStream.write(replacements + "\n");
|
|
176
|
-
await fileHandle.close();
|
|
177
|
-
await fileTempHandle.close();
|
|
178
|
-
await rename(fileTempPath, filePath);
|
|
287
|
+
return readWholeFile ? [lines, linesCount] : lines;
|
|
179
288
|
}
|
|
180
|
-
|
|
181
|
-
if
|
|
182
|
-
|
|
183
|
-
await
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
289
|
+
finally {
|
|
290
|
+
// Ensure that file handles are closed, even if an error occurred
|
|
291
|
+
rl?.close();
|
|
292
|
+
await fileHandle?.close();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Asynchronously replaces specific lines in a file based on the provided replacements map or string.
|
|
297
|
+
*
|
|
298
|
+
* @param filePath - Path of the file to modify.
|
|
299
|
+
* @param replacements - Map of line numbers to replacement values, or a single replacement value for all lines.
|
|
300
|
+
* Can be a string, number, boolean, null, array of these types, or a Record/Map of line numbers to these types.
|
|
301
|
+
* @returns Promise<string[]>
|
|
302
|
+
*
|
|
303
|
+
* Note: If the file doesn't exist and replacements is an object, it creates a new file with the specified replacements.
|
|
304
|
+
*/
|
|
305
|
+
export const replace = async (filePath, replacements) => {
|
|
306
|
+
let fileHandle, fileTempHandle, rl;
|
|
307
|
+
const fileTempPath = filePath.replace(/([^/]+)\/?$/, `.tmp/${Date.now()}-$1`);
|
|
308
|
+
try {
|
|
309
|
+
let linesCount = 0;
|
|
310
|
+
fileHandle = await open(filePath, "r");
|
|
311
|
+
fileTempHandle = await open(fileTempPath, "w");
|
|
312
|
+
rl = readLineInternface(fileHandle);
|
|
313
|
+
await _pipeline(rl, fileTempHandle.createWriteStream(), new Transform({
|
|
314
|
+
transform(line, encoding, callback) {
|
|
315
|
+
linesCount++;
|
|
316
|
+
const replacement = isObject(replacements)
|
|
317
|
+
? replacements.hasOwnProperty(linesCount)
|
|
318
|
+
? replacements[linesCount]
|
|
319
|
+
: line
|
|
320
|
+
: replacements;
|
|
321
|
+
callback(null, replacement + "\n");
|
|
322
|
+
},
|
|
323
|
+
}));
|
|
324
|
+
return [fileTempPath, filePath];
|
|
325
|
+
}
|
|
326
|
+
finally {
|
|
327
|
+
// Ensure that file handles are closed, even if an error occurred
|
|
328
|
+
rl?.close();
|
|
329
|
+
await fileHandle?.close();
|
|
330
|
+
await fileTempHandle?.close();
|
|
188
331
|
}
|
|
189
332
|
};
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
333
|
+
/**
|
|
334
|
+
* Asynchronously appends data to the beginning of a file.
|
|
335
|
+
*
|
|
336
|
+
* @param filePath - Path of the file to append to.
|
|
337
|
+
* @param data - Data to append. Can be a string, number, or an array of strings/numbers.
|
|
338
|
+
* @returns Promise<string[]>. Modifies the file by appending data.
|
|
339
|
+
*
|
|
340
|
+
*/
|
|
341
|
+
export const append = async (filePath, data) => {
|
|
342
|
+
const fileTempPath = filePath.replace(/([^/]+)\/?$/, `.tmp/${Date.now()}-$1`);
|
|
343
|
+
if (await isExists(filePath)) {
|
|
344
|
+
let fileHandle, fileTempHandle, rl;
|
|
345
|
+
try {
|
|
346
|
+
fileHandle = await open(filePath, "r");
|
|
347
|
+
fileTempHandle = await open(fileTempPath, "w");
|
|
348
|
+
rl = readLineInternface(fileHandle);
|
|
349
|
+
let isAppended = false;
|
|
350
|
+
await _pipeline(rl, fileTempHandle.createWriteStream(), new Transform({
|
|
351
|
+
transform(line, encoding, callback) {
|
|
352
|
+
if (!isAppended) {
|
|
353
|
+
isAppended = true;
|
|
354
|
+
callback(null, `${Array.isArray(data) ? data.join("\n") : data}\n${line}\n`);
|
|
355
|
+
}
|
|
356
|
+
else
|
|
357
|
+
callback(null, `${line}\n`);
|
|
358
|
+
},
|
|
359
|
+
}));
|
|
360
|
+
}
|
|
361
|
+
finally {
|
|
362
|
+
// Ensure that file handles are closed, even if an error occurred
|
|
363
|
+
rl?.close();
|
|
364
|
+
await fileHandle?.close();
|
|
365
|
+
await fileTempHandle?.close();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
else
|
|
369
|
+
await write(fileTempPath, `${Array.isArray(data) ? data.join("\n") : data}\n`);
|
|
370
|
+
return [fileTempPath, filePath];
|
|
202
371
|
};
|
|
372
|
+
/**
|
|
373
|
+
* Asynchronously removes specified lines from a file.
|
|
374
|
+
*
|
|
375
|
+
* @param filePath - Path of the file from which lines are to be removed.
|
|
376
|
+
* @param linesToDelete - A single line number or an array of line numbers to be deleted.
|
|
377
|
+
* @returns Promise<string[]>. Modifies the file by removing specified lines.
|
|
378
|
+
*
|
|
379
|
+
* Note: Creates a temporary file during the process and replaces the original file with it after removing lines.
|
|
380
|
+
*/
|
|
203
381
|
export const remove = async (filePath, linesToDelete) => {
|
|
204
|
-
let
|
|
205
|
-
const fileHandle = await open(filePath, "r"), fileTempPath =
|
|
382
|
+
let linesCount = 0;
|
|
383
|
+
const fileHandle = await open(filePath, "r"), fileTempPath = filePath.replace(/([^/]+)\/?$/, `.tmp/${Date.now()}-$1`), fileTempHandle = await open(fileTempPath, "w"), linesToDeleteArray = new Set(Array.isArray(linesToDelete)
|
|
206
384
|
? linesToDelete.map(Number)
|
|
207
|
-
: [Number(linesToDelete)]), rl = readLineInternface(fileHandle)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
385
|
+
: [Number(linesToDelete)]), rl = readLineInternface(fileHandle);
|
|
386
|
+
await _pipeline(rl, fileTempHandle.createWriteStream(), new Transform({
|
|
387
|
+
transform(line, encoding, callback) {
|
|
388
|
+
linesCount++;
|
|
389
|
+
if (!linesToDeleteArray.has(linesCount))
|
|
390
|
+
callback(null, `${line}\n`);
|
|
391
|
+
callback();
|
|
392
|
+
},
|
|
393
|
+
}));
|
|
214
394
|
await fileTempHandle.close();
|
|
215
395
|
await fileHandle.close();
|
|
396
|
+
return [fileTempPath, filePath];
|
|
216
397
|
};
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
398
|
+
/**
|
|
399
|
+
* Evaluates a comparison between two values based on a specified operator and field types.
|
|
400
|
+
*
|
|
401
|
+
* @param operator - The comparison operator (e.g., '=', '!=', '>', '<', '>=', '<=', '[]', '![]', '*', '!*').
|
|
402
|
+
* @param originalValue - The value to compare, can be a single value or an array of values.
|
|
403
|
+
* @param comparedAtValue - The value or values to compare against.
|
|
404
|
+
* @param fieldType - Optional type of the field to guide comparison (e.g., 'password', 'boolean').
|
|
405
|
+
* @param fieldChildrenType - Optional type for child elements in array inputs.
|
|
406
|
+
* @returns boolean - Result of the comparison operation.
|
|
407
|
+
*
|
|
408
|
+
* Note: Handles various data types and comparison logic, including special handling for passwords and regex patterns.
|
|
409
|
+
*/
|
|
226
410
|
const handleComparisonOperator = (operator, originalValue, comparedAtValue, fieldType, fieldChildrenType) => {
|
|
227
|
-
if
|
|
411
|
+
// Determine the field type if it's an array of potential types.
|
|
412
|
+
if (Array.isArray(fieldType)) {
|
|
228
413
|
fieldType = detectFieldType(String(originalValue), fieldType);
|
|
229
|
-
|
|
414
|
+
}
|
|
415
|
+
// Handle comparisons involving arrays.
|
|
416
|
+
if (Array.isArray(comparedAtValue) && !["[]", "![]"].includes(operator)) {
|
|
230
417
|
return comparedAtValue.some((comparedAtValueSingle) => handleComparisonOperator(operator, originalValue, comparedAtValueSingle, fieldType));
|
|
231
|
-
|
|
418
|
+
}
|
|
419
|
+
// Switch statement for different comparison operators.
|
|
232
420
|
switch (operator) {
|
|
421
|
+
// Equal (Case Insensitive for strings, specific handling for passwords and booleans).
|
|
233
422
|
case "=":
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return typeof originalValue === "string" &&
|
|
237
|
-
typeof comparedAtValue === "string"
|
|
238
|
-
? comparePassword(originalValue, comparedAtValue)
|
|
239
|
-
: false;
|
|
240
|
-
case "boolean":
|
|
241
|
-
return Number(originalValue) - Number(comparedAtValue) === 0;
|
|
242
|
-
default:
|
|
243
|
-
return originalValue === comparedAtValue;
|
|
244
|
-
}
|
|
423
|
+
return isEqual(originalValue, comparedAtValue, fieldType);
|
|
424
|
+
// Not Equal.
|
|
245
425
|
case "!=":
|
|
246
|
-
return !
|
|
426
|
+
return !isEqual(originalValue, comparedAtValue, fieldType);
|
|
427
|
+
// Greater Than.
|
|
247
428
|
case ">":
|
|
248
429
|
return (originalValue !== null &&
|
|
249
430
|
comparedAtValue !== null &&
|
|
250
431
|
originalValue > comparedAtValue);
|
|
432
|
+
// Less Than.
|
|
251
433
|
case "<":
|
|
252
434
|
return (originalValue !== null &&
|
|
253
435
|
comparedAtValue !== null &&
|
|
254
436
|
originalValue < comparedAtValue);
|
|
437
|
+
// Greater Than or Equal.
|
|
255
438
|
case ">=":
|
|
256
439
|
return (originalValue !== null &&
|
|
257
440
|
comparedAtValue !== null &&
|
|
258
441
|
originalValue >= comparedAtValue);
|
|
442
|
+
// Less Than or Equal.
|
|
259
443
|
case "<=":
|
|
260
444
|
return (originalValue !== null &&
|
|
261
445
|
comparedAtValue !== null &&
|
|
262
446
|
originalValue <= comparedAtValue);
|
|
447
|
+
// Array Contains (equality check for arrays).
|
|
263
448
|
case "[]":
|
|
264
|
-
return (
|
|
265
|
-
|
|
266
|
-
originalValue.some(comparedAtValue.includes)) ||
|
|
267
|
-
(Array.isArray(originalValue) &&
|
|
268
|
-
!Array.isArray(comparedAtValue) &&
|
|
269
|
-
originalValue.includes(comparedAtValue)) ||
|
|
270
|
-
(!Array.isArray(originalValue) &&
|
|
271
|
-
Array.isArray(comparedAtValue) &&
|
|
272
|
-
comparedAtValue.includes(originalValue)));
|
|
449
|
+
return isArrayEqual(originalValue, comparedAtValue);
|
|
450
|
+
// Array Does Not Contain.
|
|
273
451
|
case "![]":
|
|
274
|
-
return !
|
|
452
|
+
return !isArrayEqual(originalValue, comparedAtValue);
|
|
453
|
+
// Wildcard Match (using regex pattern).
|
|
275
454
|
case "*":
|
|
276
|
-
return
|
|
277
|
-
|
|
278
|
-
: "%" + String(comparedAtValue) + "%").replace(/%/g, ".*")}$`, "i").test(String(originalValue));
|
|
455
|
+
return isWildcardMatch(originalValue, comparedAtValue);
|
|
456
|
+
// Not Wildcard Match.
|
|
279
457
|
case "!*":
|
|
280
|
-
return !
|
|
458
|
+
return !isWildcardMatch(originalValue, comparedAtValue);
|
|
459
|
+
// Unsupported operator.
|
|
281
460
|
default:
|
|
282
|
-
throw new Error(operator);
|
|
461
|
+
throw new Error(`Unsupported operator: ${operator}`);
|
|
283
462
|
}
|
|
284
463
|
};
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
464
|
+
/**
|
|
465
|
+
* Helper function to check equality based on the field type.
|
|
466
|
+
*
|
|
467
|
+
* @param originalValue - The original value.
|
|
468
|
+
* @param comparedAtValue - The value to compare against.
|
|
469
|
+
* @param fieldType - Type of the field.
|
|
470
|
+
* @returns boolean - Result of the equality check.
|
|
471
|
+
*/
|
|
472
|
+
const isEqual = (originalValue, comparedAtValue, fieldType) => {
|
|
473
|
+
// Switch based on the field type for specific handling.
|
|
474
|
+
switch (fieldType) {
|
|
475
|
+
// Password comparison.
|
|
476
|
+
case "password":
|
|
477
|
+
return typeof originalValue === "string" &&
|
|
478
|
+
typeof comparedAtValue === "string"
|
|
479
|
+
? comparePassword(originalValue, comparedAtValue)
|
|
480
|
+
: false;
|
|
481
|
+
// Boolean comparison.
|
|
482
|
+
case "boolean":
|
|
483
|
+
return Number(originalValue) === Number(comparedAtValue);
|
|
484
|
+
// Default comparison.
|
|
485
|
+
default:
|
|
486
|
+
return originalValue === comparedAtValue;
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
/**
|
|
490
|
+
* Helper function to check array equality.
|
|
491
|
+
*
|
|
492
|
+
* @param originalValue - The original value.
|
|
493
|
+
* @param comparedAtValue - The value to compare against.
|
|
494
|
+
* @returns boolean - Result of the array equality check.
|
|
495
|
+
*/
|
|
496
|
+
const isArrayEqual = (originalValue, comparedAtValue) => {
|
|
497
|
+
return ((Array.isArray(originalValue) &&
|
|
498
|
+
Array.isArray(comparedAtValue) &&
|
|
499
|
+
originalValue.some((v) => comparedAtValue.includes(v))) ||
|
|
500
|
+
(Array.isArray(originalValue) &&
|
|
501
|
+
!Array.isArray(comparedAtValue) &&
|
|
502
|
+
originalValue.includes(comparedAtValue)) ||
|
|
503
|
+
(!Array.isArray(originalValue) &&
|
|
292
504
|
Array.isArray(comparedAtValue) &&
|
|
293
|
-
(
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
505
|
+
comparedAtValue.includes(originalValue)) ||
|
|
506
|
+
(!Array.isArray(originalValue) &&
|
|
507
|
+
!Array.isArray(comparedAtValue) &&
|
|
508
|
+
comparedAtValue === originalValue));
|
|
509
|
+
};
|
|
510
|
+
/**
|
|
511
|
+
* Helper function to check wildcard pattern matching using regex.
|
|
512
|
+
*
|
|
513
|
+
* @param originalValue - The original value.
|
|
514
|
+
* @param comparedAtValue - The value with wildcard pattern.
|
|
515
|
+
* @returns boolean - Result of the wildcard pattern matching.
|
|
516
|
+
*/
|
|
517
|
+
const isWildcardMatch = (originalValue, comparedAtValue) => {
|
|
518
|
+
const wildcardPattern = `^${(String(comparedAtValue).includes("%")
|
|
519
|
+
? String(comparedAtValue)
|
|
520
|
+
: "%" + String(comparedAtValue) + "%").replace(/%/g, ".*")}$`;
|
|
521
|
+
return new RegExp(wildcardPattern, "i").test(String(originalValue));
|
|
522
|
+
};
|
|
523
|
+
/**
|
|
524
|
+
* Asynchronously searches a file for lines matching specified criteria, using comparison and logical operators.
|
|
525
|
+
*
|
|
526
|
+
* @param filePath - Path of the file to search.
|
|
527
|
+
* @param operator - Comparison operator(s) for evaluation (e.g., '=', '!=', '>', '<').
|
|
528
|
+
* @param comparedAtValue - Value(s) to compare each line against.
|
|
529
|
+
* @param logicalOperator - Optional logical operator ('and' or 'or') for combining multiple comparisons.
|
|
530
|
+
* @param fieldType - Optional type of the field to guide comparison.
|
|
531
|
+
* @param fieldChildrenType - Optional type for child elements in array inputs.
|
|
532
|
+
* @param limit - Optional limit on the number of results to return.
|
|
533
|
+
* @param offset - Optional offset to start returning results from.
|
|
534
|
+
* @param readWholeFile - Flag to indicate whether to continue reading the file after reaching the limit.
|
|
535
|
+
* @param secretKey - Optional secret key for decoding, can be a string or Buffer.
|
|
536
|
+
* @returns Promise resolving to a tuple:
|
|
537
|
+
* 1. Record of line numbers and their content that match the criteria or null if none.
|
|
538
|
+
* 2. The count of found items or processed items based on the 'readWholeFile' flag.
|
|
539
|
+
*
|
|
540
|
+
* Note: Decodes each line for comparison and can handle complex queries with multiple conditions.
|
|
541
|
+
*/
|
|
542
|
+
export const search = async (filePath, operator, comparedAtValue, logicalOperator, fieldType, fieldChildrenType, limit, offset, readWholeFile, secretKey) => {
|
|
543
|
+
// Initialize a Map to store the matching lines with their line numbers.
|
|
544
|
+
const matchingLines = {};
|
|
545
|
+
// Initialize counters for line number, found items, and processed items.
|
|
546
|
+
let linesCount = 0, foundItems = 0, linesNumbers = new Set();
|
|
547
|
+
let fileHandle, rl;
|
|
548
|
+
try {
|
|
549
|
+
// Open the file for reading.
|
|
550
|
+
fileHandle = await open(filePath, "r");
|
|
551
|
+
// Create a Readline interface to read the file line by line.
|
|
552
|
+
rl = readLineInternface(fileHandle);
|
|
553
|
+
// Iterate through each line in the file.
|
|
554
|
+
for await (const line of rl) {
|
|
555
|
+
// Increment the line count for each line.
|
|
556
|
+
linesCount++;
|
|
557
|
+
// Decode the line for comparison.
|
|
558
|
+
const decodedLine = decode(line, fieldType, fieldChildrenType, secretKey);
|
|
559
|
+
// Check if the line meets the specified conditions based on comparison and logical operators.
|
|
560
|
+
const meetsConditions = (Array.isArray(operator) &&
|
|
561
|
+
Array.isArray(comparedAtValue) &&
|
|
562
|
+
((logicalOperator === "or" &&
|
|
563
|
+
operator.some((single_operator, index) => handleComparisonOperator(single_operator, decodedLine, comparedAtValue[index], fieldType))) ||
|
|
564
|
+
operator.every((single_operator, index) => handleComparisonOperator(single_operator, decodedLine, comparedAtValue[index], fieldType)))) ||
|
|
565
|
+
(!Array.isArray(operator) &&
|
|
566
|
+
handleComparisonOperator(operator, decodedLine, comparedAtValue, fieldType));
|
|
567
|
+
// If the line meets the conditions, process it.
|
|
568
|
+
if (meetsConditions) {
|
|
569
|
+
// Increment the found items counter.
|
|
570
|
+
foundItems++;
|
|
571
|
+
linesNumbers.add(linesCount);
|
|
572
|
+
// Check if the line should be skipped based on the offset.
|
|
573
|
+
if (offset && foundItems < offset)
|
|
304
574
|
continue;
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
575
|
+
// Check if the limit has been reached.
|
|
576
|
+
if (limit && foundItems > limit)
|
|
577
|
+
if (readWholeFile)
|
|
578
|
+
continue;
|
|
579
|
+
else
|
|
580
|
+
break;
|
|
581
|
+
// Store the decoded line in the result object.
|
|
582
|
+
matchingLines[linesCount] = decodedLine;
|
|
583
|
+
}
|
|
308
584
|
}
|
|
585
|
+
// Convert the Map to an object using Object.fromEntries and return the result.
|
|
586
|
+
return foundItems
|
|
587
|
+
? [
|
|
588
|
+
matchingLines,
|
|
589
|
+
readWholeFile ? foundItems : foundItems - 1,
|
|
590
|
+
linesNumbers.size ? linesNumbers : null,
|
|
591
|
+
]
|
|
592
|
+
: [null, 0, null];
|
|
593
|
+
}
|
|
594
|
+
finally {
|
|
595
|
+
// Close the file handle in the finally block to ensure it is closed even if an error occurs.
|
|
596
|
+
rl?.close();
|
|
597
|
+
await fileHandle?.close();
|
|
309
598
|
}
|
|
310
|
-
await fileHandle.close();
|
|
311
|
-
return foundItems
|
|
312
|
-
? [Object.fromEntries(RETURN), readWholeFile ? foundItems : foundItems - 1]
|
|
313
|
-
: [null, 0];
|
|
314
599
|
};
|
|
600
|
+
/**
|
|
601
|
+
* Asynchronously counts the number of lines in a file.
|
|
602
|
+
*
|
|
603
|
+
* @param filePath - Path of the file to count lines in.
|
|
604
|
+
* @returns Promise<number>. The number of lines in the file.
|
|
605
|
+
*
|
|
606
|
+
* Note: Reads through the file line by line to count the total number of lines.
|
|
607
|
+
*/
|
|
608
|
+
export const count = async (filePath) => {
|
|
609
|
+
// return Number((await exec(`wc -l < ${filePath}`)).stdout.trim());
|
|
610
|
+
let linesCount = 0;
|
|
611
|
+
if (await isExists(filePath)) {
|
|
612
|
+
let fileHandle, rl;
|
|
613
|
+
try {
|
|
614
|
+
(fileHandle = await open(filePath, "r")),
|
|
615
|
+
(rl = readLineInternface(fileHandle));
|
|
616
|
+
for await (const line of rl)
|
|
617
|
+
linesCount++;
|
|
618
|
+
}
|
|
619
|
+
finally {
|
|
620
|
+
rl?.close();
|
|
621
|
+
await fileHandle?.close();
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return linesCount;
|
|
625
|
+
};
|
|
626
|
+
/**
|
|
627
|
+
* Asynchronously calculates the sum of numerical values from specified lines in a file.
|
|
628
|
+
*
|
|
629
|
+
* @param filePath - Path of the file to read.
|
|
630
|
+
* @param lineNumbers - Optional specific line number(s) to include in the sum. If not provided, sums all lines.
|
|
631
|
+
* @returns Promise<number>. The sum of numerical values from the specified lines.
|
|
632
|
+
*
|
|
633
|
+
* Note: Decodes each line as a number using the 'decode' function. Non-numeric lines contribute 0 to the sum.
|
|
634
|
+
*/
|
|
315
635
|
export const sum = async (filePath, lineNumbers) => {
|
|
316
636
|
let sum = 0;
|
|
317
637
|
const fileHandle = await open(filePath, "r"), rl = readLineInternface(fileHandle);
|
|
318
638
|
if (lineNumbers) {
|
|
319
|
-
let
|
|
639
|
+
let linesCount = 0;
|
|
320
640
|
let lineNumbersArray = new Set(Array.isArray(lineNumbers) ? lineNumbers : [lineNumbers]);
|
|
321
641
|
for await (const line of rl) {
|
|
322
|
-
|
|
323
|
-
if (!lineNumbersArray.has(
|
|
642
|
+
linesCount++;
|
|
643
|
+
if (!lineNumbersArray.has(linesCount))
|
|
324
644
|
continue;
|
|
325
645
|
sum += +(decode(line, "number") ?? 0);
|
|
326
|
-
lineNumbersArray.delete(
|
|
646
|
+
lineNumbersArray.delete(linesCount);
|
|
327
647
|
if (!lineNumbersArray.size)
|
|
328
648
|
break;
|
|
329
649
|
}
|
|
@@ -334,20 +654,29 @@ export const sum = async (filePath, lineNumbers) => {
|
|
|
334
654
|
await fileHandle.close();
|
|
335
655
|
return sum;
|
|
336
656
|
};
|
|
657
|
+
/**
|
|
658
|
+
* Asynchronously finds the maximum numerical value from specified lines in a file.
|
|
659
|
+
*
|
|
660
|
+
* @param filePath - Path of the file to read.
|
|
661
|
+
* @param lineNumbers - Optional specific line number(s) to consider for finding the maximum value. If not provided, considers all lines.
|
|
662
|
+
* @returns Promise<number>. The maximum numerical value found in the specified lines.
|
|
663
|
+
*
|
|
664
|
+
* Note: Decodes each line as a number using the 'decode' function. Considers only numerical values for determining the maximum.
|
|
665
|
+
*/
|
|
337
666
|
export const max = async (filePath, lineNumbers) => {
|
|
338
667
|
let max = 0;
|
|
339
668
|
const fileHandle = await open(filePath, "r"), rl = readLineInternface(fileHandle);
|
|
340
669
|
if (lineNumbers) {
|
|
341
|
-
let
|
|
670
|
+
let linesCount = 0;
|
|
342
671
|
let lineNumbersArray = new Set(Array.isArray(lineNumbers) ? lineNumbers : [lineNumbers]);
|
|
343
672
|
for await (const line of rl) {
|
|
344
|
-
|
|
345
|
-
if (!lineNumbersArray.has(
|
|
673
|
+
linesCount++;
|
|
674
|
+
if (!lineNumbersArray.has(linesCount))
|
|
346
675
|
continue;
|
|
347
676
|
const lineContentNum = +(decode(line, "number") ?? 0);
|
|
348
677
|
if (lineContentNum > max)
|
|
349
678
|
max = lineContentNum;
|
|
350
|
-
lineNumbersArray.delete(
|
|
679
|
+
lineNumbersArray.delete(linesCount);
|
|
351
680
|
if (!lineNumbersArray.size)
|
|
352
681
|
break;
|
|
353
682
|
}
|
|
@@ -361,20 +690,29 @@ export const max = async (filePath, lineNumbers) => {
|
|
|
361
690
|
await fileHandle.close();
|
|
362
691
|
return max;
|
|
363
692
|
};
|
|
693
|
+
/**
|
|
694
|
+
* Asynchronously finds the minimum numerical value from specified lines in a file.
|
|
695
|
+
*
|
|
696
|
+
* @param filePath - Path of the file to read.
|
|
697
|
+
* @param lineNumbers - Optional specific line number(s) to consider for finding the minimum value. If not provided, considers all lines.
|
|
698
|
+
* @returns Promise<number>. The minimum numerical value found in the specified lines.
|
|
699
|
+
*
|
|
700
|
+
* Note: Decodes each line as a number using the 'decode' function. Considers only numerical values for determining the minimum.
|
|
701
|
+
*/
|
|
364
702
|
export const min = async (filePath, lineNumbers) => {
|
|
365
703
|
let min = 0;
|
|
366
704
|
const fileHandle = await open(filePath, "r"), rl = readLineInternface(fileHandle);
|
|
367
705
|
if (lineNumbers) {
|
|
368
|
-
let
|
|
706
|
+
let linesCount = 0;
|
|
369
707
|
let lineNumbersArray = new Set(Array.isArray(lineNumbers) ? lineNumbers : [lineNumbers]);
|
|
370
708
|
for await (const line of rl) {
|
|
371
|
-
|
|
372
|
-
if (!lineNumbersArray.has(
|
|
709
|
+
linesCount++;
|
|
710
|
+
if (!lineNumbersArray.has(linesCount))
|
|
373
711
|
continue;
|
|
374
712
|
const lineContentNum = +(decode(line, "number") ?? 0);
|
|
375
713
|
if (lineContentNum < min)
|
|
376
714
|
min = lineContentNum;
|
|
377
|
-
lineNumbersArray.delete(
|
|
715
|
+
lineNumbersArray.delete(linesCount);
|
|
378
716
|
if (!lineNumbersArray.size)
|
|
379
717
|
break;
|
|
380
718
|
}
|
|
@@ -388,13 +726,23 @@ export const min = async (filePath, lineNumbers) => {
|
|
|
388
726
|
await fileHandle.close();
|
|
389
727
|
return min;
|
|
390
728
|
};
|
|
729
|
+
/**
|
|
730
|
+
* Asynchronously sorts the lines in a file in the specified direction.
|
|
731
|
+
*
|
|
732
|
+
* @param filePath - Path of the file to be sorted.
|
|
733
|
+
* @param sortDirection - Direction for sorting: 1 or 'asc' for ascending, -1 or 'desc' for descending.
|
|
734
|
+
* @param lineNumbers - Optional specific line numbers to sort. If not provided, sorts all lines.
|
|
735
|
+
* @param _lineNumbersPerChunk - Optional parameter for handling large files, specifying the number of lines per chunk.
|
|
736
|
+
* @returns Promise<void>. Modifies the file by sorting specified lines.
|
|
737
|
+
*
|
|
738
|
+
* Note: The sorting is applied either to the entire file or to the specified lines. Large files are handled in chunks.
|
|
739
|
+
*/
|
|
391
740
|
export const sort = async (filePath, sortDirection, lineNumbers, _lineNumbersPerChunk = 100000) => { };
|
|
392
741
|
export default class File {
|
|
393
742
|
static get = get;
|
|
394
743
|
static remove = remove;
|
|
395
744
|
static search = search;
|
|
396
745
|
static replace = replace;
|
|
397
|
-
static count = count;
|
|
398
746
|
static encode = encode;
|
|
399
747
|
static decode = decode;
|
|
400
748
|
static isExists = isExists;
|
|
@@ -402,4 +750,7 @@ export default class File {
|
|
|
402
750
|
static min = min;
|
|
403
751
|
static max = max;
|
|
404
752
|
static append = append;
|
|
753
|
+
static count = count;
|
|
754
|
+
static write = write;
|
|
755
|
+
static read = read;
|
|
405
756
|
}
|