inibase 1.0.0-rc.40 → 1.0.0-rc.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/file.d.ts +1 -1
- package/dist/file.js +24 -217
- package/dist/index.d.ts +10 -8
- package/dist/index.js +211 -139
- package/dist/utils.d.ts +11 -0
- package/dist/utils.js +4 -13
- package/dist/utils.server.d.ts +49 -45
- package/dist/utils.server.js +133 -1
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ Ps: Testing by default with `user` table, with username, email, password fields
|
|
|
84
84
|
- [x] Pagination
|
|
85
85
|
- [x] Criteria
|
|
86
86
|
- [x] Columns
|
|
87
|
-
- [
|
|
87
|
+
- [x] Order By (using UNIX commands)
|
|
88
88
|
- [x] POST
|
|
89
89
|
- [x] PUT
|
|
90
90
|
- [x] DELETE
|
|
@@ -109,6 +109,8 @@ Ps: Testing by default with `user` table, with username, email, password fields
|
|
|
109
109
|
- [ ] TO-DO:
|
|
110
110
|
- [x] Improve caching
|
|
111
111
|
- [x] Commenting the code
|
|
112
|
+
- [ ] Add Backup feature (generate a tar.gz)
|
|
113
|
+
- [ ] Add Custom field validation property to schema (using RegEx?)
|
|
112
114
|
- [ ] Features:
|
|
113
115
|
- [ ] Encryption
|
|
114
116
|
- [x] Data Compression
|
package/dist/file.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export declare const read: (filePath: string, disableCompression?: boolean) => P
|
|
|
12
12
|
*/
|
|
13
13
|
export declare const isExists: (path: string) => Promise<boolean>;
|
|
14
14
|
/**
|
|
15
|
-
* Encodes the input using 'secureString' and '
|
|
15
|
+
* Encodes the input using 'secureString' and 'Inison.stringify' functions.
|
|
16
16
|
* If the input is an array, it is first secured and then joined into a string.
|
|
17
17
|
* If the input is a single value, it is directly secured.
|
|
18
18
|
*
|
package/dist/file.js
CHANGED
|
@@ -2,15 +2,13 @@ import { open, access, writeFile, readFile, constants as fsConstants, unlink, co
|
|
|
2
2
|
import { createInterface } from "node:readline";
|
|
3
3
|
import { Transform } from "node:stream";
|
|
4
4
|
import { pipeline } from "node:stream/promises";
|
|
5
|
-
import { createGzip, createGunzip,
|
|
6
|
-
import { promisify } from "node:util";
|
|
5
|
+
import { createGzip, createGunzip, gunzipSync, gzipSync } from "node:zlib";
|
|
7
6
|
import { join } from "node:path";
|
|
8
7
|
import { Worker } from "node:worker_threads";
|
|
9
|
-
import { detectFieldType,
|
|
10
|
-
import { encodeID,
|
|
8
|
+
import { detectFieldType, isJSON, isNumber, isObject } from "./utils.js";
|
|
9
|
+
import { encodeID, compare } from "./utils.server.js";
|
|
11
10
|
import Config from "./config.js";
|
|
12
|
-
|
|
13
|
-
const gunzip = promisify(gunzipAsync);
|
|
11
|
+
import Inison from "inison";
|
|
14
12
|
export const lock = async (folderPath, prefix) => {
|
|
15
13
|
let lockFile, lockFilePath = join(folderPath, `${prefix ?? ""}.locked`);
|
|
16
14
|
try {
|
|
@@ -33,12 +31,12 @@ export const unlock = async (folderPath, prefix) => {
|
|
|
33
31
|
};
|
|
34
32
|
export const write = async (filePath, data, disableCompression = false) => {
|
|
35
33
|
await writeFile(filePath, Config.isCompressionEnabled && !disableCompression
|
|
36
|
-
?
|
|
34
|
+
? gzipSync(String(data))
|
|
37
35
|
: String(data));
|
|
38
36
|
};
|
|
39
37
|
export const read = async (filePath, disableCompression = false) => {
|
|
40
38
|
return Config.isCompressionEnabled && !disableCompression
|
|
41
|
-
? (await
|
|
39
|
+
? gunzipSync(await readFile(filePath)).toString()
|
|
42
40
|
: (await readFile(filePath)).toString();
|
|
43
41
|
};
|
|
44
42
|
const _pipeline = async (rl, writeStream, transform) => {
|
|
@@ -80,7 +78,6 @@ export const isExists = async (path) => {
|
|
|
80
78
|
return false;
|
|
81
79
|
}
|
|
82
80
|
};
|
|
83
|
-
const delimiters = [",", "|", "&", "$", "#", "@", "^", ":", "!", ";"];
|
|
84
81
|
/**
|
|
85
82
|
* Secures input by encoding/escaping characters.
|
|
86
83
|
*
|
|
@@ -99,24 +96,8 @@ const secureString = (input) => {
|
|
|
99
96
|
catch (_error) {
|
|
100
97
|
decodedInput = decodeURIComponent(input.replace(/%(?![0-9][0-9a-fA-F]+)/g, ""));
|
|
101
98
|
}
|
|
102
|
-
const replacements = {
|
|
103
|
-
"<": "<",
|
|
104
|
-
">": ">",
|
|
105
|
-
",": "%2C",
|
|
106
|
-
"|": "%7C",
|
|
107
|
-
"&": "%26",
|
|
108
|
-
$: "%24",
|
|
109
|
-
"#": "%23",
|
|
110
|
-
"@": "%40",
|
|
111
|
-
"^": "%5E",
|
|
112
|
-
":": "%3A",
|
|
113
|
-
"!": "%21",
|
|
114
|
-
";": "%3B",
|
|
115
|
-
"\n": "\\n",
|
|
116
|
-
"\r": "\\r",
|
|
117
|
-
};
|
|
118
99
|
// Replace characters using a single regular expression.
|
|
119
|
-
return decodedInput.replace(
|
|
100
|
+
return decodedInput.replace(/\\n/g, "\n").replace(/\n/g, "\\n");
|
|
120
101
|
};
|
|
121
102
|
/**
|
|
122
103
|
* Secures each element in an array or a single value using secureString.
|
|
@@ -126,33 +107,16 @@ const secureString = (input) => {
|
|
|
126
107
|
*/
|
|
127
108
|
const secureArray = (arr_str) => Array.isArray(arr_str) ? arr_str.map(secureArray) : secureString(arr_str);
|
|
128
109
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* @param arr - A multidimensional array or a single level array.
|
|
132
|
-
* @param delimiter_index - Index for selecting delimiter, defaults to 0.
|
|
133
|
-
* @returns Joined string of array elements with appropriate delimiters.
|
|
134
|
-
*/
|
|
135
|
-
const joinMultidimensionalArray = (arr, delimiter_index = 0) => {
|
|
136
|
-
delimiter_index++;
|
|
137
|
-
if (isArrayOfArrays(arr))
|
|
138
|
-
arr = arr.map((ar) => joinMultidimensionalArray(ar, delimiter_index));
|
|
139
|
-
delimiter_index--;
|
|
140
|
-
return arr.join(delimiters[delimiter_index]);
|
|
141
|
-
};
|
|
142
|
-
/**
|
|
143
|
-
* Encodes the input using 'secureString' and 'joinMultidimensionalArray' functions.
|
|
110
|
+
* Encodes the input using 'secureString' and 'Inison.stringify' functions.
|
|
144
111
|
* If the input is an array, it is first secured and then joined into a string.
|
|
145
112
|
* If the input is a single value, it is directly secured.
|
|
146
113
|
*
|
|
147
114
|
* @param input - A value or array of values (string, number, boolean, null).
|
|
148
115
|
* @returns The secured and/or joined string.
|
|
149
116
|
*/
|
|
150
|
-
export const encode = (input) =>
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
? joinMultidimensionalArray(secureArray(input))
|
|
154
|
-
: secureString(input);
|
|
155
|
-
};
|
|
117
|
+
export const encode = (input) => Array.isArray(input)
|
|
118
|
+
? Inison.stringify(secureArray(input))
|
|
119
|
+
: secureString(input);
|
|
156
120
|
/**
|
|
157
121
|
* Decodes each element in an array or a single value using unSecureString.
|
|
158
122
|
*
|
|
@@ -167,44 +131,11 @@ const unSecureArray = (arr_str) => Array.isArray(arr_str) ? arr_str.map(unSecure
|
|
|
167
131
|
* @returns Decoded string or null if input is empty.
|
|
168
132
|
*/
|
|
169
133
|
const unSecureString = (input) => {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
"
|
|
174
|
-
|
|
175
|
-
"%26": "&",
|
|
176
|
-
"%24": "$",
|
|
177
|
-
"%23": "#",
|
|
178
|
-
"%40": "@",
|
|
179
|
-
"%5E": "^",
|
|
180
|
-
"%3A": ":",
|
|
181
|
-
"%21": "!",
|
|
182
|
-
"%3B": ";",
|
|
183
|
-
"\\n": "\n",
|
|
184
|
-
"\\r": "\r",
|
|
185
|
-
};
|
|
186
|
-
// Replace encoded characters with their original symbols using the defined mapping.
|
|
187
|
-
const decodedString = input.replace(/%(2C|7C|26|24|23|40|5E|3A|21|3B|\\n|\\r)/g, (match) => replacements[match]) || null;
|
|
188
|
-
if (decodedString === null)
|
|
189
|
-
return null;
|
|
190
|
-
return isNumber(decodedString) ? Number(decodedString) : decodedString;
|
|
191
|
-
};
|
|
192
|
-
/**
|
|
193
|
-
* Reverses the process of 'joinMultidimensionalArray', splitting a string back into a multidimensional array.
|
|
194
|
-
* It identifies delimiters used in the joined string and applies them recursively to reconstruct the original array structure.
|
|
195
|
-
*
|
|
196
|
-
* @param joinedString - A string, array, or multidimensional array.
|
|
197
|
-
* @returns Original array structure before joining, or the input if no delimiters are found.
|
|
198
|
-
*/
|
|
199
|
-
const reverseJoinMultidimensionalArray = (joinedString) => {
|
|
200
|
-
// Helper function for recursive array splitting based on delimiters.
|
|
201
|
-
const reverseJoinMultidimensionalArrayHelper = (arr, delimiter) => Array.isArray(arr)
|
|
202
|
-
? arr.map((ar) => reverseJoinMultidimensionalArrayHelper(ar, delimiter))
|
|
203
|
-
: arr.split(delimiter);
|
|
204
|
-
// Identify available delimiters in the input.
|
|
205
|
-
const availableDelimiters = delimiters.filter((delimiter) => joinedString.includes(delimiter));
|
|
206
|
-
// Apply delimiters recursively to reconstruct the original array structure.
|
|
207
|
-
return availableDelimiters.reduce((acc, delimiter) => reverseJoinMultidimensionalArrayHelper(acc, delimiter), joinedString);
|
|
134
|
+
if (isNumber(input))
|
|
135
|
+
return Number(input);
|
|
136
|
+
if (typeof input === "string")
|
|
137
|
+
return input.replace(/\n/g, "\\n") || null;
|
|
138
|
+
return null;
|
|
208
139
|
};
|
|
209
140
|
/**
|
|
210
141
|
* Decodes a value based on specified field types and optional secret key.
|
|
@@ -220,8 +151,6 @@ const decodeHelper = (value, fieldType, fieldChildrenType, secretKey) => {
|
|
|
220
151
|
if (Array.isArray(value) && fieldType !== "array")
|
|
221
152
|
return value.map((v) => decodeHelper(v, fieldType, fieldChildrenType, secretKey));
|
|
222
153
|
switch (fieldType) {
|
|
223
|
-
case "json":
|
|
224
|
-
return JSON.parse(value);
|
|
225
154
|
case "number":
|
|
226
155
|
return isNumber(value) ? Number(value) : null;
|
|
227
156
|
case "boolean":
|
|
@@ -264,8 +193,8 @@ export const decode = (input, fieldType, fieldChildrenType, secretKey) => {
|
|
|
264
193
|
fieldType = detectFieldType(String(input), fieldType);
|
|
265
194
|
// Decode the input using the decodeHelper function.
|
|
266
195
|
return decodeHelper(typeof input === "string"
|
|
267
|
-
? input
|
|
268
|
-
?
|
|
196
|
+
? isJSON(input)
|
|
197
|
+
? Inison.unstringify(input)
|
|
269
198
|
: unSecureString(input)
|
|
270
199
|
: input, fieldType, fieldChildrenType, secretKey);
|
|
271
200
|
};
|
|
@@ -442,130 +371,6 @@ export const remove = async (filePath, linesToDelete) => {
|
|
|
442
371
|
await fileHandle.close();
|
|
443
372
|
return [fileTempPath, filePath];
|
|
444
373
|
};
|
|
445
|
-
/**
|
|
446
|
-
* Evaluates a comparison between two values based on a specified operator and field types.
|
|
447
|
-
*
|
|
448
|
-
* @param operator - The comparison operator (e.g., '=', '!=', '>', '<', '>=', '<=', '[]', '![]', '*', '!*').
|
|
449
|
-
* @param originalValue - The value to compare, can be a single value or an array of values.
|
|
450
|
-
* @param comparedAtValue - The value or values to compare against.
|
|
451
|
-
* @param fieldType - Optional type of the field to guide comparison (e.g., 'password', 'boolean').
|
|
452
|
-
* @param fieldChildrenType - Optional type for child elements in array inputs.
|
|
453
|
-
* @returns boolean - Result of the comparison operation.
|
|
454
|
-
*
|
|
455
|
-
* Note: Handles various data types and comparison logic, including special handling for passwords and regex patterns.
|
|
456
|
-
*/
|
|
457
|
-
const handleComparisonOperator = (operator, originalValue, comparedAtValue, fieldType, fieldChildrenType) => {
|
|
458
|
-
// Determine the field type if it's an array of potential types.
|
|
459
|
-
if (Array.isArray(fieldType)) {
|
|
460
|
-
fieldType = detectFieldType(String(originalValue), fieldType);
|
|
461
|
-
}
|
|
462
|
-
// Handle comparisons involving arrays.
|
|
463
|
-
if (Array.isArray(comparedAtValue) && !["[]", "![]"].includes(operator)) {
|
|
464
|
-
return comparedAtValue.some((comparedAtValueSingle) => handleComparisonOperator(operator, originalValue, comparedAtValueSingle, fieldType));
|
|
465
|
-
}
|
|
466
|
-
// Switch statement for different comparison operators.
|
|
467
|
-
switch (operator) {
|
|
468
|
-
// Equal (Case Insensitive for strings, specific handling for passwords and booleans).
|
|
469
|
-
case "=":
|
|
470
|
-
return isEqual(originalValue, comparedAtValue, fieldType);
|
|
471
|
-
// Not Equal.
|
|
472
|
-
case "!=":
|
|
473
|
-
return !isEqual(originalValue, comparedAtValue, fieldType);
|
|
474
|
-
// Greater Than.
|
|
475
|
-
case ">":
|
|
476
|
-
return (originalValue !== null &&
|
|
477
|
-
comparedAtValue !== null &&
|
|
478
|
-
originalValue > comparedAtValue);
|
|
479
|
-
// Less Than.
|
|
480
|
-
case "<":
|
|
481
|
-
return (originalValue !== null &&
|
|
482
|
-
comparedAtValue !== null &&
|
|
483
|
-
originalValue < comparedAtValue);
|
|
484
|
-
// Greater Than or Equal.
|
|
485
|
-
case ">=":
|
|
486
|
-
return (originalValue !== null &&
|
|
487
|
-
comparedAtValue !== null &&
|
|
488
|
-
originalValue >= comparedAtValue);
|
|
489
|
-
// Less Than or Equal.
|
|
490
|
-
case "<=":
|
|
491
|
-
return (originalValue !== null &&
|
|
492
|
-
comparedAtValue !== null &&
|
|
493
|
-
originalValue <= comparedAtValue);
|
|
494
|
-
// Array Contains (equality check for arrays).
|
|
495
|
-
case "[]":
|
|
496
|
-
return isArrayEqual(originalValue, comparedAtValue);
|
|
497
|
-
// Array Does Not Contain.
|
|
498
|
-
case "![]":
|
|
499
|
-
return !isArrayEqual(originalValue, comparedAtValue);
|
|
500
|
-
// Wildcard Match (using regex pattern).
|
|
501
|
-
case "*":
|
|
502
|
-
return isWildcardMatch(originalValue, comparedAtValue);
|
|
503
|
-
// Not Wildcard Match.
|
|
504
|
-
case "!*":
|
|
505
|
-
return !isWildcardMatch(originalValue, comparedAtValue);
|
|
506
|
-
// Unsupported operator.
|
|
507
|
-
default:
|
|
508
|
-
throw new Error(`Unsupported operator: ${operator}`);
|
|
509
|
-
}
|
|
510
|
-
};
|
|
511
|
-
/**
|
|
512
|
-
* Helper function to check equality based on the field type.
|
|
513
|
-
*
|
|
514
|
-
* @param originalValue - The original value.
|
|
515
|
-
* @param comparedAtValue - The value to compare against.
|
|
516
|
-
* @param fieldType - Type of the field.
|
|
517
|
-
* @returns boolean - Result of the equality check.
|
|
518
|
-
*/
|
|
519
|
-
const isEqual = (originalValue, comparedAtValue, fieldType) => {
|
|
520
|
-
// Switch based on the field type for specific handling.
|
|
521
|
-
switch (fieldType) {
|
|
522
|
-
// Password comparison.
|
|
523
|
-
case "password":
|
|
524
|
-
return isPassword(originalValue) && typeof comparedAtValue === "string"
|
|
525
|
-
? comparePassword(originalValue, comparedAtValue)
|
|
526
|
-
: false;
|
|
527
|
-
// Boolean comparison.
|
|
528
|
-
case "boolean":
|
|
529
|
-
return Number(originalValue) === Number(comparedAtValue);
|
|
530
|
-
// Default comparison.
|
|
531
|
-
default:
|
|
532
|
-
return originalValue === comparedAtValue;
|
|
533
|
-
}
|
|
534
|
-
};
|
|
535
|
-
/**
|
|
536
|
-
* Helper function to check array equality.
|
|
537
|
-
*
|
|
538
|
-
* @param originalValue - The original value.
|
|
539
|
-
* @param comparedAtValue - The value to compare against.
|
|
540
|
-
* @returns boolean - Result of the array equality check.
|
|
541
|
-
*/
|
|
542
|
-
const isArrayEqual = (originalValue, comparedAtValue) => {
|
|
543
|
-
return ((Array.isArray(originalValue) &&
|
|
544
|
-
Array.isArray(comparedAtValue) &&
|
|
545
|
-
originalValue.some((v) => comparedAtValue.includes(v))) ||
|
|
546
|
-
(Array.isArray(originalValue) &&
|
|
547
|
-
!Array.isArray(comparedAtValue) &&
|
|
548
|
-
originalValue.includes(comparedAtValue)) ||
|
|
549
|
-
(!Array.isArray(originalValue) &&
|
|
550
|
-
Array.isArray(comparedAtValue) &&
|
|
551
|
-
comparedAtValue.includes(originalValue)) ||
|
|
552
|
-
(!Array.isArray(originalValue) &&
|
|
553
|
-
!Array.isArray(comparedAtValue) &&
|
|
554
|
-
comparedAtValue === originalValue));
|
|
555
|
-
};
|
|
556
|
-
/**
|
|
557
|
-
* Helper function to check wildcard pattern matching using regex.
|
|
558
|
-
*
|
|
559
|
-
* @param originalValue - The original value.
|
|
560
|
-
* @param comparedAtValue - The value with wildcard pattern.
|
|
561
|
-
* @returns boolean - Result of the wildcard pattern matching.
|
|
562
|
-
*/
|
|
563
|
-
const isWildcardMatch = (originalValue, comparedAtValue) => {
|
|
564
|
-
const wildcardPattern = `^${(String(comparedAtValue).includes("%")
|
|
565
|
-
? String(comparedAtValue)
|
|
566
|
-
: "%" + String(comparedAtValue) + "%").replace(/%/g, ".*")}$`;
|
|
567
|
-
return new RegExp(wildcardPattern, "i").test(String(originalValue));
|
|
568
|
-
};
|
|
569
374
|
/**
|
|
570
375
|
* Asynchronously searches a file for lines matching specified criteria, using comparison and logical operators.
|
|
571
376
|
*
|
|
@@ -606,10 +411,10 @@ export const search = async (filePath, operator, comparedAtValue, logicalOperato
|
|
|
606
411
|
const meetsConditions = (Array.isArray(operator) &&
|
|
607
412
|
Array.isArray(comparedAtValue) &&
|
|
608
413
|
((logicalOperator === "or" &&
|
|
609
|
-
operator.some((single_operator, index) =>
|
|
610
|
-
operator.every((single_operator, index) =>
|
|
414
|
+
operator.some((single_operator, index) => compare(single_operator, decodedLine, comparedAtValue[index], fieldType))) ||
|
|
415
|
+
operator.every((single_operator, index) => compare(single_operator, decodedLine, comparedAtValue[index], fieldType)))) ||
|
|
611
416
|
(!Array.isArray(operator) &&
|
|
612
|
-
|
|
417
|
+
compare(operator, decodedLine, comparedAtValue, fieldType));
|
|
613
418
|
// If the line meets the conditions, process it.
|
|
614
419
|
if (meetsConditions) {
|
|
615
420
|
// Increment the found items counter.
|
|
@@ -796,7 +601,9 @@ export function createWorker(functionName, arg) {
|
|
|
796
601
|
*
|
|
797
602
|
* Note: The sorting is applied either to the entire file or to the specified lines. Large files are handled in chunks.
|
|
798
603
|
*/
|
|
799
|
-
export const sort = async (filePath, sortDirection, lineNumbers, _lineNumbersPerChunk = 100000) => {
|
|
604
|
+
export const sort = async (filePath, sortDirection, lineNumbers, _lineNumbersPerChunk = 100000) => {
|
|
605
|
+
// return Number((await exec(`wc -l < ${filePath}`)).stdout.trim());
|
|
606
|
+
};
|
|
800
607
|
export default class File {
|
|
801
608
|
static get = get;
|
|
802
609
|
static remove = remove;
|
package/dist/index.d.ts
CHANGED
|
@@ -16,22 +16,22 @@ type FieldStringType = {
|
|
|
16
16
|
children?: never;
|
|
17
17
|
};
|
|
18
18
|
type FieldStringArrayType = {
|
|
19
|
-
type: Array<Exclude<FieldType, "
|
|
19
|
+
type: Array<Exclude<FieldType, "object">>;
|
|
20
20
|
children?: never;
|
|
21
21
|
};
|
|
22
22
|
type FieldArrayType = {
|
|
23
23
|
type: "array";
|
|
24
|
-
children: Exclude<FieldType, "array"> | Exclude<FieldType, "array"
|
|
24
|
+
children: Exclude<FieldType, "array"> | Array<Exclude<FieldType, "array">> | Schema;
|
|
25
25
|
};
|
|
26
26
|
type FieldArrayArrayType = {
|
|
27
27
|
type: Array<"array" | Exclude<FieldType, "array" | "object">>;
|
|
28
|
-
children: Exclude<FieldType, "array" | "object"> | Exclude<FieldType, "array" | "object"
|
|
28
|
+
children: Exclude<FieldType, "array" | "object"> | Array<Exclude<FieldType, "array" | "object">>;
|
|
29
29
|
};
|
|
30
30
|
type FieldObjectType = {
|
|
31
31
|
type: "object";
|
|
32
32
|
children: Schema;
|
|
33
33
|
};
|
|
34
|
-
export type Field = FieldDefault & (FieldStringType |
|
|
34
|
+
export type Field = FieldDefault & (FieldStringType | FieldStringArrayType | FieldArrayArrayType | FieldObjectType | FieldArrayType);
|
|
35
35
|
export type Schema = Field[];
|
|
36
36
|
export interface Options {
|
|
37
37
|
page?: number;
|
|
@@ -69,12 +69,13 @@ export default class Inibase {
|
|
|
69
69
|
salt: Buffer;
|
|
70
70
|
constructor(database: string, mainFolder?: string, _table?: string | null, _totalItems?: Record<string, number>, _pageInfo?: Record<string, pageInfo>, _isThreadEnabled?: boolean);
|
|
71
71
|
private throwError;
|
|
72
|
-
createWorker(functionName: "get" | "post" | "put" | "delete" | "sum" | "min" | "max", arg: any[]): Promise<any>;
|
|
72
|
+
createWorker(functionName: "get" | "post" | "put" | "delete" | "sum" | "min" | "max" | "sort", arg: any[]): Promise<any>;
|
|
73
73
|
private _decodeIdFromSchema;
|
|
74
74
|
private _schemaToIdsPath;
|
|
75
75
|
setTableSchema(tableName: string, schema: Schema): Promise<void>;
|
|
76
76
|
getTableSchema(tableName: string): Promise<Schema | undefined>;
|
|
77
|
-
|
|
77
|
+
isTableEmpty(tableName: string): Promise<never | Schema>;
|
|
78
|
+
getField(keyPath: string, schema: Schema): Field | null;
|
|
78
79
|
private validateData;
|
|
79
80
|
private formatField;
|
|
80
81
|
private formatData;
|
|
@@ -89,8 +90,8 @@ export default class Inibase {
|
|
|
89
90
|
private applyCriteria;
|
|
90
91
|
private _filterSchemaByColumns;
|
|
91
92
|
clearCache(tablePath: string): Promise<void>;
|
|
92
|
-
get(tableName: string, where?: string | number | (string | number)[] | Criteria | undefined, options?: Options | undefined, onlyOne?: true, onlyLinesNumbers?: undefined, tableSchema?: Schema): Promise<Data | null>;
|
|
93
|
-
get(tableName: string, where?: string | number | (string | number)[] | Criteria | undefined, options?: Options | undefined, onlyOne?: boolean | undefined, onlyLinesNumbers?: true, tableSchema?: Schema): Promise<number[]
|
|
93
|
+
get(tableName: string, where?: string | number | (string | number)[] | Criteria | undefined, options?: Options | undefined, onlyOne?: true, onlyLinesNumbers?: undefined, tableSchema?: Schema, skipIdColumn?: boolean): Promise<Data | null>;
|
|
94
|
+
get(tableName: string, where?: string | number | (string | number)[] | Criteria | undefined, options?: Options | undefined, onlyOne?: boolean | undefined, onlyLinesNumbers?: true, tableSchema?: Schema, skipIdColumn?: boolean): Promise<number[]>;
|
|
94
95
|
post(tableName: string, data: Data | Data[], options?: Options, returnPostedData?: boolean): Promise<void | null>;
|
|
95
96
|
post(tableName: string, data: Data, options: Options | undefined, returnPostedData: true): Promise<Data | null>;
|
|
96
97
|
post(tableName: string, data: Data[], options: Options | undefined, returnPostedData: true): Promise<Data[] | null>;
|
|
@@ -105,5 +106,6 @@ export default class Inibase {
|
|
|
105
106
|
max(tableName: string, columns: string[], where?: number | string | (number | string)[] | Criteria): Promise<Record<string, number>>;
|
|
106
107
|
min(tableName: string, columns: string, where?: number | string | (number | string)[] | Criteria): Promise<number>;
|
|
107
108
|
min(tableName: string, columns: string[], where?: number | string | (number | string)[] | Criteria): Promise<Record<string, number>>;
|
|
109
|
+
sort(tableName: string, columns: string | string[] | Record<string, 1 | -1 | "asc" | "ASC" | "desc" | "DESC">, where?: string | number | (string | number)[] | Criteria, options?: Options): Promise<any[]>;
|
|
108
110
|
}
|
|
109
111
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { unlink, rename, mkdir, readdir } from "node:fs/promises";
|
|
2
2
|
import { existsSync, appendFileSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { join, parse } from "node:path";
|
|
4
4
|
import { scryptSync, randomBytes } from "node:crypto";
|
|
5
5
|
import { Worker } from "node:worker_threads";
|
|
6
6
|
import File from "./file.js";
|
|
@@ -8,6 +8,7 @@ import Utils from "./utils.js";
|
|
|
8
8
|
import UtilsServer from "./utils.server.js";
|
|
9
9
|
import Config from "./config.js";
|
|
10
10
|
import { inspect } from "node:util";
|
|
11
|
+
import Inison from "inison";
|
|
11
12
|
export default class Inibase {
|
|
12
13
|
folder;
|
|
13
14
|
database;
|
|
@@ -146,6 +147,14 @@ export default class Inibase {
|
|
|
146
147
|
},
|
|
147
148
|
];
|
|
148
149
|
}
|
|
150
|
+
async isTableEmpty(tableName) {
|
|
151
|
+
const schema = await this.getTableSchema(tableName), tablePath = join(this.folder, this.database, tableName);
|
|
152
|
+
if (!schema)
|
|
153
|
+
throw this.throwError("NO_SCHEMA", tableName);
|
|
154
|
+
if (!(await File.isExists(join(tablePath, "id.inib"))))
|
|
155
|
+
throw this.throwError("NO_ITEMS", tableName);
|
|
156
|
+
return schema;
|
|
157
|
+
}
|
|
149
158
|
getField(keyPath, schema) {
|
|
150
159
|
let RETURN = null;
|
|
151
160
|
const keyPathSplited = keyPath.split(".");
|
|
@@ -182,7 +191,7 @@ export default class Inibase {
|
|
|
182
191
|
: undefined))
|
|
183
192
|
throw this.throwError("INVALID_TYPE", [
|
|
184
193
|
field.key,
|
|
185
|
-
field.type,
|
|
194
|
+
Array.isArray(field.type) ? field.type.join(", ") : field.type,
|
|
186
195
|
typeof data[field.key],
|
|
187
196
|
]);
|
|
188
197
|
if ((field.type === "array" || field.type === "object") &&
|
|
@@ -265,7 +274,7 @@ export default class Inibase {
|
|
|
265
274
|
? value
|
|
266
275
|
: UtilsServer.decodeID(value, this.salt);
|
|
267
276
|
case "json":
|
|
268
|
-
return
|
|
277
|
+
return Inison.stringify(value);
|
|
269
278
|
default:
|
|
270
279
|
return value;
|
|
271
280
|
}
|
|
@@ -653,25 +662,23 @@ export default class Inibase {
|
|
|
653
662
|
async get(tableName, where, options = {
|
|
654
663
|
page: 1,
|
|
655
664
|
perPage: 15,
|
|
656
|
-
}, onlyOne, onlyLinesNumbers, tableSchema) {
|
|
665
|
+
}, onlyOne, onlyLinesNumbers, tableSchema, skipIdColumn) {
|
|
657
666
|
const tablePath = join(this.folder, this.database, tableName);
|
|
658
667
|
// Ensure options.columns is an array
|
|
659
668
|
if (options.columns) {
|
|
660
669
|
options.columns = Array.isArray(options.columns)
|
|
661
670
|
? options.columns
|
|
662
671
|
: [options.columns];
|
|
663
|
-
if (
|
|
672
|
+
if (!skipIdColumn &&
|
|
673
|
+
options.columns.length &&
|
|
674
|
+
!options.columns.includes("id"))
|
|
664
675
|
options.columns.push("id");
|
|
665
676
|
}
|
|
666
677
|
// Default values for page and perPage
|
|
667
678
|
options.page = options.page || 1;
|
|
668
679
|
options.perPage = options.perPage || 15;
|
|
669
680
|
let RETURN;
|
|
670
|
-
let schema = tableSchema ?? (await this.
|
|
671
|
-
if (!schema)
|
|
672
|
-
throw this.throwError("NO_SCHEMA", tableName);
|
|
673
|
-
if (!(await File.isExists(join(tablePath, "id.inib"))))
|
|
674
|
-
return null;
|
|
681
|
+
let schema = tableSchema ?? (await this.isTableEmpty(tableName));
|
|
675
682
|
if (options.columns && options.columns.length)
|
|
676
683
|
schema = this._filterSchemaByColumns(schema, options.columns);
|
|
677
684
|
if (!where) {
|
|
@@ -692,21 +699,22 @@ export default class Inibase {
|
|
|
692
699
|
}
|
|
693
700
|
}
|
|
694
701
|
else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
|
|
695
|
-
|
|
696
|
-
// "where" in this case, is the
|
|
702
|
+
Utils.isNumber(where)) {
|
|
703
|
+
// "where" in this case, is the line(s) number(s) and not id(s)
|
|
697
704
|
let lineNumbers = where;
|
|
698
705
|
if (!Array.isArray(lineNumbers))
|
|
699
706
|
lineNumbers = [lineNumbers];
|
|
707
|
+
// useless
|
|
708
|
+
if (onlyLinesNumbers)
|
|
709
|
+
return lineNumbers;
|
|
700
710
|
RETURN = Object.values((await this.getItemsFromSchema(tableName, schema, lineNumbers, options)) ?? {});
|
|
701
711
|
if (!this.totalItems[tableName + "-*"])
|
|
702
712
|
this.totalItems[tableName + "-*"] = lineNumbers.length;
|
|
703
713
|
if (RETURN && RETURN.length && !Array.isArray(where))
|
|
704
714
|
RETURN = RETURN[0];
|
|
705
715
|
}
|
|
706
|
-
else if ((Array.isArray(where) &&
|
|
707
|
-
|
|
708
|
-
Utils.isValidID(where) ||
|
|
709
|
-
Utils.isNumber(where)) {
|
|
716
|
+
else if ((Array.isArray(where) && where.every(Utils.isValidID)) ||
|
|
717
|
+
Utils.isValidID(where)) {
|
|
710
718
|
let Ids = where;
|
|
711
719
|
if (!Array.isArray(Ids))
|
|
712
720
|
Ids = [Ids];
|
|
@@ -772,13 +780,12 @@ export default class Inibase {
|
|
|
772
780
|
page: 1,
|
|
773
781
|
perPage: 15,
|
|
774
782
|
};
|
|
775
|
-
const tablePath = join(this.folder, this.database, tableName);
|
|
783
|
+
const tablePath = join(this.folder, this.database, tableName), schema = await this.getTableSchema(tableName);
|
|
784
|
+
if (!schema)
|
|
785
|
+
throw this.throwError("NO_SCHEMA", tableName);
|
|
776
786
|
if (!returnPostedData)
|
|
777
787
|
returnPostedData = false;
|
|
778
|
-
const schema = await this.getTableSchema(tableName);
|
|
779
788
|
let RETURN;
|
|
780
|
-
if (!schema)
|
|
781
|
-
throw this.throwError("NO_SCHEMA", tableName);
|
|
782
789
|
const keys = UtilsServer.hashString(Object.keys(Array.isArray(data) ? data[0] : data).join("."));
|
|
783
790
|
let lastId = 0, totalItems = 0, renameList = [];
|
|
784
791
|
try {
|
|
@@ -824,7 +831,13 @@ export default class Inibase {
|
|
|
824
831
|
await File.write(join(tablePath, ".cache", "pagination.inib"), `${lastId},${totalItems + (Array.isArray(RETURN) ? RETURN.length : 1)}`, true);
|
|
825
832
|
}
|
|
826
833
|
if (returnPostedData)
|
|
827
|
-
return this.get(tableName,
|
|
834
|
+
return this.get(tableName, Config.isReverseEnabled
|
|
835
|
+
? Array.isArray(RETURN)
|
|
836
|
+
? RETURN.map((_, index) => index + 1)
|
|
837
|
+
: 1
|
|
838
|
+
: Array.isArray(RETURN)
|
|
839
|
+
? RETURN.map((_, index) => totalItems - index)
|
|
840
|
+
: totalItems, options, !Utils.isArrayOfObjects(data), // return only one item if data is not array of objects
|
|
828
841
|
undefined, schema);
|
|
829
842
|
}
|
|
830
843
|
finally {
|
|
@@ -838,25 +851,20 @@ export default class Inibase {
|
|
|
838
851
|
perPage: 15,
|
|
839
852
|
}, returnPostedData) {
|
|
840
853
|
let renameList = [];
|
|
841
|
-
const tablePath = join(this.folder, this.database, tableName);
|
|
842
|
-
const schema = await this.getTableSchema(tableName);
|
|
843
|
-
if (!schema)
|
|
844
|
-
throw this.throwError("NO_SCHEMA", tableName);
|
|
845
|
-
if (!(await File.isExists(join(tablePath, "id.inib"))))
|
|
846
|
-
throw this.throwError("NO_ITEMS", tableName);
|
|
854
|
+
const tablePath = join(this.folder, this.database, tableName), schema = await this.isTableEmpty(tableName);
|
|
847
855
|
data = this.formatData(data, schema, true);
|
|
848
856
|
if (!where) {
|
|
849
857
|
if (Utils.isArrayOfObjects(data)) {
|
|
850
858
|
if (!data.every((item) => item.hasOwnProperty("id") && Utils.isValidID(item.id)))
|
|
851
859
|
throw this.throwError("INVALID_ID");
|
|
852
860
|
return this.put(tableName, data, data
|
|
853
|
-
.filter((
|
|
854
|
-
.map((
|
|
861
|
+
.filter(({ id }) => id !== undefined)
|
|
862
|
+
.map(({ id }) => id));
|
|
855
863
|
}
|
|
856
864
|
else if (data.hasOwnProperty("id")) {
|
|
857
865
|
if (!Utils.isValidID(data.id))
|
|
858
866
|
throw this.throwError("INVALID_ID", data.id);
|
|
859
|
-
return this.put(tableName, data,
|
|
867
|
+
return this.put(tableName, data, data.id);
|
|
860
868
|
}
|
|
861
869
|
else {
|
|
862
870
|
let totalItems;
|
|
@@ -896,56 +904,49 @@ export default class Inibase {
|
|
|
896
904
|
}
|
|
897
905
|
}
|
|
898
906
|
}
|
|
899
|
-
else if ((Array.isArray(where) &&
|
|
900
|
-
|
|
901
|
-
|
|
907
|
+
else if ((Array.isArray(where) && where.every(Utils.isValidID)) ||
|
|
908
|
+
Utils.isValidID(where)) {
|
|
909
|
+
const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
|
|
910
|
+
return this.put(tableName, data, lineNumbers);
|
|
911
|
+
}
|
|
912
|
+
else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
|
|
902
913
|
Utils.isNumber(where)) {
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
914
|
+
// "where" in this case, is the line(s) number(s) and not id(s)
|
|
915
|
+
const pathesContents = Object.fromEntries(Object.entries(this.joinPathesContents(tablePath, Utils.isArrayOfObjects(data)
|
|
916
|
+
? data.map((item) => ({
|
|
917
|
+
...item,
|
|
918
|
+
updatedAt: Date.now(),
|
|
919
|
+
}))
|
|
920
|
+
: { ...data, updatedAt: Date.now() })).map(([path, content]) => [
|
|
921
|
+
path,
|
|
922
|
+
[...(Array.isArray(where) ? where : [where])].reduce((obj, lineNum, index) => ({
|
|
923
|
+
...obj,
|
|
924
|
+
[lineNum]: Array.isArray(content) ? content[index] : content,
|
|
925
|
+
}), {}),
|
|
926
|
+
]));
|
|
927
|
+
const keys = UtilsServer.hashString(Object.keys(pathesContents)
|
|
928
|
+
.map((path) => path.replaceAll(".inib", ""))
|
|
929
|
+
.join("."));
|
|
930
|
+
try {
|
|
931
|
+
await File.lock(join(tablePath, ".tmp"), keys);
|
|
932
|
+
await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(this.isThreadEnabled
|
|
933
|
+
? await File.createWorker("replace", [path, content])
|
|
934
|
+
: await File.replace(path, content))));
|
|
935
|
+
await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
|
|
936
|
+
renameList = [];
|
|
937
|
+
if (Config.isCacheEnabled)
|
|
938
|
+
await this.clearCache(tablePath);
|
|
939
|
+
if (returnPostedData)
|
|
940
|
+
return this.get(tableName, where, options, !Array.isArray(where), undefined, schema);
|
|
907
941
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
? data.map((item) => ({
|
|
913
|
-
...item,
|
|
914
|
-
updatedAt: Date.now(),
|
|
915
|
-
}))
|
|
916
|
-
: { ...data, updatedAt: Date.now() })).map(([path, content]) => [
|
|
917
|
-
path,
|
|
918
|
-
[...(Array.isArray(where) ? where : [where])].reduce((obj, lineNum, index) => ({
|
|
919
|
-
...obj,
|
|
920
|
-
[lineNum]: Array.isArray(content) ? content[index] : content,
|
|
921
|
-
}), {}),
|
|
922
|
-
]));
|
|
923
|
-
const keys = UtilsServer.hashString(Object.keys(pathesContents)
|
|
924
|
-
.map((path) => path.replaceAll(".inib", ""))
|
|
925
|
-
.join("."));
|
|
926
|
-
try {
|
|
927
|
-
await File.lock(join(tablePath, ".tmp"), keys);
|
|
928
|
-
await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(this.isThreadEnabled
|
|
929
|
-
? await File.createWorker("replace", [path, content])
|
|
930
|
-
: await File.replace(path, content))));
|
|
931
|
-
await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
|
|
932
|
-
renameList = [];
|
|
933
|
-
if (Config.isCacheEnabled)
|
|
934
|
-
await this.clearCache(tablePath);
|
|
935
|
-
if (returnPostedData)
|
|
936
|
-
return this.get(tableName, where, options, !Array.isArray(where), undefined, schema);
|
|
937
|
-
}
|
|
938
|
-
finally {
|
|
939
|
-
if (renameList.length)
|
|
940
|
-
await Promise.allSettled(renameList.map(async ([tempPath, _]) => unlink(tempPath)));
|
|
941
|
-
await File.unlock(join(tablePath, ".tmp"), keys);
|
|
942
|
-
}
|
|
942
|
+
finally {
|
|
943
|
+
if (renameList.length)
|
|
944
|
+
await Promise.allSettled(renameList.map(async ([tempPath, _]) => unlink(tempPath)));
|
|
945
|
+
await File.unlock(join(tablePath, ".tmp"), keys);
|
|
943
946
|
}
|
|
944
947
|
}
|
|
945
948
|
else if (Utils.isObject(where)) {
|
|
946
949
|
const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
|
|
947
|
-
if (!lineNumbers || !lineNumbers.length)
|
|
948
|
-
throw this.throwError("NO_RESULTS", tableName);
|
|
949
950
|
return this.put(tableName, data, lineNumbers);
|
|
950
951
|
}
|
|
951
952
|
else
|
|
@@ -953,12 +954,7 @@ export default class Inibase {
|
|
|
953
954
|
}
|
|
954
955
|
async delete(tableName, where, _id) {
|
|
955
956
|
let renameList = [];
|
|
956
|
-
const tablePath = join(this.folder, this.database, tableName);
|
|
957
|
-
const schema = await this.getTableSchema(tableName);
|
|
958
|
-
if (!schema)
|
|
959
|
-
throw this.throwError("NO_SCHEMA", tableName);
|
|
960
|
-
if (!(await File.isExists(join(tablePath, "id.inib"))))
|
|
961
|
-
throw this.throwError("NO_ITEMS", tableName);
|
|
957
|
+
const tablePath = join(this.folder, this.database, tableName), schema = await this.isTableEmpty(tableName);
|
|
962
958
|
if (!where) {
|
|
963
959
|
try {
|
|
964
960
|
await File.lock(join(tablePath, ".tmp"));
|
|
@@ -973,58 +969,49 @@ export default class Inibase {
|
|
|
973
969
|
}
|
|
974
970
|
return "*";
|
|
975
971
|
}
|
|
976
|
-
else if ((Array.isArray(where) &&
|
|
977
|
-
|
|
978
|
-
|
|
972
|
+
else if ((Array.isArray(where) && where.every(Utils.isValidID)) ||
|
|
973
|
+
Utils.isValidID(where)) {
|
|
974
|
+
const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
|
|
975
|
+
return this.delete(tableName, lineNumbers, where);
|
|
976
|
+
}
|
|
977
|
+
else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
|
|
979
978
|
Utils.isNumber(where)) {
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
if (!
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
await
|
|
998
|
-
await
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
: await File.remove(join(tablePath, file), where))));
|
|
1004
|
-
await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
|
|
1005
|
-
if (Config.isCacheEnabled) {
|
|
1006
|
-
await this.clearCache(tablePath);
|
|
1007
|
-
if (await File.isExists(join(tablePath, ".cache", "pagination.inib"))) {
|
|
1008
|
-
let [lastId, totalItems] = (await File.read(join(tablePath, ".cache", "pagination.inib"), true))
|
|
1009
|
-
.split(",")
|
|
1010
|
-
.map(Number);
|
|
1011
|
-
await File.write(join(tablePath, ".cache", "pagination.inib"), `${lastId},${totalItems - (Array.isArray(where) ? where.length : 1)}`, true);
|
|
1012
|
-
}
|
|
979
|
+
// "where" in this case, is the line(s) number(s) and not id(s)
|
|
980
|
+
const files = (await readdir(tablePath))?.filter((fileName) => fileName.endsWith(".inib"));
|
|
981
|
+
if (files.length) {
|
|
982
|
+
if (!_id)
|
|
983
|
+
_id = Object.entries((await File.get(join(tablePath, "id.inib"), where, "number", undefined, this.salt)) ?? {}).map(([_key, id]) => UtilsServer.encodeID(Number(id), this.salt));
|
|
984
|
+
if (!_id.length)
|
|
985
|
+
throw this.throwError("NO_RESULTS", tableName);
|
|
986
|
+
try {
|
|
987
|
+
await File.lock(join(tablePath, ".tmp"));
|
|
988
|
+
await Promise.all(files.map(async (file) => renameList.push(this.isThreadEnabled
|
|
989
|
+
? await File.createWorker("remove", [
|
|
990
|
+
join(tablePath, file),
|
|
991
|
+
where,
|
|
992
|
+
])
|
|
993
|
+
: await File.remove(join(tablePath, file), where))));
|
|
994
|
+
await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
|
|
995
|
+
if (Config.isCacheEnabled) {
|
|
996
|
+
await this.clearCache(tablePath);
|
|
997
|
+
if (await File.isExists(join(tablePath, ".cache", "pagination.inib"))) {
|
|
998
|
+
let [lastId, totalItems] = (await File.read(join(tablePath, ".cache", "pagination.inib"), true))
|
|
999
|
+
.split(",")
|
|
1000
|
+
.map(Number);
|
|
1001
|
+
await File.write(join(tablePath, ".cache", "pagination.inib"), `${lastId},${totalItems - (Array.isArray(where) ? where.length : 1)}`, true);
|
|
1013
1002
|
}
|
|
1014
|
-
return Array.isArray(_id) && _id.length === 1 ? _id[0] : _id;
|
|
1015
|
-
}
|
|
1016
|
-
finally {
|
|
1017
|
-
if (renameList.length)
|
|
1018
|
-
await Promise.allSettled(renameList.map(async ([tempPath, _]) => unlink(tempPath)));
|
|
1019
|
-
await File.unlock(join(tablePath, ".tmp"));
|
|
1020
1003
|
}
|
|
1004
|
+
return Array.isArray(_id) && _id.length === 1 ? _id[0] : _id;
|
|
1005
|
+
}
|
|
1006
|
+
finally {
|
|
1007
|
+
if (renameList.length)
|
|
1008
|
+
await Promise.allSettled(renameList.map(async ([tempPath, _]) => unlink(tempPath)));
|
|
1009
|
+
await File.unlock(join(tablePath, ".tmp"));
|
|
1021
1010
|
}
|
|
1022
1011
|
}
|
|
1023
1012
|
}
|
|
1024
1013
|
else if (Utils.isObject(where)) {
|
|
1025
1014
|
const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
|
|
1026
|
-
if (!lineNumbers || !lineNumbers.length)
|
|
1027
|
-
throw this.throwError("NO_RESULTS", tableName);
|
|
1028
1015
|
return this.delete(tableName, lineNumbers);
|
|
1029
1016
|
}
|
|
1030
1017
|
else
|
|
@@ -1033,11 +1020,7 @@ export default class Inibase {
|
|
|
1033
1020
|
}
|
|
1034
1021
|
async sum(tableName, columns, where) {
|
|
1035
1022
|
let RETURN = {};
|
|
1036
|
-
const tablePath = join(this.folder, this.database, tableName), schema = await this.
|
|
1037
|
-
if (!schema)
|
|
1038
|
-
throw this.throwError("NO_SCHEMA", tableName);
|
|
1039
|
-
if (!(await File.isExists(join(tablePath, "id.inib"))))
|
|
1040
|
-
throw this.throwError("NO_ITEMS", tableName);
|
|
1023
|
+
const tablePath = join(this.folder, this.database, tableName), schema = await this.isTableEmpty(tableName);
|
|
1041
1024
|
if (!Array.isArray(columns))
|
|
1042
1025
|
columns = [columns];
|
|
1043
1026
|
for await (const column of columns) {
|
|
@@ -1057,11 +1040,7 @@ export default class Inibase {
|
|
|
1057
1040
|
}
|
|
1058
1041
|
async max(tableName, columns, where) {
|
|
1059
1042
|
let RETURN = {};
|
|
1060
|
-
const tablePath = join(this.folder, this.database, tableName), schema = await this.
|
|
1061
|
-
if (!schema)
|
|
1062
|
-
throw this.throwError("NO_SCHEMA", tableName);
|
|
1063
|
-
if (!(await File.isExists(join(tablePath, "id.inib"))))
|
|
1064
|
-
throw this.throwError("NO_ITEMS", tableName);
|
|
1043
|
+
const tablePath = join(this.folder, this.database, tableName), schema = await this.isTableEmpty(tableName);
|
|
1065
1044
|
if (!Array.isArray(columns))
|
|
1066
1045
|
columns = [columns];
|
|
1067
1046
|
for await (const column of columns) {
|
|
@@ -1081,11 +1060,7 @@ export default class Inibase {
|
|
|
1081
1060
|
}
|
|
1082
1061
|
async min(tableName, columns, where) {
|
|
1083
1062
|
let RETURN = {};
|
|
1084
|
-
const tablePath = join(this.folder, this.database, tableName), schema = await this.
|
|
1085
|
-
if (!schema)
|
|
1086
|
-
throw this.throwError("NO_SCHEMA", tableName);
|
|
1087
|
-
if (!(await File.isExists(join(tablePath, "id.inib"))))
|
|
1088
|
-
throw this.throwError("NO_ITEMS", tableName);
|
|
1063
|
+
const tablePath = join(this.folder, this.database, tableName), schema = await this.isTableEmpty(tableName);
|
|
1089
1064
|
if (!Array.isArray(columns))
|
|
1090
1065
|
columns = [columns];
|
|
1091
1066
|
for await (const column of columns) {
|
|
@@ -1103,4 +1078,101 @@ export default class Inibase {
|
|
|
1103
1078
|
}
|
|
1104
1079
|
return RETURN;
|
|
1105
1080
|
}
|
|
1081
|
+
async sort(tableName, columns, where, options = {
|
|
1082
|
+
page: 1,
|
|
1083
|
+
perPage: 15,
|
|
1084
|
+
}) {
|
|
1085
|
+
// TO-DO: Cache Results based on "Columns and Sort Direction"
|
|
1086
|
+
const tablePath = join(this.folder, this.database, tableName), schema = await this.isTableEmpty(tableName);
|
|
1087
|
+
// Default values for page and perPage
|
|
1088
|
+
options.page = options.page || 1;
|
|
1089
|
+
options.perPage = options.perPage || 15;
|
|
1090
|
+
let sortArray, isLineNumbers = true, keepItems = [];
|
|
1091
|
+
if (Utils.isObject(columns) && !Array.isArray(columns)) {
|
|
1092
|
+
// {name: "ASC", age: "DESC"}
|
|
1093
|
+
sortArray = Object.entries(columns).map(([key, value]) => [
|
|
1094
|
+
key,
|
|
1095
|
+
typeof value === "string" ? value.toLowerCase() === "asc" : value > 0,
|
|
1096
|
+
]);
|
|
1097
|
+
}
|
|
1098
|
+
else {
|
|
1099
|
+
if (!Array.isArray(columns))
|
|
1100
|
+
columns = [columns];
|
|
1101
|
+
sortArray = columns.map((column) => [column, true]);
|
|
1102
|
+
}
|
|
1103
|
+
let cacheKey = "";
|
|
1104
|
+
// Criteria
|
|
1105
|
+
if (Config.isCacheEnabled)
|
|
1106
|
+
cacheKey = UtilsServer.hashString(inspect(sortArray, { sorted: true }));
|
|
1107
|
+
if (where) {
|
|
1108
|
+
let lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
|
|
1109
|
+
keepItems = Object.values((await File.get(join(tablePath, "id.inib"), lineNumbers, "number", undefined, this.salt)) ?? {}).map(Number);
|
|
1110
|
+
isLineNumbers = false;
|
|
1111
|
+
if (!keepItems.length)
|
|
1112
|
+
throw this.throwError("NO_RESULTS", tableName);
|
|
1113
|
+
keepItems = keepItems.slice((options.page - 1) * options.perPage, options.page * options.perPage);
|
|
1114
|
+
}
|
|
1115
|
+
if (!keepItems.length)
|
|
1116
|
+
keepItems = Array.from({ length: options.perPage }, (_, index) => (options.page - 1) * options.perPage +
|
|
1117
|
+
index +
|
|
1118
|
+
1);
|
|
1119
|
+
const filesPathes = [["id", true], ...sortArray].map((column) => join(tablePath, `${column[0]}.inib`));
|
|
1120
|
+
// Construct the paste command to merge files and filter lines by IDs
|
|
1121
|
+
const pasteCommand = `paste ${filesPathes.join(" ")}`;
|
|
1122
|
+
// Construct the sort command dynamically based on the number of files for sorting
|
|
1123
|
+
let index = 2;
|
|
1124
|
+
const sortColumns = sortArray
|
|
1125
|
+
.map(([key, ascending], i) => {
|
|
1126
|
+
const field = this.getField(key, schema);
|
|
1127
|
+
if (field)
|
|
1128
|
+
return `-k${i + index},${i + index}${field.type === "number" ? "n" : ""}${!ascending ? "r" : ""}`;
|
|
1129
|
+
else
|
|
1130
|
+
return "";
|
|
1131
|
+
})
|
|
1132
|
+
.join(" ");
|
|
1133
|
+
const sortCommand = `sort ${sortColumns}`;
|
|
1134
|
+
// Construct the awk command to keep only the specified lines after sorting
|
|
1135
|
+
const awkCommand = isLineNumbers
|
|
1136
|
+
? `awk '${keepItems.map((line) => `NR==${line}`).join(" || ")}'`
|
|
1137
|
+
: `awk 'NR==${keepItems[0]}${keepItems
|
|
1138
|
+
.map((num) => `||NR==${num}`)
|
|
1139
|
+
.join("")}'`;
|
|
1140
|
+
try {
|
|
1141
|
+
if (cacheKey)
|
|
1142
|
+
await File.lock(join(tablePath, ".tmp"), cacheKey);
|
|
1143
|
+
// Combine the commands
|
|
1144
|
+
// Execute the command synchronously
|
|
1145
|
+
const { stdout, stderr } = await UtilsServer.exec(Config.isCacheEnabled
|
|
1146
|
+
? (await File.isExists(join(tablePath, ".cache", `${cacheKey}.inib`)))
|
|
1147
|
+
? `${awkCommand} ${join(tablePath, ".cache", `${cacheKey}.inib`)}`
|
|
1148
|
+
: `${pasteCommand} | ${sortCommand} -o ${join(tablePath, ".cache", `${cacheKey}.inib`)} && ${awkCommand} ${join(tablePath, ".cache", `${cacheKey}.inib`)}`
|
|
1149
|
+
: `${pasteCommand} | ${sortCommand} | ${awkCommand}`, {
|
|
1150
|
+
encoding: "utf-8",
|
|
1151
|
+
});
|
|
1152
|
+
// Parse the result and extract the specified lines
|
|
1153
|
+
const lines = stdout.trim().split("\n");
|
|
1154
|
+
const outputArray = lines.map((line) => {
|
|
1155
|
+
const splitedFileColumns = line.split("\t"); // Assuming tab-separated columns
|
|
1156
|
+
const outputObject = {};
|
|
1157
|
+
// Extract values for each file, including "id.inib"
|
|
1158
|
+
filesPathes.forEach((fileName, index) => {
|
|
1159
|
+
const Field = this.getField(parse(fileName).name, schema);
|
|
1160
|
+
if (Field)
|
|
1161
|
+
outputObject[Field.key] = File.decode(splitedFileColumns[index], Field?.type, Field?.children, this.salt);
|
|
1162
|
+
});
|
|
1163
|
+
return outputObject;
|
|
1164
|
+
});
|
|
1165
|
+
const restOfColumns = await this.get(tableName, outputArray.map(({ id }) => id), options, undefined, undefined, schema, true);
|
|
1166
|
+
return restOfColumns
|
|
1167
|
+
? outputArray.map((item, index) => ({
|
|
1168
|
+
...item,
|
|
1169
|
+
...restOfColumns[index],
|
|
1170
|
+
}))
|
|
1171
|
+
: outputArray;
|
|
1172
|
+
}
|
|
1173
|
+
finally {
|
|
1174
|
+
if (cacheKey)
|
|
1175
|
+
await File.unlock(join(tablePath, ".tmp"), cacheKey);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1106
1178
|
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -142,6 +142,13 @@ export declare const isDate: (input: any) => boolean;
|
|
|
142
142
|
* @returns A boolean indicating whether the input is a string representing a valid ID of length 32.
|
|
143
143
|
*/
|
|
144
144
|
export declare const isValidID: (input: any) => input is string;
|
|
145
|
+
/**
|
|
146
|
+
* Checks if a given string is a valid JSON.
|
|
147
|
+
*
|
|
148
|
+
* @param {string} str - The string to be checked.
|
|
149
|
+
* @returns {boolean} Returns true if the string is valid JSON, otherwise false.
|
|
150
|
+
*/
|
|
151
|
+
export declare const isJSON: (str: string) => boolean;
|
|
145
152
|
/**
|
|
146
153
|
* Identifies and returns properties that have changed between two objects.
|
|
147
154
|
*
|
|
@@ -171,6 +178,8 @@ export declare function FormatObjectCriteriaValue(value: string, isParentArray?:
|
|
|
171
178
|
ComparisonOperator,
|
|
172
179
|
string | number | boolean | null | (string | number | null)[]
|
|
173
180
|
];
|
|
181
|
+
type ValidKey = number | string;
|
|
182
|
+
export declare const swapKeyValue: <K extends ValidKey, V extends ValidKey>(object: Record<K, V>) => Record<V, K>;
|
|
174
183
|
export default class Utils {
|
|
175
184
|
static isNumber: (input: any) => input is number;
|
|
176
185
|
static isObject: (obj: any) => obj is Record<any, any>;
|
|
@@ -192,4 +201,6 @@ export default class Utils {
|
|
|
192
201
|
static validateFieldType: (value: any, fieldType: FieldType | FieldType[], fieldChildrenType?: FieldType | FieldType[] | undefined) => boolean;
|
|
193
202
|
static isArrayOfNulls: (input: any) => input is null[] | null[][];
|
|
194
203
|
static FormatObjectCriteriaValue: typeof FormatObjectCriteriaValue;
|
|
204
|
+
static swapKeyValue: <K extends ValidKey, V extends ValidKey>(object: Record<K, V>) => Record<V, K>;
|
|
195
205
|
}
|
|
206
|
+
export {};
|
package/dist/utils.js
CHANGED
|
@@ -216,18 +216,7 @@ export const isValidID = (input) => {
|
|
|
216
216
|
* @param {string} str - The string to be checked.
|
|
217
217
|
* @returns {boolean} Returns true if the string is valid JSON, otherwise false.
|
|
218
218
|
*/
|
|
219
|
-
|
|
220
|
-
try {
|
|
221
|
-
// Attempt to parse the string as JSON
|
|
222
|
-
JSON.parse(str);
|
|
223
|
-
// If parsing succeeds, return true
|
|
224
|
-
return true;
|
|
225
|
-
}
|
|
226
|
-
catch (error) {
|
|
227
|
-
// If an error occurs during parsing, return false
|
|
228
|
-
return false;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
219
|
+
export const isJSON = (str) => str[0] === "{" || str[0] === "[";
|
|
231
220
|
/**
|
|
232
221
|
* Identifies and returns properties that have changed between two objects.
|
|
233
222
|
*
|
|
@@ -302,7 +291,7 @@ export const validateFieldType = (value, fieldType, fieldChildrenType) => {
|
|
|
302
291
|
if (Array.isArray(fieldType))
|
|
303
292
|
return detectFieldType(value, fieldType) !== undefined;
|
|
304
293
|
if (fieldType === "array" && fieldChildrenType && Array.isArray(value))
|
|
305
|
-
return value.
|
|
294
|
+
return value.every((v) => detectFieldType(v, Array.isArray(fieldChildrenType)
|
|
306
295
|
? fieldChildrenType
|
|
307
296
|
: [fieldChildrenType]) !== undefined);
|
|
308
297
|
switch (fieldType) {
|
|
@@ -403,6 +392,7 @@ export function FormatObjectCriteriaValue(value, isParentArray = false) {
|
|
|
403
392
|
return ["=", value];
|
|
404
393
|
}
|
|
405
394
|
}
|
|
395
|
+
export const swapKeyValue = (object) => Object.entries(object).reduce((swapped, [key, value]) => ({ ...swapped, [value]: key }), {});
|
|
406
396
|
export default class Utils {
|
|
407
397
|
static isNumber = isNumber;
|
|
408
398
|
static isObject = isObject;
|
|
@@ -424,4 +414,5 @@ export default class Utils {
|
|
|
424
414
|
static validateFieldType = validateFieldType;
|
|
425
415
|
static isArrayOfNulls = isArrayOfNulls;
|
|
426
416
|
static FormatObjectCriteriaValue = FormatObjectCriteriaValue;
|
|
417
|
+
static swapKeyValue = swapKeyValue;
|
|
427
418
|
}
|
package/dist/utils.server.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
-
|
|
2
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
3
|
+
import type { ComparisonOperator, FieldType, Schema } from "./index.js";
|
|
4
|
+
import { exec as execAsync } from "node:child_process";
|
|
5
|
+
export declare const exec: typeof execAsync.__promisify__;
|
|
3
6
|
/**
|
|
4
7
|
* Generates a hashed password using SHA-256.
|
|
5
8
|
*
|
|
@@ -48,56 +51,57 @@ export declare const findLastIdNumber: (schema: Schema, secretKeyOrSalt: string
|
|
|
48
51
|
* @param encodeIDs - If true, IDs will be encoded, else they will remain as numbers.
|
|
49
52
|
* @returns The updated schema with encoded IDs.
|
|
50
53
|
*/
|
|
51
|
-
export declare const addIdToSchema: (schema: Schema, oldIndex: number | undefined, secretKeyOrSalt: string | number | Buffer, encodeIDs?: boolean) => (
|
|
52
|
-
id?: string | number | undefined;
|
|
53
|
-
key: string;
|
|
54
|
-
required?: boolean | undefined;
|
|
55
|
-
} & {
|
|
56
|
-
type: "string" | "number" | "boolean" | "id" | "url" | "html" | "table" | "email" | "json" | "date" | "password" | "ip";
|
|
57
|
-
children?: undefined;
|
|
58
|
-
}) | ({
|
|
59
|
-
id?: string | number | undefined;
|
|
60
|
-
key: string;
|
|
61
|
-
required?: boolean | undefined;
|
|
62
|
-
} & {
|
|
63
|
-
type: "object";
|
|
64
|
-
children: Schema;
|
|
65
|
-
}) | ({
|
|
66
|
-
id?: string | number | undefined;
|
|
67
|
-
key: string;
|
|
68
|
-
required?: boolean | undefined;
|
|
69
|
-
} & {
|
|
70
|
-
type: "array";
|
|
71
|
-
children: "string" | "number" | "boolean" | "object" | "id" | "url" | "html" | "table" | "email" | "json" | "date" | "password" | "ip" | Schema | ("string" | "number" | "boolean" | "object" | "id" | "url" | "html" | "table" | "email" | "json" | "date" | "password" | "ip")[];
|
|
72
|
-
}))[];
|
|
54
|
+
export declare const addIdToSchema: (schema: Schema, oldIndex: number | undefined, secretKeyOrSalt: string | number | Buffer, encodeIDs?: boolean) => import("./index.js").Field[];
|
|
73
55
|
export declare const hashString: (str: string) => string;
|
|
56
|
+
/**
|
|
57
|
+
* Evaluates a comparison between two values based on a specified operator and field types.
|
|
58
|
+
*
|
|
59
|
+
* @param operator - The comparison operator (e.g., '=', '!=', '>', '<', '>=', '<=', '[]', '![]', '*', '!*').
|
|
60
|
+
* @param originalValue - The value to compare, can be a single value or an array of values.
|
|
61
|
+
* @param comparedAtValue - The value or values to compare against.
|
|
62
|
+
* @param fieldType - Optional type of the field to guide comparison (e.g., 'password', 'boolean').
|
|
63
|
+
* @param fieldChildrenType - Optional type for child elements in array inputs.
|
|
64
|
+
* @returns boolean - Result of the comparison operation.
|
|
65
|
+
*
|
|
66
|
+
* Note: Handles various data types and comparison logic, including special handling for passwords and regex patterns.
|
|
67
|
+
*/
|
|
68
|
+
export declare const compare: (operator: ComparisonOperator, originalValue: string | number | boolean | null | (string | number | boolean | null)[], comparedAtValue: string | number | boolean | null | (string | number | boolean | null)[], fieldType?: FieldType | FieldType[], fieldChildrenType?: FieldType | FieldType[]) => boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Helper function to check equality based on the field type.
|
|
71
|
+
*
|
|
72
|
+
* @param originalValue - The original value.
|
|
73
|
+
* @param comparedAtValue - The value to compare against.
|
|
74
|
+
* @param fieldType - Type of the field.
|
|
75
|
+
* @returns boolean - Result of the equality check.
|
|
76
|
+
*/
|
|
77
|
+
export declare const isEqual: (originalValue: string | number | boolean | null | (string | number | boolean | null)[], comparedAtValue: string | number | boolean | null | (string | number | boolean | null)[], fieldType?: FieldType | FieldType[]) => boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Helper function to check array equality.
|
|
80
|
+
*
|
|
81
|
+
* @param originalValue - The original value.
|
|
82
|
+
* @param comparedAtValue - The value to compare against.
|
|
83
|
+
* @returns boolean - Result of the array equality check.
|
|
84
|
+
*/
|
|
85
|
+
export declare const isArrayEqual: (originalValue: string | number | boolean | null | (string | number | boolean | null)[], comparedAtValue: string | number | boolean | null | (string | number | boolean | null)[]) => boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Helper function to check wildcard pattern matching using regex.
|
|
88
|
+
*
|
|
89
|
+
* @param originalValue - The original value.
|
|
90
|
+
* @param comparedAtValue - The value with wildcard pattern.
|
|
91
|
+
* @returns boolean - Result of the wildcard pattern matching.
|
|
92
|
+
*/
|
|
93
|
+
export declare const isWildcardMatch: (originalValue: string | number | boolean | null | (string | number | boolean | null)[], comparedAtValue: string | number | boolean | null | (string | number | boolean | null)[]) => boolean;
|
|
74
94
|
export default class UtilsServer {
|
|
75
95
|
static encodeID: (id: string | number, secretKeyOrSalt: string | number | Buffer) => string;
|
|
76
96
|
static decodeID: (input: string, secretKeyOrSalt: string | number | Buffer) => number;
|
|
77
97
|
static hashPassword: (password: string) => string;
|
|
78
98
|
static comparePassword: (hashedPassword: string, inputPassword: string) => boolean;
|
|
79
99
|
static findLastIdNumber: (schema: Schema, secretKeyOrSalt: string | number | Buffer) => number;
|
|
80
|
-
static addIdToSchema: (schema: Schema, oldIndex: number | undefined, secretKeyOrSalt: string | number | Buffer, encodeIDs?: boolean | undefined) => (
|
|
81
|
-
id?: string | number | undefined;
|
|
82
|
-
key: string;
|
|
83
|
-
required?: boolean | undefined;
|
|
84
|
-
} & {
|
|
85
|
-
type: "string" | "number" | "boolean" | "id" | "url" | "html" | "table" | "email" | "json" | "date" | "password" | "ip";
|
|
86
|
-
children?: undefined;
|
|
87
|
-
}) | ({
|
|
88
|
-
id?: string | number | undefined;
|
|
89
|
-
key: string;
|
|
90
|
-
required?: boolean | undefined;
|
|
91
|
-
} & {
|
|
92
|
-
type: "object";
|
|
93
|
-
children: Schema;
|
|
94
|
-
}) | ({
|
|
95
|
-
id?: string | number | undefined;
|
|
96
|
-
key: string;
|
|
97
|
-
required?: boolean | undefined;
|
|
98
|
-
} & {
|
|
99
|
-
type: "array";
|
|
100
|
-
children: "string" | "number" | "boolean" | "object" | "id" | "url" | "html" | "table" | "email" | "json" | "date" | "password" | "ip" | Schema | ("string" | "number" | "boolean" | "object" | "id" | "url" | "html" | "table" | "email" | "json" | "date" | "password" | "ip")[];
|
|
101
|
-
}))[];
|
|
100
|
+
static addIdToSchema: (schema: Schema, oldIndex: number | undefined, secretKeyOrSalt: string | number | Buffer, encodeIDs?: boolean | undefined) => import("./index.js").Field[];
|
|
102
101
|
static hashString: (str: string) => string;
|
|
102
|
+
static exec: typeof execAsync.__promisify__;
|
|
103
|
+
static compare: (operator: ComparisonOperator, originalValue: string | number | boolean | (string | number | boolean | null)[] | null, comparedAtValue: string | number | boolean | (string | number | boolean | null)[] | null, fieldType?: FieldType | FieldType[] | undefined, fieldChildrenType?: FieldType | FieldType[] | undefined) => boolean;
|
|
104
|
+
static isEqual: (originalValue: string | number | boolean | (string | number | boolean | null)[] | null, comparedAtValue: string | number | boolean | (string | number | boolean | null)[] | null, fieldType?: FieldType | FieldType[] | undefined) => boolean;
|
|
105
|
+
static isArrayEqual: (originalValue: string | number | boolean | (string | number | boolean | null)[] | null, comparedAtValue: string | number | boolean | (string | number | boolean | null)[] | null) => boolean;
|
|
106
|
+
static isWildcardMatch: (originalValue: string | number | boolean | (string | number | boolean | null)[] | null, comparedAtValue: string | number | boolean | (string | number | boolean | null)[] | null) => boolean;
|
|
103
107
|
}
|
package/dist/utils.server.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { createCipheriv, createDecipheriv, randomBytes, scryptSync, createHash, } from "node:crypto";
|
|
2
|
-
import { isArrayOfObjects, isNumber, isValidID } from "./utils.js";
|
|
2
|
+
import { detectFieldType, isArrayOfObjects, isNumber, isPassword, isValidID, } from "./utils.js";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import { exec as execAsync } from "node:child_process";
|
|
5
|
+
export const exec = promisify(execAsync);
|
|
3
6
|
/**
|
|
4
7
|
* Generates a hashed password using SHA-256.
|
|
5
8
|
*
|
|
@@ -111,6 +114,130 @@ export const addIdToSchema = (schema, oldIndex = 0, secretKeyOrSalt, encodeIDs)
|
|
|
111
114
|
return field;
|
|
112
115
|
});
|
|
113
116
|
export const hashString = (str) => createHash("sha256").update(str).digest("hex");
|
|
117
|
+
/**
|
|
118
|
+
* Evaluates a comparison between two values based on a specified operator and field types.
|
|
119
|
+
*
|
|
120
|
+
* @param operator - The comparison operator (e.g., '=', '!=', '>', '<', '>=', '<=', '[]', '![]', '*', '!*').
|
|
121
|
+
* @param originalValue - The value to compare, can be a single value or an array of values.
|
|
122
|
+
* @param comparedAtValue - The value or values to compare against.
|
|
123
|
+
* @param fieldType - Optional type of the field to guide comparison (e.g., 'password', 'boolean').
|
|
124
|
+
* @param fieldChildrenType - Optional type for child elements in array inputs.
|
|
125
|
+
* @returns boolean - Result of the comparison operation.
|
|
126
|
+
*
|
|
127
|
+
* Note: Handles various data types and comparison logic, including special handling for passwords and regex patterns.
|
|
128
|
+
*/
|
|
129
|
+
export const compare = (operator, originalValue, comparedAtValue, fieldType, fieldChildrenType) => {
|
|
130
|
+
// Determine the field type if it's an array of potential types.
|
|
131
|
+
if (Array.isArray(fieldType)) {
|
|
132
|
+
fieldType = detectFieldType(String(originalValue), fieldType);
|
|
133
|
+
}
|
|
134
|
+
// Handle comparisons involving arrays.
|
|
135
|
+
if (Array.isArray(comparedAtValue) && !["[]", "![]"].includes(operator)) {
|
|
136
|
+
return comparedAtValue.some((comparedAtValueSingle) => compare(operator, originalValue, comparedAtValueSingle, fieldType));
|
|
137
|
+
}
|
|
138
|
+
// Switch statement for different comparison operators.
|
|
139
|
+
switch (operator) {
|
|
140
|
+
// Equal (Case Insensitive for strings, specific handling for passwords and booleans).
|
|
141
|
+
case "=":
|
|
142
|
+
return isEqual(originalValue, comparedAtValue, fieldType);
|
|
143
|
+
// Not Equal.
|
|
144
|
+
case "!=":
|
|
145
|
+
return !isEqual(originalValue, comparedAtValue, fieldType);
|
|
146
|
+
// Greater Than.
|
|
147
|
+
case ">":
|
|
148
|
+
return (originalValue !== null &&
|
|
149
|
+
comparedAtValue !== null &&
|
|
150
|
+
originalValue > comparedAtValue);
|
|
151
|
+
// Less Than.
|
|
152
|
+
case "<":
|
|
153
|
+
return (originalValue !== null &&
|
|
154
|
+
comparedAtValue !== null &&
|
|
155
|
+
originalValue < comparedAtValue);
|
|
156
|
+
// Greater Than or Equal.
|
|
157
|
+
case ">=":
|
|
158
|
+
return (originalValue !== null &&
|
|
159
|
+
comparedAtValue !== null &&
|
|
160
|
+
originalValue >= comparedAtValue);
|
|
161
|
+
// Less Than or Equal.
|
|
162
|
+
case "<=":
|
|
163
|
+
return (originalValue !== null &&
|
|
164
|
+
comparedAtValue !== null &&
|
|
165
|
+
originalValue <= comparedAtValue);
|
|
166
|
+
// Array Contains (equality check for arrays).
|
|
167
|
+
case "[]":
|
|
168
|
+
return isArrayEqual(originalValue, comparedAtValue);
|
|
169
|
+
// Array Does Not Contain.
|
|
170
|
+
case "![]":
|
|
171
|
+
return !isArrayEqual(originalValue, comparedAtValue);
|
|
172
|
+
// Wildcard Match (using regex pattern).
|
|
173
|
+
case "*":
|
|
174
|
+
return isWildcardMatch(originalValue, comparedAtValue);
|
|
175
|
+
// Not Wildcard Match.
|
|
176
|
+
case "!*":
|
|
177
|
+
return !isWildcardMatch(originalValue, comparedAtValue);
|
|
178
|
+
// Unsupported operator.
|
|
179
|
+
default:
|
|
180
|
+
throw new Error(`Unsupported operator: ${operator}`);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* Helper function to check equality based on the field type.
|
|
185
|
+
*
|
|
186
|
+
* @param originalValue - The original value.
|
|
187
|
+
* @param comparedAtValue - The value to compare against.
|
|
188
|
+
* @param fieldType - Type of the field.
|
|
189
|
+
* @returns boolean - Result of the equality check.
|
|
190
|
+
*/
|
|
191
|
+
export const isEqual = (originalValue, comparedAtValue, fieldType) => {
|
|
192
|
+
// Switch based on the field type for specific handling.
|
|
193
|
+
switch (fieldType) {
|
|
194
|
+
// Password comparison.
|
|
195
|
+
case "password":
|
|
196
|
+
return isPassword(originalValue) && typeof comparedAtValue === "string"
|
|
197
|
+
? comparePassword(originalValue, comparedAtValue)
|
|
198
|
+
: false;
|
|
199
|
+
// Boolean comparison.
|
|
200
|
+
case "boolean":
|
|
201
|
+
return Number(originalValue) === Number(comparedAtValue);
|
|
202
|
+
// Default comparison.
|
|
203
|
+
default:
|
|
204
|
+
return originalValue === comparedAtValue;
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Helper function to check array equality.
|
|
209
|
+
*
|
|
210
|
+
* @param originalValue - The original value.
|
|
211
|
+
* @param comparedAtValue - The value to compare against.
|
|
212
|
+
* @returns boolean - Result of the array equality check.
|
|
213
|
+
*/
|
|
214
|
+
export const isArrayEqual = (originalValue, comparedAtValue) => {
|
|
215
|
+
return ((Array.isArray(originalValue) &&
|
|
216
|
+
Array.isArray(comparedAtValue) &&
|
|
217
|
+
originalValue.some((v) => comparedAtValue.includes(v))) ||
|
|
218
|
+
(Array.isArray(originalValue) &&
|
|
219
|
+
!Array.isArray(comparedAtValue) &&
|
|
220
|
+
originalValue.includes(comparedAtValue)) ||
|
|
221
|
+
(!Array.isArray(originalValue) &&
|
|
222
|
+
Array.isArray(comparedAtValue) &&
|
|
223
|
+
comparedAtValue.includes(originalValue)) ||
|
|
224
|
+
(!Array.isArray(originalValue) &&
|
|
225
|
+
!Array.isArray(comparedAtValue) &&
|
|
226
|
+
comparedAtValue === originalValue));
|
|
227
|
+
};
|
|
228
|
+
/**
|
|
229
|
+
* Helper function to check wildcard pattern matching using regex.
|
|
230
|
+
*
|
|
231
|
+
* @param originalValue - The original value.
|
|
232
|
+
* @param comparedAtValue - The value with wildcard pattern.
|
|
233
|
+
* @returns boolean - Result of the wildcard pattern matching.
|
|
234
|
+
*/
|
|
235
|
+
export const isWildcardMatch = (originalValue, comparedAtValue) => {
|
|
236
|
+
const wildcardPattern = `^${(String(comparedAtValue).includes("%")
|
|
237
|
+
? String(comparedAtValue)
|
|
238
|
+
: "%" + String(comparedAtValue) + "%").replace(/%/g, ".*")}$`;
|
|
239
|
+
return new RegExp(wildcardPattern, "i").test(String(originalValue));
|
|
240
|
+
};
|
|
114
241
|
export default class UtilsServer {
|
|
115
242
|
static encodeID = encodeID;
|
|
116
243
|
static decodeID = decodeID;
|
|
@@ -119,4 +246,9 @@ export default class UtilsServer {
|
|
|
119
246
|
static findLastIdNumber = findLastIdNumber;
|
|
120
247
|
static addIdToSchema = addIdToSchema;
|
|
121
248
|
static hashString = hashString;
|
|
249
|
+
static exec = exec;
|
|
250
|
+
static compare = compare;
|
|
251
|
+
static isEqual = isEqual;
|
|
252
|
+
static isArrayEqual = isArrayEqual;
|
|
253
|
+
static isWildcardMatch = isWildcardMatch;
|
|
122
254
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "inibase",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.43",
|
|
4
4
|
"author": {
|
|
5
5
|
"name": "Karim Amahtil",
|
|
6
6
|
"email": "karim.amahtil@gmail.com"
|
|
@@ -53,6 +53,9 @@
|
|
|
53
53
|
"types": "./dist",
|
|
54
54
|
"typesVersions": {
|
|
55
55
|
"*": {
|
|
56
|
+
"class": [
|
|
57
|
+
"./dist/index.d.ts"
|
|
58
|
+
],
|
|
56
59
|
"thread": [
|
|
57
60
|
"./dist/index.thread.d.ts"
|
|
58
61
|
],
|
|
@@ -78,6 +81,9 @@
|
|
|
78
81
|
"tinybench": "^2.6.0",
|
|
79
82
|
"typescript": "^5.3.3"
|
|
80
83
|
},
|
|
84
|
+
"dependencies": {
|
|
85
|
+
"inison": "^1.0.0-rc.2"
|
|
86
|
+
},
|
|
81
87
|
"scripts": {
|
|
82
88
|
"build": "npx tsc",
|
|
83
89
|
"test": "npx tsx watch --expose-gc --env-file=.env ./index.test",
|