inibase 1.0.0-rc.31 → 1.0.0-rc.33

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,62 +710,50 @@ 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);
730
714
  if (!returnPostedData)
731
715
  returnPostedData = false;
732
716
  const schema = await this.getTableSchema(tableName);
733
717
  let RETURN;
734
718
  if (!schema)
735
719
  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
- }
745
- else {
746
- [lastIdObj, totalItems] = await File.get(idFilePath, -1, "number", undefined, this.salt, true);
747
- if (lastIdObj)
748
- lastId = Number(Object.entries(lastIdObj)[0][1]) ?? 0;
749
- }
750
- }
751
- if (Utils.isArrayOfObjects(data))
752
- RETURN = data.map(({ id, updatedAt, createdAt, ...rest }) => ({
753
- id: ++lastId,
754
- ...rest,
755
- createdAt: Date.now(),
756
- }));
757
- else
758
- RETURN = (({ id, updatedAt, createdAt, ...rest }) => ({
759
- id: ++lastId,
760
- ...rest,
761
- createdAt: Date.now(),
762
- }))(data);
763
- if (!RETURN)
764
- throw this.throwError("NO_DATA");
720
+ let lastId = 0, totalItems = 0, renameList = [];
765
721
  try {
722
+ await File.lock(join(tablePath, ".tmp"));
723
+ if (await File.isExists(join(tablePath, "id.inib"))) {
724
+ if (Config.isCacheEnabled &&
725
+ (await File.isExists(join(tablePath, ".tmp", "pagination.inib"))))
726
+ [lastId, totalItems] = (await File.read(join(tablePath, ".tmp", "pagination.inib"), true))
727
+ .split(",")
728
+ .map(Number);
729
+ else {
730
+ let lastIdObj;
731
+ [lastIdObj, totalItems] = await File.get(join(tablePath, "id.inib"), -1, "number", undefined, this.salt, true);
732
+ if (lastIdObj)
733
+ lastId = Number(Object.keys(lastIdObj)[0] ?? 0);
734
+ }
735
+ }
736
+ if (Utils.isArrayOfObjects(data))
737
+ RETURN = data.map(({ id, updatedAt, createdAt, ...rest }) => ({
738
+ id: ++lastId,
739
+ ...rest,
740
+ createdAt: Date.now(),
741
+ }));
742
+ else
743
+ RETURN = (({ id, updatedAt, createdAt, ...rest }) => ({
744
+ id: ++lastId,
745
+ ...rest,
746
+ createdAt: Date.now(),
747
+ }))(data);
748
+ if (!RETURN)
749
+ throw this.throwError("NO_DATA");
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))
@@ -778,18 +762,20 @@ export default class Inibase {
778
762
  }
779
763
  finally {
780
764
  if (renameList.length)
781
- await Promise.all(renameList.map(async ([tempPath, _]) => (await File.isExists(tempPath)) ? unlink(tempPath) : undefined));
765
+ await Promise.allSettled(renameList.map(async ([tempPath, _]) => unlink(tempPath)));
766
+ await File.unlock(join(tablePath, ".tmp"));
782
767
  }
783
768
  }
784
769
  async put(tableName, data, where, options = {
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);
795
781
  if (!where) {
@@ -806,7 +792,7 @@ export default class Inibase {
806
792
  return this.put(tableName, data, UtilsServer.decodeID(data.id, this.salt));
807
793
  }
808
794
  else {
809
- const pathesContents = this.joinPathesContents(join(this.folder, this.database, tableName), Utils.isArrayOfObjects(data)
795
+ const pathesContents = this.joinPathesContents(join(tablePath), Utils.isArrayOfObjects(data)
810
796
  ? data.map((item) => ({
811
797
  ...(({ id, ...restOfData }) => restOfData)(item),
812
798
  updatedAt: Date.now(),
@@ -815,26 +801,22 @@ export default class Inibase {
815
801
  ...(({ id, ...restOfData }) => restOfData)(data),
816
802
  updatedAt: Date.now(),
817
803
  });
818
- let testFileHandle;
819
804
  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));
805
+ await File.lock(join(tablePath, ".tmp"));
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.allSettled(renameList.map(async ([tempPath, _]) => unlink(tempPath)));
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,23 @@ 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 File.lock(join(tablePath, ".tmp"));
848
+ await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(await File.replace(path, content))));
849
+ await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
850
+ renameList = [];
851
+ if (Config.isCacheEnabled)
852
+ await Promise.all((await readdir(join(tablePath, ".tmp")))
853
+ ?.filter((fileName) => !["pagination.inib", "locked.inib"].includes(fileName))
854
+ .map(async (file) => unlink(join(tablePath, ".tmp", file))));
855
+ if (returnPostedData)
856
+ return this.get(tableName, where, options, !Array.isArray(where), undefined, schema);
871
857
  }
872
858
  finally {
873
- await testFileHandle?.close();
859
+ if (renameList.length)
860
+ await Promise.allSettled(renameList.map(async ([tempPath, _]) => unlink(tempPath)));
861
+ await File.unlock(join(tablePath, ".tmp"));
874
862
  }
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))));
881
- }
882
- if (returnPostedData)
883
- return this.get(tableName, where, options, !Array.isArray(where), undefined, schema);
884
863
  }
885
864
  }
886
865
  else if (Utils.isObject(where)) {
@@ -893,20 +872,26 @@ export default class Inibase {
893
872
  throw this.throwError("INVALID_PARAMETERS", tableName);
894
873
  }
895
874
  async delete(tableName, where, _id) {
875
+ let renameList = [];
876
+ const tablePath = join(this.folder, this.database, tableName);
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))));
883
+ try {
884
+ await File.lock(join(tablePath, ".tmp"));
885
+ await Promise.all((await readdir(join(tablePath)))
886
+ ?.filter((fileName) => fileName.endsWith(".inib"))
887
+ .map(async (file) => unlink(join(tablePath, file))));
888
+ if (Config.isCacheEnabled)
889
+ await Promise.all((await readdir(join(tablePath, ".tmp")))
890
+ ?.filter((fileName) => !["pagination.inib", "locked.inib"].includes(fileName))
891
+ .map(async (file) => unlink(join(tablePath, ".tmp", file))));
892
+ }
893
+ finally {
894
+ await File.unlock(join(tablePath, ".tmp"));
910
895
  }
911
896
  return "*";
912
897
  }
@@ -924,34 +909,34 @@ export default class Inibase {
924
909
  else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
925
910
  Utils.isNumber(where)) {
926
911
  // "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"));
912
+ const files = (await readdir(join(tablePath)))?.filter((fileName) => fileName.endsWith(".inib"));
928
913
  if (files.length) {
929
914
  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));
915
+ _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
916
  if (!_id.length)
932
917
  throw this.throwError("NO_ITEMS", tableName);
933
- let testFileHandle;
934
918
  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));
919
+ await File.lock(join(tablePath, ".tmp"));
920
+ await Promise.all(files.map(async (file) => renameList.push(await File.remove(join(tablePath, file), where))));
921
+ await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
922
+ if (Config.isCacheEnabled) {
923
+ await Promise.all((await readdir(join(tablePath, ".tmp")))
924
+ ?.filter((fileName) => !["pagination.inib", "locked.inib"].includes(fileName))
925
+ .map(async (file) => unlink(join(tablePath, ".tmp", file))));
926
+ if (await File.isExists(join(tablePath, ".tmp", "pagination.inib"))) {
927
+ let [lastId, totalItems] = (await File.read(join(tablePath, ".tmp", "pagination.inib"), true))
928
+ .split(",")
929
+ .map(Number);
930
+ await File.write(join(tablePath, ".tmp", "pagination.inib"), `${lastId},${totalItems - (Array.isArray(where) ? where.length : 1)}`, true);
931
+ }
932
+ }
933
+ return Array.isArray(_id) && _id.length === 1 ? _id[0] : _id;
940
934
  }
941
935
  finally {
942
- await testFileHandle?.close();
936
+ if (renameList.length)
937
+ await Promise.allSettled(renameList.map(async ([tempPath, _]) => unlink(tempPath)));
938
+ await File.unlock(join(tablePath, ".tmp"));
943
939
  }
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);
953
- }
954
- return Array.isArray(_id) && _id.length === 1 ? _id[0] : _id;
955
940
  }
956
941
  }
957
942
  }
@@ -967,10 +952,10 @@ export default class Inibase {
967
952
  }
968
953
  async sum(tableName, columns, where) {
969
954
  let RETURN = {};
970
- const schema = await this.getTableSchema(tableName);
955
+ const tablePath = join(this.folder, this.database, tableName), schema = await this.getTableSchema(tableName);
971
956
  if (!schema)
972
957
  throw this.throwError("NO_SCHEMA", tableName);
973
- if (!(await File.isExists(join(this.folder, this.database, tableName, "id.inib"))))
958
+ if (!(await File.isExists(join(tablePath, "id.inib"))))
974
959
  throw this.throwError("NO_ITEMS", tableName);
975
960
  if (!Array.isArray(columns))
976
961
  columns = [columns];
@@ -991,10 +976,10 @@ export default class Inibase {
991
976
  }
992
977
  async max(tableName, columns, where) {
993
978
  let RETURN = {};
994
- const schema = await this.getTableSchema(tableName);
979
+ const tablePath = join(this.folder, this.database, tableName), schema = await this.getTableSchema(tableName);
995
980
  if (!schema)
996
981
  throw this.throwError("NO_SCHEMA", tableName);
997
- if (!(await File.isExists(join(this.folder, this.database, tableName, "id.inib"))))
982
+ if (!(await File.isExists(join(tablePath, "id.inib"))))
998
983
  throw this.throwError("NO_ITEMS", tableName);
999
984
  if (!Array.isArray(columns))
1000
985
  columns = [columns];
@@ -1015,10 +1000,10 @@ export default class Inibase {
1015
1000
  }
1016
1001
  async min(tableName, columns, where) {
1017
1002
  let RETURN = {};
1018
- const schema = await this.getTableSchema(tableName);
1003
+ const tablePath = join(this.folder, this.database, tableName), schema = await this.getTableSchema(tableName);
1019
1004
  if (!schema)
1020
1005
  throw this.throwError("NO_SCHEMA", tableName);
1021
- if (!(await File.isExists(join(this.folder, this.database, tableName, "id.inib"))))
1006
+ if (!(await File.isExists(join(tablePath, "id.inib"))))
1022
1007
  throw this.throwError("NO_ITEMS", tableName);
1023
1008
  if (!Array.isArray(columns))
1024
1009
  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.33",
4
4
  "author": {
5
5
  "name": "Karim Amahtil",
6
6
  "email": "karim.amahtil@gmail.com"