inibase 1.0.0-rc.25 → 1.0.0-rc.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,32 +1,32 @@
1
- import { unlink, rename, readFile, writeFile, mkdir, readdir, } from "node:fs/promises";
1
+ import { unlink, rename, mkdir, readdir } from "node:fs/promises";
2
2
  import { existsSync, appendFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
+ import { cpus } from "node:os";
4
5
  import { scryptSync, randomBytes } from "node:crypto";
5
6
  import File from "./file.js";
6
7
  import Utils from "./utils.js";
7
8
  import UtilsServer from "./utils.server.js";
9
+ import Config from "./config.js";
10
+ process.env.UV_THREADPOOL_SIZE = cpus().length.toString();
8
11
  export default class Inibase {
9
12
  folder;
10
13
  database;
11
14
  table;
12
15
  pageInfo;
13
- cache;
14
16
  totalItems;
15
17
  salt;
16
18
  constructor(database, mainFolder = ".") {
17
19
  this.database = database;
18
20
  this.folder = mainFolder;
19
21
  this.table = null;
20
- this.cache = new Map();
21
22
  this.totalItems = {};
22
- this.pageInfo = { page: 1, per_page: 15 };
23
+ this.pageInfo = {};
23
24
  if (!existsSync(".env") || !process.env.INIBASE_SECRET) {
24
25
  this.salt = scryptSync(randomBytes(16), randomBytes(16), 32);
25
26
  appendFileSync(".env", `INIBASE_SECRET=${this.salt.toString("hex")}\n`);
26
27
  }
27
- else {
28
+ else
28
29
  this.salt = Buffer.from(process.env.INIBASE_SECRET, "hex");
29
- }
30
30
  }
31
31
  throwError(code, variable, language = "en") {
32
32
  const errorMessages = {
@@ -55,7 +55,9 @@ export default class Inibase {
55
55
  }
56
56
  async setTableSchema(tableName, schema) {
57
57
  const decodeIdFromSchema = (schema) => schema.map((field) => {
58
- if (field.children && Utils.isArrayOfObjects(field.children))
58
+ if ((field.type === "array" || field.type === "object") &&
59
+ field.children &&
60
+ Utils.isArrayOfObjects(field.children))
59
61
  field.children = decodeIdFromSchema(field.children);
60
62
  if (field.id && !Utils.isNumber(field.id))
61
63
  field.id = UtilsServer.decodeID(field.id, this.salt);
@@ -67,12 +69,16 @@ export default class Inibase {
67
69
  const TablePath = join(this.folder, this.database, tableName), TableSchemaPath = join(TablePath, "schema.json");
68
70
  if (!(await File.isExists(TablePath)))
69
71
  await mkdir(TablePath, { recursive: true });
72
+ if (!(await File.isExists(join(TablePath, ".tmp"))))
73
+ await mkdir(join(TablePath, ".tmp"));
70
74
  if (await File.isExists(TableSchemaPath)) {
71
75
  // update columns files names based on field id
72
76
  const schemaToIdsPath = (schema, prefix = "") => {
73
77
  let RETURN = {};
74
78
  for (const field of schema)
75
- if (field.children && Utils.isArrayOfObjects(field.children)) {
79
+ if ((field.type === "array" || field.type === "object") &&
80
+ field.children &&
81
+ Utils.isArrayOfObjects(field.children)) {
76
82
  Utils.deepMerge(RETURN, schemaToIdsPath(field.children, (prefix ?? "") + field.key + "."));
77
83
  }
78
84
  else if (Utils.isValidID(field.id))
@@ -85,17 +91,16 @@ export default class Inibase {
85
91
  if (await File.isExists(join(TablePath, oldPath)))
86
92
  await rename(join(TablePath, oldPath), join(TablePath, newPath));
87
93
  }
88
- await writeFile(join(TablePath, "schema.json"), JSON.stringify(decodeIdFromSchema(schema)));
94
+ await File.write(join(TablePath, "schema.json"), JSON.stringify(decodeIdFromSchema(schema), null, 2), true);
89
95
  }
90
96
  async getTableSchema(tableName) {
91
97
  const TableSchemaPath = join(this.folder, this.database, tableName, "schema.json");
92
98
  if (!(await File.isExists(TableSchemaPath)))
93
99
  return undefined;
94
- if (!this.cache.has(TableSchemaPath))
95
- this.cache.set(TableSchemaPath, await readFile(TableSchemaPath, "utf8"));
96
- if (!this.cache.get(TableSchemaPath))
100
+ const schemaFile = await File.read(TableSchemaPath, true);
101
+ if (!schemaFile)
97
102
  return undefined;
98
- const schema = JSON.parse(this.cache.get(TableSchemaPath) ?? ""), lastIdNumber = UtilsServer.findLastIdNumber(schema, this.salt);
103
+ const schema = JSON.parse(schemaFile), lastIdNumber = UtilsServer.findLastIdNumber(schema, this.salt);
99
104
  return [
100
105
  {
101
106
  id: UtilsServer.encodeID(0, this.salt),
@@ -118,49 +123,45 @@ export default class Inibase {
118
123
  },
119
124
  ];
120
125
  }
121
- getField(keyPath, schema, property) {
126
+ getField(keyPath, schema) {
127
+ let RETURN = null;
122
128
  const keyPathSplited = keyPath.split(".");
123
129
  for (const [index, key] of keyPathSplited.entries()) {
124
- if (key === "*")
125
- continue;
126
130
  const foundItem = schema.find((item) => item.key === key);
127
131
  if (!foundItem)
128
132
  return null;
129
133
  if (index === keyPathSplited.length - 1)
130
- schema = foundItem;
134
+ RETURN = foundItem;
131
135
  if ((foundItem.type === "array" || foundItem.type === "object") &&
132
136
  foundItem.children &&
133
137
  Utils.isArrayOfObjects(foundItem.children))
134
- schema = foundItem.children;
135
- }
136
- if (property) {
137
- switch (property) {
138
- case "type":
139
- return schema.type;
140
- case "children":
141
- return schema.children;
142
- default:
143
- return schema[property];
144
- }
138
+ RETURN = foundItem.children;
145
139
  }
146
- else
147
- return schema;
140
+ if (!RETURN)
141
+ return null;
142
+ return Utils.isArrayOfObjects(RETURN) ? RETURN[0] : RETURN;
148
143
  }
149
144
  validateData(data, schema, skipRequiredField = false) {
150
145
  if (Utils.isArrayOfObjects(data))
151
146
  for (const single_data of data)
152
147
  this.validateData(single_data, schema, skipRequiredField);
153
148
  else if (Utils.isObject(data)) {
154
- for (const { key, type, required, children } of schema) {
155
- if (!data.hasOwnProperty(key) && required && !skipRequiredField)
156
- throw this.throwError("FIELD_REQUIRED", key);
157
- if (data.hasOwnProperty(key) &&
158
- !Utils.validateFieldType(data[key], type, children && !Utils.isArrayOfObjects(children) ? children : undefined))
159
- throw this.throwError("INVALID_TYPE", key + " " + type + " " + data[key]);
160
- if ((type === "array" || type === "object") &&
161
- children &&
162
- Utils.isArrayOfObjects(children))
163
- this.validateData(data[key], children, skipRequiredField);
149
+ for (const field of schema) {
150
+ if (!data.hasOwnProperty(field.key) &&
151
+ field.required &&
152
+ !skipRequiredField)
153
+ throw this.throwError("FIELD_REQUIRED", field.key);
154
+ if (data.hasOwnProperty(field.key) &&
155
+ !Utils.validateFieldType(data[field.key], field.type, (field.type === "array" || field.type === "object") &&
156
+ field.children &&
157
+ !Utils.isArrayOfObjects(field.children)
158
+ ? field.children
159
+ : undefined))
160
+ throw this.throwError("INVALID_TYPE", field.key + " " + field.type + " " + data[field.key]);
161
+ if ((field.type === "array" || field.type === "object") &&
162
+ field.children &&
163
+ Utils.isArrayOfObjects(field.children))
164
+ this.validateData(data[field.key], field.children, skipRequiredField);
164
165
  }
165
166
  }
166
167
  }
@@ -169,10 +170,8 @@ export default class Inibase {
169
170
  field.type = Utils.detectFieldType(value, field.type) ?? field.type[0];
170
171
  switch (field.type) {
171
172
  case "array":
172
- if (!Array.isArray(value))
173
- value = [value];
174
173
  if (typeof field.children === "string") {
175
- if (field.type === "array" && field.children === "table") {
174
+ if (field.children === "table") {
176
175
  if (Array.isArray(value)) {
177
176
  if (Utils.isArrayOfObjects(value)) {
178
177
  if (value.every((item) => item.hasOwnProperty("id") &&
@@ -194,8 +193,8 @@ export default class Inibase {
194
193
  else if (Utils.isNumber(value))
195
194
  return [Number(value)];
196
195
  }
197
- else if (value.hasOwnProperty(field.key))
198
- return value;
196
+ else
197
+ return Array.isArray(value) ? value : [value];
199
198
  }
200
199
  else if (Utils.isArrayOfObjects(field.children))
201
200
  return this.formatData(value, field.children, formatOnlyAvailiableKeys);
@@ -304,10 +303,7 @@ export default class Inibase {
304
303
  const path = join(this.folder, this.database, tableName);
305
304
  let RETURN = {};
306
305
  for await (const field of schema) {
307
- if ((field.type === "array" ||
308
- (Array.isArray(field.type) &&
309
- field.type.includes("array"))) &&
310
- field.children) {
306
+ if (field.type === "array" && field.children) {
311
307
  if (Utils.isArrayOfObjects(field.children)) {
312
308
  if (field.children.filter((children) => children.type === "array" &&
313
309
  Utils.isArrayOfObjects(children.children)).length) {
@@ -389,8 +385,7 @@ export default class Inibase {
389
385
  options.columns = options.columns
390
386
  .filter((column) => column.includes(`${field.key}.`))
391
387
  .map((column) => column.replace(`${field.key}.`, ""));
392
- const [items, total_lines] = await File.get(join(path, (prefix ?? "") + field.key + ".inib"), linesNumber, field.type, field.children, this.salt);
393
- this.totalItems[tableName + "-" + field.key] = total_lines;
388
+ const items = await File.get(join(path, (prefix ?? "") + field.key + ".inib"), linesNumber, field.type, field.children, this.salt);
394
389
  if (items)
395
390
  for await (const [index, item] of Object.entries(items)) {
396
391
  if (!RETURN[index])
@@ -401,8 +396,7 @@ export default class Inibase {
401
396
  }
402
397
  }
403
398
  else if (await File.isExists(join(path, (prefix ?? "") + field.key + ".inib"))) {
404
- const [items, total_lines] = await File.get(join(path, (prefix ?? "") + field.key + ".inib"), linesNumber, field.type, field?.children, this.salt);
405
- this.totalItems[tableName + "-" + field.key] = total_lines;
399
+ const items = await File.get(join(path, (prefix ?? "") + field.key + ".inib"), linesNumber, field.type, field?.children, this.salt);
406
400
  if (items)
407
401
  for (const [index, item] of Object.entries(items)) {
408
402
  if (!RETURN[index])
@@ -433,8 +427,7 @@ export default class Inibase {
433
427
  .filter((column) => column.includes(`${field.key}.`) &&
434
428
  !column.includes(`${field.key}.`))
435
429
  .map((column) => column.replace(`${field.key}.`, ""));
436
- const [items, total_lines] = await File.get(join(path, (prefix ?? "") + field.key + ".inib"), linesNumber, "number", undefined, this.salt);
437
- this.totalItems[tableName + "-" + field.key] = total_lines;
430
+ const items = await File.get(join(path, (prefix ?? "") + field.key + ".inib"), linesNumber, "number", undefined, this.salt);
438
431
  if (items)
439
432
  for await (const [index, item] of Object.entries(items)) {
440
433
  if (!RETURN[index])
@@ -446,8 +439,7 @@ export default class Inibase {
446
439
  }
447
440
  }
448
441
  else if (await File.isExists(join(path, (prefix ?? "") + field.key + ".inib"))) {
449
- const [items, total_lines] = await File.get(join(path, (prefix ?? "") + field.key + ".inib"), linesNumber, field.type, field?.children, this.salt);
450
- this.totalItems[tableName + "-" + field.key] = total_lines;
442
+ const items = await File.get(join(path, (prefix ?? "") + field.key + ".inib"), linesNumber, field.type, field?.children, this.salt);
451
443
  if (items)
452
444
  for (const [index, item] of Object.entries(items)) {
453
445
  if (!RETURN[index])
@@ -518,23 +510,26 @@ export default class Inibase {
518
510
  }
519
511
  }
520
512
  async applyCriteria(tableName, schema, options, criteria, allTrue) {
521
- let RETURN = {};
513
+ let RETURN = {}, RETURN_LineNumbers = null;
522
514
  if (!criteria)
523
- return null;
515
+ return [null, null];
524
516
  if (criteria.and && Utils.isObject(criteria.and)) {
525
- const searchResult = await this.applyCriteria(tableName, schema, options, criteria.and, true);
517
+ const [searchResult, lineNumbers] = await this.applyCriteria(tableName, schema, options, criteria.and, true);
526
518
  if (searchResult) {
527
519
  RETURN = Utils.deepMerge(RETURN, Object.fromEntries(Object.entries(searchResult).filter(([_k, v], _i) => Object.keys(v).length === Object.keys(criteria.and ?? {}).length)));
528
520
  delete criteria.and;
521
+ RETURN_LineNumbers = lineNumbers;
529
522
  }
530
523
  else
531
- return null;
524
+ return [null, null];
532
525
  }
533
526
  if (criteria.or && Utils.isObject(criteria.or)) {
534
- const searchResult = await this.applyCriteria(tableName, schema, options, criteria.or, false);
527
+ const [searchResult, lineNumbers] = await this.applyCriteria(tableName, schema, options, criteria.or, false);
535
528
  delete criteria.or;
536
- if (searchResult)
529
+ if (searchResult) {
537
530
  RETURN = Utils.deepMerge(RETURN, searchResult);
531
+ RETURN_LineNumbers = lineNumbers;
532
+ }
538
533
  }
539
534
  if (Object.keys(criteria).length > 0) {
540
535
  if (allTrue === undefined)
@@ -597,7 +592,7 @@ export default class Inibase {
597
592
  searchOperator = "=";
598
593
  searchComparedAtValue = value;
599
594
  }
600
- const [searchResult, total_lines] = await File.search(join(this.folder, this.database, tableName, key + ".inib"), searchOperator ?? "=", searchComparedAtValue ?? null, searchLogicalOperator, field?.type, field?.children, options.per_page, options.page - 1 * options.per_page + 1, true, this.salt);
595
+ const [searchResult, totalLines, linesNumbers] = await File.search(join(this.folder, this.database, tableName, key + ".inib"), searchOperator ?? "=", searchComparedAtValue ?? null, searchLogicalOperator, field?.type, field?.children, options.perPage, options.page - 1 * options.perPage + 1, true, this.salt);
601
596
  if (searchResult) {
602
597
  RETURN = Utils.deepMerge(RETURN, Object.fromEntries(Object.entries(searchResult).map(([id, value]) => [
603
598
  id,
@@ -605,7 +600,8 @@ export default class Inibase {
605
600
  [key]: value,
606
601
  },
607
602
  ])));
608
- this.totalItems[tableName + "-" + key] = total_lines;
603
+ this.totalItems[tableName + "-" + key] = totalLines;
604
+ RETURN_LineNumbers = linesNumbers;
609
605
  }
610
606
  if (allTrue && index > 0) {
611
607
  if (!Object.keys(RETURN).length)
@@ -616,31 +612,10 @@ export default class Inibase {
616
612
  }
617
613
  }
618
614
  }
619
- return Object.keys(RETURN).length ? RETURN : null;
615
+ return [Object.keys(RETURN).length ? RETURN : null, RETURN_LineNumbers];
620
616
  }
621
- async get(tableName, where, options = {
622
- page: 1,
623
- per_page: 15,
624
- }, onlyOne, onlyLinesNumbers) {
625
- if (options.columns) {
626
- if (!Array.isArray(options.columns))
627
- options.columns = [options.columns];
628
- if (options.columns.length &&
629
- !options.columns.includes("id"))
630
- options.columns.push("id");
631
- }
632
- if (!options.page)
633
- options.page = 1;
634
- if (!options.per_page)
635
- options.per_page = 15;
636
- let RETURN;
637
- let schema = await this.getTableSchema(tableName);
638
- if (!schema)
639
- throw this.throwError("NO_SCHEMA", tableName);
640
- const idFilePath = join(this.folder, this.database, tableName, "id.inib");
641
- if (!(await File.isExists(idFilePath)))
642
- return null;
643
- const filterSchemaByColumns = (schema, columns) => schema
617
+ _filterSchemaByColumns(schema, columns) {
618
+ return schema
644
619
  .map((field) => {
645
620
  if (columns.some((column) => column.startsWith("!")))
646
621
  return columns.includes("!" + field.key) ? null : field;
@@ -650,7 +625,7 @@ export default class Inibase {
650
625
  Utils.isArrayOfObjects(field.children) &&
651
626
  columns.filter((column) => column.startsWith(field.key + ".") ||
652
627
  column.startsWith("!" + field.key + ".")).length) {
653
- field.children = filterSchemaByColumns(field.children, columns
628
+ field.children = this._filterSchemaByColumns(field.children, columns
654
629
  .filter((column) => column.startsWith(field.key + ".") ||
655
630
  column.startsWith("!" + field.key + "."))
656
631
  .map((column) => column.replace(field.key + ".", "")));
@@ -659,13 +634,55 @@ export default class Inibase {
659
634
  return null;
660
635
  })
661
636
  .filter((i) => i);
637
+ }
638
+ async get(tableName, where, options = {
639
+ page: 1,
640
+ perPage: 15,
641
+ }, onlyOne, onlyLinesNumbers, tableSchema) {
642
+ // Ensure options.columns is an array
643
+ if (options.columns) {
644
+ options.columns = Array.isArray(options.columns)
645
+ ? options.columns
646
+ : [options.columns];
647
+ if (options.columns.length && !options.columns.includes("id"))
648
+ options.columns.push("id");
649
+ }
650
+ // Default values for page and perPage
651
+ options.page = options.page || 1;
652
+ options.perPage = options.perPage || 15;
653
+ let RETURN;
654
+ let schema = tableSchema ?? (await this.getTableSchema(tableName));
655
+ if (!schema)
656
+ throw this.throwError("NO_SCHEMA", tableName);
657
+ const idFilePath = join(this.folder, this.database, tableName, "id.inib");
658
+ if (!(await File.isExists(idFilePath)))
659
+ return null;
662
660
  if (options.columns && options.columns.length)
663
- schema = filterSchemaByColumns(schema, options.columns);
661
+ schema = this._filterSchemaByColumns(schema, options.columns);
664
662
  if (!where) {
665
663
  // Display all data
666
- RETURN = Object.values(await this.getItemsFromSchema(tableName, schema, Array.from({ length: options.per_page }, (_, index) => (options.page - 1) * options.per_page +
664
+ RETURN = Object.values(await this.getItemsFromSchema(tableName, schema, Array.from({ length: options.perPage }, (_, index) => (options.page - 1) * options.perPage +
667
665
  index +
668
666
  1), options));
667
+ if (Config.isCacheEnabled &&
668
+ (await File.isExists(join(this.folder, this.database, tableName, ".tmp", "totalItems.inib"))))
669
+ this.totalItems[tableName + "-*"] = Number(await File.read(join(this.folder, this.database, tableName, ".tmp", "totalItems.inib"), true));
670
+ else {
671
+ this.totalItems[tableName + "-*"] = await File.count(idFilePath);
672
+ if (Config.isCacheEnabled)
673
+ await File.write(join(this.folder, this.database, tableName, ".tmp", "totalItems.inib"), String(this.totalItems[tableName + "-*"]), true);
674
+ }
675
+ }
676
+ else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
677
+ (Utils.isNumber(where) && !onlyLinesNumbers)) {
678
+ let lineNumbers = where;
679
+ if (!Array.isArray(lineNumbers))
680
+ lineNumbers = [lineNumbers];
681
+ RETURN = Object.values((await this.getItemsFromSchema(tableName, schema, lineNumbers, options)) ?? {});
682
+ if (!this.totalItems[tableName + "-*"])
683
+ this.totalItems[tableName + "-*"] = lineNumbers.length;
684
+ if (RETURN && RETURN.length && !Array.isArray(where))
685
+ RETURN = RETURN[0];
669
686
  }
670
687
  else if ((Array.isArray(where) &&
671
688
  (where.every(Utils.isValidID) || where.every(Utils.isNumber))) ||
@@ -682,29 +699,56 @@ export default class Inibase {
682
699
  ? Object.keys(lineNumbers).map(Number)
683
700
  : null;
684
701
  RETURN = Object.values((await this.getItemsFromSchema(tableName, schema, Object.keys(lineNumbers).map(Number), options)) ?? {});
702
+ if (!this.totalItems[tableName + "-*"]) {
703
+ if (Config.isCacheEnabled &&
704
+ (await File.isExists(join(this.folder, this.database, tableName, ".tmp", "totalItems.inib"))))
705
+ this.totalItems[tableName + "-*"] = Number(await File.read(join(this.folder, this.database, tableName, ".tmp", "totalItems.inib"), true));
706
+ else {
707
+ this.totalItems[tableName + "-*"] = await File.count(idFilePath);
708
+ if (Config.isCacheEnabled)
709
+ await File.write(join(this.folder, this.database, tableName, ".tmp", "totalItems.inib"), String(this.totalItems[tableName + "-*"]), true);
710
+ }
711
+ }
685
712
  if (RETURN && RETURN.length && !Array.isArray(where))
686
713
  RETURN = RETURN[0];
687
714
  }
688
715
  else if (Utils.isObject(where)) {
716
+ let cachedFilePath = "";
689
717
  // Criteria
690
- RETURN = await this.applyCriteria(tableName, schema, options, where);
691
- if (RETURN) {
692
- if (onlyLinesNumbers)
693
- return Object.keys(RETURN).map(Number);
694
- const alreadyExistsColumns = Object.keys(Object.values(RETURN)[0]);
695
- RETURN = Object.values(Utils.deepMerge(RETURN, await this.getItemsFromSchema(tableName, schema.filter((field) => !alreadyExistsColumns.includes(field.key)), Object.keys(RETURN).map(Number), options)));
718
+ if (Config.isCacheEnabled)
719
+ cachedFilePath = join(this.folder, this.database, tableName, ".tmp", `${UtilsServer.hashObject(where)}.inib`);
720
+ if (Config.isCacheEnabled && (await File.isExists(cachedFilePath))) {
721
+ const cachedItems = (await File.read(cachedFilePath, true)).split(",");
722
+ this.totalItems[tableName + "-*"] = cachedItems.length;
723
+ return this.get(tableName, cachedItems
724
+ .slice((options.page - 1) * options.perPage, options.page * options.perPage)
725
+ .map(Number), options, undefined, undefined, schema);
726
+ }
727
+ else {
728
+ let linesNumbers;
729
+ [RETURN, linesNumbers] = await this.applyCriteria(tableName, schema, options, where);
730
+ if (RETURN && linesNumbers) {
731
+ if (onlyLinesNumbers)
732
+ return Object.keys(RETURN).map(Number);
733
+ const alreadyExistsColumns = Object.keys(Object.values(RETURN)[0]);
734
+ RETURN = Object.values(Utils.deepMerge(RETURN, await this.getItemsFromSchema(tableName, schema.filter((field) => !alreadyExistsColumns.includes(field.key)), Object.keys(RETURN).map(Number), options)));
735
+ if (Config.isCacheEnabled)
736
+ await File.write(cachedFilePath, Array.from(linesNumbers).join(","), true);
737
+ }
696
738
  }
697
739
  }
698
740
  if (!RETURN ||
699
741
  (Utils.isObject(RETURN) && !Object.keys(RETURN).length) ||
700
742
  (Array.isArray(RETURN) && !RETURN.length))
701
743
  return null;
702
- const greatestTotalItems = Math.max(...Object.entries(this.totalItems)
703
- .filter(([k]) => k.startsWith(tableName + "-"))
704
- .map(([, v]) => v));
705
- this.pageInfo = {
744
+ const greatestTotalItems = this.totalItems[tableName + "-*"] ??
745
+ Math.max(...Object.entries(this.totalItems)
746
+ .filter(([k]) => k.startsWith(tableName + "-"))
747
+ .map(([, v]) => v));
748
+ this.pageInfo[tableName] = {
706
749
  ...(({ columns, ...restOfOptions }) => restOfOptions)(options),
707
- total_pages: Math.ceil(greatestTotalItems / options.per_page),
750
+ perPage: Array.isArray(RETURN) ? RETURN.length : 1,
751
+ totalPages: Math.ceil(greatestTotalItems / options.perPage),
708
752
  total: greatestTotalItems,
709
753
  };
710
754
  return onlyOne && Array.isArray(RETURN) ? RETURN[0] : RETURN;
@@ -713,7 +757,7 @@ export default class Inibase {
713
757
  if (!options)
714
758
  options = {
715
759
  page: 1,
716
- per_page: 15,
760
+ perPage: 15,
717
761
  };
718
762
  if (!returnPostedData)
719
763
  returnPostedData = false;
@@ -721,46 +765,54 @@ export default class Inibase {
721
765
  let RETURN;
722
766
  if (!schema)
723
767
  throw this.throwError("NO_SCHEMA", tableName);
724
- const idFilePath = join(this.folder, this.database, tableName, "id.inib");
725
- let [last_line_number, last_id] = (await File.isExists(idFilePath))
726
- ? Object.entries((await File.get(idFilePath, -1, "number", undefined, this.salt))[0] ??
727
- {})[0].map(Number) ?? [0, 0]
728
- : [0, 0];
768
+ const idFilePath = join(this.folder, this.database, tableName, "id.inib"), cashFolderPath = join(this.folder, this.database, tableName, ".tmp");
769
+ let lastId = 0, totalItems = 0, lastIdObj;
770
+ if (await File.isExists(idFilePath)) {
771
+ if (await File.isExists(join(cashFolderPath, "lastId.inib"))) {
772
+ lastId = Number(await File.read(join(cashFolderPath, "lastId.inib"), true));
773
+ if (await File.isExists(join(cashFolderPath, "totalItems.inib")))
774
+ totalItems = Number(await File.read(join(cashFolderPath, "totalItems.inib"), true));
775
+ else
776
+ totalItems = await File.count(join(this.folder, this.database, tableName, "id.inib"));
777
+ }
778
+ else {
779
+ [lastIdObj, totalItems] = await File.get(idFilePath, -1, "number", undefined, this.salt, true);
780
+ if (lastIdObj)
781
+ lastId = Number(Object.entries(lastIdObj)[0][1]) ?? 0;
782
+ }
783
+ }
729
784
  if (Utils.isArrayOfObjects(data))
730
785
  RETURN = data.map(({ id, updatedAt, createdAt, ...rest }) => ({
731
- id: ++last_id,
786
+ id: ++lastId,
732
787
  ...rest,
733
788
  createdAt: Date.now(),
734
789
  }));
735
790
  else
736
791
  RETURN = (({ id, updatedAt, createdAt, ...rest }) => ({
737
- id: ++last_id,
792
+ id: ++lastId,
738
793
  ...rest,
739
794
  createdAt: Date.now(),
740
795
  }))(data);
741
796
  if (!RETURN)
742
797
  throw this.throwError("NO_DATA");
743
798
  RETURN = this.formatData(RETURN, schema);
744
- const pathesContents = this.joinPathesContents(join(this.folder, this.database, tableName), RETURN);
745
- last_line_number += 1;
799
+ const pathesContents = this.joinPathesContents(join(this.folder, this.database, tableName), Array.isArray(RETURN) ? RETURN.toReversed() : RETURN);
800
+ const renameList = [];
746
801
  for await (const [path, content] of Object.entries(pathesContents))
747
- await File.append(path,
748
- // Array.isArray(content)
749
- // ? content.reduce((obj, value, index) => {
750
- // obj[last_line_number + index] = value;
751
- // return obj;
752
- // }, {})
753
- // : { [last_line_number]: content }
754
- content, last_line_number);
802
+ renameList.push(await File.append(path, content));
803
+ for await (const [tempPath, filePath] of renameList)
804
+ await rename(tempPath, filePath);
805
+ await File.write(join(cashFolderPath, "lastId.inib"), lastId.toString(), true);
806
+ await File.write(join(cashFolderPath, "totalItems.inib"), String(totalItems + (Array.isArray(RETURN) ? RETURN.length : 1)), true);
755
807
  if (returnPostedData)
756
808
  return this.get(tableName, Utils.isArrayOfObjects(RETURN)
757
809
  ? RETURN.map((data) => Number(data.id))
758
- : RETURN.id, options, !Utils.isArrayOfObjects(data) // return only one item if data is not array of objects
759
- );
810
+ : RETURN.id, options, !Utils.isArrayOfObjects(data), // return only one item if data is not array of objects
811
+ undefined, schema);
760
812
  }
761
813
  async put(tableName, data, where, options = {
762
814
  page: 1,
763
- per_page: 15,
815
+ perPage: 15,
764
816
  }, returnPostedData) {
765
817
  const schema = await this.getTableSchema(tableName);
766
818
  if (!schema)
@@ -794,8 +846,14 @@ export default class Inibase {
794
846
  });
795
847
  for await (const [path, content] of Object.entries(pathesContents))
796
848
  await File.replace(path, content);
849
+ if (Config.isCacheEnabled) {
850
+ const cacheFiles = (await readdir(join(this.folder, this.database, tableName, ".tmp")))?.filter((fileName) => !["lastId.inib", "totalItems.inib"].includes(fileName));
851
+ if (cacheFiles.length)
852
+ for await (const file of cacheFiles)
853
+ await unlink(join(this.folder, this.database, tableName, ".tmp", file));
854
+ }
797
855
  if (returnPostedData)
798
- return this.get(tableName, where, options);
856
+ return this.get(tableName, where, options, undefined, undefined, schema);
799
857
  }
800
858
  }
801
859
  else if ((Array.isArray(where) &&
@@ -804,7 +862,7 @@ export default class Inibase {
804
862
  Utils.isNumber(where)) {
805
863
  if ((Array.isArray(where) && where.every(Utils.isValidID)) ||
806
864
  Utils.isValidID(where)) {
807
- const lineNumbers = await this.get(tableName, where, undefined, undefined, true);
865
+ const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
808
866
  return this.put(tableName, data, lineNumbers);
809
867
  }
810
868
  else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
@@ -822,14 +880,23 @@ export default class Inibase {
822
880
  [lineNum]: Array.isArray(content) ? content[index] : content,
823
881
  }), {}),
824
882
  ]));
883
+ const renameList = [];
825
884
  for await (const [path, content] of Object.entries(pathesContents))
826
- await File.replace(path, content);
885
+ renameList.push(await File.replace(path, content));
886
+ for await (const [tempPath, filePath] of renameList)
887
+ await rename(tempPath, filePath);
888
+ if (Config.isCacheEnabled) {
889
+ const cacheFiles = (await readdir(join(this.folder, this.database, tableName, ".tmp")))?.filter((fileName) => !["lastId.inib", "totalItems.inib"].includes(fileName));
890
+ if (cacheFiles.length)
891
+ for await (const file of cacheFiles)
892
+ await unlink(join(this.folder, this.database, tableName, ".tmp", file));
893
+ }
827
894
  if (returnPostedData)
828
- return this.get(tableName, where, options, !Array.isArray(where));
895
+ return this.get(tableName, where, options, !Array.isArray(where), undefined, schema);
829
896
  }
830
897
  }
831
898
  else if (Utils.isObject(where)) {
832
- const lineNumbers = await this.get(tableName, where, undefined, undefined, true);
899
+ const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
833
900
  if (!lineNumbers || !lineNumbers.length)
834
901
  throw this.throwError("NO_ITEMS", tableName);
835
902
  return this.put(tableName, data, lineNumbers);
@@ -849,6 +916,12 @@ export default class Inibase {
849
916
  if (files.length)
850
917
  for await (const file of files)
851
918
  await unlink(join(this.folder, this.database, tableName, file));
919
+ if (Config.isCacheEnabled) {
920
+ const cacheFiles = (await readdir(join(this.folder, this.database, tableName, ".tmp")))?.filter((fileName) => !["lastId.inib", "totalItems.inib"].includes(fileName));
921
+ if (cacheFiles.length)
922
+ for await (const file of cacheFiles)
923
+ await unlink(join(this.folder, this.database, tableName, ".tmp", file));
924
+ }
852
925
  return "*";
853
926
  }
854
927
  else if ((Array.isArray(where) &&
@@ -857,7 +930,7 @@ export default class Inibase {
857
930
  Utils.isNumber(where)) {
858
931
  if ((Array.isArray(where) && where.every(Utils.isValidID)) ||
859
932
  Utils.isValidID(where)) {
860
- const lineNumbers = await this.get(tableName, where, undefined, undefined, true);
933
+ const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
861
934
  if (!lineNumbers)
862
935
  return null;
863
936
  return this.delete(tableName, lineNumbers, where);
@@ -868,17 +941,29 @@ export default class Inibase {
868
941
  const files = (await readdir(join(this.folder, this.database, tableName)))?.filter((fileName) => fileName.endsWith(".inib"));
869
942
  if (files.length) {
870
943
  if (!_id)
871
- _id = Object.entries((await File.get(join(this.folder, this.database, tableName, "id.inib"), where, "number", undefined, this.salt))[0] ?? {}).map(([_key, id]) => UtilsServer.encodeID(Number(id), this.salt));
944
+ _id = Object.entries((await File.get(join(this.folder, this.database, tableName, "id.inib"), where, "number", undefined, this.salt)) ?? {}).map(([_key, id]) => UtilsServer.encodeID(Number(id), this.salt));
872
945
  if (!_id.length)
873
946
  throw this.throwError("NO_ITEMS", tableName);
947
+ const renameList = [];
874
948
  for await (const file of files)
875
- await File.remove(join(this.folder, this.database, tableName, file), where);
949
+ renameList.push(await File.remove(join(this.folder, this.database, tableName, file), where));
950
+ for await (const [tempPath, filePath] of renameList)
951
+ await rename(tempPath, filePath);
952
+ if (Config.isCacheEnabled) {
953
+ const cacheFiles = (await readdir(join(this.folder, this.database, tableName, ".tmp")))?.filter((fileName) => !["lastId.inib", "totalItems.inib"].includes(fileName));
954
+ if (cacheFiles.length)
955
+ for await (const file of cacheFiles)
956
+ await unlink(join(this.folder, this.database, tableName, ".tmp", file));
957
+ await File.write(join(this.folder, this.database, tableName, ".tmp", "totalItems.inib"), String(((await File.isExists(join(this.folder, this.database, tableName, ".tmp", "totalItems.inib")))
958
+ ? Number(await File.read(join(this.folder, this.database, tableName, ".tmp", "totalItems.inib"), true))
959
+ : await File.count(join(this.folder, this.database, tableName, "id.inib"))) - (Array.isArray(where) ? where.length : 1)), true);
960
+ }
876
961
  return Array.isArray(_id) && _id.length === 1 ? _id[0] : _id;
877
962
  }
878
963
  }
879
964
  }
880
965
  else if (Utils.isObject(where)) {
881
- const lineNumbers = await this.get(tableName, where, undefined, undefined, true);
966
+ const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
882
967
  if (!lineNumbers || !lineNumbers.length)
883
968
  throw this.throwError("NO_ITEMS", tableName);
884
969
  return this.delete(tableName, lineNumbers);
@@ -900,7 +985,7 @@ export default class Inibase {
900
985
  const columnPath = join(this.folder, this.database, tableName, column + ".inib");
901
986
  if (await File.isExists(columnPath)) {
902
987
  if (where) {
903
- const lineNumbers = await this.get(tableName, where, undefined, undefined, true);
988
+ const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
904
989
  RETURN[column] = lineNumbers
905
990
  ? await File.sum(columnPath, lineNumbers)
906
991
  : 0;
@@ -924,7 +1009,7 @@ export default class Inibase {
924
1009
  const columnPath = join(this.folder, this.database, tableName, column + ".inib");
925
1010
  if (await File.isExists(columnPath)) {
926
1011
  if (where) {
927
- const lineNumbers = await this.get(tableName, where, undefined, undefined, true);
1012
+ const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
928
1013
  RETURN[column] = lineNumbers
929
1014
  ? await File.max(columnPath, lineNumbers)
930
1015
  : 0;
@@ -948,7 +1033,7 @@ export default class Inibase {
948
1033
  const columnPath = join(this.folder, this.database, tableName, column + ".inib");
949
1034
  if (await File.isExists(columnPath)) {
950
1035
  if (where) {
951
- const lineNumbers = await this.get(tableName, where, undefined, undefined, true);
1036
+ const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
952
1037
  RETURN[column] = lineNumbers
953
1038
  ? await File.min(columnPath, lineNumbers)
954
1039
  : 0;