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 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
- - [ ] Order By
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 'joinMultidimensionalArray' functions.
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, gzip as gzipAsync, gunzip as gunzipAsync, } from "node:zlib";
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, isArrayOfArrays, isNumber, isObject, isPassword, } from "./utils.js";
10
- import { encodeID, comparePassword } from "./utils.server.js";
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
- const gzip = promisify(gzipAsync);
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
- ? await gzip(String(data))
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 gunzip(await readFile(filePath))).toString()
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
- "<": "&lt;",
104
- ">": "&gt;",
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(/[<>,|&$#@^:!\n\r]/g, (match) => replacements[match]);
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
- * Joins elements of a multidimensional array into a string.
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
- // Use the optimized secureArray and joinMultidimensionalArray functions.
152
- return Array.isArray(input)
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
- // Define a mapping of encoded characters to their original symbols.
171
- const replacements = {
172
- "&lt;": "<",
173
- "%2C": ",",
174
- "%7C": "|",
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.includes(",")
268
- ? unSecureArray(reverseJoinMultidimensionalArray(input))
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) => handleComparisonOperator(single_operator, decodedLine, comparedAtValue[index], fieldType))) ||
610
- operator.every((single_operator, index) => handleComparisonOperator(single_operator, decodedLine, comparedAtValue[index], fieldType)))) ||
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
- handleComparisonOperator(operator, decodedLine, comparedAtValue, fieldType));
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, "array" | "object">>;
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">[] | Schema;
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 | (FieldStringArrayType & FieldArrayArrayType) | FieldObjectType | FieldArrayType);
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
- getField(keyPath: string, schema: Schema): (FieldDefault & FieldStringType) | (FieldDefault & FieldObjectType) | (FieldDefault & FieldArrayType) | null;
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[] | null>;
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 JSON.stringify(value);
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 (options.columns.length && !options.columns.includes("id"))
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.getTableSchema(tableName));
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
- (Utils.isNumber(where) && !onlyLinesNumbers)) {
696
- // "where" in this case, is the lineNumbers instead of IDs
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
- (where.every(Utils.isValidID) || where.every(Utils.isNumber))) ||
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, Array.isArray(RETURN) ? RETURN.map((_, index) => index + 1) : 1, options, !Utils.isArrayOfObjects(data), // return only one item if data is not array of objects
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((item) => item.id !== undefined)
854
- .map((item) => item.id));
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, UtilsServer.decodeID(data.id, this.salt));
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
- (where.every(Utils.isValidID) || where.every(Utils.isNumber))) ||
901
- Utils.isValidID(where) ||
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
- if ((Array.isArray(where) && where.every(Utils.isValidID)) ||
904
- Utils.isValidID(where)) {
905
- const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
906
- return this.put(tableName, data, lineNumbers);
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
- else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
909
- Utils.isNumber(where)) {
910
- // "where" in this case, is the line(s) number(s) and not id(s)
911
- const pathesContents = Object.fromEntries(Object.entries(this.joinPathesContents(tablePath, Utils.isArrayOfObjects(data)
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
- (where.every(Utils.isValidID) || where.every(Utils.isNumber))) ||
978
- Utils.isValidID(where) ||
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
- if ((Array.isArray(where) && where.every(Utils.isValidID)) ||
981
- Utils.isValidID(where)) {
982
- const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
983
- if (!lineNumbers)
984
- return null;
985
- return this.delete(tableName, lineNumbers, where);
986
- }
987
- else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
988
- Utils.isNumber(where)) {
989
- // "where" in this case, is the line(s) number(s) and not id(s)
990
- const files = (await readdir(tablePath))?.filter((fileName) => fileName.endsWith(".inib"));
991
- if (files.length) {
992
- if (!_id)
993
- _id = Object.entries((await File.get(join(tablePath, "id.inib"), where, "number", undefined, this.salt)) ?? {}).map(([_key, id]) => UtilsServer.encodeID(Number(id), this.salt));
994
- if (!_id.length)
995
- throw this.throwError("NO_RESULTS", tableName);
996
- try {
997
- await File.lock(join(tablePath, ".tmp"));
998
- await Promise.all(files.map(async (file) => renameList.push(this.isThreadEnabled
999
- ? await File.createWorker("remove", [
1000
- join(tablePath, file),
1001
- where,
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.getTableSchema(tableName);
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.getTableSchema(tableName);
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.getTableSchema(tableName);
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
- function isJSON(str) {
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.some((v) => detectFieldType(v, Array.isArray(fieldChildrenType)
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
  }
@@ -1,5 +1,8 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
- import { type Schema } from "./index.js";
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
  }
@@ -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.40",
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",