inibase 1.0.0-rc.30 → 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
@@ -30,12 +53,16 @@ const _pipeline = async (rl, writeStream, transform) => {
30
53
  * @returns A readline.Interface instance configured with the provided file stream.
31
54
  */
32
55
  const readLineInternface = (fileHandle) => {
33
- return createInterface({
34
- input: Config.isCompressionEnabled
35
- ? fileHandle.createReadStream().pipe(createGunzip())
36
- : fileHandle.createReadStream(),
37
- crlfDelay: Infinity,
38
- });
56
+ const [major, minor, patch] = process.versions.node.split(".").map(Number);
57
+ return major > 18 ||
58
+ (major === 18 && minor >= 11 && !Config.isCompressionEnabled)
59
+ ? fileHandle.readLines()
60
+ : createInterface({
61
+ input: Config.isCompressionEnabled
62
+ ? fileHandle.createReadStream().pipe(createGunzip())
63
+ : fileHandle.createReadStream(),
64
+ crlfDelay: Infinity,
65
+ });
39
66
  };
40
67
  /**
41
68
  * Checks if a file or directory exists at the specified path.
@@ -190,7 +217,6 @@ const decodeHelper = (value, fieldType, fieldChildrenType, secretKey) => {
190
217
  if (Array.isArray(value) && fieldType !== "array")
191
218
  return value.map((v) => decodeHelper(v, fieldType, fieldChildrenType, secretKey));
192
219
  switch (fieldType) {
193
- case "table":
194
220
  case "number":
195
221
  return isNumber(value) ? Number(value) : null;
196
222
  case "boolean":
@@ -204,6 +230,7 @@ const decodeHelper = (value, fieldType, fieldChildrenType, secretKey) => {
204
230
  ? detectFieldType(v, fieldChildrenType)
205
231
  : fieldChildrenType, undefined, secretKey))
206
232
  : value;
233
+ case "table":
207
234
  case "id":
208
235
  return isNumber(value) && secretKey
209
236
  ? encodeID(value, secretKey)
@@ -227,8 +254,8 @@ export const decode = (input, fieldType, fieldChildrenType, secretKey) => {
227
254
  return null;
228
255
  if (input === null || input === "")
229
256
  return null;
257
+ // Detect the fieldType based on the input and the provided array of possible types.
230
258
  if (Array.isArray(fieldType))
231
- // Detect the fieldType based on the input and the provided array of possible types.
232
259
  fieldType = detectFieldType(String(input), fieldType);
233
260
  // Decode the input using the decodeHelper function.
234
261
  return decodeHelper(typeof input === "string"
@@ -761,4 +788,6 @@ export default class File {
761
788
  static count = count;
762
789
  static write = write;
763
790
  static read = read;
791
+ static lock = lock;
792
+ static unlock = unlock;
764
793
  }
package/dist/index.d.ts CHANGED
@@ -86,7 +86,7 @@ export default class Inibase {
86
86
  put(tableName: string, data: Data, where: number | string | (number | string)[] | Criteria | undefined, options: Options | undefined, returnPostedData: true): Promise<Data | null>;
87
87
  put(tableName: string, data: Data[], where: number | string | (number | string)[] | Criteria | undefined, options: Options | undefined, returnPostedData: true): Promise<Data[] | null>;
88
88
  delete(tableName: string, where?: number | string, _id?: string | string[]): Promise<string | null>;
89
- delete(tableName: string, where?: (number | string)[], _id?: string | string[]): Promise<string[] | null>;
89
+ delete(tableName: string, where?: (number | string)[] | Criteria, _id?: string | string[]): Promise<string[] | null>;
90
90
  sum(tableName: string, columns: string, where?: number | string | (number | string)[] | Criteria): Promise<number>;
91
91
  sum(tableName: string, columns: string[], where?: number | string | (number | string)[] | Criteria): Promise<Record<string, number>>;
92
92
  max(tableName: string, columns: string, where?: number | string | (number | string)[] | Criteria): Promise<number>;
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,10 +665,12 @@ 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;
672
+ if (onlyLinesNumbers)
673
+ return cachedItems.map(Number);
676
674
  return this.get(tableName, cachedItems
677
675
  .slice((options.page - 1) * options.perPage, options.page * options.perPage)
678
676
  .map(Number), options, undefined, undefined, schema);
@@ -712,37 +710,26 @@ export default class Inibase {
712
710
  page: 1,
713
711
  perPage: 15,
714
712
  };
713
+ const tablePath = join(this.folder, this.database, tableName);
714
+ await File.lock(join(tablePath, ".tmp"));
715
715
  if (!returnPostedData)
716
716
  returnPostedData = false;
717
717
  const schema = await this.getTableSchema(tableName);
718
718
  let RETURN;
719
719
  if (!schema)
720
720
  throw this.throwError("NO_SCHEMA", tableName);
721
- const idFilePath = join(this.folder, this.database, tableName, "id.inib"), cashFolderPath = join(this.folder, this.database, tableName, ".tmp");
722
- let testFileHandle;
723
- try {
724
- testFileHandle = await open(join(cashFolderPath, "id.inib"), "wx");
725
- }
726
- catch ({ message }) {
727
- if (message.split(":")[0] === "EEXIST")
728
- return await new Promise((resolve, reject) => setTimeout(() => resolve(this.post(tableName, data, options, returnPostedData)), 13));
729
- }
730
- finally {
731
- await testFileHandle?.close();
732
- }
733
- let lastId = 0, totalItems = 0, lastIdObj;
734
- if (await File.isExists(idFilePath)) {
735
- if (await File.isExists(join(cashFolderPath, "lastId.inib"))) {
736
- lastId = Number(await File.read(join(cashFolderPath, "lastId.inib"), true));
737
- if (await File.isExists(join(cashFolderPath, "totalItems.inib")))
738
- totalItems = Number(await File.read(join(cashFolderPath, "totalItems.inib"), true));
739
- else
740
- totalItems = await File.count(join(this.folder, this.database, tableName, "id.inib"));
741
- }
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);
742
728
  else {
743
- [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);
744
731
  if (lastIdObj)
745
- lastId = Number(Object.entries(lastIdObj)[0][1]) ?? 0;
732
+ lastId = Number(Object.keys(lastIdObj)[0] ?? 0);
746
733
  }
747
734
  }
748
735
  if (Utils.isArrayOfObjects(data))
@@ -759,29 +746,39 @@ export default class Inibase {
759
746
  }))(data);
760
747
  if (!RETURN)
761
748
  throw this.throwError("NO_DATA");
762
- RETURN = this.formatData(RETURN, schema);
763
- const pathesContents = this.joinPathesContents(join(this.folder, this.database, tableName), Array.isArray(RETURN) ? RETURN.toReversed() : RETURN);
764
- const renameList = await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => File.append(path, content)));
765
- await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
766
- await File.write(join(cashFolderPath, "lastId.inib"), lastId.toString(), true);
767
- await File.write(join(cashFolderPath, "totalItems.inib"), String(totalItems + (Array.isArray(RETURN) ? RETURN.length : 1)), true);
768
- if (returnPostedData)
769
- return this.get(tableName, Utils.isArrayOfObjects(RETURN)
770
- ? RETURN.map((data) => Number(data.id))
771
- : RETURN.id, options, !Utils.isArrayOfObjects(data), // return only one item if data is not array of objects
772
- undefined, schema);
749
+ try {
750
+ RETURN = this.formatData(RETURN, schema);
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))));
753
+ await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
754
+ renameList = [];
755
+ if (Config.isCacheEnabled)
756
+ await File.write(join(tablePath, ".tmp", "pagination.inib"), `${lastId},${totalItems + (Array.isArray(RETURN) ? RETURN.length : 1)}`, true);
757
+ if (returnPostedData)
758
+ return this.get(tableName, Utils.isArrayOfObjects(RETURN)
759
+ ? RETURN.map((data) => Number(data.id))
760
+ : RETURN.id, options, !Utils.isArrayOfObjects(data), // return only one item if data is not array of objects
761
+ undefined, schema);
762
+ }
763
+ finally {
764
+ await File.unlock(join(tablePath, ".tmp"));
765
+ if (renameList.length)
766
+ await Promise.all(renameList.map(async ([tempPath, _]) => (await File.isExists(tempPath)) ? unlink(tempPath) : undefined));
767
+ }
773
768
  }
774
769
  async put(tableName, data, where, options = {
775
770
  page: 1,
776
771
  perPage: 15,
777
772
  }, returnPostedData) {
773
+ let renameList = [];
774
+ const tablePath = join(this.folder, this.database, tableName);
778
775
  const schema = await this.getTableSchema(tableName);
779
776
  if (!schema)
780
777
  throw this.throwError("NO_SCHEMA", tableName);
781
- const idFilePath = join(this.folder, this.database, tableName, "id.inib");
782
- if (!(await File.isExists(idFilePath)))
778
+ if (!(await File.isExists(join(tablePath, "id.inib"))))
783
779
  throw this.throwError("NO_ITEMS", tableName);
784
780
  data = this.formatData(data, schema, true);
781
+ await File.lock(join(tablePath, ".tmp"));
785
782
  if (!where) {
786
783
  if (Utils.isArrayOfObjects(data)) {
787
784
  if (!data.every((item) => item.hasOwnProperty("id") && Utils.isValidID(item.id)))
@@ -796,7 +793,7 @@ export default class Inibase {
796
793
  return this.put(tableName, data, UtilsServer.decodeID(data.id, this.salt));
797
794
  }
798
795
  else {
799
- const pathesContents = this.joinPathesContents(join(this.folder, this.database, tableName), Utils.isArrayOfObjects(data)
796
+ const pathesContents = this.joinPathesContents(join(tablePath), Utils.isArrayOfObjects(data)
800
797
  ? data.map((item) => ({
801
798
  ...(({ id, ...restOfData }) => restOfData)(item),
802
799
  updatedAt: Date.now(),
@@ -805,26 +802,21 @@ export default class Inibase {
805
802
  ...(({ id, ...restOfData }) => restOfData)(data),
806
803
  updatedAt: Date.now(),
807
804
  });
808
- let testFileHandle;
809
805
  try {
810
- testFileHandle = await open(Object.keys(pathesContents)[0].replace(/([^/]+)\/?$/, `.tmp/$1`), "wx");
811
- }
812
- catch ({ message }) {
813
- if (message.split(":")[0] === "EEXIST")
814
- 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);
815
814
  }
816
815
  finally {
817
- await testFileHandle?.close();
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"));
818
819
  }
819
- const renameList = await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => File.replace(path, content)));
820
- await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
821
- if (Config.isCacheEnabled) {
822
- const cacheFiles = (await readdir(join(this.folder, this.database, tableName, ".tmp")))?.filter((fileName) => !["lastId.inib", "totalItems.inib"].includes(fileName));
823
- if (cacheFiles.length)
824
- await Promise.all(cacheFiles.map(async (file) => unlink(join(this.folder, this.database, tableName, ".tmp", file))));
825
- }
826
- if (returnPostedData)
827
- return this.get(tableName, where, options, undefined, undefined, schema);
828
820
  }
829
821
  }
830
822
  else if ((Array.isArray(where) &&
@@ -839,7 +831,7 @@ export default class Inibase {
839
831
  else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
840
832
  Utils.isNumber(where)) {
841
833
  // "where" in this case, is the line(s) number(s) and not id(s)
842
- 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)
843
835
  ? data.map((item) => ({
844
836
  ...item,
845
837
  updatedAt: Date.now(),
@@ -851,26 +843,22 @@ export default class Inibase {
851
843
  [lineNum]: Array.isArray(content) ? content[index] : content,
852
844
  }), {}),
853
845
  ]));
854
- let testFileHandle;
855
846
  try {
856
- testFileHandle = await open(Object.keys(pathesContents)[0].replace(/([^/]+)\/?$/, `.tmp/$1`), "wx");
857
- }
858
- catch ({ message }) {
859
- if (message.split(":")[0] === "EEXIST")
860
- 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);
861
856
  }
862
857
  finally {
863
- await testFileHandle?.close();
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"));
864
861
  }
865
- const renameList = await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => File.replace(path, content)));
866
- await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
867
- if (Config.isCacheEnabled) {
868
- const cacheFiles = (await readdir(join(this.folder, this.database, tableName, ".tmp")))?.filter((fileName) => !["lastId.inib", "totalItems.inib"].includes(fileName));
869
- if (cacheFiles.length)
870
- await Promise.all(cacheFiles.map(async (file) => unlink(join(this.folder, this.database, tableName, ".tmp", file))));
871
- }
872
- if (returnPostedData)
873
- return this.get(tableName, where, options, !Array.isArray(where), undefined, schema);
874
862
  }
875
863
  }
876
864
  else if (Utils.isObject(where)) {
@@ -883,21 +871,22 @@ export default class Inibase {
883
871
  throw this.throwError("INVALID_PARAMETERS", tableName);
884
872
  }
885
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"));
886
877
  const schema = await this.getTableSchema(tableName);
887
878
  if (!schema)
888
879
  throw this.throwError("NO_SCHEMA", tableName);
889
- const idFilePath = join(this.folder, this.database, tableName, "id.inib");
890
- if (!(await File.isExists(idFilePath)))
880
+ if (!(await File.isExists(join(tablePath, "id.inib"))))
891
881
  throw this.throwError("NO_ITEMS", tableName);
892
882
  if (!where) {
893
- const files = (await readdir(join(this.folder, this.database, tableName)))?.filter((fileName) => fileName.endsWith(".inib"));
894
- if (files.length)
895
- await Promise.all(files.map(async (file) => unlink(join(this.folder, this.database, tableName, file))));
896
- if (Config.isCacheEnabled) {
897
- const cacheFiles = (await readdir(join(this.folder, this.database, tableName, ".tmp")))?.filter((fileName) => !["lastId.inib", "totalItems.inib"].includes(fileName));
898
- if (cacheFiles.length)
899
- await Promise.all(cacheFiles.map(async (file) => unlink(join(this.folder, this.database, tableName, ".tmp", file))));
900
- }
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))));
901
890
  return "*";
902
891
  }
903
892
  else if ((Array.isArray(where) &&
@@ -914,34 +903,33 @@ export default class Inibase {
914
903
  else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
915
904
  Utils.isNumber(where)) {
916
905
  // "where" in this case, is the line(s) number(s) and not id(s)
917
- 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"));
918
907
  if (files.length) {
919
908
  if (!_id)
920
- _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));
921
910
  if (!_id.length)
922
911
  throw this.throwError("NO_ITEMS", tableName);
923
- let testFileHandle;
924
912
  try {
925
- testFileHandle = await open(join(this.folder, this.database, tableName, ".tmp", "id.inib"), "wx");
926
- }
927
- catch ({ message }) {
928
- if (message.split(":")[0] === "EEXIST")
929
- 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;
930
927
  }
931
928
  finally {
932
- await testFileHandle?.close();
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"));
933
932
  }
934
- const renameList = await Promise.all(files.map(async (file) => File.remove(join(this.folder, this.database, tableName, file), where)));
935
- await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
936
- if (Config.isCacheEnabled) {
937
- const cacheFiles = (await readdir(join(this.folder, this.database, tableName, ".tmp")))?.filter((fileName) => !["lastId.inib", "totalItems.inib"].includes(fileName));
938
- if (cacheFiles.length)
939
- await Promise.all(cacheFiles.map(async (file) => unlink(join(this.folder, this.database, tableName, ".tmp", file))));
940
- 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")))
941
- ? Number(await File.read(join(this.folder, this.database, tableName, ".tmp", "totalItems.inib"), true))
942
- : await File.count(join(this.folder, this.database, tableName, "id.inib"))) - (Array.isArray(where) ? where.length : 1)), true);
943
- }
944
- return Array.isArray(_id) && _id.length === 1 ? _id[0] : _id;
945
933
  }
946
934
  }
947
935
  }
@@ -957,10 +945,10 @@ export default class Inibase {
957
945
  }
958
946
  async sum(tableName, columns, where) {
959
947
  let RETURN = {};
960
- const schema = await this.getTableSchema(tableName);
948
+ const tablePath = join(this.folder, this.database, tableName), schema = await this.getTableSchema(tableName);
961
949
  if (!schema)
962
950
  throw this.throwError("NO_SCHEMA", tableName);
963
- if (!(await File.isExists(join(this.folder, this.database, tableName, "id.inib"))))
951
+ if (!(await File.isExists(join(tablePath, "id.inib"))))
964
952
  throw this.throwError("NO_ITEMS", tableName);
965
953
  if (!Array.isArray(columns))
966
954
  columns = [columns];
@@ -981,10 +969,10 @@ export default class Inibase {
981
969
  }
982
970
  async max(tableName, columns, where) {
983
971
  let RETURN = {};
984
- const schema = await this.getTableSchema(tableName);
972
+ const tablePath = join(this.folder, this.database, tableName), schema = await this.getTableSchema(tableName);
985
973
  if (!schema)
986
974
  throw this.throwError("NO_SCHEMA", tableName);
987
- if (!(await File.isExists(join(this.folder, this.database, tableName, "id.inib"))))
975
+ if (!(await File.isExists(join(tablePath, "id.inib"))))
988
976
  throw this.throwError("NO_ITEMS", tableName);
989
977
  if (!Array.isArray(columns))
990
978
  columns = [columns];
@@ -1005,10 +993,10 @@ export default class Inibase {
1005
993
  }
1006
994
  async min(tableName, columns, where) {
1007
995
  let RETURN = {};
1008
- const schema = await this.getTableSchema(tableName);
996
+ const tablePath = join(this.folder, this.database, tableName), schema = await this.getTableSchema(tableName);
1009
997
  if (!schema)
1010
998
  throw this.throwError("NO_SCHEMA", tableName);
1011
- if (!(await File.isExists(join(this.folder, this.database, tableName, "id.inib"))))
999
+ if (!(await File.isExists(join(tablePath, "id.inib"))))
1012
1000
  throw this.throwError("NO_ITEMS", tableName);
1013
1001
  if (!Array.isArray(columns))
1014
1002
  columns = [columns];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inibase",
3
- "version": "1.0.0-rc.30",
3
+ "version": "1.0.0-rc.32",
4
4
  "author": {
5
5
  "name": "Karim Amahtil",
6
6
  "email": "karim.amahtil@gmail.com"