inibase 1.0.0-rc.24 → 1.0.0-rc.26

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/dist/file.d.ts CHANGED
@@ -1,29 +1,159 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
2
  import { ComparisonOperator, FieldType } from "./index.js";
3
+ /**
4
+ * Checks if a file or directory exists at the specified path.
5
+ *
6
+ * @param path - The path to the file or directory.
7
+ * @returns A Promise that resolves to true if the file/directory exists, false otherwise.
8
+ */
3
9
  export declare const isExists: (path: string) => Promise<boolean>;
10
+ /**
11
+ * Encodes the input using 'secureString' and 'joinMultidimensionalArray' functions.
12
+ * If the input is an array, it is first secured and then joined into a string.
13
+ * If the input is a single value, it is directly secured.
14
+ *
15
+ * @param input - A value or array of values (string, number, boolean, null).
16
+ * @param secretKey - Optional secret key for encoding, can be a string or Buffer.
17
+ * @returns The secured and/or joined string.
18
+ */
4
19
  export declare const encode: (input: string | number | boolean | null | (string | number | boolean | null)[], secretKey?: string | Buffer) => string | number | boolean | null;
20
+ /**
21
+ * Decodes the input based on the specified field type(s) and an optional secret key.
22
+ * Handles different formats of input, including strings, numbers, and their array representations.
23
+ *
24
+ * @param input - The input to be decoded, can be a string, number, or null.
25
+ * @param fieldType - Optional type of the field to guide decoding (e.g., 'number', 'boolean').
26
+ * @param fieldChildrenType - Optional type for child elements in array inputs.
27
+ * @param secretKey - Optional secret key for decoding, can be a string or Buffer.
28
+ * @returns Decoded value as a string, number, boolean, or array of these, or null if no fieldType or input is null/empty.
29
+ */
5
30
  export declare const decode: (input: string | null | number, fieldType?: FieldType | FieldType[], fieldChildrenType?: FieldType | FieldType[], secretKey?: string | Buffer) => string | number | boolean | null | (string | number | null | boolean)[];
31
+ /**
32
+ * Asynchronously reads and decodes data from a file at specified line numbers.
33
+ * Decodes each line based on specified field types and an optional secret key.
34
+ *
35
+ * @param filePath - Path of the file to be read.
36
+ * @param lineNumbers - Optional line number(s) to read from the file. If -1, reads the last line.
37
+ * @param fieldType - Optional type of the field to guide decoding (e.g., 'number', 'boolean').
38
+ * @param fieldChildrenType - Optional type for child elements in array inputs.
39
+ * @param secretKey - Optional secret key for decoding, can be a string or Buffer.
40
+ * @returns Promise resolving to a tuple:
41
+ * 1. Record of line numbers and their decoded content or null if no lines are read.
42
+ * 2. Total count of lines processed.
43
+ */
6
44
  export declare const get: (filePath: string, lineNumbers?: number | number[], fieldType?: FieldType | FieldType[], fieldChildrenType?: FieldType | FieldType[], secretKey?: string | Buffer) => Promise<[
7
45
  Record<number, string | number | boolean | null | (string | number | boolean | (string | number | boolean)[] | null)[]> | null,
8
46
  number
9
47
  ]>;
10
- export declare const replace: (filePath: string, replacements: string | number | boolean | null | (string | number | boolean | null)[] | Record<number, string | boolean | number | null | (string | boolean | number | null)[]> | Map<number, string | number | boolean | (string | number | boolean)[]>) => Promise<void>;
11
- export declare const append: (filePath: string, data: string | number | (string | number)[], startsAt?: number) => Promise<void>;
12
- export declare const remove: (filePath: string, linesToDelete: number | number[]) => Promise<void>;
48
+ /**
49
+ * Asynchronously replaces specific lines in a file based on the provided replacements map or string.
50
+ *
51
+ * @param filePath - Path of the file to modify.
52
+ * @param replacements - Map of line numbers to replacement values, or a single replacement value for all lines.
53
+ * Can be a string, number, boolean, null, array of these types, or a Record/Map of line numbers to these types.
54
+ * @returns Promise<string[]>
55
+ *
56
+ * Note: If the file doesn't exist and replacements is an object, it creates a new file with the specified replacements.
57
+ */
58
+ export declare const replace: (filePath: string, replacements: string | number | boolean | null | (string | number | boolean | null)[] | Record<number, string | boolean | number | null | (string | boolean | number | null)[]>) => Promise<string[]>;
59
+ /**
60
+ * Asynchronously appends data to a file.
61
+ *
62
+ * @param filePath - Path of the file to append to.
63
+ * @param data - Data to append. Can be a string, number, or an array of strings/numbers.
64
+ * @returns Promise<string[]>. Modifies the file by appending data.
65
+ *
66
+ */
67
+ export declare const append: (filePath: string, data: string | number | (string | number)[]) => Promise<string[]>;
68
+ /**
69
+ * Asynchronously removes specified lines from a file.
70
+ *
71
+ * @param filePath - Path of the file from which lines are to be removed.
72
+ * @param linesToDelete - A single line number or an array of line numbers to be deleted.
73
+ * @returns Promise<string[]>. Modifies the file by removing specified lines.
74
+ *
75
+ * Note: Creates a temporary file during the process and replaces the original file with it after removing lines.
76
+ */
77
+ export declare const remove: (filePath: string, linesToDelete: number | number[]) => Promise<string[]>;
78
+ /**
79
+ * Asynchronously counts the number of lines in a file.
80
+ *
81
+ * @param filePath - Path of the file to count lines in.
82
+ * @returns Promise<number>. The number of lines in the file.
83
+ *
84
+ * Note: Reads through the file line by line to count the total number of lines.
85
+ */
13
86
  export declare const count: (filePath: string) => Promise<number>;
87
+ /**
88
+ * Asynchronously searches a file for lines matching specified criteria, using comparison and logical operators.
89
+ *
90
+ * @param filePath - Path of the file to search.
91
+ * @param operator - Comparison operator(s) for evaluation (e.g., '=', '!=', '>', '<').
92
+ * @param comparedAtValue - Value(s) to compare each line against.
93
+ * @param logicalOperator - Optional logical operator ('and' or 'or') for combining multiple comparisons.
94
+ * @param fieldType - Optional type of the field to guide comparison.
95
+ * @param fieldChildrenType - Optional type for child elements in array inputs.
96
+ * @param limit - Optional limit on the number of results to return.
97
+ * @param offset - Optional offset to start returning results from.
98
+ * @param readWholeFile - Flag to indicate whether to continue reading the file after reaching the limit.
99
+ * @param secretKey - Optional secret key for decoding, can be a string or Buffer.
100
+ * @returns Promise resolving to a tuple:
101
+ * 1. Record of line numbers and their content that match the criteria or null if none.
102
+ * 2. The count of found items or processed items based on the 'readWholeFile' flag.
103
+ *
104
+ * Note: Decodes each line for comparison and can handle complex queries with multiple conditions.
105
+ */
14
106
  export declare const search: (filePath: string, operator: ComparisonOperator | ComparisonOperator[], comparedAtValue: string | number | boolean | null | (string | number | boolean | null)[], logicalOperator?: "and" | "or", fieldType?: FieldType | FieldType[], fieldChildrenType?: FieldType | FieldType[], limit?: number, offset?: number, readWholeFile?: boolean, secretKey?: string | Buffer) => Promise<[
15
107
  Record<number, string | number | boolean | (string | number | boolean | null)[] | null> | null,
16
108
  number
17
109
  ]>;
110
+ /**
111
+ * Asynchronously calculates the sum of numerical values from specified lines in a file.
112
+ *
113
+ * @param filePath - Path of the file to read.
114
+ * @param lineNumbers - Optional specific line number(s) to include in the sum. If not provided, sums all lines.
115
+ * @returns Promise<number>. The sum of numerical values from the specified lines.
116
+ *
117
+ * Note: Decodes each line as a number using the 'decode' function. Non-numeric lines contribute 0 to the sum.
118
+ */
18
119
  export declare const sum: (filePath: string, lineNumbers?: number | number[]) => Promise<number>;
120
+ /**
121
+ * Asynchronously finds the maximum numerical value from specified lines in a file.
122
+ *
123
+ * @param filePath - Path of the file to read.
124
+ * @param lineNumbers - Optional specific line number(s) to consider for finding the maximum value. If not provided, considers all lines.
125
+ * @returns Promise<number>. The maximum numerical value found in the specified lines.
126
+ *
127
+ * Note: Decodes each line as a number using the 'decode' function. Considers only numerical values for determining the maximum.
128
+ */
19
129
  export declare const max: (filePath: string, lineNumbers?: number | number[]) => Promise<number>;
130
+ /**
131
+ * Asynchronously finds the minimum numerical value from specified lines in a file.
132
+ *
133
+ * @param filePath - Path of the file to read.
134
+ * @param lineNumbers - Optional specific line number(s) to consider for finding the minimum value. If not provided, considers all lines.
135
+ * @returns Promise<number>. The minimum numerical value found in the specified lines.
136
+ *
137
+ * Note: Decodes each line as a number using the 'decode' function. Considers only numerical values for determining the minimum.
138
+ */
20
139
  export declare const min: (filePath: string, lineNumbers?: number | number[]) => Promise<number>;
140
+ /**
141
+ * Asynchronously sorts the lines in a file in the specified direction.
142
+ *
143
+ * @param filePath - Path of the file to be sorted.
144
+ * @param sortDirection - Direction for sorting: 1 or 'asc' for ascending, -1 or 'desc' for descending.
145
+ * @param lineNumbers - Optional specific line numbers to sort. If not provided, sorts all lines.
146
+ * @param _lineNumbersPerChunk - Optional parameter for handling large files, specifying the number of lines per chunk.
147
+ * @returns Promise<void>. Modifies the file by sorting specified lines.
148
+ *
149
+ * Note: The sorting is applied either to the entire file or to the specified lines. Large files are handled in chunks.
150
+ */
21
151
  export declare const sort: (filePath: string, sortDirection: 1 | -1 | "asc" | "desc", lineNumbers?: number | number[], _lineNumbersPerChunk?: number) => Promise<void>;
22
152
  export default class File {
23
153
  static get: (filePath: string, lineNumbers?: number | number[] | undefined, fieldType?: FieldType | FieldType[] | undefined, fieldChildrenType?: FieldType | FieldType[] | undefined, secretKey?: string | Buffer | undefined) => Promise<[Record<number, string | number | boolean | (string | number | boolean | (string | number | boolean)[] | null)[] | null> | null, number]>;
24
- static remove: (filePath: string, linesToDelete: number | number[]) => Promise<void>;
154
+ static remove: (filePath: string, linesToDelete: number | number[]) => Promise<string[]>;
25
155
  static search: (filePath: string, operator: ComparisonOperator | ComparisonOperator[], comparedAtValue: string | number | boolean | (string | number | boolean | null)[] | null, logicalOperator?: "and" | "or" | undefined, fieldType?: FieldType | FieldType[] | undefined, fieldChildrenType?: FieldType | FieldType[] | undefined, limit?: number | undefined, offset?: number | undefined, readWholeFile?: boolean | undefined, secretKey?: string | Buffer | undefined) => Promise<[Record<number, string | number | boolean | (string | number | boolean | null)[] | null> | null, number]>;
26
- static replace: (filePath: string, replacements: string | number | boolean | (string | number | boolean | null)[] | Record<number, string | number | boolean | (string | number | boolean | null)[] | null> | Map<number, string | number | boolean | (string | number | boolean)[]> | null) => Promise<void>;
156
+ static replace: (filePath: string, replacements: string | number | boolean | (string | number | boolean | null)[] | Record<number, string | number | boolean | (string | number | boolean | null)[] | null> | null) => Promise<string[]>;
27
157
  static count: (filePath: string) => Promise<number>;
28
158
  static encode: (input: string | number | boolean | (string | number | boolean | null)[] | null, secretKey?: string | Buffer | undefined) => string | number | boolean | null;
29
159
  static decode: (input: string | number | null, fieldType?: FieldType | FieldType[] | undefined, fieldChildrenType?: FieldType | FieldType[] | undefined, secretKey?: string | Buffer | undefined) => string | number | boolean | (string | number | boolean | null)[] | null;
@@ -31,5 +161,5 @@ export default class File {
31
161
  static sum: (filePath: string, lineNumbers?: number | number[] | undefined) => Promise<number>;
32
162
  static min: (filePath: string, lineNumbers?: number | number[] | undefined) => Promise<number>;
33
163
  static max: (filePath: string, lineNumbers?: number | number[] | undefined) => Promise<number>;
34
- static append: (filePath: string, data: string | number | (string | number)[], startsAt?: number) => Promise<void>;
164
+ static append: (filePath: string, data: string | number | (string | number)[]) => Promise<string[]>;
35
165
  }
package/dist/file.js CHANGED
@@ -1,23 +1,46 @@
1
- import { open, rename, stat, writeFile, appendFile, } from "node:fs/promises";
1
+ import { open, access, constants, writeFile, } from "node:fs/promises";
2
2
  import { createInterface } from "node:readline";
3
+ import { Transform } from "node:stream";
4
+ import { pipeline } from "node:stream/promises";
3
5
  import { detectFieldType, isArrayOfArrays, isNumber, isObject, } from "./utils.js";
4
6
  import { encodeID, comparePassword } from "./utils.server.js";
7
+ /**
8
+ * Creates a readline interface for a given file handle.
9
+ *
10
+ * @param fileHandle - The file handle from which to create a read stream.
11
+ * @returns A readline.Interface instance configured with the provided file stream.
12
+ */
5
13
  const readLineInternface = (fileHandle) => {
6
- return createInterface({
7
- input: fileHandle.createReadStream(),
8
- crlfDelay: Infinity,
9
- });
14
+ const [major, minor, patch] = process.versions.node.split(".").map(Number);
15
+ return major > 18 || (major === 18 && minor >= 11)
16
+ ? fileHandle.readLines()
17
+ : createInterface({
18
+ input: fileHandle.createReadStream(), // .pipe(createInflate())
19
+ crlfDelay: Infinity,
20
+ });
10
21
  };
22
+ /**
23
+ * Checks if a file or directory exists at the specified path.
24
+ *
25
+ * @param path - The path to the file or directory.
26
+ * @returns A Promise that resolves to true if the file/directory exists, false otherwise.
27
+ */
11
28
  export const isExists = async (path) => {
12
29
  try {
13
- await stat(path);
30
+ await access(path, constants.R_OK | constants.W_OK);
14
31
  return true;
15
32
  }
16
33
  catch {
17
34
  return false;
18
35
  }
19
36
  };
20
- const delimiters = [",", "|", "&", "$", "#", "@", "^", "%", ":", "!", ";"];
37
+ const delimiters = [",", "|", "&", "$", "#", "@", "^", ":", "!", ";"];
38
+ /**
39
+ * Secures input by encoding/escaping characters.
40
+ *
41
+ * @param input - String, number, boolean, or null.
42
+ * @returns Encoded string for true/false, special characters in strings, or original input.
43
+ */
21
44
  const secureString = (input) => {
22
45
  if (["true", "false"].includes(String(input)))
23
46
  return input ? 1 : 0;
@@ -32,7 +55,6 @@ const secureString = (input) => {
32
55
  .replaceAll("#", "%23")
33
56
  .replaceAll("@", "%40")
34
57
  .replaceAll("^", "%5E")
35
- .replaceAll("%", "%25")
36
58
  .replaceAll(":", "%3A")
37
59
  .replaceAll("!", "%21")
38
60
  .replaceAll(";", "%3B")
@@ -40,7 +62,20 @@ const secureString = (input) => {
40
62
  .replaceAll("\r", "\\r")
41
63
  : input;
42
64
  };
65
+ /**
66
+ * Secures each element in an array or a single value using secureString.
67
+ *
68
+ * @param arr_str - An array or a single value of any type.
69
+ * @returns An array with each element secured, or a single secured value.
70
+ */
43
71
  const secureArray = (arr_str) => Array.isArray(arr_str) ? arr_str.map(secureArray) : secureString(arr_str);
72
+ /**
73
+ * Joins elements of a multidimensional array into a string.
74
+ *
75
+ * @param arr - A multidimensional array or a single level array.
76
+ * @param delimiter_index - Index for selecting delimiter, defaults to 0.
77
+ * @returns Joined string of array elements with appropriate delimiters.
78
+ */
44
79
  const joinMultidimensionalArray = (arr, delimiter_index = 0) => {
45
80
  delimiter_index++;
46
81
  if (isArrayOfArrays(arr))
@@ -48,11 +83,26 @@ const joinMultidimensionalArray = (arr, delimiter_index = 0) => {
48
83
  delimiter_index--;
49
84
  return arr.join(delimiters[delimiter_index]);
50
85
  };
86
+ /**
87
+ * Encodes the input using 'secureString' and 'joinMultidimensionalArray' functions.
88
+ * If the input is an array, it is first secured and then joined into a string.
89
+ * If the input is a single value, it is directly secured.
90
+ *
91
+ * @param input - A value or array of values (string, number, boolean, null).
92
+ * @param secretKey - Optional secret key for encoding, can be a string or Buffer.
93
+ * @returns The secured and/or joined string.
94
+ */
51
95
  export const encode = (input, secretKey) => {
52
96
  return Array.isArray(input)
53
97
  ? joinMultidimensionalArray(secureArray(input))
54
98
  : secureString(input);
55
99
  };
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
+ */
56
106
  const unSecureString = (input) => input
57
107
  .replaceAll("&lt;", "<")
58
108
  .replaceAll("&gt;", ">")
@@ -63,13 +113,25 @@ const unSecureString = (input) => input
63
113
  .replaceAll("%23", "#")
64
114
  .replaceAll("%40", "@")
65
115
  .replaceAll("%5E", "^")
66
- .replaceAll("%25", "%")
67
116
  .replaceAll("%3A", ":")
68
117
  .replaceAll("%21", "!")
69
118
  .replaceAll("%3B", ";")
70
119
  .replaceAll("\\n", "\n")
71
120
  .replaceAll("\\r", "\r") || null;
121
+ /**
122
+ * Decodes each element in an array or a single value using unSecureString.
123
+ *
124
+ * @param arr_str - An array or a single value of any type.
125
+ * @returns An array with each element decoded, or a single decoded value.
126
+ */
72
127
  const unSecureArray = (arr_str) => Array.isArray(arr_str) ? arr_str.map(unSecureArray) : unSecureString(arr_str);
128
+ /**
129
+ * Reverses the process of 'joinMultidimensionalArray', splitting a string back into a multidimensional array.
130
+ * It identifies delimiters used in the joined string and applies them recursively to reconstruct the original array structure.
131
+ *
132
+ * @param joinedString - A string, array, or multidimensional array.
133
+ * @returns Original array structure before joining, or the input if no delimiters are found.
134
+ */
73
135
  const reverseJoinMultidimensionalArray = (joinedString) => {
74
136
  const reverseJoinMultidimensionalArrayHelper = (arr, delimiter) => Array.isArray(arr)
75
137
  ? arr.map((ar) => reverseJoinMultidimensionalArrayHelper(ar, delimiter))
@@ -82,6 +144,16 @@ const reverseJoinMultidimensionalArray = (joinedString) => {
82
144
  }
83
145
  return joinedString;
84
146
  };
147
+ /**
148
+ * Decodes a value based on specified field types and optional secret key.
149
+ * Handles different data types and structures, including nested arrays.
150
+ *
151
+ * @param value - The value to be decoded, can be string, number, or array.
152
+ * @param fieldType - Optional type of the field to guide decoding (e.g., 'number', 'boolean').
153
+ * @param fieldChildrenType - Optional type for children elements, used for arrays.
154
+ * @param secretKey - Optional secret key for decoding, can be string or Buffer.
155
+ * @returns Decoded value, transformed according to the specified field type(s).
156
+ */
85
157
  const decodeHelper = (value, fieldType, fieldChildrenType, secretKey) => {
86
158
  if (Array.isArray(value) && fieldType !== "array")
87
159
  return value.map((v) => decodeHelper(v, fieldType, fieldChildrenType, secretKey));
@@ -108,6 +180,16 @@ const decodeHelper = (value, fieldType, fieldChildrenType, secretKey) => {
108
180
  return value;
109
181
  }
110
182
  };
183
+ /**
184
+ * Decodes the input based on the specified field type(s) and an optional secret key.
185
+ * Handles different formats of input, including strings, numbers, and their array representations.
186
+ *
187
+ * @param input - The input to be decoded, can be a string, number, or null.
188
+ * @param fieldType - Optional type of the field to guide decoding (e.g., 'number', 'boolean').
189
+ * @param fieldChildrenType - Optional type for child elements in array inputs.
190
+ * @param secretKey - Optional secret key for decoding, can be a string or Buffer.
191
+ * @returns Decoded value as a string, number, boolean, or array of these, or null if no fieldType or input is null/empty.
192
+ */
111
193
  export const decode = (input, fieldType, fieldChildrenType, secretKey) => {
112
194
  if (!fieldType)
113
195
  return null;
@@ -121,6 +203,19 @@ export const decode = (input, fieldType, fieldChildrenType, secretKey) => {
121
203
  : unSecureString(input)
122
204
  : input, fieldType, fieldChildrenType, secretKey);
123
205
  };
206
+ /**
207
+ * Asynchronously reads and decodes data from a file at specified line numbers.
208
+ * Decodes each line based on specified field types and an optional secret key.
209
+ *
210
+ * @param filePath - Path of the file to be read.
211
+ * @param lineNumbers - Optional line number(s) to read from the file. If -1, reads the last line.
212
+ * @param fieldType - Optional type of the field to guide decoding (e.g., 'number', 'boolean').
213
+ * @param fieldChildrenType - Optional type for child elements in array inputs.
214
+ * @param secretKey - Optional secret key for decoding, can be a string or Buffer.
215
+ * @returns Promise resolving to a tuple:
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
+ */
124
219
  export const get = async (filePath, lineNumbers, fieldType, fieldChildrenType, secretKey) => {
125
220
  const fileHandle = await open(filePath, "r"), rl = readLineInternface(fileHandle);
126
221
  let lines = new Map(), lineCount = 0;
@@ -151,71 +246,122 @@ export const get = async (filePath, lineNumbers, fieldType, fieldChildrenType, s
151
246
  await fileHandle.close();
152
247
  return [lines.size ? Object.fromEntries(lines) : null, lineCount];
153
248
  };
249
+ /**
250
+ * Asynchronously replaces specific lines in a file based on the provided replacements map or string.
251
+ *
252
+ * @param filePath - Path of the file to modify.
253
+ * @param replacements - Map of line numbers to replacement values, or a single replacement value for all lines.
254
+ * Can be a string, number, boolean, null, array of these types, or a Record/Map of line numbers to these types.
255
+ * @returns Promise<string[]>
256
+ *
257
+ * Note: If the file doesn't exist and replacements is an object, it creates a new file with the specified replacements.
258
+ */
154
259
  export const replace = async (filePath, replacements) => {
155
- if (await isExists(filePath)) {
156
- let lineCount = 0;
157
- const fileHandle = await open(filePath, "r"), fileTempPath = `${filePath.replace(".inib", "")}-${Date.now()}.tmp`, fileTempHandle = await open(fileTempPath, "w"), rl = readLineInternface(fileHandle), writeStream = fileTempHandle.createWriteStream();
158
- if (isObject(replacements)) {
159
- if (!(replacements instanceof Map))
160
- replacements = new Map(Object.entries(replacements));
161
- for await (const line of rl) {
260
+ let lineCount = 0;
261
+ const fileHandle = await open(filePath, "r"), fileTempPath = filePath.replace(/([^/]+)\/?$/, `.tmp/${Date.now()}-$1`), fileTempHandle = await open(fileTempPath, "w"), rl = readLineInternface(fileHandle);
262
+ if (isObject(replacements))
263
+ await pipeline(rl, new Transform({
264
+ transform(line, encoding, callback) {
162
265
  lineCount++;
163
- writeStream.write((replacements.has(lineCount.toString())
164
- ? replacements.get(lineCount.toString())
266
+ callback(null, (replacements.hasOwnProperty(lineCount)
267
+ ? replacements[lineCount]
165
268
  : line) + "\n");
166
- }
167
- const newLinesNumbers = new Set([...replacements.keys()].filter((num) => num > lineCount));
168
- if (newLinesNumbers.size) {
169
- if (Math.min(...newLinesNumbers) - lineCount - 1 > 1)
170
- writeStream.write("\n".repeat(Math.min(...newLinesNumbers) - lineCount - 1));
171
- for await (const newLineNumber of newLinesNumbers)
172
- writeStream.write(replacements.get(newLineNumber.toString()) + "\n");
173
- }
174
- }
175
- else
176
- for await (const _line of rl)
177
- writeStream.write(replacements + "\n");
269
+ },
270
+ final(callback) {
271
+ const newLinesNumbers = Object.entries(replacements)
272
+ .map(([key, value]) => Number(key))
273
+ .filter((num) => num > lineCount);
274
+ if (newLinesNumbers.length) {
275
+ if (Math.min(...newLinesNumbers) - lineCount - 1 > 1)
276
+ this.push("\n".repeat(Math.min(...newLinesNumbers) - lineCount - 1));
277
+ this.push(newLinesNumbers
278
+ .map((newLineNumber) => replacements[newLineNumber])
279
+ .join("\n") + "\n");
280
+ }
281
+ callback();
282
+ },
283
+ }),
284
+ // createDeflate(),
285
+ fileTempHandle.createWriteStream());
286
+ else
287
+ await pipeline(rl, new Transform({
288
+ transform(line, encoding, callback) {
289
+ lineCount++;
290
+ callback(null, replacements + "\n");
291
+ },
292
+ }),
293
+ // createDeflate(),
294
+ fileTempHandle.createWriteStream());
295
+ await fileHandle.close();
296
+ await fileTempHandle.close();
297
+ return [fileTempPath, filePath];
298
+ };
299
+ /**
300
+ * Asynchronously appends data to a file.
301
+ *
302
+ * @param filePath - Path of the file to append to.
303
+ * @param data - Data to append. Can be a string, number, or an array of strings/numbers.
304
+ * @returns Promise<string[]>. Modifies the file by appending data.
305
+ *
306
+ */
307
+ export const append = async (filePath, data) => {
308
+ const fileTempPath = filePath.replace(/([^/]+)\/?$/, `.tmp/${Date.now()}-$1`);
309
+ if (await isExists(filePath)) {
310
+ const fileHandle = await open(filePath, "r"), fileTempHandle = await open(fileTempPath, "w"), rl = readLineInternface(fileHandle);
311
+ await pipeline(rl, new Transform({
312
+ transform(line, encoding, callback) {
313
+ callback(null, `${line}\n`);
314
+ },
315
+ final(callback) {
316
+ this.push((Array.isArray(data) ? data.join("\n") : data) + "\n");
317
+ callback();
318
+ },
319
+ }),
320
+ // createDeflate(),
321
+ fileTempHandle.createWriteStream());
178
322
  await fileHandle.close();
179
323
  await fileTempHandle.close();
180
- await rename(fileTempPath, filePath);
181
- }
182
- else if (isObject(replacements)) {
183
- if (!(replacements instanceof Map))
184
- replacements = new Map(Object.entries(replacements).map(([key, value]) => [Number(key), value]));
185
- await writeFile(filePath, (Math.min(...replacements.keys()) - 1 > 1
186
- ? "\n".repeat(Math.min(...replacements.keys()) - 1)
187
- : "") +
188
- Array.from(new Map([...replacements.entries()].sort(([keyA], [keyB]) => keyA - keyB)).values()).join("\n") +
189
- "\n");
190
324
  }
325
+ else
326
+ await writeFile(fileTempPath, `${Array.isArray(data) ? data.join("\n") : data}\n`);
327
+ return [fileTempPath, filePath];
191
328
  };
192
- export const append = async (filePath, data, startsAt = 1) => {
193
- let currentNumberOfLines = 0;
194
- const doesFileExists = await isExists(filePath);
195
- if (doesFileExists)
196
- currentNumberOfLines = await count(filePath);
197
- await appendFile(filePath, (currentNumberOfLines > 0
198
- ? startsAt - currentNumberOfLines - 1 > 0
199
- ? "\n".repeat(startsAt - currentNumberOfLines - 1)
200
- : ""
201
- : "") +
202
- (Array.isArray(data) ? data.join("\n") : data) +
203
- "\n");
204
- };
329
+ /**
330
+ * Asynchronously removes specified lines from a file.
331
+ *
332
+ * @param filePath - Path of the file from which lines are to be removed.
333
+ * @param linesToDelete - A single line number or an array of line numbers to be deleted.
334
+ * @returns Promise<string[]>. Modifies the file by removing specified lines.
335
+ *
336
+ * Note: Creates a temporary file during the process and replaces the original file with it after removing lines.
337
+ */
205
338
  export const remove = async (filePath, linesToDelete) => {
206
339
  let lineCount = 0;
207
- const fileHandle = await open(filePath, "r"), fileTempPath = `${filePath.replace(".inib", "")}-${Date.now()}.tmp`, fileTempHandle = await open(fileTempPath, "w"), linesToDeleteArray = new Set(Array.isArray(linesToDelete)
340
+ const fileHandle = await open(filePath, "r"), fileTempPath = filePath.replace(/([^/]+)\/?$/, `.tmp/${Date.now()}-$1`), fileTempHandle = await open(fileTempPath, "w"), linesToDeleteArray = new Set(Array.isArray(linesToDelete)
208
341
  ? linesToDelete.map(Number)
209
- : [Number(linesToDelete)]), rl = readLineInternface(fileHandle), writeStream = fileTempHandle.createWriteStream();
210
- for await (const line of rl) {
211
- lineCount++;
212
- if (!linesToDeleteArray.has(lineCount))
213
- writeStream.write(`${line}\n`);
214
- }
215
- await rename(fileTempPath, filePath);
342
+ : [Number(linesToDelete)]), rl = readLineInternface(fileHandle);
343
+ await pipeline(rl, new Transform({
344
+ transform(line, encoding, callback) {
345
+ lineCount++;
346
+ if (!linesToDeleteArray.has(lineCount))
347
+ callback(null, `${line}\n`);
348
+ callback();
349
+ },
350
+ }),
351
+ // createDeflate(),
352
+ fileTempHandle.createWriteStream());
216
353
  await fileTempHandle.close();
217
354
  await fileHandle.close();
355
+ return [fileTempPath, filePath];
218
356
  };
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
+ */
219
365
  export const count = async (filePath) => {
220
366
  // return Number((await exec(`wc -l < ${filePath}`)).stdout.trim());
221
367
  let lineCount = 0;
@@ -225,6 +371,18 @@ export const count = async (filePath) => {
225
371
  await fileHandle.close();
226
372
  return lineCount;
227
373
  };
374
+ /**
375
+ * Evaluates a comparison between two values based on a specified operator and field types.
376
+ *
377
+ * @param operator - The comparison operator (e.g., '=', '!=', '>', '<', '>=', '<=', '[]', '![]', '*', '!*').
378
+ * @param originalValue - The value to compare, can be a single value or an array of values.
379
+ * @param comparedAtValue - The value or values to compare against.
380
+ * @param fieldType - Optional type of the field to guide comparison (e.g., 'password', 'boolean').
381
+ * @param fieldChildrenType - Optional type for child elements in array inputs.
382
+ * @returns boolean - Result of the comparison operation.
383
+ *
384
+ * Note: Handles various data types and comparison logic, including special handling for passwords and regex patterns.
385
+ */
228
386
  const handleComparisonOperator = (operator, originalValue, comparedAtValue, fieldType, fieldChildrenType) => {
229
387
  if (Array.isArray(fieldType))
230
388
  fieldType = detectFieldType(String(originalValue), fieldType);
@@ -284,6 +442,25 @@ const handleComparisonOperator = (operator, originalValue, comparedAtValue, fiel
284
442
  throw new Error(operator);
285
443
  }
286
444
  };
445
+ /**
446
+ * Asynchronously searches a file for lines matching specified criteria, using comparison and logical operators.
447
+ *
448
+ * @param filePath - Path of the file to search.
449
+ * @param operator - Comparison operator(s) for evaluation (e.g., '=', '!=', '>', '<').
450
+ * @param comparedAtValue - Value(s) to compare each line against.
451
+ * @param logicalOperator - Optional logical operator ('and' or 'or') for combining multiple comparisons.
452
+ * @param fieldType - Optional type of the field to guide comparison.
453
+ * @param fieldChildrenType - Optional type for child elements in array inputs.
454
+ * @param limit - Optional limit on the number of results to return.
455
+ * @param offset - Optional offset to start returning results from.
456
+ * @param readWholeFile - Flag to indicate whether to continue reading the file after reaching the limit.
457
+ * @param secretKey - Optional secret key for decoding, can be a string or Buffer.
458
+ * @returns Promise resolving to a tuple:
459
+ * 1. Record of line numbers and their content that match the criteria or null if none.
460
+ * 2. The count of found items or processed items based on the 'readWholeFile' flag.
461
+ *
462
+ * Note: Decodes each line for comparison and can handle complex queries with multiple conditions.
463
+ */
287
464
  export const search = async (filePath, operator, comparedAtValue, logicalOperator, fieldType, fieldChildrenType, limit, offset, readWholeFile, secretKey) => {
288
465
  let RETURN = new Map(), lineCount = 0, foundItems = 0;
289
466
  const fileHandle = await open(filePath, "r"), rl = readLineInternface(fileHandle);
@@ -314,6 +491,15 @@ export const search = async (filePath, operator, comparedAtValue, logicalOperato
314
491
  ? [Object.fromEntries(RETURN), readWholeFile ? foundItems : foundItems - 1]
315
492
  : [null, 0];
316
493
  };
494
+ /**
495
+ * Asynchronously calculates the sum of numerical values from specified lines in a file.
496
+ *
497
+ * @param filePath - Path of the file to read.
498
+ * @param lineNumbers - Optional specific line number(s) to include in the sum. If not provided, sums all lines.
499
+ * @returns Promise<number>. The sum of numerical values from the specified lines.
500
+ *
501
+ * Note: Decodes each line as a number using the 'decode' function. Non-numeric lines contribute 0 to the sum.
502
+ */
317
503
  export const sum = async (filePath, lineNumbers) => {
318
504
  let sum = 0;
319
505
  const fileHandle = await open(filePath, "r"), rl = readLineInternface(fileHandle);
@@ -336,6 +522,15 @@ export const sum = async (filePath, lineNumbers) => {
336
522
  await fileHandle.close();
337
523
  return sum;
338
524
  };
525
+ /**
526
+ * Asynchronously finds the maximum numerical value from specified lines in a file.
527
+ *
528
+ * @param filePath - Path of the file to read.
529
+ * @param lineNumbers - Optional specific line number(s) to consider for finding the maximum value. If not provided, considers all lines.
530
+ * @returns Promise<number>. The maximum numerical value found in the specified lines.
531
+ *
532
+ * Note: Decodes each line as a number using the 'decode' function. Considers only numerical values for determining the maximum.
533
+ */
339
534
  export const max = async (filePath, lineNumbers) => {
340
535
  let max = 0;
341
536
  const fileHandle = await open(filePath, "r"), rl = readLineInternface(fileHandle);
@@ -363,6 +558,15 @@ export const max = async (filePath, lineNumbers) => {
363
558
  await fileHandle.close();
364
559
  return max;
365
560
  };
561
+ /**
562
+ * Asynchronously finds the minimum numerical value from specified lines in a file.
563
+ *
564
+ * @param filePath - Path of the file to read.
565
+ * @param lineNumbers - Optional specific line number(s) to consider for finding the minimum value. If not provided, considers all lines.
566
+ * @returns Promise<number>. The minimum numerical value found in the specified lines.
567
+ *
568
+ * Note: Decodes each line as a number using the 'decode' function. Considers only numerical values for determining the minimum.
569
+ */
366
570
  export const min = async (filePath, lineNumbers) => {
367
571
  let min = 0;
368
572
  const fileHandle = await open(filePath, "r"), rl = readLineInternface(fileHandle);
@@ -390,6 +594,17 @@ export const min = async (filePath, lineNumbers) => {
390
594
  await fileHandle.close();
391
595
  return min;
392
596
  };
597
+ /**
598
+ * Asynchronously sorts the lines in a file in the specified direction.
599
+ *
600
+ * @param filePath - Path of the file to be sorted.
601
+ * @param sortDirection - Direction for sorting: 1 or 'asc' for ascending, -1 or 'desc' for descending.
602
+ * @param lineNumbers - Optional specific line numbers to sort. If not provided, sorts all lines.
603
+ * @param _lineNumbersPerChunk - Optional parameter for handling large files, specifying the number of lines per chunk.
604
+ * @returns Promise<void>. Modifies the file by sorting specified lines.
605
+ *
606
+ * Note: The sorting is applied either to the entire file or to the specified lines. Large files are handled in chunks.
607
+ */
393
608
  export const sort = async (filePath, sortDirection, lineNumbers, _lineNumbersPerChunk = 100000) => { };
394
609
  export default class File {
395
610
  static get = get;
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { unlink, rename, readFile, writeFile, mkdir, readdir, } from "node:fs/promises";
2
2
  import { existsSync, appendFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
+ // import { cpus } from "node:os";
4
5
  import { scryptSync, randomBytes } from "node:crypto";
5
6
  import File from "./file.js";
6
7
  import Utils from "./utils.js";
@@ -67,6 +68,8 @@ export default class Inibase {
67
68
  const TablePath = join(this.folder, this.database, tableName), TableSchemaPath = join(TablePath, "schema.json");
68
69
  if (!(await File.isExists(TablePath)))
69
70
  await mkdir(TablePath, { recursive: true });
71
+ if (!(await File.isExists(join(TablePath, ".tmp"))))
72
+ await mkdir(join(TablePath, ".tmp"));
70
73
  if (await File.isExists(TableSchemaPath)) {
71
74
  // update columns files names based on field id
72
75
  const schemaToIdsPath = (schema, prefix = "") => {
@@ -721,20 +724,21 @@ export default class Inibase {
721
724
  let RETURN;
722
725
  if (!schema)
723
726
  throw this.throwError("NO_SCHEMA", tableName);
724
- const idFilePath = join(this.folder, this.database, tableName, "id.inib");
725
- let [last_line_number, last_id] = (await File.isExists(idFilePath))
726
- ? Object.entries((await File.get(idFilePath, -1, "number", undefined, this.salt))[0] ??
727
- {})[0].map(Number) ?? [0, 0]
728
- : [0, 0];
727
+ const idFilePath = join(this.folder, this.database, tableName, "id.inib"), idCacheFilePath = join(this.folder, this.database, tableName, ".tmp", "lastId.inib");
728
+ let lastId = (await File.isExists(idFilePath))
729
+ ? Number((await File.isExists(idCacheFilePath))
730
+ ? (await readFile(idCacheFilePath)).toString()
731
+ : Object.entries((await File.get(idFilePath, -1, "number", undefined, this.salt))[0] ?? {})[0][1] ?? 0)
732
+ : 0;
729
733
  if (Utils.isArrayOfObjects(data))
730
734
  RETURN = data.map(({ id, updatedAt, createdAt, ...rest }) => ({
731
- id: ++last_id,
735
+ id: ++lastId,
732
736
  ...rest,
733
737
  createdAt: Date.now(),
734
738
  }));
735
739
  else
736
740
  RETURN = (({ id, updatedAt, createdAt, ...rest }) => ({
737
- id: ++last_id,
741
+ id: ++lastId,
738
742
  ...rest,
739
743
  createdAt: Date.now(),
740
744
  }))(data);
@@ -742,16 +746,11 @@ export default class Inibase {
742
746
  throw this.throwError("NO_DATA");
743
747
  RETURN = this.formatData(RETURN, schema);
744
748
  const pathesContents = this.joinPathesContents(join(this.folder, this.database, tableName), RETURN);
745
- last_line_number += 1;
749
+ const renameList = [];
746
750
  for await (const [path, content] of Object.entries(pathesContents))
747
- await File.append(path,
748
- // Array.isArray(content)
749
- // ? content.reduce((obj, value, index) => {
750
- // obj[last_line_number + index] = value;
751
- // return obj;
752
- // }, {})
753
- // : { [last_line_number]: content }
754
- content, last_line_number);
751
+ renameList.push(await File.append(path, content));
752
+ await Promise.all(renameList.map(([tempPath, filePath]) => rename(tempPath, filePath)));
753
+ await writeFile(idCacheFilePath, lastId.toString());
755
754
  if (returnPostedData)
756
755
  return this.get(tableName, Utils.isArrayOfObjects(RETURN)
757
756
  ? RETURN.map((data) => Number(data.id))
@@ -822,8 +821,10 @@ export default class Inibase {
822
821
  [lineNum]: Array.isArray(content) ? content[index] : content,
823
822
  }), {}),
824
823
  ]));
824
+ const renameList = [];
825
825
  for await (const [path, content] of Object.entries(pathesContents))
826
- await File.replace(path, content);
826
+ renameList.push(await File.replace(path, content));
827
+ await Promise.all(renameList.map(([tempPath, filePath]) => rename(tempPath, filePath)));
827
828
  if (returnPostedData)
828
829
  return this.get(tableName, where, options, !Array.isArray(where));
829
830
  }
@@ -847,8 +848,7 @@ export default class Inibase {
847
848
  if (!where) {
848
849
  const files = (await readdir(join(this.folder, this.database, tableName)))?.filter((fileName) => fileName.endsWith(".inib"));
849
850
  if (files.length)
850
- for await (const file of files)
851
- await unlink(join(this.folder, this.database, tableName, file));
851
+ await Promise.all(files.map((file) => unlink(join(this.folder, this.database, tableName, file))));
852
852
  return "*";
853
853
  }
854
854
  else if ((Array.isArray(where) &&
@@ -871,8 +871,10 @@ export default class Inibase {
871
871
  _id = Object.entries((await File.get(join(this.folder, this.database, tableName, "id.inib"), where, "number", undefined, this.salt))[0] ?? {}).map(([_key, id]) => UtilsServer.encodeID(Number(id), this.salt));
872
872
  if (!_id.length)
873
873
  throw this.throwError("NO_ITEMS", tableName);
874
+ const renameList = [];
874
875
  for await (const file of files)
875
- await File.remove(join(this.folder, this.database, tableName, file), where);
876
+ renameList.push(await File.remove(join(this.folder, this.database, tableName, file), where));
877
+ await Promise.all(renameList.map(([tempPath, filePath]) => rename(tempPath, filePath)));
876
878
  return Array.isArray(_id) && _id.length === 1 ? _id[0] : _id;
877
879
  }
878
880
  }
package/dist/utils.d.ts CHANGED
@@ -1,23 +1,178 @@
1
1
  import { type FieldType } from "./index.js";
2
+ /**
3
+ * Type guard function to check if the input is an array of objects.
4
+ *
5
+ * @param input - The value to be checked.
6
+ * @returns boolean - True if the input is an array of objects, false otherwise.
7
+ *
8
+ * Note: Considers empty arrays and arrays where every element is an object.
9
+ */
2
10
  export declare const isArrayOfObjects: (input: any) => input is Record<any, any>[];
11
+ /**
12
+ * Type guard function to check if the input is an array of arrays.
13
+ *
14
+ * @param input - The value to be checked.
15
+ * @returns boolean - True if the input is an array of arrays, false otherwise.
16
+ *
17
+ * Note: Considers empty arrays and arrays where every element is also an array.
18
+ */
3
19
  export declare const isArrayOfArrays: (input: any) => input is any[][];
20
+ /**
21
+ * Type guard function to check if the input is an array of nulls or an array of arrays of nulls.
22
+ *
23
+ * @param input - The value to be checked.
24
+ * @returns boolean - True if the input is an array consisting entirely of nulls or arrays of nulls, false otherwise.
25
+ *
26
+ * Note: Recursively checks each element, allowing for nested arrays of nulls.
27
+ */
4
28
  export declare const isArrayOfNulls: (input: any) => input is null[] | null[][];
29
+ /**
30
+ * Type guard function to check if the input is an object.
31
+ *
32
+ * @param obj - The value to be checked.
33
+ * @returns boolean - True if the input is an object (excluding arrays), false otherwise.
34
+ *
35
+ * Note: Checks if the input is non-null and either has 'Object' as its constructor name or is of type 'object' without being an array.
36
+ */
5
37
  export declare const isObject: (obj: any) => obj is Record<any, any>;
38
+ /**
39
+ * Recursively merges properties from a source object into a target object. If a property exists in both, the source's value overwrites the target's.
40
+ *
41
+ * @param target - The target object to merge properties into.
42
+ * @param source - The source object from which properties are merged.
43
+ * @returns any - The modified target object with merged properties.
44
+ *
45
+ * Note: Performs a deep merge for nested objects. Non-object properties are directly overwritten.
46
+ */
6
47
  export declare const deepMerge: (target: any, source: any) => any;
48
+ /**
49
+ * Combines an array of objects into a single object. If the same key exists in multiple objects, the values are merged.
50
+ *
51
+ * @param arr - Array of objects to be combined.
52
+ * @returns Record<string, any> - A single object with combined keys and values.
53
+ *
54
+ * Note: Handles nested objects by recursively combining them. Non-object values with the same key are merged into arrays.
55
+ */
7
56
  export declare const combineObjects: (arr: Record<string, any>[]) => Record<string, any>;
57
+ /**
58
+ * Type guard function to check if the input is a number.
59
+ *
60
+ * @param input - The value to be checked.
61
+ * @returns boolean - True if the input is a number, false otherwise.
62
+ *
63
+ * Note: Validates that the input can be parsed as a float and that subtracting zero results in a number, ensuring it's a numeric value.
64
+ */
8
65
  export declare const isNumber: (input: any) => input is number;
66
+ /**
67
+ * Checks if the input is a valid email format.
68
+ *
69
+ * @param input - The value to be checked.
70
+ * @returns boolean - True if the input matches the email format, false otherwise.
71
+ *
72
+ * Note: Uses a regular expression to validate the email format, ensuring it has parts separated by '@' and contains a domain with a period.
73
+ */
9
74
  export declare const isEmail: (input: any) => boolean;
75
+ /**
76
+ * Checks if the input is a valid URL format.
77
+ *
78
+ * @param input - The value to be checked.
79
+ * @returns boolean - True if the input matches the URL format, false otherwise.
80
+ *
81
+ * Note: Validates URLs including protocols (http/https), domain names, IP addresses, ports, paths, query strings, and fragments.
82
+ * Also recognizes 'tel:' and 'mailto:' as valid URL formats, as well as strings starting with '#' without spaces.
83
+ */
10
84
  export declare const isURL: (input: any) => boolean;
85
+ /**
86
+ * Checks if the input contains HTML tags or entities.
87
+ *
88
+ * @param input - The value to be checked.
89
+ * @returns boolean - True if the input contains HTML tags or entities, false otherwise.
90
+ *
91
+ * Note: Uses a regular expression to detect HTML tags (like <tag>) and entities (like &entity;).
92
+ * Recognizes both opening and closing tags, as well as self-closing tags.
93
+ */
11
94
  export declare const isHTML: (input: any) => boolean;
95
+ /**
96
+ * Type guard function to check if the input is a string, excluding strings that match specific formats (number, boolean, email, URL, IP).
97
+ *
98
+ * @param input - The value to be checked.
99
+ * @returns boolean - True if the input is a string that doesn't match the specific formats, false otherwise.
100
+ *
101
+ * Note: Validates the input against being a number, boolean, email, URL, or IP address to ensure it's a general string.
102
+ */
12
103
  export declare const isString: (input: any) => input is string;
104
+ /**
105
+ * Checks if the input is a valid IP address format.
106
+ *
107
+ * @param input - The value to be checked.
108
+ * @returns boolean - True if the input matches the IP address format, false otherwise.
109
+ *
110
+ * Note: Uses a regular expression to validate IP addresses, ensuring they consist of four octets, each ranging from 0 to 255.
111
+ */
13
112
  export declare const isIP: (input: any) => boolean;
113
+ /**
114
+ * Type guard function to check if the input is a boolean or a string representation of a boolean.
115
+ *
116
+ * @param input - The value to be checked.
117
+ * @returns boolean - True if the input is a boolean value or 'true'/'false' strings, false otherwise.
118
+ *
119
+ * Note: Recognizes both boolean literals (true, false) and their string representations ("true", "false").
120
+ */
14
121
  export declare const isBoolean: (input: any) => input is boolean;
122
+ /**
123
+ * Type guard function to check if the input is a password based on a specific length criterion.
124
+ *
125
+ * @param input - The value to be checked.
126
+ * @returns boolean - True if the input is a string with a length of 161 characters, false otherwise.
127
+ *
128
+ * Note: Specifically checks for string length to determine if it matches the defined password length criterion.
129
+ */
15
130
  export declare const isPassword: (input: any) => input is string;
131
+ /**
132
+ * Checks if the input can be converted to a valid date.
133
+ *
134
+ * @param input - The input to be checked, can be of any type.
135
+ * @returns A boolean indicating whether the input is a valid date.
136
+ */
16
137
  export declare const isDate: (input: any) => boolean;
138
+ /**
139
+ * Checks if the input is a valid ID.
140
+ *
141
+ * @param input - The input to be checked, can be of any type.
142
+ * @returns A boolean indicating whether the input is a string representing a valid ID of length 32.
143
+ */
17
144
  export declare const isValidID: (input: any) => input is string;
145
+ /**
146
+ * Identifies and returns properties that have changed between two objects.
147
+ *
148
+ * @param obj1 - The first object for comparison, with string keys and values.
149
+ * @param obj2 - The second object for comparison, with string keys and values.
150
+ * @returns A record of changed properties with original values from obj1 and new values from obj2, or null if no changes are found.
151
+ */
18
152
  export declare const findChangedProperties: (obj1: Record<string, string>, obj2: Record<string, string>) => Record<string, string> | null;
153
+ /**
154
+ * Detects the field type of an input based on available types.
155
+ *
156
+ * @param input - The input whose field type is to be detected.
157
+ * @param availableTypes - An array of potential field types to consider.
158
+ * @returns The detected field type as a string, or undefined if no matching type is found.
159
+ */
19
160
  export declare const detectFieldType: (input: any, availableTypes: FieldType[]) => FieldType | undefined;
161
+ /**
162
+ * Validates if the given value matches the specified field type(s).
163
+ *
164
+ * @param value - The value to be validated.
165
+ * @param fieldType - The expected field type or an array of possible field types.
166
+ * @param fieldChildrenType - Optional; the expected type(s) of children elements, used if the field type is an array.
167
+ * @returns A boolean indicating whether the value matches the specified field type(s).
168
+ */
20
169
  export declare const validateFieldType: (value: any, fieldType: FieldType | FieldType[], fieldChildrenType?: FieldType | FieldType[]) => boolean;
170
+ /**
171
+ * Converts a nested object to dot notation, flattening the object's structure.
172
+ *
173
+ * @param input - The input object to be converted.
174
+ * @returns A flattened object using dot notation for keys.
175
+ */
21
176
  export declare const objectToDotNotation: (input: Record<string, any>) => Record<string, string | number | (string | number)[]>;
22
177
  export default class Utils {
23
178
  static isNumber: (input: any) => input is number;
package/dist/utils.js CHANGED
@@ -1,9 +1,50 @@
1
+ /**
2
+ * Type guard function to check if the input is an array of objects.
3
+ *
4
+ * @param input - The value to be checked.
5
+ * @returns boolean - True if the input is an array of objects, false otherwise.
6
+ *
7
+ * Note: Considers empty arrays and arrays where every element is an object.
8
+ */
1
9
  export const isArrayOfObjects = (input) => Array.isArray(input) && (input.length === 0 || input.every(isObject));
10
+ /**
11
+ * Type guard function to check if the input is an array of arrays.
12
+ *
13
+ * @param input - The value to be checked.
14
+ * @returns boolean - True if the input is an array of arrays, false otherwise.
15
+ *
16
+ * Note: Considers empty arrays and arrays where every element is also an array.
17
+ */
2
18
  export const isArrayOfArrays = (input) => Array.isArray(input) && (input.length === 0 || input.every(Array.isArray));
19
+ /**
20
+ * Type guard function to check if the input is an array of nulls or an array of arrays of nulls.
21
+ *
22
+ * @param input - The value to be checked.
23
+ * @returns boolean - True if the input is an array consisting entirely of nulls or arrays of nulls, false otherwise.
24
+ *
25
+ * Note: Recursively checks each element, allowing for nested arrays of nulls.
26
+ */
3
27
  export const isArrayOfNulls = (input) => input.every((_input) => Array.isArray(_input) ? isArrayOfNulls(_input) : _input === null);
28
+ /**
29
+ * Type guard function to check if the input is an object.
30
+ *
31
+ * @param obj - The value to be checked.
32
+ * @returns boolean - True if the input is an object (excluding arrays), false otherwise.
33
+ *
34
+ * Note: Checks if the input is non-null and either has 'Object' as its constructor name or is of type 'object' without being an array.
35
+ */
4
36
  export const isObject = (obj) => obj != null &&
5
37
  (obj.constructor.name === "Object" ||
6
38
  (typeof obj === "object" && !Array.isArray(obj)));
39
+ /**
40
+ * Recursively merges properties from a source object into a target object. If a property exists in both, the source's value overwrites the target's.
41
+ *
42
+ * @param target - The target object to merge properties into.
43
+ * @param source - The source object from which properties are merged.
44
+ * @returns any - The modified target object with merged properties.
45
+ *
46
+ * Note: Performs a deep merge for nested objects. Non-object properties are directly overwritten.
47
+ */
7
48
  export const deepMerge = (target, source) => {
8
49
  for (const key in source) {
9
50
  if (source.hasOwnProperty(key)) {
@@ -15,6 +56,14 @@ export const deepMerge = (target, source) => {
15
56
  }
16
57
  return target;
17
58
  };
59
+ /**
60
+ * Combines an array of objects into a single object. If the same key exists in multiple objects, the values are merged.
61
+ *
62
+ * @param arr - Array of objects to be combined.
63
+ * @returns Record<string, any> - A single object with combined keys and values.
64
+ *
65
+ * Note: Handles nested objects by recursively combining them. Non-object values with the same key are merged into arrays.
66
+ */
18
67
  export const combineObjects = (arr) => {
19
68
  const result = {};
20
69
  for (const obj of arr) {
@@ -49,8 +98,33 @@ export const combineObjects = (arr) => {
49
98
  }
50
99
  return result;
51
100
  };
101
+ /**
102
+ * Type guard function to check if the input is a number.
103
+ *
104
+ * @param input - The value to be checked.
105
+ * @returns boolean - True if the input is a number, false otherwise.
106
+ *
107
+ * Note: Validates that the input can be parsed as a float and that subtracting zero results in a number, ensuring it's a numeric value.
108
+ */
52
109
  export const isNumber = (input) => !isNaN(parseFloat(input)) && !isNaN(input - 0);
110
+ /**
111
+ * Checks if the input is a valid email format.
112
+ *
113
+ * @param input - The value to be checked.
114
+ * @returns boolean - True if the input matches the email format, false otherwise.
115
+ *
116
+ * Note: Uses a regular expression to validate the email format, ensuring it has parts separated by '@' and contains a domain with a period.
117
+ */
53
118
  export const isEmail = (input) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(input));
119
+ /**
120
+ * Checks if the input is a valid URL format.
121
+ *
122
+ * @param input - The value to be checked.
123
+ * @returns boolean - True if the input matches the URL format, false otherwise.
124
+ *
125
+ * Note: Validates URLs including protocols (http/https), domain names, IP addresses, ports, paths, query strings, and fragments.
126
+ * Also recognizes 'tel:' and 'mailto:' as valid URL formats, as well as strings starting with '#' without spaces.
127
+ */
54
128
  export const isURL = (input) => {
55
129
  if (typeof input !== "string")
56
130
  return false;
@@ -68,20 +142,80 @@ export const isURL = (input) => {
68
142
  return !!pattern.test(input);
69
143
  }
70
144
  };
145
+ /**
146
+ * Checks if the input contains HTML tags or entities.
147
+ *
148
+ * @param input - The value to be checked.
149
+ * @returns boolean - True if the input contains HTML tags or entities, false otherwise.
150
+ *
151
+ * Note: Uses a regular expression to detect HTML tags (like <tag>) and entities (like &entity;).
152
+ * Recognizes both opening and closing tags, as well as self-closing tags.
153
+ */
71
154
  export const isHTML = (input) => /<\/?\s*[a-z-][^>]*\s*>|(\&(?:[\w\d]+|#\d+|#x[a-f\d]+);)/g.test(input);
155
+ /**
156
+ * Type guard function to check if the input is a string, excluding strings that match specific formats (number, boolean, email, URL, IP).
157
+ *
158
+ * @param input - The value to be checked.
159
+ * @returns boolean - True if the input is a string that doesn't match the specific formats, false otherwise.
160
+ *
161
+ * Note: Validates the input against being a number, boolean, email, URL, or IP address to ensure it's a general string.
162
+ */
72
163
  export const isString = (input) => Object.prototype.toString.call(input) === "[object String]" &&
73
164
  [isNumber, isBoolean, isEmail, isURL, isIP].every((fn) => !fn(input));
165
+ /**
166
+ * Checks if the input is a valid IP address format.
167
+ *
168
+ * @param input - The value to be checked.
169
+ * @returns boolean - True if the input matches the IP address format, false otherwise.
170
+ *
171
+ * Note: Uses a regular expression to validate IP addresses, ensuring they consist of four octets, each ranging from 0 to 255.
172
+ */
74
173
  export const isIP = (input) => /^(?:(?:^|\.)(?:2(?:5[0-5]|[0-4]\d)|1?\d?\d)){4}$/.test(input);
174
+ /**
175
+ * Type guard function to check if the input is a boolean or a string representation of a boolean.
176
+ *
177
+ * @param input - The value to be checked.
178
+ * @returns boolean - True if the input is a boolean value or 'true'/'false' strings, false otherwise.
179
+ *
180
+ * Note: Recognizes both boolean literals (true, false) and their string representations ("true", "false").
181
+ */
75
182
  export const isBoolean = (input) => typeof input === "boolean" ||
76
183
  input === "true" ||
77
184
  input === "false" ||
78
185
  input === true ||
79
186
  input === false;
187
+ /**
188
+ * Type guard function to check if the input is a password based on a specific length criterion.
189
+ *
190
+ * @param input - The value to be checked.
191
+ * @returns boolean - True if the input is a string with a length of 161 characters, false otherwise.
192
+ *
193
+ * Note: Specifically checks for string length to determine if it matches the defined password length criterion.
194
+ */
80
195
  export const isPassword = (input) => input.length === 161;
196
+ /**
197
+ * Checks if the input can be converted to a valid date.
198
+ *
199
+ * @param input - The input to be checked, can be of any type.
200
+ * @returns A boolean indicating whether the input is a valid date.
201
+ */
81
202
  export const isDate = (input) => !isNaN(new Date(input).getTime()) || !isNaN(Date.parse(input));
203
+ /**
204
+ * Checks if the input is a valid ID.
205
+ *
206
+ * @param input - The input to be checked, can be of any type.
207
+ * @returns A boolean indicating whether the input is a string representing a valid ID of length 32.
208
+ */
82
209
  export const isValidID = (input) => {
83
210
  return typeof input === "string" && input.length === 32;
84
211
  };
212
+ /**
213
+ * Identifies and returns properties that have changed between two objects.
214
+ *
215
+ * @param obj1 - The first object for comparison, with string keys and values.
216
+ * @param obj2 - The second object for comparison, with string keys and values.
217
+ * @returns A record of changed properties with original values from obj1 and new values from obj2, or null if no changes are found.
218
+ */
85
219
  export const findChangedProperties = (obj1, obj2) => {
86
220
  const result = {};
87
221
  for (const key1 in obj1)
@@ -89,6 +223,13 @@ export const findChangedProperties = (obj1, obj2) => {
89
223
  result[obj1[key1]] = obj2[key1];
90
224
  return Object.keys(result).length ? result : null;
91
225
  };
226
+ /**
227
+ * Detects the field type of an input based on available types.
228
+ *
229
+ * @param input - The input whose field type is to be detected.
230
+ * @param availableTypes - An array of potential field types to consider.
231
+ * @returns The detected field type as a string, or undefined if no matching type is found.
232
+ */
92
233
  export const detectFieldType = (input, availableTypes) => {
93
234
  if (!Array.isArray(input)) {
94
235
  if ((input === "0" ||
@@ -126,6 +267,14 @@ export const detectFieldType = (input, availableTypes) => {
126
267
  return "array";
127
268
  return undefined;
128
269
  };
270
+ /**
271
+ * Validates if the given value matches the specified field type(s).
272
+ *
273
+ * @param value - The value to be validated.
274
+ * @param fieldType - The expected field type or an array of possible field types.
275
+ * @param fieldChildrenType - Optional; the expected type(s) of children elements, used if the field type is an array.
276
+ * @returns A boolean indicating whether the value matches the specified field type(s).
277
+ */
129
278
  export const validateFieldType = (value, fieldType, fieldChildrenType) => {
130
279
  if (value === null)
131
280
  return true;
@@ -177,6 +326,12 @@ export const validateFieldType = (value, fieldType, fieldChildrenType) => {
177
326
  return false;
178
327
  }
179
328
  };
329
+ /**
330
+ * Converts a nested object to dot notation, flattening the object's structure.
331
+ *
332
+ * @param input - The input object to be converted.
333
+ * @returns A flattened object using dot notation for keys.
334
+ */
180
335
  export const objectToDotNotation = (input) => {
181
336
  const result = {};
182
337
  const stack = [
@@ -1,10 +1,52 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
2
  import { type Schema } from "./index.js";
3
+ /**
4
+ * Generates a hashed password using SHA-256.
5
+ *
6
+ * @param password - The plain text password to hash.
7
+ * @returns A string containing the salt and the hashed password, separated by a colon.
8
+ */
3
9
  export declare const hashPassword: (password: string) => string;
10
+ /**
11
+ * Compares a hashed password with an input password to verify a match.
12
+ *
13
+ * @param hashedPassword - The hashed password, containing both the salt and the hash, separated by a colon.
14
+ * @param inputPassword - The plain text input password to compare against the hashed password.
15
+ * @returns A boolean indicating whether the input password matches the hashed password.
16
+ */
4
17
  export declare const comparePassword: (hashedPassword: string, inputPassword: string) => boolean;
18
+ /**
19
+ * Encodes an ID using AES-256-CBC encryption.
20
+ *
21
+ * @param id - The ID to encode, either a number or a string.
22
+ * @param secretKeyOrSalt - The secret key or salt for encryption, can be a string, number, or Buffer.
23
+ * @returns The encoded ID as a hexadecimal string.
24
+ */
5
25
  export declare const encodeID: (id: number | string, secretKeyOrSalt: string | number | Buffer) => string;
26
+ /**
27
+ * Decodes an encrypted ID using AES-256-CBC decryption.
28
+ *
29
+ * @param input - The encrypted ID as a hexadecimal string.
30
+ * @param secretKeyOrSalt - The secret key or salt used for decryption, can be a string, number, or Buffer.
31
+ * @returns The decoded ID as a number.
32
+ */
6
33
  export declare const decodeID: (input: string, secretKeyOrSalt: string | number | Buffer) => number;
34
+ /**
35
+ * Finds the last ID number in a schema, potentially decoding it if encrypted.
36
+ *
37
+ * @param schema - The schema to search, defined as an array of schema objects.
38
+ * @param secretKeyOrSalt - The secret key or salt for decoding an encrypted ID, can be a string, number, or Buffer.
39
+ * @returns The last ID number in the schema, decoded if necessary.
40
+ */
7
41
  export declare const findLastIdNumber: (schema: Schema, secretKeyOrSalt: string | number | Buffer) => number;
42
+ /**
43
+ * Adds or updates IDs in a schema, encoding them using a provided secret key or salt.
44
+ *
45
+ * @param schema - The schema to update, defined as an array of schema objects.
46
+ * @param oldIndex - The starting index for generating new IDs, defaults to 0.
47
+ * @param secretKeyOrSalt - The secret key or salt for encoding IDs, can be a string, number, or Buffer.
48
+ * @returns The updated schema with encoded IDs.
49
+ */
8
50
  export declare const addIdToSchema: (schema: Schema, oldIndex: number | undefined, secretKeyOrSalt: string | number | Buffer) => import("./index.js").Field[];
9
51
  export default class UtilsServer {
10
52
  static encodeID: (id: string | number, secretKeyOrSalt: string | number | Buffer) => string;
@@ -1,5 +1,11 @@
1
1
  import { createCipheriv, createDecipheriv, randomBytes, scryptSync, createHash, } from "node:crypto";
2
2
  import { isArrayOfObjects, isNumber, isValidID } from "./utils.js";
3
+ /**
4
+ * Generates a hashed password using SHA-256.
5
+ *
6
+ * @param password - The plain text password to hash.
7
+ * @returns A string containing the salt and the hashed password, separated by a colon.
8
+ */
3
9
  export const hashPassword = (password) => {
4
10
  const salt = randomBytes(16).toString("hex");
5
11
  const hash = createHash("sha256")
@@ -7,6 +13,13 @@ export const hashPassword = (password) => {
7
13
  .digest("hex");
8
14
  return `${salt}:${hash}`;
9
15
  };
16
+ /**
17
+ * Compares a hashed password with an input password to verify a match.
18
+ *
19
+ * @param hashedPassword - The hashed password, containing both the salt and the hash, separated by a colon.
20
+ * @param inputPassword - The plain text input password to compare against the hashed password.
21
+ * @returns A boolean indicating whether the input password matches the hashed password.
22
+ */
10
23
  export const comparePassword = (hashedPassword, inputPassword) => {
11
24
  const [salt, originalHash] = hashedPassword.split(":");
12
25
  const inputHash = createHash("sha256")
@@ -14,6 +27,13 @@ export const comparePassword = (hashedPassword, inputPassword) => {
14
27
  .digest("hex");
15
28
  return inputHash === originalHash;
16
29
  };
30
+ /**
31
+ * Encodes an ID using AES-256-CBC encryption.
32
+ *
33
+ * @param id - The ID to encode, either a number or a string.
34
+ * @param secretKeyOrSalt - The secret key or salt for encryption, can be a string, number, or Buffer.
35
+ * @returns The encoded ID as a hexadecimal string.
36
+ */
17
37
  export const encodeID = (id, secretKeyOrSalt) => {
18
38
  let cipher;
19
39
  if (Buffer.isBuffer(secretKeyOrSalt))
@@ -24,6 +44,13 @@ export const encodeID = (id, secretKeyOrSalt) => {
24
44
  }
25
45
  return cipher.update(id.toString(), "utf8", "hex") + cipher.final("hex");
26
46
  };
47
+ /**
48
+ * Decodes an encrypted ID using AES-256-CBC decryption.
49
+ *
50
+ * @param input - The encrypted ID as a hexadecimal string.
51
+ * @param secretKeyOrSalt - The secret key or salt used for decryption, can be a string, number, or Buffer.
52
+ * @returns The decoded ID as a number.
53
+ */
27
54
  export const decodeID = (input, secretKeyOrSalt) => {
28
55
  let decipher;
29
56
  if (Buffer.isBuffer(secretKeyOrSalt))
@@ -34,6 +61,13 @@ export const decodeID = (input, secretKeyOrSalt) => {
34
61
  }
35
62
  return Number(decipher.update(input, "hex", "utf8") + decipher.final("utf8"));
36
63
  };
64
+ /**
65
+ * Finds the last ID number in a schema, potentially decoding it if encrypted.
66
+ *
67
+ * @param schema - The schema to search, defined as an array of schema objects.
68
+ * @param secretKeyOrSalt - The secret key or salt for decoding an encrypted ID, can be a string, number, or Buffer.
69
+ * @returns The last ID number in the schema, decoded if necessary.
70
+ */
37
71
  export const findLastIdNumber = (schema, secretKeyOrSalt) => {
38
72
  const lastField = schema[schema.length - 1];
39
73
  if (lastField) {
@@ -47,6 +81,14 @@ export const findLastIdNumber = (schema, secretKeyOrSalt) => {
47
81
  }
48
82
  return 0;
49
83
  };
84
+ /**
85
+ * Adds or updates IDs in a schema, encoding them using a provided secret key or salt.
86
+ *
87
+ * @param schema - The schema to update, defined as an array of schema objects.
88
+ * @param oldIndex - The starting index for generating new IDs, defaults to 0.
89
+ * @param secretKeyOrSalt - The secret key or salt for encoding IDs, can be a string, number, or Buffer.
90
+ * @returns The updated schema with encoded IDs.
91
+ */
50
92
  export const addIdToSchema = (schema, oldIndex = 0, secretKeyOrSalt) => schema.map((field) => {
51
93
  if (!field.id) {
52
94
  oldIndex++;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inibase",
3
- "version": "1.0.0-rc.24",
3
+ "version": "1.0.0-rc.26",
4
4
  "author": {
5
5
  "name": "Karim Amahtil",
6
6
  "email": "karim.amahtil@gmail.com"