inibase 1.0.0-rc.31 → 1.0.0-rc.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/file.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
2
  import { ComparisonOperator, FieldType } from "./index.js";
3
+ export declare const lock: (folderPath: string) => Promise<void>;
4
+ export declare const unlock: (folderPath: string) => Promise<void>;
3
5
  export declare const write: (filePath: string, data: any, disableCompression?: boolean) => Promise<void>;
4
6
  export declare const read: (filePath: string, disableCompression?: boolean) => Promise<string>;
5
7
  /**
@@ -168,4 +170,6 @@ export default class File {
168
170
  static count: (filePath: string) => Promise<number>;
169
171
  static write: (filePath: string, data: any, disableCompression?: boolean) => Promise<void>;
170
172
  static read: (filePath: string, disableCompression?: boolean) => Promise<string>;
173
+ static lock: (folderPath: string) => Promise<void>;
174
+ static unlock: (folderPath: string) => Promise<void>;
171
175
  }
package/dist/file.js CHANGED
@@ -1,16 +1,39 @@
1
- import { open, access, writeFile, readFile, constants as fsConstants, } from "node:fs/promises";
1
+ import { open, access, writeFile, readFile, constants as fsConstants, unlink, } from "node:fs/promises";
2
2
  import { createInterface } from "node:readline";
3
3
  import { Transform } from "node:stream";
4
4
  import { pipeline } from "node:stream/promises";
5
5
  import { createGzip, createGunzip, gzip as gzipAsync, gunzip as gunzipAsync, } from "node:zlib";
6
6
  import { promisify } from "node:util";
7
+ import { join } from "node:path";
7
8
  import { detectFieldType, isArrayOfArrays, isNumber, isObject, } from "./utils.js";
8
9
  import { encodeID, comparePassword } from "./utils.server.js";
9
10
  import Config from "./config.js";
10
11
  const gzip = promisify(gzipAsync);
11
12
  const gunzip = promisify(gunzipAsync);
13
+ export const lock = async (folderPath) => {
14
+ let lockFile, lockFilePath = join(folderPath, "locked.inib");
15
+ try {
16
+ lockFile = await open(lockFilePath, "wx");
17
+ return;
18
+ }
19
+ catch ({ message }) {
20
+ if (message.split(":")[0] === "EEXIST")
21
+ return await new Promise((resolve, reject) => setTimeout(() => resolve(lock(folderPath)), 13));
22
+ }
23
+ finally {
24
+ await lockFile?.close();
25
+ }
26
+ };
27
+ export const unlock = async (folderPath) => {
28
+ try {
29
+ await unlink(join(folderPath, "locked.inib"));
30
+ }
31
+ catch { }
32
+ };
12
33
  export const write = async (filePath, data, disableCompression = false) => {
13
- await writeFile(filePath, Config.isCompressionEnabled && !disableCompression ? await gzip(data) : data);
34
+ await writeFile(filePath, Config.isCompressionEnabled && !disableCompression
35
+ ? await gzip(String(data))
36
+ : String(data));
14
37
  };
15
38
  export const read = async (filePath, disableCompression = false) => {
16
39
  return Config.isCompressionEnabled && !disableCompression
@@ -765,4 +788,6 @@ export default class File {
765
788
  static count = count;
766
789
  static write = write;
767
790
  static read = read;
791
+ static lock = lock;
792
+ static unlock = unlock;
768
793
  }
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { unlink, rename, mkdir, readdir, open } 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
4
  import { cpus } from "node:os";
@@ -66,12 +66,12 @@ export default class Inibase {
66
66
  // remove id from schema
67
67
  schema = schema.filter(({ key }) => !["id", "createdAt", "updatedAt"].includes(key));
68
68
  schema = UtilsServer.addIdToSchema(schema, UtilsServer.findLastIdNumber(schema, this.salt), this.salt);
69
- const TablePath = join(this.folder, this.database, tableName), TableSchemaPath = join(TablePath, "schema.json");
70
- if (!(await File.isExists(TablePath)))
71
- await mkdir(TablePath, { recursive: true });
72
- if (!(await File.isExists(join(TablePath, ".tmp"))))
73
- await mkdir(join(TablePath, ".tmp"));
74
- if (await File.isExists(TableSchemaPath)) {
69
+ const tablePath = join(this.folder, this.database, tableName), tableSchemaPath = join(tablePath, "schema.json");
70
+ if (!(await File.isExists(tablePath)))
71
+ await mkdir(tablePath, { recursive: true });
72
+ if (!(await File.isExists(join(tablePath, ".tmp"))))
73
+ await mkdir(join(tablePath, ".tmp"));
74
+ if (await File.isExists(tableSchemaPath)) {
75
75
  // update columns files names based on field id
76
76
  const schemaToIdsPath = (schema, prefix = "") => {
77
77
  let RETURN = {};
@@ -88,17 +88,17 @@ export default class Inibase {
88
88
  }, replaceOldPathes = Utils.findChangedProperties(schemaToIdsPath((await this.getTableSchema(tableName)) ?? []), schemaToIdsPath(schema));
89
89
  if (replaceOldPathes)
90
90
  await Promise.all(Object.entries(replaceOldPathes).map(async ([oldPath, newPath]) => {
91
- if (await File.isExists(join(TablePath, oldPath)))
92
- await rename(join(TablePath, oldPath), join(TablePath, newPath));
91
+ if (await File.isExists(join(tablePath, oldPath)))
92
+ await rename(join(tablePath, oldPath), join(tablePath, newPath));
93
93
  }));
94
94
  }
95
- await File.write(join(TablePath, "schema.json"), JSON.stringify(decodeIdFromSchema(schema), null, 2), true);
95
+ await File.write(join(tablePath, "schema.json"), JSON.stringify(decodeIdFromSchema(schema), null, 2), true);
96
96
  }
97
97
  async getTableSchema(tableName) {
98
- const TableSchemaPath = join(this.folder, this.database, tableName, "schema.json");
99
- if (!(await File.isExists(TableSchemaPath)))
98
+ const tableSchemaPath = join(this.folder, this.database, tableName, "schema.json");
99
+ if (!(await File.isExists(tableSchemaPath)))
100
100
  return undefined;
101
- const schemaFile = await File.read(TableSchemaPath, true);
101
+ const schemaFile = await File.read(tableSchemaPath, true);
102
102
  if (!schemaFile)
103
103
  return undefined;
104
104
  const schema = JSON.parse(schemaFile), lastIdNumber = UtilsServer.findLastIdNumber(schema, this.salt);
@@ -301,7 +301,7 @@ export default class Inibase {
301
301
  ]));
302
302
  }
303
303
  async getItemsFromSchema(tableName, schema, linesNumber, options, prefix) {
304
- const path = join(this.folder, this.database, tableName);
304
+ const tablePath = join(this.folder, this.database, tableName);
305
305
  let RETURN = {};
306
306
  await Promise.all(schema.map(async (field) => {
307
307
  if ((field.type === "array" ||
@@ -392,7 +392,7 @@ export default class Inibase {
392
392
  options.columns = options.columns
393
393
  .filter((column) => column.includes(`${field.key}.`))
394
394
  .map((column) => column.replace(`${field.key}.`, ""));
395
- const items = await File.get(join(path, (prefix ?? "") + field.key + ".inib"), linesNumber, field.type, field.children, this.salt);
395
+ const items = await File.get(join(tablePath, (prefix ?? "") + field.key + ".inib"), linesNumber, field.type, field.children, this.salt);
396
396
  if (items)
397
397
  await Promise.all(Object.entries(items).map(async ([index, item]) => {
398
398
  if (!RETURN[index])
@@ -402,8 +402,8 @@ export default class Inibase {
402
402
  : this.getDefaultValue(field);
403
403
  }));
404
404
  }
405
- else if (await File.isExists(join(path, (prefix ?? "") + field.key + ".inib"))) {
406
- const items = await File.get(join(path, (prefix ?? "") + field.key + ".inib"), linesNumber, field.type, field?.children, this.salt);
405
+ else if (await File.isExists(join(tablePath, (prefix ?? "") + field.key + ".inib"))) {
406
+ const items = await File.get(join(tablePath, (prefix ?? "") + field.key + ".inib"), linesNumber, field.type, field?.children, this.salt);
407
407
  if (items)
408
408
  for (const [index, item] of Object.entries(items)) {
409
409
  if (!RETURN[index])
@@ -428,13 +428,13 @@ export default class Inibase {
428
428
  }
429
429
  else if (field.type === "table") {
430
430
  if ((await File.isExists(join(this.folder, this.database, field.key))) &&
431
- (await File.isExists(join(path, (prefix ?? "") + field.key + ".inib")))) {
431
+ (await File.isExists(join(tablePath, (prefix ?? "") + field.key + ".inib")))) {
432
432
  if (options.columns)
433
433
  options.columns = options.columns
434
434
  .filter((column) => column.includes(`${field.key}.`) &&
435
435
  !column.includes(`${field.key}.`))
436
436
  .map((column) => column.replace(`${field.key}.`, ""));
437
- const items = await File.get(join(path, (prefix ?? "") + field.key + ".inib"), linesNumber, "number", undefined, this.salt);
437
+ const items = await File.get(join(tablePath, (prefix ?? "") + field.key + ".inib"), linesNumber, "number", undefined, this.salt);
438
438
  if (items)
439
439
  for await (const [index, item] of Object.entries(items)) {
440
440
  if (!RETURN[index])
@@ -445,8 +445,8 @@ export default class Inibase {
445
445
  }
446
446
  }
447
447
  }
448
- else if (await File.isExists(join(path, (prefix ?? "") + field.key + ".inib"))) {
449
- const items = await File.get(join(path, (prefix ?? "") + field.key + ".inib"), linesNumber, field.type, field?.children, this.salt);
448
+ else if (await File.isExists(join(tablePath, (prefix ?? "") + field.key + ".inib"))) {
449
+ const items = await File.get(join(tablePath, (prefix ?? "") + field.key + ".inib"), linesNumber, field.type, field?.children, this.salt);
450
450
  if (items)
451
451
  for (const [index, item] of Object.entries(items)) {
452
452
  if (!RETURN[index])
@@ -463,6 +463,7 @@ export default class Inibase {
463
463
  return RETURN;
464
464
  }
465
465
  async applyCriteria(tableName, schema, options, criteria, allTrue) {
466
+ const tablePath = join(this.folder, this.database, tableName);
466
467
  let RETURN = {}, RETURN_LineNumbers = null;
467
468
  if (!criteria)
468
469
  return [null, null];
@@ -545,7 +546,7 @@ export default class Inibase {
545
546
  searchOperator = "=";
546
547
  searchComparedAtValue = value;
547
548
  }
548
- 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);
549
+ const [searchResult, totalLines, linesNumbers] = await File.search(join(tablePath, key + ".inib"), searchOperator ?? "=", searchComparedAtValue ?? null, searchLogicalOperator, field?.type, field?.children, options.perPage, options.page - 1 * options.perPage + 1, true, this.salt);
549
550
  if (searchResult) {
550
551
  RETURN = Utils.deepMerge(RETURN, Object.fromEntries(Object.entries(searchResult).map(([id, value]) => [
551
552
  id,
@@ -592,6 +593,7 @@ export default class Inibase {
592
593
  page: 1,
593
594
  perPage: 15,
594
595
  }, onlyOne, onlyLinesNumbers, tableSchema) {
596
+ const tablePath = join(this.folder, this.database, tableName);
595
597
  // Ensure options.columns is an array
596
598
  if (options.columns) {
597
599
  options.columns = Array.isArray(options.columns)
@@ -607,8 +609,7 @@ export default class Inibase {
607
609
  let schema = tableSchema ?? (await this.getTableSchema(tableName));
608
610
  if (!schema)
609
611
  throw this.throwError("NO_SCHEMA", tableName);
610
- const idFilePath = join(this.folder, this.database, tableName, "id.inib");
611
- if (!(await File.isExists(idFilePath)))
612
+ if (!(await File.isExists(join(tablePath, "id.inib"))))
612
613
  return null;
613
614
  if (options.columns && options.columns.length)
614
615
  schema = this._filterSchemaByColumns(schema, options.columns);
@@ -618,12 +619,15 @@ export default class Inibase {
618
619
  index +
619
620
  1), options));
620
621
  if (Config.isCacheEnabled &&
621
- (await File.isExists(join(this.folder, this.database, tableName, ".tmp", "totalItems.inib"))))
622
- this.totalItems[tableName + "-*"] = Number(await File.read(join(this.folder, this.database, tableName, ".tmp", "totalItems.inib"), true));
622
+ (await File.isExists(join(tablePath, ".tmp", "pagination.inib"))))
623
+ this.totalItems[tableName + "-*"] = Number((await File.read(join(tablePath, ".tmp", "pagination.inib"), true)).split(",")[1]);
623
624
  else {
624
- this.totalItems[tableName + "-*"] = await File.count(idFilePath);
625
+ let [lastId, totalItems] = await File.get(join(tablePath, "id.inib"), -1, "number", undefined, this.salt, true);
626
+ if (lastId)
627
+ lastId = Number(Object.keys(lastId)[0] ?? 0);
628
+ this.totalItems[tableName + "-*"] = totalItems;
625
629
  if (Config.isCacheEnabled)
626
- await File.write(join(this.folder, this.database, tableName, ".tmp", "totalItems.inib"), String(this.totalItems[tableName + "-*"]), true);
630
+ await File.write(join(tablePath, ".tmp", "pagination.inib"), `${lastId},${totalItems}`, true);
627
631
  }
628
632
  }
629
633
  else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
@@ -644,7 +648,7 @@ export default class Inibase {
644
648
  let Ids = where;
645
649
  if (!Array.isArray(Ids))
646
650
  Ids = [Ids];
647
- const [lineNumbers, countItems] = await File.search(idFilePath, "[]", Ids.map((id) => Utils.isNumber(id) ? Number(id) : UtilsServer.decodeID(id, this.salt)), undefined, "number", undefined, Ids.length, 0, false, this.salt);
651
+ const [lineNumbers, countItems] = await File.search(join(tablePath, "id.inib"), "[]", Ids.map((id) => Utils.isNumber(id) ? Number(id) : UtilsServer.decodeID(id, this.salt)), undefined, "number", undefined, Ids.length, 0, !this.totalItems[tableName + "-*"], this.salt);
648
652
  if (!lineNumbers)
649
653
  throw this.throwError("INVALID_ID", where);
650
654
  if (onlyLinesNumbers)
@@ -652,16 +656,8 @@ export default class Inibase {
652
656
  ? Object.keys(lineNumbers).map(Number)
653
657
  : null;
654
658
  RETURN = Object.values((await this.getItemsFromSchema(tableName, schema, Object.keys(lineNumbers).map(Number), options)) ?? {});
655
- if (!this.totalItems[tableName + "-*"]) {
656
- if (Config.isCacheEnabled &&
657
- (await File.isExists(join(this.folder, this.database, tableName, ".tmp", "totalItems.inib"))))
658
- this.totalItems[tableName + "-*"] = Number(await File.read(join(this.folder, this.database, tableName, ".tmp", "totalItems.inib"), true));
659
- else {
660
- this.totalItems[tableName + "-*"] = await File.count(idFilePath);
661
- if (Config.isCacheEnabled)
662
- await File.write(join(this.folder, this.database, tableName, ".tmp", "totalItems.inib"), String(this.totalItems[tableName + "-*"]), true);
663
- }
664
- }
659
+ if (!this.totalItems[tableName + "-*"])
660
+ this.totalItems[tableName + "-*"] = countItems;
665
661
  if (RETURN && RETURN.length && !Array.isArray(where))
666
662
  RETURN = RETURN[0];
667
663
  }
@@ -669,7 +665,7 @@ export default class Inibase {
669
665
  let cachedFilePath = "";
670
666
  // Criteria
671
667
  if (Config.isCacheEnabled)
672
- cachedFilePath = join(this.folder, this.database, tableName, ".tmp", `${UtilsServer.hashObject(where)}.inib`);
668
+ cachedFilePath = join(tablePath, ".tmp", `${UtilsServer.hashObject(where)}.inib`);
673
669
  if (Config.isCacheEnabled && (await File.isExists(cachedFilePath))) {
674
670
  const cachedItems = (await File.read(cachedFilePath, true)).split(",");
675
671
  this.totalItems[tableName + "-*"] = cachedItems.length;
@@ -714,38 +710,26 @@ export default class Inibase {
714
710
  page: 1,
715
711
  perPage: 15,
716
712
  };
717
- const idFilePath = join(this.folder, this.database, tableName, "id.inib"), cashFolderPath = join(this.folder, this.database, tableName, ".tmp");
718
- let testFileHandle, renameList = [];
719
- try {
720
- testFileHandle = await open(join(cashFolderPath, "id.inib"), "wx");
721
- renameList = [[join(cashFolderPath, "id.inib"), ""]];
722
- }
723
- catch ({ message }) {
724
- if (message.split(":")[0] === "EEXIST")
725
- return await new Promise((resolve, reject) => setTimeout(() => resolve(this.post(tableName, data, options, returnPostedData)), 13));
726
- }
727
- finally {
728
- await testFileHandle?.close();
729
- }
713
+ const tablePath = join(this.folder, this.database, tableName);
714
+ await File.lock(join(tablePath, ".tmp"));
730
715
  if (!returnPostedData)
731
716
  returnPostedData = false;
732
717
  const schema = await this.getTableSchema(tableName);
733
718
  let RETURN;
734
719
  if (!schema)
735
720
  throw this.throwError("NO_SCHEMA", tableName);
736
- let lastId = 0, totalItems = 0, lastIdObj;
737
- if (await File.isExists(idFilePath)) {
738
- if (await File.isExists(join(cashFolderPath, "lastId.inib"))) {
739
- lastId = Number(await File.read(join(cashFolderPath, "lastId.inib"), true));
740
- if (await File.isExists(join(cashFolderPath, "totalItems.inib")))
741
- totalItems = Number(await File.read(join(cashFolderPath, "totalItems.inib"), true));
742
- else
743
- totalItems = await File.count(join(this.folder, this.database, tableName, "id.inib"));
744
- }
721
+ let lastId = 0, totalItems = 0, renameList = [];
722
+ if (await File.isExists(join(tablePath, "id.inib"))) {
723
+ if (Config.isCacheEnabled &&
724
+ (await File.isExists(join(tablePath, ".tmp", "pagination.inib"))))
725
+ [lastId, totalItems] = (await File.read(join(tablePath, ".tmp", "pagination.inib"), true))
726
+ .split(",")
727
+ .map(Number);
745
728
  else {
746
- [lastIdObj, totalItems] = await File.get(idFilePath, -1, "number", undefined, this.salt, true);
729
+ let lastIdObj;
730
+ [lastIdObj, totalItems] = await File.get(join(tablePath, "id.inib"), -1, "number", undefined, this.salt, true);
747
731
  if (lastIdObj)
748
- lastId = Number(Object.entries(lastIdObj)[0][1]) ?? 0;
732
+ lastId = Number(Object.keys(lastIdObj)[0] ?? 0);
749
733
  }
750
734
  }
751
735
  if (Utils.isArrayOfObjects(data))
@@ -764,12 +748,12 @@ export default class Inibase {
764
748
  throw this.throwError("NO_DATA");
765
749
  try {
766
750
  RETURN = this.formatData(RETURN, schema);
767
- const pathesContents = this.joinPathesContents(join(this.folder, this.database, tableName), Array.isArray(RETURN) ? RETURN.toReversed() : RETURN);
768
- renameList = await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => File.append(path, content)));
751
+ const pathesContents = this.joinPathesContents(join(tablePath), Array.isArray(RETURN) ? RETURN.toReversed() : RETURN);
752
+ await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(await File.append(path, content))));
769
753
  await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
770
754
  renameList = [];
771
- await File.write(join(cashFolderPath, "lastId.inib"), lastId.toString(), true);
772
- await File.write(join(cashFolderPath, "totalItems.inib"), String(totalItems + (Array.isArray(RETURN) ? RETURN.length : 1)), true);
755
+ if (Config.isCacheEnabled)
756
+ await File.write(join(tablePath, ".tmp", "pagination.inib"), `${lastId},${totalItems + (Array.isArray(RETURN) ? RETURN.length : 1)}`, true);
773
757
  if (returnPostedData)
774
758
  return this.get(tableName, Utils.isArrayOfObjects(RETURN)
775
759
  ? RETURN.map((data) => Number(data.id))
@@ -777,6 +761,7 @@ export default class Inibase {
777
761
  undefined, schema);
778
762
  }
779
763
  finally {
764
+ await File.unlock(join(tablePath, ".tmp"));
780
765
  if (renameList.length)
781
766
  await Promise.all(renameList.map(async ([tempPath, _]) => (await File.isExists(tempPath)) ? unlink(tempPath) : undefined));
782
767
  }
@@ -785,13 +770,15 @@ export default class Inibase {
785
770
  page: 1,
786
771
  perPage: 15,
787
772
  }, returnPostedData) {
773
+ let renameList = [];
774
+ const tablePath = join(this.folder, this.database, tableName);
788
775
  const schema = await this.getTableSchema(tableName);
789
776
  if (!schema)
790
777
  throw this.throwError("NO_SCHEMA", tableName);
791
- const idFilePath = join(this.folder, this.database, tableName, "id.inib");
792
- if (!(await File.isExists(idFilePath)))
778
+ if (!(await File.isExists(join(tablePath, "id.inib"))))
793
779
  throw this.throwError("NO_ITEMS", tableName);
794
780
  data = this.formatData(data, schema, true);
781
+ await File.lock(join(tablePath, ".tmp"));
795
782
  if (!where) {
796
783
  if (Utils.isArrayOfObjects(data)) {
797
784
  if (!data.every((item) => item.hasOwnProperty("id") && Utils.isValidID(item.id)))
@@ -806,7 +793,7 @@ export default class Inibase {
806
793
  return this.put(tableName, data, UtilsServer.decodeID(data.id, this.salt));
807
794
  }
808
795
  else {
809
- const pathesContents = this.joinPathesContents(join(this.folder, this.database, tableName), Utils.isArrayOfObjects(data)
796
+ const pathesContents = this.joinPathesContents(join(tablePath), Utils.isArrayOfObjects(data)
810
797
  ? data.map((item) => ({
811
798
  ...(({ id, ...restOfData }) => restOfData)(item),
812
799
  updatedAt: Date.now(),
@@ -815,26 +802,21 @@ export default class Inibase {
815
802
  ...(({ id, ...restOfData }) => restOfData)(data),
816
803
  updatedAt: Date.now(),
817
804
  });
818
- let testFileHandle;
819
805
  try {
820
- testFileHandle = await open(Object.keys(pathesContents)[0].replace(/([^/]+)\/?$/, `.tmp/$1`), "wx");
821
- }
822
- catch ({ message }) {
823
- if (message.split(":")[0] === "EEXIST")
824
- return await new Promise((resolve, reject) => setTimeout(() => resolve(this.put(tableName, data, where, options)), 13));
806
+ await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(await File.replace(path, content))));
807
+ await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
808
+ if (Config.isCacheEnabled)
809
+ await Promise.all((await readdir(join(tablePath, ".tmp")))
810
+ ?.filter((fileName) => !["pagination.inib", "locked.inib"].includes(fileName))
811
+ .map(async (file) => unlink(join(tablePath, ".tmp", file))));
812
+ if (returnPostedData)
813
+ return this.get(tableName, where, options, undefined, undefined, schema);
825
814
  }
826
815
  finally {
827
- await testFileHandle?.close();
828
- }
829
- const renameList = await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => File.replace(path, content)));
830
- await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
831
- if (Config.isCacheEnabled) {
832
- const cacheFiles = (await readdir(join(this.folder, this.database, tableName, ".tmp")))?.filter((fileName) => !["lastId.inib", "totalItems.inib"].includes(fileName));
833
- if (cacheFiles.length)
834
- await Promise.all(cacheFiles.map(async (file) => unlink(join(this.folder, this.database, tableName, ".tmp", file))));
816
+ if (renameList.length)
817
+ await Promise.all(renameList.map(async ([tempPath, _]) => (await File.isExists(tempPath)) ? unlink(tempPath) : undefined));
818
+ await File.unlock(join(tablePath, ".tmp"));
835
819
  }
836
- if (returnPostedData)
837
- return this.get(tableName, where, options, undefined, undefined, schema);
838
820
  }
839
821
  }
840
822
  else if ((Array.isArray(where) &&
@@ -849,7 +831,7 @@ export default class Inibase {
849
831
  else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
850
832
  Utils.isNumber(where)) {
851
833
  // "where" in this case, is the line(s) number(s) and not id(s)
852
- const pathesContents = Object.fromEntries(Object.entries(this.joinPathesContents(join(this.folder, this.database, tableName), Utils.isArrayOfObjects(data)
834
+ const pathesContents = Object.fromEntries(Object.entries(this.joinPathesContents(join(tablePath), Utils.isArrayOfObjects(data)
853
835
  ? data.map((item) => ({
854
836
  ...item,
855
837
  updatedAt: Date.now(),
@@ -861,26 +843,22 @@ export default class Inibase {
861
843
  [lineNum]: Array.isArray(content) ? content[index] : content,
862
844
  }), {}),
863
845
  ]));
864
- let testFileHandle;
865
846
  try {
866
- testFileHandle = await open(Object.keys(pathesContents)[0].replace(/([^/]+)\/?$/, `.tmp/$1`), "wx");
867
- }
868
- catch ({ message }) {
869
- if (message.split(":")[0] === "EEXIST")
870
- return await new Promise((resolve, reject) => setTimeout(() => resolve(this.put(tableName, data, where, options)), 13));
847
+ await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(await File.replace(path, content))));
848
+ await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
849
+ renameList = [];
850
+ if (Config.isCacheEnabled)
851
+ await Promise.all((await readdir(join(tablePath, ".tmp")))
852
+ ?.filter((fileName) => !["pagination.inib", "locked.inib"].includes(fileName))
853
+ .map(async (file) => unlink(join(tablePath, ".tmp", file))));
854
+ if (returnPostedData)
855
+ return this.get(tableName, where, options, !Array.isArray(where), undefined, schema);
871
856
  }
872
857
  finally {
873
- await testFileHandle?.close();
874
- }
875
- const renameList = await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => File.replace(path, content)));
876
- await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
877
- if (Config.isCacheEnabled) {
878
- const cacheFiles = (await readdir(join(this.folder, this.database, tableName, ".tmp")))?.filter((fileName) => !["lastId.inib", "totalItems.inib"].includes(fileName));
879
- if (cacheFiles.length)
880
- await Promise.all(cacheFiles.map(async (file) => unlink(join(this.folder, this.database, tableName, ".tmp", file))));
858
+ if (renameList.length)
859
+ await Promise.all(renameList.map(async ([tempPath, _]) => (await File.isExists(tempPath)) ? unlink(tempPath) : undefined));
860
+ await File.unlock(join(tablePath, ".tmp"));
881
861
  }
882
- if (returnPostedData)
883
- return this.get(tableName, where, options, !Array.isArray(where), undefined, schema);
884
862
  }
885
863
  }
886
864
  else if (Utils.isObject(where)) {
@@ -893,21 +871,22 @@ export default class Inibase {
893
871
  throw this.throwError("INVALID_PARAMETERS", tableName);
894
872
  }
895
873
  async delete(tableName, where, _id) {
874
+ let renameList = [];
875
+ const tablePath = join(this.folder, this.database, tableName);
876
+ await File.lock(join(tablePath, ".tmp"));
896
877
  const schema = await this.getTableSchema(tableName);
897
878
  if (!schema)
898
879
  throw this.throwError("NO_SCHEMA", tableName);
899
- const idFilePath = join(this.folder, this.database, tableName, "id.inib");
900
- if (!(await File.isExists(idFilePath)))
880
+ if (!(await File.isExists(join(tablePath, "id.inib"))))
901
881
  throw this.throwError("NO_ITEMS", tableName);
902
882
  if (!where) {
903
- const files = (await readdir(join(this.folder, this.database, tableName)))?.filter((fileName) => fileName.endsWith(".inib"));
904
- if (files.length)
905
- await Promise.all(files.map(async (file) => unlink(join(this.folder, this.database, tableName, file))));
906
- if (Config.isCacheEnabled) {
907
- const cacheFiles = (await readdir(join(this.folder, this.database, tableName, ".tmp")))?.filter((fileName) => !["lastId.inib", "totalItems.inib"].includes(fileName));
908
- if (cacheFiles.length)
909
- await Promise.all(cacheFiles.map(async (file) => unlink(join(this.folder, this.database, tableName, ".tmp", file))));
910
- }
883
+ await Promise.all((await readdir(join(tablePath)))
884
+ ?.filter((fileName) => fileName.endsWith(".inib"))
885
+ .map(async (file) => unlink(join(tablePath, file))));
886
+ if (Config.isCacheEnabled)
887
+ await Promise.all((await readdir(join(tablePath, ".tmp")))
888
+ ?.filter((fileName) => !["pagination.inib", "locked.inib"].includes(fileName))
889
+ .map(async (file) => unlink(join(tablePath, ".tmp", file))));
911
890
  return "*";
912
891
  }
913
892
  else if ((Array.isArray(where) &&
@@ -924,34 +903,33 @@ export default class Inibase {
924
903
  else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
925
904
  Utils.isNumber(where)) {
926
905
  // "where" in this case, is the line(s) number(s) and not id(s)
927
- const files = (await readdir(join(this.folder, this.database, tableName)))?.filter((fileName) => fileName.endsWith(".inib"));
906
+ const files = (await readdir(join(tablePath)))?.filter((fileName) => fileName.endsWith(".inib"));
928
907
  if (files.length) {
929
908
  if (!_id)
930
- _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));
909
+ _id = Object.entries((await File.get(join(tablePath, "id.inib"), where, "number", undefined, this.salt)) ?? {}).map(([_key, id]) => UtilsServer.encodeID(Number(id), this.salt));
931
910
  if (!_id.length)
932
911
  throw this.throwError("NO_ITEMS", tableName);
933
- let testFileHandle;
934
912
  try {
935
- testFileHandle = await open(join(this.folder, this.database, tableName, ".tmp", "id.inib"), "wx");
936
- }
937
- catch ({ message }) {
938
- if (message.split(":")[0] === "EEXIST")
939
- return await new Promise((resolve, reject) => setTimeout(() => resolve(this.delete(tableName, where, _id)), 13));
913
+ await Promise.all(files.map(async (file) => renameList.push(await File.remove(join(tablePath, file), where))));
914
+ await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
915
+ if (Config.isCacheEnabled) {
916
+ await Promise.all((await readdir(join(tablePath, ".tmp")))
917
+ ?.filter((fileName) => !["pagination.inib", "locked.inib"].includes(fileName))
918
+ .map(async (file) => unlink(join(tablePath, ".tmp", file))));
919
+ if (await File.isExists(join(tablePath, ".tmp", "pagination.inib"))) {
920
+ let [lastId, totalItems] = (await File.read(join(tablePath, ".tmp", "pagination.inib"), true))
921
+ .split(",")
922
+ .map(Number);
923
+ await File.write(join(tablePath, ".tmp", "pagination.inib"), `${lastId},${totalItems - (Array.isArray(where) ? where.length : 1)}`, true);
924
+ }
925
+ }
926
+ return Array.isArray(_id) && _id.length === 1 ? _id[0] : _id;
940
927
  }
941
928
  finally {
942
- await testFileHandle?.close();
943
- }
944
- const renameList = await Promise.all(files.map(async (file) => File.remove(join(this.folder, this.database, tableName, file), where)));
945
- await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
946
- if (Config.isCacheEnabled) {
947
- const cacheFiles = (await readdir(join(this.folder, this.database, tableName, ".tmp")))?.filter((fileName) => !["lastId.inib", "totalItems.inib"].includes(fileName));
948
- if (cacheFiles.length)
949
- await Promise.all(cacheFiles.map(async (file) => unlink(join(this.folder, this.database, tableName, ".tmp", file))));
950
- 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")))
951
- ? Number(await File.read(join(this.folder, this.database, tableName, ".tmp", "totalItems.inib"), true))
952
- : await File.count(join(this.folder, this.database, tableName, "id.inib"))) - (Array.isArray(where) ? where.length : 1)), true);
929
+ if (renameList.length)
930
+ await Promise.all(renameList.map(async ([tempPath, _]) => (await File.isExists(tempPath)) ? unlink(tempPath) : undefined));
931
+ await File.unlock(join(tablePath, ".tmp"));
953
932
  }
954
- return Array.isArray(_id) && _id.length === 1 ? _id[0] : _id;
955
933
  }
956
934
  }
957
935
  }
@@ -967,10 +945,10 @@ export default class Inibase {
967
945
  }
968
946
  async sum(tableName, columns, where) {
969
947
  let RETURN = {};
970
- const schema = await this.getTableSchema(tableName);
948
+ const tablePath = join(this.folder, this.database, tableName), schema = await this.getTableSchema(tableName);
971
949
  if (!schema)
972
950
  throw this.throwError("NO_SCHEMA", tableName);
973
- if (!(await File.isExists(join(this.folder, this.database, tableName, "id.inib"))))
951
+ if (!(await File.isExists(join(tablePath, "id.inib"))))
974
952
  throw this.throwError("NO_ITEMS", tableName);
975
953
  if (!Array.isArray(columns))
976
954
  columns = [columns];
@@ -991,10 +969,10 @@ export default class Inibase {
991
969
  }
992
970
  async max(tableName, columns, where) {
993
971
  let RETURN = {};
994
- const schema = await this.getTableSchema(tableName);
972
+ const tablePath = join(this.folder, this.database, tableName), schema = await this.getTableSchema(tableName);
995
973
  if (!schema)
996
974
  throw this.throwError("NO_SCHEMA", tableName);
997
- if (!(await File.isExists(join(this.folder, this.database, tableName, "id.inib"))))
975
+ if (!(await File.isExists(join(tablePath, "id.inib"))))
998
976
  throw this.throwError("NO_ITEMS", tableName);
999
977
  if (!Array.isArray(columns))
1000
978
  columns = [columns];
@@ -1015,10 +993,10 @@ export default class Inibase {
1015
993
  }
1016
994
  async min(tableName, columns, where) {
1017
995
  let RETURN = {};
1018
- const schema = await this.getTableSchema(tableName);
996
+ const tablePath = join(this.folder, this.database, tableName), schema = await this.getTableSchema(tableName);
1019
997
  if (!schema)
1020
998
  throw this.throwError("NO_SCHEMA", tableName);
1021
- if (!(await File.isExists(join(this.folder, this.database, tableName, "id.inib"))))
999
+ if (!(await File.isExists(join(tablePath, "id.inib"))))
1022
1000
  throw this.throwError("NO_ITEMS", tableName);
1023
1001
  if (!Array.isArray(columns))
1024
1002
  columns = [columns];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inibase",
3
- "version": "1.0.0-rc.31",
3
+ "version": "1.0.0-rc.32",
4
4
  "author": {
5
5
  "name": "Karim Amahtil",
6
6
  "email": "karim.amahtil@gmail.com"