inibase 1.0.0-rc.0 → 1.0.0-rc.11

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/file.ts CHANGED
@@ -1,26 +1,195 @@
1
- import { createWriteStream, unlinkSync, renameSync, existsSync } from "fs";
2
- import { open } from "fs/promises";
3
- import { parse } from "path";
1
+ import { createWriteStream, createReadStream, WriteStream } from "node:fs";
2
+ import { open, unlink, rename, stat } from "node:fs/promises";
3
+ import { Interface, createInterface } from "node:readline";
4
+ import { parse } from "node:path";
4
5
  import { ComparisonOperator, FieldType } from ".";
5
- import Utils from "./utils";
6
+ import { detectFieldType, isArrayOfArrays, isNumber } from "./utils";
7
+ import { encodeID, comparePassword } from "./utils.server";
6
8
 
7
- export const encodeFileName = (fileName: string, extension?: string) => {
8
- return (
9
- fileName.replaceAll("%", "%25").replaceAll("*", "%") +
10
- (extension ? `.${extension}` : "")
11
- );
9
+ const doesSupportReadLines = () => {
10
+ const [major, minor, patch] = process.versions.node.split(".").map(Number);
11
+ return major >= 18 && minor >= 11;
12
12
  };
13
13
 
14
- export const decodeFileName = (fileName: string) => {
15
- return fileName.replaceAll("%", "*").replaceAll("*25", "%");
14
+ export const isExists = async (path: string) => {
15
+ try {
16
+ await stat(path);
17
+ return true;
18
+ } catch {
19
+ return false;
20
+ }
21
+ };
22
+
23
+ const delimiters = [",", "|", "&", "$", "#", "@", "^", "%", ":", "!", ";"];
24
+
25
+ export const encode = (
26
+ input:
27
+ | string
28
+ | number
29
+ | boolean
30
+ | null
31
+ | (string | number | boolean | null)[],
32
+ secretKey?: string | Buffer
33
+ ) => {
34
+ const secureString = (input: string | number | boolean | null) => {
35
+ if (["true", "false"].includes(String(input))) return input ? 1 : 0;
36
+ return typeof input === "string"
37
+ ? decodeURIComponent(input)
38
+ .replaceAll("<", "&lt;")
39
+ .replaceAll(">", "&gt;")
40
+ .replaceAll(",", "%2C")
41
+ .replaceAll("|", "%7C")
42
+ .replaceAll("&", "%26")
43
+ .replaceAll("$", "%24")
44
+ .replaceAll("#", "%23")
45
+ .replaceAll("@", "%40")
46
+ .replaceAll("^", "%5E")
47
+ .replaceAll("%", "%25")
48
+ .replaceAll(":", "%3A")
49
+ .replaceAll("!", "%21")
50
+ .replaceAll(";", "%3B")
51
+ .replaceAll("\n", "\\n")
52
+ .replaceAll("\r", "\\r")
53
+ : input;
54
+ },
55
+ secureArray = (arr_str: any[] | any): any[] | any =>
56
+ Array.isArray(arr_str) ? arr_str.map(secureArray) : secureString(arr_str),
57
+ joinMultidimensionalArray = (
58
+ arr: any[] | any[][],
59
+ delimiter_index = 0
60
+ ): string => {
61
+ delimiter_index++;
62
+ if (isArrayOfArrays(arr))
63
+ arr = arr.map((ar: any[]) =>
64
+ joinMultidimensionalArray(ar, delimiter_index)
65
+ );
66
+ delimiter_index--;
67
+ return arr.join(delimiters[delimiter_index]);
68
+ };
69
+ return Array.isArray(input)
70
+ ? joinMultidimensionalArray(secureArray(input))
71
+ : secureString(input);
72
+ };
73
+
74
+ export const decode = (
75
+ input: string | null | number,
76
+ fieldType?: FieldType | FieldType[],
77
+ fieldChildrenType?: FieldType | FieldType[],
78
+ secretKey?: string | Buffer
79
+ ): string | number | boolean | null | (string | number | null | boolean)[] => {
80
+ if (!fieldType) return null;
81
+ const unSecureString = (input: string) =>
82
+ decodeURIComponent(input)
83
+ .replaceAll("&lt;", "<")
84
+ .replaceAll("&gt;", ">")
85
+ .replaceAll("%2C", ",")
86
+ .replaceAll("%7C", "|")
87
+ .replaceAll("%26", "&")
88
+ .replaceAll("%24", "$")
89
+ .replaceAll("%23", "#")
90
+ .replaceAll("%40", "@")
91
+ .replaceAll("%5E", "^")
92
+ .replaceAll("%25", "%")
93
+ .replaceAll("%3A", ":")
94
+ .replaceAll("%21", "!")
95
+ .replaceAll("%3B", ";")
96
+ .replaceAll("\\n", "\n")
97
+ .replaceAll("\\r", "\r") || null,
98
+ unSecureArray = (arr_str: any[] | any): any[] | any =>
99
+ Array.isArray(arr_str)
100
+ ? arr_str.map(unSecureArray)
101
+ : unSecureString(arr_str),
102
+ reverseJoinMultidimensionalArray = (
103
+ joinedString: string | any[] | any[][]
104
+ ): any | any[] | any[][] => {
105
+ const reverseJoinMultidimensionalArrayHelper = (
106
+ arr: any | any[] | any[][],
107
+ delimiter: string
108
+ ) =>
109
+ Array.isArray(arr)
110
+ ? arr.map((ar: any) =>
111
+ reverseJoinMultidimensionalArrayHelper(ar, delimiter)
112
+ )
113
+ : arr.split(delimiter);
114
+
115
+ const availableDelimiters = delimiters.filter((delimiter) =>
116
+ joinedString.includes(delimiter)
117
+ );
118
+ for (const delimiter of availableDelimiters) {
119
+ joinedString = Array.isArray(joinedString)
120
+ ? reverseJoinMultidimensionalArrayHelper(joinedString, delimiter)
121
+ : joinedString.split(delimiter);
122
+ }
123
+ return joinedString;
124
+ },
125
+ decodeHelper = (value: string | number | any[]) => {
126
+ if (Array.isArray(value) && fieldType !== "array")
127
+ return value.map(decodeHelper);
128
+ switch (fieldType as FieldType) {
129
+ case "table":
130
+ case "number":
131
+ return isNumber(value) ? Number(value) : null;
132
+ case "boolean":
133
+ return typeof value === "string" ? value === "true" : Boolean(value);
134
+ case "array":
135
+ if (!Array.isArray(value)) return [value];
136
+
137
+ if (fieldChildrenType)
138
+ return value.map(
139
+ (v) =>
140
+ decode(
141
+ v,
142
+ Array.isArray(fieldChildrenType)
143
+ ? detectFieldType(v, fieldChildrenType)
144
+ : fieldChildrenType,
145
+ undefined,
146
+ secretKey
147
+ ) as string | number | boolean | null
148
+ );
149
+ else return value;
150
+ case "id":
151
+ return isNumber(value) ? encodeID(value as number, secretKey) : value;
152
+ default:
153
+ return value;
154
+ }
155
+ };
156
+ if (input === null || input === "") return null;
157
+ if (Array.isArray(fieldType))
158
+ fieldType = detectFieldType(String(input), fieldType);
159
+ return decodeHelper(
160
+ typeof input === "string"
161
+ ? input.includes(",")
162
+ ? unSecureArray(reverseJoinMultidimensionalArray(input))
163
+ : unSecureString(input)
164
+ : input
165
+ );
16
166
  };
17
167
 
18
168
  export const get = async (
19
169
  filePath: string,
20
- fieldType?: FieldType,
21
- lineNumbers?: number | number[]
22
- ) => {
23
- const file = await open(filePath);
170
+ lineNumbers?: number | number[],
171
+ fieldType?: FieldType | FieldType[],
172
+ fieldChildrenType?: FieldType | FieldType[],
173
+ secretKey?: string | Buffer
174
+ ): Promise<
175
+ [
176
+ Record<
177
+ number,
178
+ | string
179
+ | number
180
+ | boolean
181
+ | (string | number | boolean | (string | number | boolean)[])[]
182
+ > | null,
183
+ number
184
+ ]
185
+ > => {
186
+ let rl: Interface;
187
+ if (doesSupportReadLines()) rl = (await open(filePath)).readLines();
188
+ else
189
+ rl = createInterface({
190
+ input: createReadStream(filePath),
191
+ crlfDelay: Infinity,
192
+ });
24
193
  let lines: Record<
25
194
  number,
26
195
  | string
@@ -32,27 +201,36 @@ export const get = async (
32
201
  lineCount = 0;
33
202
 
34
203
  if (!lineNumbers) {
35
- for await (const line of file.readLines())
36
- lineCount++, (lines[lineCount] = Utils.decode(line, fieldType));
204
+ for await (const line of rl)
205
+ lineCount++,
206
+ (lines[lineCount] = decode(
207
+ line,
208
+ fieldType,
209
+ fieldChildrenType,
210
+ secretKey
211
+ ));
37
212
  } else if (lineNumbers === -1) {
38
- let lastLine;
39
- for await (const line of file.readLines()) lineCount++, (lastLine = line);
40
- if (lastLine) lines = { [lineCount]: Utils.decode(lastLine, fieldType) };
213
+ let lastLine: string;
214
+ for await (const line of rl) lineCount++, (lastLine = line);
215
+ if (lastLine)
216
+ lines = {
217
+ [lineCount]: decode(lastLine, fieldType, fieldChildrenType, secretKey),
218
+ };
41
219
  } else {
42
220
  let lineNumbersArray = [
43
221
  ...(Array.isArray(lineNumbers) ? lineNumbers : [lineNumbers]),
44
222
  ];
45
- for await (const line of file.readLines()) {
223
+ for await (const line of rl) {
46
224
  lineCount++;
47
225
  if (!lineNumbersArray.includes(lineCount)) continue;
48
226
  const indexOfLineCount = lineNumbersArray.indexOf(lineCount);
49
- lines[lineCount] = Utils.decode(line, fieldType);
227
+ lines[lineCount] = decode(line, fieldType, fieldChildrenType, secretKey);
50
228
  lineNumbersArray[indexOfLineCount] = 0;
51
229
  if (!lineNumbersArray.filter((lineN) => lineN !== 0).length) break;
52
230
  }
53
231
  }
54
232
 
55
- return lines ?? null;
233
+ return [lines ?? null, lineCount];
56
234
  };
57
235
 
58
236
  export const replace = async (
@@ -66,35 +244,48 @@ export const replace = async (
66
244
  | Record<
67
245
  number,
68
246
  string | boolean | number | null | (string | boolean | number | null)[]
69
- >
247
+ >,
248
+ secretKey?: string | Buffer
70
249
  ) => {
71
- if (existsSync(filePath)) {
72
- const file = await open(filePath, "w+"),
250
+ if (await isExists(filePath)) {
251
+ let rl: Interface, writeStream: WriteStream;
252
+ if (doesSupportReadLines()) {
253
+ const file = await open(filePath, "w+");
254
+ rl = file.readLines();
73
255
  writeStream = file.createWriteStream();
256
+ } else {
257
+ rl = createInterface({
258
+ input: createReadStream(filePath),
259
+ crlfDelay: Infinity,
260
+ });
261
+ writeStream = createWriteStream(filePath);
262
+ }
74
263
  if (typeof replacements === "object" && !Array.isArray(replacements)) {
75
264
  let lineCount = 0;
76
- for await (const line of file.readLines()) {
265
+ for await (const line of rl) {
77
266
  lineCount++;
78
267
  writeStream.write(
79
268
  (lineCount in replacements
80
- ? Utils.encode(replacements[lineCount])
269
+ ? encode(replacements[lineCount], secretKey)
81
270
  : line) + "\n"
82
271
  );
83
272
  }
84
273
  } else
85
- for await (const _line of file.readLines())
86
- writeStream.write(Utils.encode(replacements) + "\n");
274
+ for await (const _line of rl)
275
+ writeStream.write(encode(replacements, secretKey) + "\n");
87
276
 
88
277
  writeStream.end();
89
278
  } else if (typeof replacements === "object" && !Array.isArray(replacements)) {
90
- const file = await open(filePath, "w"),
91
- writeStream = file.createWriteStream(),
92
- largestLinesNumbers =
93
- Math.max(...Object.keys(replacements).map(Number)) + 1;
279
+ let writeStream: WriteStream;
280
+ if (doesSupportReadLines())
281
+ writeStream = (await open(filePath, "w")).createWriteStream();
282
+ else writeStream = createWriteStream(filePath);
283
+ const largestLinesNumbers =
284
+ Math.max(...Object.keys(replacements).map(Number)) + 1;
94
285
  for (let lineCount = 1; lineCount < largestLinesNumbers; lineCount++) {
95
286
  writeStream.write(
96
287
  (lineCount in replacements
97
- ? Utils.encode(replacements[lineCount])
288
+ ? encode(replacements[lineCount], secretKey)
98
289
  : "") + "\n"
99
290
  );
100
291
  }
@@ -108,39 +299,52 @@ export const remove = async (
108
299
  ): Promise<void> => {
109
300
  let lineCount = 0;
110
301
 
111
- const tempFilePath = `${filePath}.tmp`,
302
+ const tempFilePath = `${filePath}-${Date.now()}.tmp`,
112
303
  linesToDeleteArray = [
113
304
  ...(Array.isArray(linesToDelete) ? linesToDelete : [linesToDelete]),
114
- ],
115
- writeStream = createWriteStream(tempFilePath),
116
- file = await open(filePath);
305
+ ];
117
306
 
118
- for await (const line of file.readLines()) {
307
+ let rl: Interface, writeStream: WriteStream;
308
+ if (doesSupportReadLines()) {
309
+ rl = (await open(filePath)).readLines();
310
+ writeStream = (await open(tempFilePath, "w+")).createWriteStream();
311
+ } else {
312
+ rl = createInterface({
313
+ input: createReadStream(filePath),
314
+ crlfDelay: Infinity,
315
+ });
316
+ writeStream = createWriteStream(tempFilePath);
317
+ }
318
+
319
+ for await (const line of rl) {
119
320
  lineCount++;
120
321
  if (!linesToDeleteArray.includes(lineCount)) {
121
322
  writeStream.write(`${line}\n`);
122
323
  }
123
324
  }
124
- writeStream.end();
125
- writeStream.on("finish", () => {
126
- unlinkSync(filePath); // Remove the original file
127
- renameSync(tempFilePath, filePath); // Rename the temp file to the original file name
325
+ writeStream.end(async () => {
326
+ await unlink(filePath); // Remove the original file
327
+ await rename(tempFilePath, filePath); // Rename the temp file to the original file name
128
328
  });
129
329
  };
130
330
 
131
331
  export const count = async (filePath: string): Promise<number> => {
132
- let lineCount = 0;
332
+ let lineCount = 0,
333
+ rl: Interface;
334
+ if (doesSupportReadLines()) rl = (await open(filePath)).readLines();
335
+ else
336
+ rl = createInterface({
337
+ input: createReadStream(filePath),
338
+ crlfDelay: Infinity,
339
+ });
133
340
 
134
- const file = await open(filePath);
135
-
136
- for await (const line of file.readLines()) lineCount++;
341
+ for await (const line of rl) lineCount++;
137
342
 
138
343
  return lineCount;
139
344
  };
140
345
 
141
346
  export const search = async (
142
347
  filePath: string,
143
- fieldType: FieldType,
144
348
  operator: ComparisonOperator | ComparisonOperator[],
145
349
  comparedAtValue:
146
350
  | string
@@ -149,9 +353,12 @@ export const search = async (
149
353
  | null
150
354
  | (string | number | boolean | null)[],
151
355
  logicalOperator?: "and" | "or",
356
+ fieldType?: FieldType | FieldType[],
357
+ fieldChildrenType?: FieldType | FieldType[],
152
358
  limit?: number,
153
359
  offset?: number,
154
- readWholeFile?: boolean
360
+ readWholeFile?: boolean,
361
+ secretKey?: string | Buffer
155
362
  ): Promise<
156
363
  [
157
364
  Record<
@@ -166,7 +373,7 @@ export const search = async (
166
373
  > => {
167
374
  const handleComparisonOperator = (
168
375
  operator: ComparisonOperator,
169
- value:
376
+ originalValue:
170
377
  | string
171
378
  | number
172
379
  | boolean
@@ -178,71 +385,80 @@ export const search = async (
178
385
  | boolean
179
386
  | null
180
387
  | (string | number | boolean | null)[],
181
- fieldType: FieldType
388
+ fieldType?: FieldType | FieldType[],
389
+ fieldChildrenType?: FieldType | FieldType[]
182
390
  ): boolean => {
183
- // check if not array or object
391
+ if (Array.isArray(fieldType))
392
+ fieldType = detectFieldType(String(originalValue), fieldType);
393
+ if (Array.isArray(comparedAtValue) && !["[]", "![]"].includes(operator))
394
+ return comparedAtValue.some((comparedAtValueSingle) =>
395
+ handleComparisonOperator(
396
+ operator,
397
+ originalValue,
398
+ comparedAtValueSingle,
399
+ fieldType
400
+ )
401
+ );
402
+ // check if not array or object // it can't be array or object!
184
403
  switch (operator) {
185
404
  case "=":
186
- return fieldType === "password" &&
187
- typeof value === "string" &&
188
- typeof comparedAtValue === "string"
189
- ? Utils.comparePassword(value, comparedAtValue)
190
- : value === comparedAtValue;
405
+ switch (fieldType) {
406
+ case "password":
407
+ return typeof originalValue === "string" &&
408
+ typeof comparedAtValue === "string"
409
+ ? comparePassword(originalValue, comparedAtValue)
410
+ : false;
411
+ case "boolean":
412
+ return Number(originalValue) - Number(comparedAtValue) === 0;
413
+ default:
414
+ return originalValue === comparedAtValue;
415
+ }
191
416
  case "!=":
192
417
  return !handleComparisonOperator(
193
418
  "=",
194
- value,
419
+ originalValue,
195
420
  comparedAtValue,
196
421
  fieldType
197
422
  );
198
423
  case ">":
199
- return (
200
- value !== null && comparedAtValue !== null && value > comparedAtValue
201
- );
424
+ return originalValue > comparedAtValue;
202
425
  case "<":
203
- return (
204
- value !== null && comparedAtValue !== null && value < comparedAtValue
205
- );
426
+ return originalValue < comparedAtValue;
206
427
  case ">=":
207
- return (
208
- value !== null && comparedAtValue !== null && value >= comparedAtValue
209
- );
428
+ return originalValue >= comparedAtValue;
210
429
  case "<=":
211
- return (
212
- value !== null && comparedAtValue !== null && value <= comparedAtValue
213
- );
430
+ return originalValue <= comparedAtValue;
214
431
  case "[]":
215
432
  return (
216
- (Array.isArray(value) &&
433
+ (Array.isArray(originalValue) &&
217
434
  Array.isArray(comparedAtValue) &&
218
- value.some(comparedAtValue.includes)) ||
219
- (Array.isArray(value) &&
435
+ originalValue.some(comparedAtValue.includes)) ||
436
+ (Array.isArray(originalValue) &&
220
437
  !Array.isArray(comparedAtValue) &&
221
- value.includes(comparedAtValue)) ||
222
- (!Array.isArray(value) &&
438
+ originalValue.includes(comparedAtValue)) ||
439
+ (!Array.isArray(originalValue) &&
223
440
  Array.isArray(comparedAtValue) &&
224
- comparedAtValue.includes(value))
441
+ comparedAtValue.includes(originalValue))
225
442
  );
226
443
  case "![]":
227
444
  return !handleComparisonOperator(
228
445
  "[]",
229
- value,
446
+ originalValue,
230
447
  comparedAtValue,
231
448
  fieldType
232
449
  );
233
450
  case "*":
234
- return (
235
- value !== null &&
236
- comparedAtValue !== null &&
237
- new RegExp(
238
- `^${comparedAtValue.toString().replace(/%/g, ".*")}$`,
239
- "i"
240
- ).test(value.toString())
241
- );
451
+ return new RegExp(
452
+ `^${(String(comparedAtValue).includes("%")
453
+ ? String(comparedAtValue)
454
+ : "%" + String(comparedAtValue) + "%"
455
+ ).replace(/%/g, ".*")}$`,
456
+ "i"
457
+ ).test(String(originalValue));
242
458
  case "!*":
243
459
  return !handleComparisonOperator(
244
460
  "*",
245
- value,
461
+ originalValue,
246
462
  comparedAtValue,
247
463
  fieldType
248
464
  );
@@ -260,16 +476,21 @@ export const search = async (
260
476
  > = {},
261
477
  lineCount = 0,
262
478
  foundItems = 0;
479
+ let rl: Interface;
480
+ if (doesSupportReadLines()) rl = (await open(filePath)).readLines();
481
+ else
482
+ rl = createInterface({
483
+ input: createReadStream(filePath),
484
+ crlfDelay: Infinity,
485
+ });
263
486
 
264
- const file = await open(filePath),
265
- columnName = decodeFileName(parse(filePath).name);
487
+ const columnName = parse(filePath).name;
266
488
 
267
- for await (const line of file.readLines()) {
489
+ for await (const line of rl) {
268
490
  lineCount++;
269
- const decodedLine = Utils.decode(line, fieldType);
491
+ const decodedLine = decode(line, fieldType, fieldChildrenType, secretKey);
270
492
  if (
271
- decodedLine &&
272
- ((Array.isArray(operator) &&
493
+ (Array.isArray(operator) &&
273
494
  Array.isArray(comparedAtValue) &&
274
495
  ((logicalOperator &&
275
496
  logicalOperator === "or" &&
@@ -289,13 +510,13 @@ export const search = async (
289
510
  fieldType
290
511
  )
291
512
  ))) ||
292
- (!Array.isArray(operator) &&
293
- handleComparisonOperator(
294
- operator,
295
- decodedLine,
296
- comparedAtValue,
297
- fieldType
298
- )))
513
+ (!Array.isArray(operator) &&
514
+ handleComparisonOperator(
515
+ operator,
516
+ decodedLine,
517
+ comparedAtValue,
518
+ fieldType
519
+ ))
299
520
  ) {
300
521
  foundItems++;
301
522
  if (offset && foundItems < offset) continue;
@@ -313,10 +534,11 @@ export const search = async (
313
534
 
314
535
  export default class File {
315
536
  static get = get;
316
- static count = count;
317
537
  static remove = remove;
318
538
  static search = search;
319
539
  static replace = replace;
320
- static encodeFileName = encodeFileName;
321
- static decodeFileName = decodeFileName;
540
+ static count = count;
541
+ static encode = encode;
542
+ static decode = decode;
543
+ static isExists = isExists;
322
544
  }