inibase 1.0.0-rc.26 → 1.0.0-rc.28
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 +22 -16
- package/dist/file.js +376 -242
- package/dist/index.d.ts +15 -14
- package/dist/index.js +214 -128
- package/dist/utils.server.d.ts +46 -2
- package/dist/utils.server.js +30 -0
- package/package.json +3 -5
package/dist/file.js
CHANGED
|
@@ -1,9 +1,28 @@
|
|
|
1
|
-
import { open, access,
|
|
1
|
+
import { open, access, writeFile, readFile, constants as fsConstants, } from "node:fs/promises";
|
|
2
2
|
import { createInterface } from "node:readline";
|
|
3
3
|
import { Transform } from "node:stream";
|
|
4
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";
|
|
5
7
|
import { detectFieldType, isArrayOfArrays, isNumber, isObject, } from "./utils.js";
|
|
6
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
|
+
};
|
|
7
26
|
/**
|
|
8
27
|
* Creates a readline interface for a given file handle.
|
|
9
28
|
*
|
|
@@ -11,13 +30,12 @@ import { encodeID, comparePassword } from "./utils.server.js";
|
|
|
11
30
|
* @returns A readline.Interface instance configured with the provided file stream.
|
|
12
31
|
*/
|
|
13
32
|
const readLineInternface = (fileHandle) => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
});
|
|
33
|
+
return createInterface({
|
|
34
|
+
input: Config.isCompressionEnabled
|
|
35
|
+
? fileHandle.createReadStream().pipe(createGunzip())
|
|
36
|
+
: fileHandle.createReadStream(),
|
|
37
|
+
crlfDelay: Infinity,
|
|
38
|
+
});
|
|
21
39
|
};
|
|
22
40
|
/**
|
|
23
41
|
* Checks if a file or directory exists at the specified path.
|
|
@@ -27,7 +45,7 @@ const readLineInternface = (fileHandle) => {
|
|
|
27
45
|
*/
|
|
28
46
|
export const isExists = async (path) => {
|
|
29
47
|
try {
|
|
30
|
-
await access(path,
|
|
48
|
+
await access(path, fsConstants.R_OK | fsConstants.W_OK);
|
|
31
49
|
return true;
|
|
32
50
|
}
|
|
33
51
|
catch {
|
|
@@ -44,23 +62,33 @@ const delimiters = [",", "|", "&", "$", "#", "@", "^", ":", "!", ";"];
|
|
|
44
62
|
const secureString = (input) => {
|
|
45
63
|
if (["true", "false"].includes(String(input)))
|
|
46
64
|
return input ? 1 : 0;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
:
|
|
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]);
|
|
64
92
|
};
|
|
65
93
|
/**
|
|
66
94
|
* Secures each element in an array or a single value using secureString.
|
|
@@ -93,31 +121,11 @@ const joinMultidimensionalArray = (arr, delimiter_index = 0) => {
|
|
|
93
121
|
* @returns The secured and/or joined string.
|
|
94
122
|
*/
|
|
95
123
|
export const encode = (input, secretKey) => {
|
|
124
|
+
// Use the optimized secureArray and joinMultidimensionalArray functions.
|
|
96
125
|
return Array.isArray(input)
|
|
97
126
|
? joinMultidimensionalArray(secureArray(input))
|
|
98
127
|
: secureString(input);
|
|
99
128
|
};
|
|
100
|
-
/**
|
|
101
|
-
* Reverses the encoding done by 'secureString'. Replaces encoded characters with their original symbols.
|
|
102
|
-
*
|
|
103
|
-
* @param input - Encoded string.
|
|
104
|
-
* @returns Decoded string or null if input is empty.
|
|
105
|
-
*/
|
|
106
|
-
const unSecureString = (input) => input
|
|
107
|
-
.replaceAll("<", "<")
|
|
108
|
-
.replaceAll(">", ">")
|
|
109
|
-
.replaceAll("%2C", ",")
|
|
110
|
-
.replaceAll("%7C", "|")
|
|
111
|
-
.replaceAll("%26", "&")
|
|
112
|
-
.replaceAll("%24", "$")
|
|
113
|
-
.replaceAll("%23", "#")
|
|
114
|
-
.replaceAll("%40", "@")
|
|
115
|
-
.replaceAll("%5E", "^")
|
|
116
|
-
.replaceAll("%3A", ":")
|
|
117
|
-
.replaceAll("%21", "!")
|
|
118
|
-
.replaceAll("%3B", ";")
|
|
119
|
-
.replaceAll("\\n", "\n")
|
|
120
|
-
.replaceAll("\\r", "\r") || null;
|
|
121
129
|
/**
|
|
122
130
|
* Decodes each element in an array or a single value using unSecureString.
|
|
123
131
|
*
|
|
@@ -125,6 +133,32 @@ const unSecureString = (input) => input
|
|
|
125
133
|
* @returns An array with each element decoded, or a single decoded value.
|
|
126
134
|
*/
|
|
127
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
|
+
};
|
|
128
162
|
/**
|
|
129
163
|
* Reverses the process of 'joinMultidimensionalArray', splitting a string back into a multidimensional array.
|
|
130
164
|
* It identifies delimiters used in the joined string and applies them recursively to reconstruct the original array structure.
|
|
@@ -133,16 +167,14 @@ const unSecureArray = (arr_str) => Array.isArray(arr_str) ? arr_str.map(unSecure
|
|
|
133
167
|
* @returns Original array structure before joining, or the input if no delimiters are found.
|
|
134
168
|
*/
|
|
135
169
|
const reverseJoinMultidimensionalArray = (joinedString) => {
|
|
170
|
+
// Helper function for recursive array splitting based on delimiters.
|
|
136
171
|
const reverseJoinMultidimensionalArrayHelper = (arr, delimiter) => Array.isArray(arr)
|
|
137
172
|
? arr.map((ar) => reverseJoinMultidimensionalArrayHelper(ar, delimiter))
|
|
138
173
|
: arr.split(delimiter);
|
|
174
|
+
// Identify available delimiters in the input.
|
|
139
175
|
const availableDelimiters = delimiters.filter((delimiter) => joinedString.includes(delimiter));
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
? reverseJoinMultidimensionalArrayHelper(joinedString, delimiter)
|
|
143
|
-
: joinedString.split(delimiter);
|
|
144
|
-
}
|
|
145
|
-
return joinedString;
|
|
176
|
+
// Apply delimiters recursively to reconstruct the original array structure.
|
|
177
|
+
return availableDelimiters.reduce((acc, delimiter) => reverseJoinMultidimensionalArrayHelper(acc, delimiter), joinedString);
|
|
146
178
|
};
|
|
147
179
|
/**
|
|
148
180
|
* Decodes a value based on specified field types and optional secret key.
|
|
@@ -196,56 +228,53 @@ export const decode = (input, fieldType, fieldChildrenType, secretKey) => {
|
|
|
196
228
|
if (input === null || input === "")
|
|
197
229
|
return null;
|
|
198
230
|
if (Array.isArray(fieldType))
|
|
231
|
+
// Detect the fieldType based on the input and the provided array of possible types.
|
|
199
232
|
fieldType = detectFieldType(String(input), fieldType);
|
|
233
|
+
// Decode the input using the decodeHelper function.
|
|
200
234
|
return decodeHelper(typeof input === "string"
|
|
201
235
|
? input.includes(",")
|
|
202
236
|
? unSecureArray(reverseJoinMultidimensionalArray(input))
|
|
203
237
|
: unSecureString(input)
|
|
204
238
|
: input, fieldType, fieldChildrenType, secretKey);
|
|
205
239
|
};
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
* 1. Record of line numbers and their decoded content or null if no lines are read.
|
|
217
|
-
* 2. Total count of lines processed.
|
|
218
|
-
*/
|
|
219
|
-
export const get = async (filePath, lineNumbers, fieldType, fieldChildrenType, secretKey) => {
|
|
220
|
-
const fileHandle = await open(filePath, "r"), rl = readLineInternface(fileHandle);
|
|
221
|
-
let lines = new Map(), lineCount = 0;
|
|
222
|
-
if (!lineNumbers) {
|
|
223
|
-
for await (const line of rl)
|
|
224
|
-
lineCount++,
|
|
225
|
-
lines.set(lineCount, decode(line, fieldType, fieldChildrenType, secretKey));
|
|
226
|
-
}
|
|
227
|
-
else if (lineNumbers === -1) {
|
|
228
|
-
let lastLine = null;
|
|
229
|
-
for await (const line of rl)
|
|
230
|
-
lineCount++, (lastLine = line);
|
|
231
|
-
if (lastLine)
|
|
232
|
-
lines.set(lineCount, decode(lastLine, fieldType, fieldChildrenType, secretKey));
|
|
233
|
-
}
|
|
234
|
-
else {
|
|
235
|
-
let lineNumbersArray = new Set(Array.isArray(lineNumbers) ? lineNumbers : [lineNumbers]);
|
|
236
|
-
for await (const line of rl) {
|
|
237
|
-
lineCount++;
|
|
238
|
-
if (!lineNumbersArray.has(lineCount))
|
|
239
|
-
continue;
|
|
240
|
-
lines.set(lineCount, decode(line, fieldType, fieldChildrenType, secretKey));
|
|
241
|
-
lineNumbersArray.delete(lineCount);
|
|
242
|
-
if (!lineNumbersArray.size)
|
|
243
|
-
break;
|
|
240
|
+
export async function get(filePath, lineNumbers, fieldType, fieldChildrenType, secretKey, readWholeFile = false) {
|
|
241
|
+
let fileHandle, rl;
|
|
242
|
+
try {
|
|
243
|
+
fileHandle = await open(filePath, "r");
|
|
244
|
+
rl = readLineInternface(fileHandle);
|
|
245
|
+
let lines = {}, linesCount = 0;
|
|
246
|
+
if (!lineNumbers) {
|
|
247
|
+
for await (const line of rl)
|
|
248
|
+
linesCount++,
|
|
249
|
+
(lines[linesCount] = decode(line, fieldType, fieldChildrenType, secretKey));
|
|
244
250
|
}
|
|
251
|
+
else if (lineNumbers === -1) {
|
|
252
|
+
let lastLine = null;
|
|
253
|
+
for await (const line of rl)
|
|
254
|
+
linesCount++, (lastLine = line);
|
|
255
|
+
if (lastLine)
|
|
256
|
+
lines[linesCount] = decode(lastLine, fieldType, fieldChildrenType, secretKey);
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
let lineNumbersArray = new Set(Array.isArray(lineNumbers) ? lineNumbers : [lineNumbers]);
|
|
260
|
+
for await (const line of rl) {
|
|
261
|
+
linesCount++;
|
|
262
|
+
if (!lineNumbersArray.has(linesCount))
|
|
263
|
+
continue;
|
|
264
|
+
lines[linesCount] = decode(line, fieldType, fieldChildrenType, secretKey);
|
|
265
|
+
lineNumbersArray.delete(linesCount);
|
|
266
|
+
if (!lineNumbersArray.size && !readWholeFile)
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return readWholeFile ? [lines, linesCount] : lines;
|
|
245
271
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
272
|
+
finally {
|
|
273
|
+
// Ensure that file handles are closed, even if an error occurred
|
|
274
|
+
rl?.close();
|
|
275
|
+
await fileHandle?.close();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
249
278
|
/**
|
|
250
279
|
* Asynchronously replaces specific lines in a file based on the provided replacements map or string.
|
|
251
280
|
*
|
|
@@ -257,47 +286,52 @@ export const get = async (filePath, lineNumbers, fieldType, fieldChildrenType, s
|
|
|
257
286
|
* Note: If the file doesn't exist and replacements is an object, it creates a new file with the specified replacements.
|
|
258
287
|
*/
|
|
259
288
|
export const replace = async (filePath, replacements) => {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
289
|
+
const fileTempPath = filePath.replace(/([^/]+)\/?$/, `.tmp/${Date.now()}-$1`);
|
|
290
|
+
if (await isExists(filePath)) {
|
|
291
|
+
let fileHandle, fileTempHandle, rl;
|
|
292
|
+
try {
|
|
293
|
+
let linesCount = 0;
|
|
294
|
+
fileHandle = await open(filePath, "r");
|
|
295
|
+
fileTempHandle = await open(fileTempPath, "w");
|
|
296
|
+
rl = readLineInternface(fileHandle);
|
|
297
|
+
await _pipeline(rl, fileTempHandle.createWriteStream(), new Transform({
|
|
298
|
+
transform(line, encoding, callback) {
|
|
299
|
+
linesCount++;
|
|
300
|
+
const replacement = isObject(replacements)
|
|
301
|
+
? replacements.hasOwnProperty(linesCount)
|
|
302
|
+
? replacements[linesCount]
|
|
303
|
+
: line
|
|
304
|
+
: replacements;
|
|
305
|
+
return callback(null, replacement + "\n");
|
|
306
|
+
},
|
|
307
|
+
}));
|
|
308
|
+
return [fileTempPath, filePath];
|
|
309
|
+
}
|
|
310
|
+
finally {
|
|
311
|
+
// Ensure that file handles are closed, even if an error occurred
|
|
312
|
+
rl?.close();
|
|
313
|
+
await fileHandle?.close();
|
|
314
|
+
await fileTempHandle?.close();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
else if (isObject(replacements)) {
|
|
318
|
+
let replacementsKeys = Object.keys(replacements)
|
|
319
|
+
.map(Number)
|
|
320
|
+
.toSorted((a, b) => a - b);
|
|
321
|
+
await write(fileTempPath, "\n".repeat(replacementsKeys[0] - 1) +
|
|
322
|
+
replacementsKeys
|
|
323
|
+
.map((lineNumber, index) => index === 0 || lineNumber - replacementsKeys[index - 1] - 1 === 0
|
|
324
|
+
? replacements[lineNumber]
|
|
325
|
+
: "\n".repeat(lineNumber - replacementsKeys[index - 1] - 1) +
|
|
326
|
+
replacements[lineNumber])
|
|
327
|
+
.join("\n") +
|
|
328
|
+
"\n");
|
|
329
|
+
return [fileTempPath, filePath];
|
|
330
|
+
}
|
|
331
|
+
return [];
|
|
298
332
|
};
|
|
299
333
|
/**
|
|
300
|
-
* Asynchronously appends data to a file.
|
|
334
|
+
* Asynchronously appends data to the beginning of a file.
|
|
301
335
|
*
|
|
302
336
|
* @param filePath - Path of the file to append to.
|
|
303
337
|
* @param data - Data to append. Can be a string, number, or an array of strings/numbers.
|
|
@@ -307,23 +341,32 @@ export const replace = async (filePath, replacements) => {
|
|
|
307
341
|
export const append = async (filePath, data) => {
|
|
308
342
|
const fileTempPath = filePath.replace(/([^/]+)\/?$/, `.tmp/${Date.now()}-$1`);
|
|
309
343
|
if (await isExists(filePath)) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
callback
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
+
return callback(null, `${Array.isArray(data) ? data.join("\n") : data}\n${line}\n`);
|
|
355
|
+
}
|
|
356
|
+
else
|
|
357
|
+
return 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
|
+
}
|
|
324
367
|
}
|
|
325
368
|
else
|
|
326
|
-
await
|
|
369
|
+
await write(fileTempPath, `${Array.isArray(data) ? data.join("\n") : data}\n`);
|
|
327
370
|
return [fileTempPath, filePath];
|
|
328
371
|
};
|
|
329
372
|
/**
|
|
@@ -336,41 +379,22 @@ export const append = async (filePath, data) => {
|
|
|
336
379
|
* Note: Creates a temporary file during the process and replaces the original file with it after removing lines.
|
|
337
380
|
*/
|
|
338
381
|
export const remove = async (filePath, linesToDelete) => {
|
|
339
|
-
let
|
|
382
|
+
let linesCount = 0;
|
|
340
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)
|
|
341
384
|
? linesToDelete.map(Number)
|
|
342
385
|
: [Number(linesToDelete)]), rl = readLineInternface(fileHandle);
|
|
343
|
-
await
|
|
386
|
+
await _pipeline(rl, fileTempHandle.createWriteStream(), new Transform({
|
|
344
387
|
transform(line, encoding, callback) {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
callback(null, `${line}\n`)
|
|
348
|
-
|
|
388
|
+
linesCount++;
|
|
389
|
+
return !linesToDeleteArray.has(linesCount)
|
|
390
|
+
? callback(null, `${line}\n`)
|
|
391
|
+
: callback();
|
|
349
392
|
},
|
|
350
|
-
})
|
|
351
|
-
// createDeflate(),
|
|
352
|
-
fileTempHandle.createWriteStream());
|
|
393
|
+
}));
|
|
353
394
|
await fileTempHandle.close();
|
|
354
395
|
await fileHandle.close();
|
|
355
396
|
return [fileTempPath, filePath];
|
|
356
397
|
};
|
|
357
|
-
/**
|
|
358
|
-
* Asynchronously counts the number of lines in a file.
|
|
359
|
-
*
|
|
360
|
-
* @param filePath - Path of the file to count lines in.
|
|
361
|
-
* @returns Promise<number>. The number of lines in the file.
|
|
362
|
-
*
|
|
363
|
-
* Note: Reads through the file line by line to count the total number of lines.
|
|
364
|
-
*/
|
|
365
|
-
export const count = async (filePath) => {
|
|
366
|
-
// return Number((await exec(`wc -l < ${filePath}`)).stdout.trim());
|
|
367
|
-
let lineCount = 0;
|
|
368
|
-
const fileHandle = await open(filePath, "r"), rl = readLineInternface(fileHandle);
|
|
369
|
-
for await (const line of rl)
|
|
370
|
-
lineCount++;
|
|
371
|
-
await fileHandle.close();
|
|
372
|
-
return lineCount;
|
|
373
|
-
};
|
|
374
398
|
/**
|
|
375
399
|
* Evaluates a comparison between two values based on a specified operator and field types.
|
|
376
400
|
*
|
|
@@ -384,64 +408,118 @@ export const count = async (filePath) => {
|
|
|
384
408
|
* Note: Handles various data types and comparison logic, including special handling for passwords and regex patterns.
|
|
385
409
|
*/
|
|
386
410
|
const handleComparisonOperator = (operator, originalValue, comparedAtValue, fieldType, fieldChildrenType) => {
|
|
387
|
-
if
|
|
411
|
+
// Determine the field type if it's an array of potential types.
|
|
412
|
+
if (Array.isArray(fieldType)) {
|
|
388
413
|
fieldType = detectFieldType(String(originalValue), fieldType);
|
|
389
|
-
|
|
414
|
+
}
|
|
415
|
+
// Handle comparisons involving arrays.
|
|
416
|
+
if (Array.isArray(comparedAtValue) && !["[]", "![]"].includes(operator)) {
|
|
390
417
|
return comparedAtValue.some((comparedAtValueSingle) => handleComparisonOperator(operator, originalValue, comparedAtValueSingle, fieldType));
|
|
391
|
-
|
|
418
|
+
}
|
|
419
|
+
// Switch statement for different comparison operators.
|
|
392
420
|
switch (operator) {
|
|
421
|
+
// Equal (Case Insensitive for strings, specific handling for passwords and booleans).
|
|
393
422
|
case "=":
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
return typeof originalValue === "string" &&
|
|
397
|
-
typeof comparedAtValue === "string"
|
|
398
|
-
? comparePassword(originalValue, comparedAtValue)
|
|
399
|
-
: false;
|
|
400
|
-
case "boolean":
|
|
401
|
-
return Number(originalValue) - Number(comparedAtValue) === 0;
|
|
402
|
-
default:
|
|
403
|
-
return originalValue === comparedAtValue;
|
|
404
|
-
}
|
|
423
|
+
return isEqual(originalValue, comparedAtValue, fieldType);
|
|
424
|
+
// Not Equal.
|
|
405
425
|
case "!=":
|
|
406
|
-
return !
|
|
426
|
+
return !isEqual(originalValue, comparedAtValue, fieldType);
|
|
427
|
+
// Greater Than.
|
|
407
428
|
case ">":
|
|
408
429
|
return (originalValue !== null &&
|
|
409
430
|
comparedAtValue !== null &&
|
|
410
431
|
originalValue > comparedAtValue);
|
|
432
|
+
// Less Than.
|
|
411
433
|
case "<":
|
|
412
434
|
return (originalValue !== null &&
|
|
413
435
|
comparedAtValue !== null &&
|
|
414
436
|
originalValue < comparedAtValue);
|
|
437
|
+
// Greater Than or Equal.
|
|
415
438
|
case ">=":
|
|
416
439
|
return (originalValue !== null &&
|
|
417
440
|
comparedAtValue !== null &&
|
|
418
441
|
originalValue >= comparedAtValue);
|
|
442
|
+
// Less Than or Equal.
|
|
419
443
|
case "<=":
|
|
420
444
|
return (originalValue !== null &&
|
|
421
445
|
comparedAtValue !== null &&
|
|
422
446
|
originalValue <= comparedAtValue);
|
|
447
|
+
// Array Contains (equality check for arrays).
|
|
423
448
|
case "[]":
|
|
424
|
-
return (
|
|
425
|
-
|
|
426
|
-
originalValue.some(comparedAtValue.includes)) ||
|
|
427
|
-
(Array.isArray(originalValue) &&
|
|
428
|
-
!Array.isArray(comparedAtValue) &&
|
|
429
|
-
originalValue.includes(comparedAtValue)) ||
|
|
430
|
-
(!Array.isArray(originalValue) &&
|
|
431
|
-
Array.isArray(comparedAtValue) &&
|
|
432
|
-
comparedAtValue.includes(originalValue)));
|
|
449
|
+
return isArrayEqual(originalValue, comparedAtValue);
|
|
450
|
+
// Array Does Not Contain.
|
|
433
451
|
case "![]":
|
|
434
|
-
return !
|
|
452
|
+
return !isArrayEqual(originalValue, comparedAtValue);
|
|
453
|
+
// Wildcard Match (using regex pattern).
|
|
435
454
|
case "*":
|
|
436
|
-
return
|
|
437
|
-
|
|
438
|
-
: "%" + String(comparedAtValue) + "%").replace(/%/g, ".*")}$`, "i").test(String(originalValue));
|
|
455
|
+
return isWildcardMatch(originalValue, comparedAtValue);
|
|
456
|
+
// Not Wildcard Match.
|
|
439
457
|
case "!*":
|
|
440
|
-
return !
|
|
458
|
+
return !isWildcardMatch(originalValue, comparedAtValue);
|
|
459
|
+
// Unsupported operator.
|
|
441
460
|
default:
|
|
442
|
-
throw new Error(operator);
|
|
461
|
+
throw new Error(`Unsupported operator: ${operator}`);
|
|
443
462
|
}
|
|
444
463
|
};
|
|
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) &&
|
|
504
|
+
Array.isArray(comparedAtValue) &&
|
|
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
|
+
};
|
|
445
523
|
/**
|
|
446
524
|
* Asynchronously searches a file for lines matching specified criteria, using comparison and logical operators.
|
|
447
525
|
*
|
|
@@ -462,34 +540,88 @@ const handleComparisonOperator = (operator, originalValue, comparedAtValue, fiel
|
|
|
462
540
|
* Note: Decodes each line for comparison and can handle complex queries with multiple conditions.
|
|
463
541
|
*/
|
|
464
542
|
export const search = async (filePath, operator, comparedAtValue, logicalOperator, fieldType, fieldChildrenType, limit, offset, readWholeFile, secretKey) => {
|
|
465
|
-
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
if
|
|
482
|
-
|
|
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)
|
|
483
574
|
continue;
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
+
}
|
|
487
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];
|
|
488
593
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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();
|
|
598
|
+
}
|
|
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;
|
|
493
625
|
};
|
|
494
626
|
/**
|
|
495
627
|
* Asynchronously calculates the sum of numerical values from specified lines in a file.
|
|
@@ -504,14 +636,14 @@ export const sum = async (filePath, lineNumbers) => {
|
|
|
504
636
|
let sum = 0;
|
|
505
637
|
const fileHandle = await open(filePath, "r"), rl = readLineInternface(fileHandle);
|
|
506
638
|
if (lineNumbers) {
|
|
507
|
-
let
|
|
639
|
+
let linesCount = 0;
|
|
508
640
|
let lineNumbersArray = new Set(Array.isArray(lineNumbers) ? lineNumbers : [lineNumbers]);
|
|
509
641
|
for await (const line of rl) {
|
|
510
|
-
|
|
511
|
-
if (!lineNumbersArray.has(
|
|
642
|
+
linesCount++;
|
|
643
|
+
if (!lineNumbersArray.has(linesCount))
|
|
512
644
|
continue;
|
|
513
645
|
sum += +(decode(line, "number") ?? 0);
|
|
514
|
-
lineNumbersArray.delete(
|
|
646
|
+
lineNumbersArray.delete(linesCount);
|
|
515
647
|
if (!lineNumbersArray.size)
|
|
516
648
|
break;
|
|
517
649
|
}
|
|
@@ -535,16 +667,16 @@ export const max = async (filePath, lineNumbers) => {
|
|
|
535
667
|
let max = 0;
|
|
536
668
|
const fileHandle = await open(filePath, "r"), rl = readLineInternface(fileHandle);
|
|
537
669
|
if (lineNumbers) {
|
|
538
|
-
let
|
|
670
|
+
let linesCount = 0;
|
|
539
671
|
let lineNumbersArray = new Set(Array.isArray(lineNumbers) ? lineNumbers : [lineNumbers]);
|
|
540
672
|
for await (const line of rl) {
|
|
541
|
-
|
|
542
|
-
if (!lineNumbersArray.has(
|
|
673
|
+
linesCount++;
|
|
674
|
+
if (!lineNumbersArray.has(linesCount))
|
|
543
675
|
continue;
|
|
544
676
|
const lineContentNum = +(decode(line, "number") ?? 0);
|
|
545
677
|
if (lineContentNum > max)
|
|
546
678
|
max = lineContentNum;
|
|
547
|
-
lineNumbersArray.delete(
|
|
679
|
+
lineNumbersArray.delete(linesCount);
|
|
548
680
|
if (!lineNumbersArray.size)
|
|
549
681
|
break;
|
|
550
682
|
}
|
|
@@ -571,16 +703,16 @@ export const min = async (filePath, lineNumbers) => {
|
|
|
571
703
|
let min = 0;
|
|
572
704
|
const fileHandle = await open(filePath, "r"), rl = readLineInternface(fileHandle);
|
|
573
705
|
if (lineNumbers) {
|
|
574
|
-
let
|
|
706
|
+
let linesCount = 0;
|
|
575
707
|
let lineNumbersArray = new Set(Array.isArray(lineNumbers) ? lineNumbers : [lineNumbers]);
|
|
576
708
|
for await (const line of rl) {
|
|
577
|
-
|
|
578
|
-
if (!lineNumbersArray.has(
|
|
709
|
+
linesCount++;
|
|
710
|
+
if (!lineNumbersArray.has(linesCount))
|
|
579
711
|
continue;
|
|
580
712
|
const lineContentNum = +(decode(line, "number") ?? 0);
|
|
581
713
|
if (lineContentNum < min)
|
|
582
714
|
min = lineContentNum;
|
|
583
|
-
lineNumbersArray.delete(
|
|
715
|
+
lineNumbersArray.delete(linesCount);
|
|
584
716
|
if (!lineNumbersArray.size)
|
|
585
717
|
break;
|
|
586
718
|
}
|
|
@@ -611,7 +743,6 @@ export default class File {
|
|
|
611
743
|
static remove = remove;
|
|
612
744
|
static search = search;
|
|
613
745
|
static replace = replace;
|
|
614
|
-
static count = count;
|
|
615
746
|
static encode = encode;
|
|
616
747
|
static decode = decode;
|
|
617
748
|
static isExists = isExists;
|
|
@@ -619,4 +750,7 @@ export default class File {
|
|
|
619
750
|
static min = min;
|
|
620
751
|
static max = max;
|
|
621
752
|
static append = append;
|
|
753
|
+
static count = count;
|
|
754
|
+
static write = write;
|
|
755
|
+
static read = read;
|
|
622
756
|
}
|