inibase 1.0.0-rc.0 → 1.0.0-rc.10

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