inibase 1.0.0-rc.107 → 1.0.0-rc.109

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
@@ -54,6 +54,15 @@ const users = await db.get("user", { favoriteFoods: "![]Pizza,Burger" });
54
54
  <npm|pnpm|yarn|bun> install inibase
55
55
  ```
56
56
 
57
+ > [!WARNING]
58
+ > If you're using **Windows**, the following Unix commands are required: `zcat`, `sed`, `gzip`, and `echo`.
59
+ >
60
+ > To use the missing commands, you need to install additional tools:
61
+ > - **[GnuWin32](http://gnuwin32.sourceforge.net/)**: Provides individual GNU utilities for Windows.
62
+ > - **[Cygwin](https://www.cygwin.com/)**: Offers a full Unix-like environment for Windows.
63
+ >
64
+ > Alternatively, consider using the **Windows Subsystem for Linux (WSL)** to run a Linux environment on Windows. Learn more [here](https://learn.microsoft.com/en-us/windows/wsl/).
65
+
57
66
  ## How it works?
58
67
 
59
68
  `Inibase` organizes data into databases, tables, and columns, each stored in separate files.
@@ -667,21 +676,22 @@ await db.get("user", undefined, { sort: {age: -1, username: "asc"} });
667
676
 
668
677
  ### Bulk
669
678
 
670
- | | 10 | 100 | 1000 |
671
- |--------|-----------------|-----------------|-----------------|
672
- | POST | 11 ms (0.65 mb) | 19 ms (1.00 mb) | 85 ms (4.58 mb) |
673
- | GET | 14 ms (2.77 mb) | 12 ms (3.16 mb) | 34 ms (1.38 mb) |
674
- | PUT | 6 ms (1.11 mb) | 5 ms (1.37 mb) | 10 ms (1.12 mb) |
675
- | DELETE | 17 ms (1.68 mb) | 14 ms (5.45 mb) | 25 ms (5.94 mb) |
679
+ | | 10 | 100 | 1000 |
680
+ |--------|-------------------|-------------------|-------------------|
681
+ | POST | 11 ms (0.66 mb) | 5 ms (1.02 mb) | 24 ms (1.44 mb) |
682
+ | GET | 29 ms (2.86 mb) | 24 ms (2.81 mb) | 36 ms (0.89 mb) |
683
+ | PUT | 21 ms (2.68 mb) | 16 ms (2.90 mb) | 12 ms (0.63 mb) |
684
+ | DELETE | 14 ms (0.82 mb) | 13 ms (0.84 mb) | 2 ms (0.17 mb) |
685
+
676
686
 
677
687
  ### Single
678
688
 
679
- | | 10 | 100 | 1000 |
680
- |--------|-------------------|--------------------|--------------------|
681
- | POST | 43 ms (4.70 mb) | 387 ms (6.36 mb) | 5341 ms (24.73 mb) |
682
- | GET | 99 ms (12.51 mb) | 846 ms (30.68 mb) | 7103 ms (30.86 mb) |
683
- | PUT | 33 ms (10.29 mb) | 312 ms (11.06 mb) | 3539 ms (14.87 mb) |
684
- | DELETE | 134 ms (13.50 mb) | 1224 ms (16.57 mb) | 7339 ms (11.46 mb) |
689
+ | | 10 | 100 | 1000 |
690
+ |--------|---------------------|--------------------|--------------------|
691
+ | POST | 45 ms (1.07 mb) | 12 ms (0.52 mb) | 11 ms (0.37 mb) |
692
+ | GET | 200 ms (2.15 mb) | 192 ms (2.72 mb) | 190 ms (2.31 mb) |
693
+ | PUT | 49 ms (3.22 mb) | 17 ms (2.98 mb) | 17 ms (3.06 mb) |
694
+ | DELETE | 118 ms (0.59 mb) | 113 ms (0.51 mb) | 103 ms (3.14 mb) |
685
695
 
686
696
  > Default testing uses a table with username, email, and password fields, ensuring password encryption is included in the process<br>
687
697
  > To run benchmarks, install _typescript_ & _[tsx](https://github.com/privatenumber/tsx)_ globally and run `benchmark` by default bulk, for single use `benchmark --single|-s`
package/dist/file.d.ts CHANGED
@@ -108,15 +108,6 @@ export declare const remove: (filePath: string, linesToDelete: number | number[]
108
108
  * Note: Decodes each line for comparison and can handle complex queries with multiple conditions.
109
109
  */
110
110
  export declare const search: (filePath: string, operator: ComparisonOperator | ComparisonOperator[], comparedAtValue: string | number | boolean | null | (string | number | boolean | null)[], logicalOperator?: "and" | "or", fieldType?: FieldType | FieldType[], fieldChildrenType?: FieldType | FieldType[] | Schema, limit?: number, offset?: number, readWholeFile?: boolean, secretKey?: string | Buffer) => Promise<[Record<number, string | number | boolean | null | (string | number | boolean | null)[]> | null, number, Set<number> | null]>;
111
- /**
112
- * Asynchronously counts the number of lines in a file.
113
- *
114
- * @param filePath - Path of the file to count lines in.
115
- * @returns Promise<number>. The number of lines in the file.
116
- *
117
- * Note: Reads through the file line by line to count the total number of lines.
118
- */
119
- export declare const count: (filePath: string) => Promise<number>;
120
111
  /**
121
112
  * Asynchronously calculates the sum of numerical values from specified lines in a file.
122
113
  *
package/dist/file.js CHANGED
@@ -176,7 +176,7 @@ export const decode = (input, fieldType, fieldChildrenType, secretKey) => {
176
176
  if (!fieldType)
177
177
  return null;
178
178
  if (input === null || input === "")
179
- return null;
179
+ return undefined;
180
180
  // Detect the fieldType based on the input and the provided array of possible types.
181
181
  if (Array.isArray(fieldType))
182
182
  fieldType = detectFieldType(String(input), fieldType);
@@ -187,6 +187,29 @@ export const decode = (input, fieldType, fieldChildrenType, secretKey) => {
187
187
  : unSecureString(input)
188
188
  : input, fieldType, fieldChildrenType, secretKey);
189
189
  };
190
+ function _groupIntoRanges(arr, action = "p") {
191
+ if (arr.length === 0)
192
+ return [];
193
+ arr.sort((a, b) => a - b); // Ensure the array is sorted
194
+ const ranges = [];
195
+ let start = arr[0];
196
+ let end = arr[0];
197
+ for (let i = 1; i < arr.length; i++) {
198
+ if (arr[i] === end + 1) {
199
+ // Continue the range
200
+ end = arr[i];
201
+ }
202
+ else {
203
+ // End the current range and start a new one
204
+ ranges.push(start === end ? `${start}` : `${start},${end}`);
205
+ start = arr[i];
206
+ end = arr[i];
207
+ }
208
+ }
209
+ // Push the last range
210
+ ranges.push(start === end ? `${start}` : `${start},${end}`);
211
+ return ranges.map((range) => `${range}${action}`).join(";");
212
+ }
190
213
  export async function get(filePath, lineNumbers, fieldType, fieldChildrenType, secretKey, readWholeFile = false) {
191
214
  let fileHandle = null;
192
215
  try {
@@ -224,8 +247,8 @@ export async function get(filePath, lineNumbers, fieldType, fieldChildrenType, s
224
247
  }
225
248
  const escapedFilePath = `${escapeShellPath(filePath)}`;
226
249
  const command = filePath.endsWith(".gz")
227
- ? `zcat "${escapedFilePath}" | sed -n '${lineNumbers.join("p;")}p'`
228
- : `sed -n '${lineNumbers.join("p;")}p' "${escapedFilePath}"`, foundedLines = (await exec(command)).stdout.trimEnd().split("\n");
250
+ ? `zcat "${escapedFilePath}" | sed -n '${_groupIntoRanges(lineNumbers)}'`
251
+ : `sed -n '${_groupIntoRanges(lineNumbers)}' "${escapedFilePath}"`, foundedLines = (await exec(command)).stdout.trimEnd().split("\n");
229
252
  let index = 0;
230
253
  for (const line of foundedLines) {
231
254
  lines[lineNumbers[index]] = decode(line, fieldType, fieldChildrenType, secretKey);
@@ -454,8 +477,8 @@ export const remove = async (filePath, linesToDelete) => {
454
477
  const escapedFilePath = `${escapeShellPath(filePath)}`;
455
478
  const escapedFileTempPath = `${escapeShellPath(fileTempPath)}`;
456
479
  const command = filePath.endsWith(".gz")
457
- ? `zcat ${escapedFilePath} | sed "${linesToDelete.join("d;")}d" | gzip > ${fileTempPath}`
458
- : `sed "${linesToDelete.join("d;")}d" ${escapedFilePath} > ${escapedFileTempPath}`;
480
+ ? `zcat ${escapedFilePath} | sed '${_groupIntoRanges(linesToDelete, "d")}' | gzip > ${fileTempPath}`
481
+ : `sed '${_groupIntoRanges(linesToDelete, "d")}' ${escapedFilePath} > ${escapedFileTempPath}`;
459
482
  await exec(command);
460
483
  return [fileTempPath, filePath];
461
484
  }
@@ -535,31 +558,6 @@ export const search = async (filePath, operator, comparedAtValue, logicalOperato
535
558
  await fileHandle?.close();
536
559
  }
537
560
  };
538
- /**
539
- * Asynchronously counts the number of lines in a file.
540
- *
541
- * @param filePath - Path of the file to count lines in.
542
- * @returns Promise<number>. The number of lines in the file.
543
- *
544
- * Note: Reads through the file line by line to count the total number of lines.
545
- */
546
- export const count = async (filePath) => {
547
- // Number((await exec(`wc -l < ${filePath}`)).stdout.trimEnd());
548
- let linesCount = 0;
549
- if (await isExists(filePath)) {
550
- let fileHandle = null;
551
- try {
552
- fileHandle = await open(filePath, "r");
553
- const rl = createReadLineInternface(filePath, fileHandle);
554
- for await (const _ of rl)
555
- linesCount++;
556
- }
557
- finally {
558
- await fileHandle?.close();
559
- }
560
- }
561
- return linesCount;
562
- };
563
561
  /**
564
562
  * Asynchronously calculates the sum of numerical values from specified lines in a file.
565
563
  *
package/dist/index.d.ts CHANGED
@@ -100,8 +100,16 @@ export default class Inibase {
100
100
  private _combineObjectsToArray;
101
101
  private _CombineData;
102
102
  private joinPathesContents;
103
- private _getItemsFromSchemaHelper;
104
- private getItemsFromSchema;
103
+ private _processSchemaDataHelper;
104
+ private processSchemaData;
105
+ private isSimpleField;
106
+ private processSimpleField;
107
+ private isArrayField;
108
+ private processArrayField;
109
+ private isObjectField;
110
+ private processObjectField;
111
+ private isTableField;
112
+ private processTableField;
105
113
  private applyCriteria;
106
114
  private _filterSchemaByColumns;
107
115
  /**
package/dist/index.js CHANGED
@@ -438,15 +438,6 @@ export default class Inibase {
438
438
  });
439
439
  switch (field.type) {
440
440
  case "array":
441
- return Utils.isArrayOfObjects(field.children)
442
- ? [
443
- this.getDefaultValue({
444
- ...field,
445
- type: "object",
446
- children: field.children,
447
- }),
448
- ]
449
- : null;
450
441
  case "object": {
451
442
  if (!field.children || !Utils.isArrayOfObjects(field.children))
452
443
  return null;
@@ -496,12 +487,14 @@ export default class Inibase {
496
487
  newCombinedData[join(tablePath, `${key}${this.getFileExtension(tableName)}`)] = value;
497
488
  return newCombinedData;
498
489
  }
499
- _getItemsFromSchemaHelper(RETURN, item, index, field) {
490
+ _processSchemaDataHelper(RETURN, item, index, field) {
491
+ // If the item is an object, we need to process its children
500
492
  if (Utils.isObject(item)) {
501
493
  if (!RETURN[index])
502
- RETURN[index] = {};
494
+ RETURN[index] = {}; // Ensure the index exists
503
495
  if (!RETURN[index][field.key])
504
496
  RETURN[index][field.key] = [];
497
+ // Process children fields (recursive if needed)
505
498
  for (const child_field of field.children.filter((children) => children.type === "array" &&
506
499
  Utils.isArrayOfObjects(children.children))) {
507
500
  if (Utils.isObject(item[child_field.key])) {
@@ -522,8 +515,8 @@ export default class Inibase {
522
515
  }
523
516
  else {
524
517
  value[_i].forEach((_element, _index) => {
525
- // Recursive call
526
- this._getItemsFromSchemaHelper(RETURN[index][field.key][_i][child_field.key][_index], _element, _index, child_field);
518
+ // Recursive call to handle nested structure
519
+ this._processSchemaDataHelper(RETURN, _element, _index, child_field);
527
520
  // Perform property assignments
528
521
  if (!RETURN[index][field.key][_i][child_field.key][_index])
529
522
  RETURN[index][field.key][_i][child_field.key][_index] = {};
@@ -537,175 +530,228 @@ export default class Inibase {
537
530
  }
538
531
  }
539
532
  }
540
- async getItemsFromSchema(tableName, schema, linesNumber, options, prefix) {
541
- const tablePath = join(this.databasePath, tableName);
533
+ async processSchemaData(tableName, schema, linesNumber, options, prefix) {
542
534
  const RETURN = {};
543
- for await (const field of schema) {
544
- if ((field.type === "array" ||
545
- (Array.isArray(field.type) && field.type.includes("array"))) &&
546
- field.children) {
547
- if (Utils.isArrayOfObjects(field.children)) {
548
- if (field.children.filter((children) => children.type === "array" &&
549
- Utils.isArrayOfObjects(children.children)).length) {
550
- // one of children has array field type and has children array of object = Schema
551
- const childItems = await this.getItemsFromSchema(tableName, field.children.filter((children) => children.type === "array" &&
552
- Utils.isArrayOfObjects(children.children)), linesNumber, options, `${(prefix ?? "") + field.key}.`);
553
- if (childItems)
554
- for (const [index, item] of Object.entries(childItems))
555
- this._getItemsFromSchemaHelper(RETURN, item, index, field);
556
- field.children = field.children.filter((children) => children.type !== "array" ||
557
- !Utils.isArrayOfObjects(children.children));
558
- }
559
- const fieldItems = await this.getItemsFromSchema(tableName, field.children, linesNumber, options, `${(prefix ?? "") + field.key}.`);
560
- if (fieldItems)
561
- for (const [index, item] of Object.entries(fieldItems)) {
562
- if (!RETURN[index])
563
- RETURN[index] = {};
564
- if (Utils.isObject(item)) {
565
- if (!Utils.isArrayOfNulls(Object.values(item))) {
566
- if (RETURN[index][field.key])
567
- Object.entries(item).forEach(([key, value], _index) => {
568
- for (let _index = 0; _index < value.length; _index++)
569
- if (RETURN[index][field.key][_index])
570
- Object.assign(RETURN[index][field.key][_index], {
571
- [key]: value[_index],
572
- });
573
- else
574
- RETURN[index][field.key][_index] = {
575
- [key]: value[_index],
576
- };
577
- });
578
- else if (Object.values(item).every((_i) => Utils.isArrayOfArrays(_i) || Array.isArray(_i)) &&
579
- prefix)
580
- RETURN[index][field.key] = item;
581
- else {
582
- RETURN[index][field.key] = [];
583
- Object.entries(item).forEach(([key, value], _ind) => {
584
- if (!Array.isArray(value)) {
585
- RETURN[index][field.key][_ind] = {};
586
- RETURN[index][field.key][_ind][key] = value;
587
- }
535
+ for (const field of schema) {
536
+ // If the field is of simple type (non-recursive), process it directly
537
+ if (this.isSimpleField(field.type)) {
538
+ await this.processSimpleField(tableName, field, linesNumber, RETURN, options, prefix);
539
+ }
540
+ else if (this.isArrayField(field.type)) {
541
+ // Process array fields (recursive if needed)
542
+ await this.processArrayField(tableName, field, linesNumber, RETURN, options, prefix);
543
+ }
544
+ else if (this.isObjectField(field.type)) {
545
+ // Process object fields (recursive if needed)
546
+ await this.processObjectField(tableName, field, linesNumber, RETURN, options, prefix);
547
+ }
548
+ else if (this.isTableField(field.type)) {
549
+ // Process table reference fields
550
+ await this.processTableField(tableName, field, linesNumber, RETURN, options, prefix);
551
+ }
552
+ }
553
+ return RETURN;
554
+ }
555
+ // Helper function to determine if a field is simple
556
+ isSimpleField(fieldType) {
557
+ const simpleTypes = [
558
+ "string",
559
+ "number",
560
+ "boolean",
561
+ "date",
562
+ "email",
563
+ "password",
564
+ "html",
565
+ "ip",
566
+ "json",
567
+ "id",
568
+ ];
569
+ if (Array.isArray(fieldType))
570
+ return fieldType.every((type) => typeof type === "string" && simpleTypes.includes(type));
571
+ return simpleTypes.includes(fieldType);
572
+ }
573
+ // Process a simple field (non-recursive)
574
+ async processSimpleField(tableName, field, linesNumber, RETURN, _options, prefix) {
575
+ const fieldPath = join(this.databasePath, tableName, `${prefix ?? ""}${field.key}${this.getFileExtension(tableName)}`);
576
+ if (await File.isExists(fieldPath)) {
577
+ const items = await File.get(fieldPath, linesNumber, field.type, field.children, this.salt);
578
+ if (items) {
579
+ for (const [index, item] of Object.entries(items)) {
580
+ if (typeof item === "undefined")
581
+ continue; // Skip undefined items
582
+ if (!RETURN[index])
583
+ RETURN[index] = {}; // Ensure the index exists
584
+ RETURN[index][field.key] = item; // Assign item to the RETURN object
585
+ }
586
+ }
587
+ }
588
+ }
589
+ // Helper function to check if the field type is array
590
+ isArrayField(fieldType) {
591
+ return ((Array.isArray(fieldType) &&
592
+ fieldType.every((type) => typeof type === "string") &&
593
+ fieldType.includes("array")) ||
594
+ fieldType === "array");
595
+ }
596
+ // Process array fields (recursive if needed)
597
+ async processArrayField(tableName, field, linesNumber, RETURN, options, prefix) {
598
+ if (Array.isArray(field.children)) {
599
+ if (this.isSimpleField(field.children)) {
600
+ await this.processSimpleField(tableName, field, linesNumber, RETURN, options, prefix);
601
+ }
602
+ else if (this.isTableField(field.children)) {
603
+ await this.processTableField(tableName, field, linesNumber, RETURN, options, prefix);
604
+ }
605
+ else {
606
+ // Handling array of objects and filtering nested arrays
607
+ const nestedArrayFields = field.children.filter((child) => child.type === "array" &&
608
+ Utils.isArrayOfObjects(child.children));
609
+ if (nestedArrayFields.length > 0) {
610
+ // one of children has array field type and has children array of object = Schema
611
+ const childItems = await this.processSchemaData(tableName, field.children.filter((children) => children.type === "array" &&
612
+ Utils.isArrayOfObjects(children.children)), linesNumber, options, `${(prefix ?? "") + field.key}.`);
613
+ if (childItems)
614
+ for (const [index, item] of Object.entries(childItems))
615
+ this._processSchemaDataHelper(RETURN, item, index, field);
616
+ // Remove nested arrays after processing
617
+ field.children = field.children.filter((child) => child.type === "array" &&
618
+ Utils.isArrayOfObjects(child.children));
619
+ }
620
+ // Process remaining items for the field's children
621
+ const items = await this.processSchemaData(tableName, field.children, linesNumber, options, `${(prefix ?? "") + field.key}.`);
622
+ // Process the items after retrieval
623
+ if (items) {
624
+ for (const [index, item] of Object.entries(items)) {
625
+ if (typeof item === "undefined")
626
+ continue; // Skip undefined items
627
+ if (!RETURN[index])
628
+ RETURN[index] = {};
629
+ if (Utils.isObject(item)) {
630
+ if (!Utils.isArrayOfNulls(Object.values(item))) {
631
+ if (RETURN[index][field.key])
632
+ Object.entries(item).forEach(([key, value], _index) => {
633
+ for (let _index = 0; _index < value.length; _index++)
634
+ if (RETURN[index][field.key][_index])
635
+ Object.assign(RETURN[index][field.key][_index], {
636
+ [key]: value[_index],
637
+ });
588
638
  else
589
- for (let _i = 0; _i < value.length; _i++) {
590
- if (value[_i] === null ||
591
- (Array.isArray(value[_i]) &&
592
- Utils.isArrayOfNulls(value[_i])))
593
- continue;
594
- if (!RETURN[index][field.key][_i])
595
- RETURN[index][field.key][_i] = {};
596
- RETURN[index][field.key][_i][key] = value[_i];
597
- }
598
- });
599
- }
639
+ RETURN[index][field.key][_index] = {
640
+ [key]: value[_index],
641
+ };
642
+ });
643
+ else if (Object.values(item).every((_i) => Utils.isArrayOfArrays(_i) || Array.isArray(_i)) &&
644
+ prefix)
645
+ RETURN[index][field.key] = item;
646
+ else {
647
+ RETURN[index][field.key] = [];
648
+ Object.entries(item).forEach(([key, value], _ind) => {
649
+ if (!Array.isArray(value)) {
650
+ RETURN[index][field.key][_ind] = {};
651
+ RETURN[index][field.key][_ind][key] = value;
652
+ }
653
+ else
654
+ for (let _i = 0; _i < value.length; _i++) {
655
+ if (value[_i] === null ||
656
+ (Array.isArray(value[_i]) &&
657
+ Utils.isArrayOfNulls(value[_i])))
658
+ continue;
659
+ if (!RETURN[index][field.key][_i])
660
+ RETURN[index][field.key][_i] = {};
661
+ RETURN[index][field.key][_i][key] = value[_i];
662
+ }
663
+ });
600
664
  }
601
- else
602
- RETURN[index][field.key] = null;
603
665
  }
604
666
  else
605
- RETURN[index][field.key] = item;
606
- }
607
- }
608
- else if (field.children === "table" ||
609
- (Array.isArray(field.type) && field.type.includes("table")) ||
610
- (Array.isArray(field.children) && field.children.includes("table"))) {
611
- if (field.table &&
612
- (await File.isExists(join(this.databasePath, field.table))) &&
613
- (await File.isExists(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension(tableName)}`)))) {
614
- const itemsIDs = await File.get(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension(tableName)}`), linesNumber, field.type, field.children, this.salt);
615
- if (itemsIDs) {
616
- const searchableIDs = new Map();
617
- for (const [lineNumber, lineContent] of Object.entries(itemsIDs)) {
618
- if (!RETURN[lineNumber])
619
- RETURN[lineNumber] = {};
620
- if (lineContent !== null && lineContent !== undefined)
621
- searchableIDs.set(lineNumber, lineContent);
622
- }
623
- if (searchableIDs.size) {
624
- const items = await this.get(field.table, Array.from(new Set(Array.from(searchableIDs.values()).flat())), {
625
- ...options,
626
- perPage: Number.POSITIVE_INFINITY,
627
- columns: options.columns
628
- ?.filter((column) => column.includes(`${field.key}.`))
629
- .map((column) => column.replace(`${field.key}.`, "")),
630
- });
631
- if (items) {
632
- for (const [lineNumber, lineContent,] of searchableIDs.entries()) {
633
- const foundedItems = items.filter(({ id }) => lineContent.includes(id));
634
- if (foundedItems)
635
- RETURN[lineNumber][field.key] = foundedItems;
636
- }
637
- }
638
- }
667
+ RETURN[index][field.key] = null;
639
668
  }
669
+ else
670
+ RETURN[index][field.key] = item;
640
671
  }
641
672
  }
642
- else if (await File.isExists(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension(tableName)}`))) {
643
- const items = await File.get(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension(tableName)}`), linesNumber, field.type, field.children, this.salt);
644
- if (items)
645
- for (const [index, item] of Object.entries(items)) {
646
- if (!RETURN[index])
647
- RETURN[index] = {};
648
- if (item !== null && item !== undefined)
649
- RETURN[index][field.key] = item;
650
- }
673
+ }
674
+ }
675
+ else if (this.isSimpleField(field.children)) {
676
+ // If `children` is FieldType, handle it as an array of simple types (no recursion needed here)
677
+ await this.processSimpleField(tableName, field, linesNumber, RETURN, options, prefix);
678
+ }
679
+ else if (this.isTableField(field.children)) {
680
+ await this.processTableField(tableName, field, linesNumber, RETURN, options, prefix);
681
+ }
682
+ }
683
+ // Helper function to check if the field type is object
684
+ isObjectField(fieldType) {
685
+ return (fieldType === "object" ||
686
+ (Array.isArray(fieldType) &&
687
+ fieldType.every((type) => typeof type === "string") &&
688
+ fieldType.includes("object")));
689
+ }
690
+ // Process object fields (recursive if needed)
691
+ async processObjectField(tableName, field, linesNumber, RETURN, options, prefix) {
692
+ if (Array.isArray(field.children)) {
693
+ // If `children` is a Schema (array of Field objects), recurse
694
+ const items = await this.processSchemaData(tableName, field.children, linesNumber, options, `${prefix ?? ""}${field.key}.`);
695
+ for (const [index, item] of Object.entries(items)) {
696
+ if (typeof item === "undefined")
697
+ continue; // Skip undefined items
698
+ if (!RETURN[index])
699
+ RETURN[index] = {};
700
+ if (Utils.isObject(item)) {
701
+ if (!Object.values(item).every((i) => i === null))
702
+ RETURN[index][field.key] = item;
651
703
  }
652
704
  }
653
- else if (field.type === "object") {
654
- const fieldItems = await this.getItemsFromSchema(tableName, field.children, linesNumber, options, `${(prefix ?? "") + field.key}.`);
655
- if (fieldItems)
656
- for (const [index, item] of Object.entries(fieldItems)) {
657
- if (!RETURN[index])
658
- RETURN[index] = {};
659
- if (Utils.isObject(item)) {
660
- if (!Object.values(item).every((i) => i === null))
661
- RETURN[index][field.key] = item;
662
- }
705
+ }
706
+ }
707
+ // Helper function to check if the field type is table
708
+ isTableField(fieldType) {
709
+ return (fieldType === "table" ||
710
+ (Array.isArray(fieldType) &&
711
+ fieldType.every((type) => typeof type === "string") &&
712
+ fieldType.includes("table")));
713
+ }
714
+ // Process table reference fields
715
+ async processTableField(tableName, field, linesNumber, RETURN, options, prefix) {
716
+ if (field.table &&
717
+ (await File.isExists(join(this.databasePath, field.table)))) {
718
+ const fieldPath = join(this.databasePath, tableName, `${prefix ?? ""}${field.key}${this.getFileExtension(tableName)}`);
719
+ if (await File.isExists(fieldPath)) {
720
+ const itemsIDs = await File.get(fieldPath, linesNumber, field.type, field.children, this.salt);
721
+ const isArrayField = this.isArrayField(field.type);
722
+ if (itemsIDs) {
723
+ const searchableIDs = new Map();
724
+ for (const [lineNumber, lineContent] of Object.entries(itemsIDs)) {
725
+ if (typeof lineContent === "undefined")
726
+ continue; // Skip undefined items
727
+ if (!RETURN[lineNumber])
728
+ RETURN[lineNumber] = {};
729
+ if (lineContent !== null && lineContent !== undefined)
730
+ searchableIDs.set(lineNumber, lineContent);
663
731
  }
664
- }
665
- else if (field.type === "table") {
666
- if (field.table &&
667
- (await File.isExists(join(this.databasePath, field.table))) &&
668
- (await File.isExists(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension(tableName)}`)))) {
669
- const itemsIDs = await File.get(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension(tableName)}`), linesNumber, field.type, field.children, this.salt);
670
- if (itemsIDs) {
671
- const searchableIDs = new Map();
672
- for (const [lineNumber, lineContent] of Object.entries(itemsIDs)) {
673
- if (!RETURN[lineNumber])
674
- RETURN[lineNumber] = {};
675
- if (lineContent !== null && lineContent !== undefined)
676
- searchableIDs.set(lineNumber, lineContent);
677
- }
678
- if (searchableIDs.size) {
679
- const items = await this.get(field.table, Array.from(new Set(searchableIDs.values())), {
680
- ...options,
681
- perPage: Number.POSITIVE_INFINITY,
682
- columns: options.columns
683
- ?.filter((column) => column.includes(`${field.key}.`))
684
- .map((column) => column.replace(`${field.key}.`, "")),
685
- });
686
- if (items) {
687
- for (const [lineNumber, lineContent,] of searchableIDs.entries()) {
688
- const foundedItem = items.find(({ id }) => id === lineContent);
689
- if (foundedItem)
690
- RETURN[lineNumber][field.key] = foundedItem;
691
- }
732
+ if (searchableIDs.size) {
733
+ const items = await this.get(field.table, isArrayField
734
+ ? Array.from(new Set(Array.from(searchableIDs.values()).flat()))
735
+ : Array.from(new Set(searchableIDs.values())), {
736
+ ...options,
737
+ perPage: Number.POSITIVE_INFINITY,
738
+ columns: options.columns
739
+ ?.filter((column) => column.includes(`${field.key}.`))
740
+ .map((column) => column.replace(`${field.key}.`, "")),
741
+ });
742
+ if (items) {
743
+ for (const [lineNumber, lineContent] of searchableIDs.entries()) {
744
+ const foundedItem = isArrayField
745
+ ? items.filter(({ id }) => lineContent.includes(id))
746
+ : items.find(({ id }) => id === lineContent);
747
+ if (foundedItem)
748
+ RETURN[lineNumber][field.key] = foundedItem;
692
749
  }
693
750
  }
694
751
  }
695
752
  }
696
753
  }
697
- else if (await File.isExists(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension(tableName)}`))) {
698
- const items = await File.get(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension(tableName)}`), linesNumber, field.type, field.children, this.salt);
699
- if (items)
700
- for (const [index, item] of Object.entries(items)) {
701
- if (!RETURN[index])
702
- RETURN[index] = {};
703
- if (item !== null && item !== undefined)
704
- RETURN[index][field.key] = item;
705
- }
706
- }
707
754
  }
708
- return RETURN;
709
755
  }
710
756
  async applyCriteria(tableName, schema, options, criteria, allTrue) {
711
757
  const tablePath = join(this.databasePath, tableName);
@@ -973,7 +1019,7 @@ export default class Inibase {
973
1019
  }
974
1020
  if (!where) {
975
1021
  // Display all data
976
- RETURN = Object.values(await this.getItemsFromSchema(tableName, schema, Array.from({ length: options.perPage }, (_, index) => (options.page - 1) * options.perPage +
1022
+ RETURN = Object.values(await this.processSchemaData(tableName, schema, Array.from({ length: options.perPage }, (_, index) => (options.page - 1) * options.perPage +
977
1023
  index +
978
1024
  1), options));
979
1025
  if (!this.totalItems[`${tableName}-*`])
@@ -990,7 +1036,7 @@ export default class Inibase {
990
1036
  // useless
991
1037
  if (onlyLinesNumbers)
992
1038
  return lineNumbers;
993
- RETURN = Object.values((await this.getItemsFromSchema(tableName, schema, lineNumbers, options)) ?? {});
1039
+ RETURN = Object.values((await this.processSchemaData(tableName, schema, lineNumbers, options)) ?? {});
994
1040
  if (RETURN?.length && !Array.isArray(where))
995
1041
  RETURN = RETURN[0];
996
1042
  }
@@ -1013,7 +1059,7 @@ export default class Inibase {
1013
1059
  if (!options.columns?.length)
1014
1060
  options.columns = undefined;
1015
1061
  }
1016
- RETURN = Object.values((await this.getItemsFromSchema(tableName, schema, Object.keys(lineNumbers).map(Number), options)) ?? {});
1062
+ RETURN = Object.values((await this.processSchemaData(tableName, schema, Object.keys(lineNumbers).map(Number), options)) ?? {});
1017
1063
  if (RETURN?.length && !Array.isArray(where))
1018
1064
  RETURN = RETURN[0];
1019
1065
  }
@@ -1045,7 +1091,7 @@ export default class Inibase {
1045
1091
  const alreadyExistsColumns = Object.keys(Object.values(RETURN)[0]), alreadyExistsColumnsIDs = Utils.flattenSchema(schema)
1046
1092
  .filter(({ key }) => alreadyExistsColumns.includes(key))
1047
1093
  .map(({ id }) => id);
1048
- RETURN = Object.values(Utils.deepMerge(RETURN, await this.getItemsFromSchema(tableName, Utils.filterSchema(schema, ({ id, type, children }) => !alreadyExistsColumnsIDs.includes(id) ||
1094
+ RETURN = Object.values(Utils.deepMerge(RETURN, await this.processSchemaData(tableName, Utils.filterSchema(schema, ({ id, type, children }) => !alreadyExistsColumnsIDs.includes(id) ||
1049
1095
  Utils.isFieldType("table", type, children)), Object.keys(RETURN).map(Number), options)));
1050
1096
  if (this.tables[tableName].config.cache)
1051
1097
  await writeFile(cachedFilePath, Array.from(linesNumbers).join(","));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inibase",
3
- "version": "1.0.0-rc.107",
3
+ "version": "1.0.0-rc.109",
4
4
  "type": "module",
5
5
  "author": {
6
6
  "name": "Karim Amahtil",
@@ -75,7 +75,7 @@
75
75
  },
76
76
  "dependencies": {
77
77
  "dotenv": "^16.4.5",
78
- "inison": "1.0.0-rc.4"
78
+ "inison": "latest"
79
79
  },
80
80
  "scripts": {
81
81
  "prepublish": "npx tsc",