inibase 1.0.0-rc.27 → 1.0.0-rc.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![npmjs](https://img.shields.io/npm/dm/inibase.svg?style=flat)](https://www.npmjs.org/package/inibase) [![License](https://img.shields.io/github/license/inicontent/inibase.svg?style=flat&colorA=18181B&colorB=28CF8D)](./LICENSE) [![Activity](https://img.shields.io/github/commit-activity/m/inicontent/inibase)](https://github.com/inicontent/inibase/pulse) [![GitHub stars](https://img.shields.io/github/stars/inicontent/inibase?style=social)](https://github.com/inicontent/inibase)
6
6
 
7
- > A file-based & memory-efficient, serverless relational database management system :fire:
7
+ > A file-based & memory-efficient, serverless, ACID compliant, relational database management system :fire:
8
8
 
9
9
  ## Features
10
10
 
@@ -74,6 +74,8 @@ To simplify the idea, each database has tables, each table has columns, each col
74
74
  | PUT | 33 ms (10.29 mb) | 312 ms (11.06 mb) | 3539 ms (14.87 mb) |
75
75
  | DELETE | 134 ms (13.50 mb) | 1224 ms (16.57 mb) | 7339 ms (11.46 mb) |
76
76
 
77
+ Ps: Testing by default with `user` table, with username, email, password fields _so results include password encryption process_
78
+
77
79
 
78
80
  ## Roadmap
79
81
 
package/dist/file.d.ts CHANGED
@@ -43,7 +43,7 @@ export declare const decode: (input: string | null | number, fieldType?: FieldTy
43
43
  * 1. Record of line numbers and their decoded content or null if no lines are read.
44
44
  * 2. Total count of lines processed.
45
45
  */
46
- export declare function get(filePath: string, lineNumbers?: number | number[], fieldType?: FieldType | FieldType[], fieldChildrenType?: FieldType | FieldType[], secretKey?: string | Buffer): Promise<Record<number, string | number | boolean | null | (string | number | boolean | (string | number | boolean)[] | null)[]> | null>;
46
+ export declare function get(filePath: string, lineNumbers?: number | number[], fieldType?: FieldType | FieldType[], fieldChildrenType?: FieldType | FieldType[], secretKey?: string | Buffer, readWholeFile?: false): Promise<Record<number, string | number | boolean | null | (string | number | boolean | (string | number | boolean)[] | null)[]> | null>;
47
47
  export declare function get(filePath: string, lineNumbers: undefined | number | number[], fieldType: undefined | FieldType | FieldType[], fieldChildrenType: undefined | FieldType | FieldType[], secretKey: undefined | string | Buffer, readWholeFile: true): Promise<[
48
48
  Record<number, string | number | boolean | null | (string | number | boolean | (string | number | boolean)[] | null)[]> | null,
49
49
  number
package/dist/file.js CHANGED
@@ -187,46 +187,29 @@ const reverseJoinMultidimensionalArray = (joinedString) => {
187
187
  * @returns Decoded value, transformed according to the specified field type(s).
188
188
  */
189
189
  const decodeHelper = (value, fieldType, fieldChildrenType, secretKey) => {
190
- // Use a stack-based approach for efficient processing without recursion.
191
- const stack = [{ value }];
192
- while (stack.length > 0) {
193
- // Explicitly check if stack.pop() is not undefined.
194
- const stackItem = stack.pop();
195
- if (!stackItem) {
196
- // Skip the rest of the loop if the stack item is undefined.
197
- continue;
198
- }
199
- const { value } = stackItem;
200
- if (Array.isArray(value) && fieldType !== "array") {
201
- // If the value is an array and the fieldType is not 'array', process each element.
202
- stack.push(...value.map((v) => ({ value: v })));
203
- }
204
- else {
205
- switch (fieldType) {
206
- // Handle different field types with appropriate decoding logic.
207
- case "table":
208
- case "number":
209
- return isNumber(value) ? Number(value) : null;
210
- case "boolean":
211
- return typeof value === "string" ? value === "true" : Boolean(value);
212
- case "array":
213
- if (!Array.isArray(value))
214
- return [value];
215
- if (fieldChildrenType)
216
- // Decode each element in the array based on the specified fieldChildrenType.
217
- return fieldChildrenType
218
- ? value.map((v) => decode(v, Array.isArray(fieldChildrenType)
219
- ? detectFieldType(v, fieldChildrenType)
220
- : fieldChildrenType, undefined, secretKey))
221
- : value;
222
- case "id":
223
- return isNumber(value) && secretKey
224
- ? encodeID(value, secretKey)
225
- : value;
226
- default:
227
- return value;
228
- }
229
- }
190
+ if (Array.isArray(value) && fieldType !== "array")
191
+ return value.map((v) => decodeHelper(v, fieldType, fieldChildrenType, secretKey));
192
+ switch (fieldType) {
193
+ case "table":
194
+ case "number":
195
+ return isNumber(value) ? Number(value) : null;
196
+ case "boolean":
197
+ return typeof value === "string" ? value === "true" : Boolean(value);
198
+ case "array":
199
+ if (!Array.isArray(value))
200
+ return [value];
201
+ if (fieldChildrenType)
202
+ return fieldChildrenType
203
+ ? value.map((v) => decode(v, Array.isArray(fieldChildrenType)
204
+ ? detectFieldType(v, fieldChildrenType)
205
+ : fieldChildrenType, undefined, secretKey))
206
+ : value;
207
+ case "id":
208
+ return isNumber(value) && secretKey
209
+ ? encodeID(value, secretKey)
210
+ : value;
211
+ default:
212
+ return value;
230
213
  }
231
214
  };
232
215
  /**
@@ -254,7 +237,7 @@ export const decode = (input, fieldType, fieldChildrenType, secretKey) => {
254
237
  : unSecureString(input)
255
238
  : input, fieldType, fieldChildrenType, secretKey);
256
239
  };
257
- export async function get(filePath, lineNumbers, fieldType, fieldChildrenType, secretKey, readWholeFile) {
240
+ export async function get(filePath, lineNumbers, fieldType, fieldChildrenType, secretKey, readWholeFile = false) {
258
241
  let fileHandle, rl;
259
242
  try {
260
243
  fileHandle = await open(filePath, "r");
@@ -303,32 +286,49 @@ export async function get(filePath, lineNumbers, fieldType, fieldChildrenType, s
303
286
  * Note: If the file doesn't exist and replacements is an object, it creates a new file with the specified replacements.
304
287
  */
305
288
  export const replace = async (filePath, replacements) => {
306
- let fileHandle, fileTempHandle, rl;
307
- const fileTempPath = filePath.replace(/([^/]+)\/?$/, `.tmp/${Date.now()}-$1`);
308
- try {
309
- let linesCount = 0;
310
- fileHandle = await open(filePath, "r");
311
- fileTempHandle = await open(fileTempPath, "w");
312
- rl = readLineInternface(fileHandle);
313
- await _pipeline(rl, fileTempHandle.createWriteStream(), new Transform({
314
- transform(line, encoding, callback) {
315
- linesCount++;
316
- const replacement = isObject(replacements)
317
- ? replacements.hasOwnProperty(linesCount)
318
- ? replacements[linesCount]
319
- : line
320
- : replacements;
321
- callback(null, replacement + "\n");
322
- },
323
- }));
324
- return [fileTempPath, filePath];
289
+ const fileTempPath = filePath.replace(/([^/]+)\/?$/, `.tmp/$1`);
290
+ if (await isExists(filePath)) {
291
+ let fileHandle, fileTempHandle, rl;
292
+ try {
293
+ let linesCount = 0;
294
+ fileHandle = await open(filePath, "r");
295
+ fileTempHandle = await open(fileTempPath, "w");
296
+ rl = readLineInternface(fileHandle);
297
+ await _pipeline(rl, fileTempHandle.createWriteStream(), new Transform({
298
+ transform(line, encoding, callback) {
299
+ linesCount++;
300
+ const replacement = isObject(replacements)
301
+ ? replacements.hasOwnProperty(linesCount)
302
+ ? replacements[linesCount]
303
+ : line
304
+ : replacements;
305
+ return callback(null, replacement + "\n");
306
+ },
307
+ }));
308
+ return [fileTempPath, filePath];
309
+ }
310
+ finally {
311
+ // Ensure that file handles are closed, even if an error occurred
312
+ rl?.close();
313
+ await fileHandle?.close();
314
+ await fileTempHandle?.close();
315
+ }
325
316
  }
326
- finally {
327
- // Ensure that file handles are closed, even if an error occurred
328
- rl?.close();
329
- await fileHandle?.close();
330
- await fileTempHandle?.close();
317
+ else if (isObject(replacements)) {
318
+ let replacementsKeys = Object.keys(replacements)
319
+ .map(Number)
320
+ .toSorted((a, b) => a - b);
321
+ await write(fileTempPath, "\n".repeat(replacementsKeys[0] - 1) +
322
+ replacementsKeys
323
+ .map((lineNumber, index) => index === 0 || lineNumber - replacementsKeys[index - 1] - 1 === 0
324
+ ? replacements[lineNumber]
325
+ : "\n".repeat(lineNumber - replacementsKeys[index - 1] - 1) +
326
+ replacements[lineNumber])
327
+ .join("\n") +
328
+ "\n");
329
+ return [fileTempPath, filePath];
331
330
  }
331
+ return [];
332
332
  };
333
333
  /**
334
334
  * Asynchronously appends data to the beginning of a file.
@@ -339,7 +339,7 @@ export const replace = async (filePath, replacements) => {
339
339
  *
340
340
  */
341
341
  export const append = async (filePath, data) => {
342
- const fileTempPath = filePath.replace(/([^/]+)\/?$/, `.tmp/${Date.now()}-$1`);
342
+ const fileTempPath = filePath.replace(/([^/]+)\/?$/, `.tmp/$1`);
343
343
  if (await isExists(filePath)) {
344
344
  let fileHandle, fileTempHandle, rl;
345
345
  try {
@@ -351,10 +351,10 @@ export const append = async (filePath, data) => {
351
351
  transform(line, encoding, callback) {
352
352
  if (!isAppended) {
353
353
  isAppended = true;
354
- callback(null, `${Array.isArray(data) ? data.join("\n") : data}\n${line}\n`);
354
+ return callback(null, `${Array.isArray(data) ? data.join("\n") : data}\n${line}\n`);
355
355
  }
356
356
  else
357
- callback(null, `${line}\n`);
357
+ return callback(null, `${line}\n`);
358
358
  },
359
359
  }));
360
360
  }
@@ -366,7 +366,7 @@ export const append = async (filePath, data) => {
366
366
  }
367
367
  }
368
368
  else
369
- await write(fileTempPath, `${Array.isArray(data) ? data.join("\n") : data}\n`);
369
+ await write(fileTempPath, `${Array.isArray(data) ? data.join("\n") : data}\n`, undefined);
370
370
  return [fileTempPath, filePath];
371
371
  };
372
372
  /**
@@ -379,16 +379,24 @@ export const append = async (filePath, data) => {
379
379
  * Note: Creates a temporary file during the process and replaces the original file with it after removing lines.
380
380
  */
381
381
  export const remove = async (filePath, linesToDelete) => {
382
- let linesCount = 0;
383
- const fileHandle = await open(filePath, "r"), fileTempPath = filePath.replace(/([^/]+)\/?$/, `.tmp/${Date.now()}-$1`), fileTempHandle = await open(fileTempPath, "w"), linesToDeleteArray = new Set(Array.isArray(linesToDelete)
382
+ let linesCount = 0, deletedCount = 0;
383
+ const fileHandle = await open(filePath, "r"), fileTempPath = filePath.replace(/([^/]+)\/?$/, `.tmp/$1`), fileTempHandle = await open(fileTempPath, "w"), linesToDeleteArray = new Set(Array.isArray(linesToDelete)
384
384
  ? linesToDelete.map(Number)
385
385
  : [Number(linesToDelete)]), rl = readLineInternface(fileHandle);
386
386
  await _pipeline(rl, fileTempHandle.createWriteStream(), new Transform({
387
387
  transform(line, encoding, callback) {
388
388
  linesCount++;
389
- if (!linesToDeleteArray.has(linesCount))
390
- callback(null, `${line}\n`);
391
- callback();
389
+ if (linesToDeleteArray.has(linesCount)) {
390
+ deletedCount++;
391
+ return callback();
392
+ }
393
+ else
394
+ return callback(null, `${line}\n`);
395
+ },
396
+ final(callback) {
397
+ if (deletedCount === linesCount)
398
+ this.push("\n");
399
+ return callback();
392
400
  },
393
401
  }));
394
402
  await fileTempHandle.close();
package/dist/index.d.ts CHANGED
@@ -13,23 +13,25 @@ type FieldDefault = {
13
13
  };
14
14
  type FieldStringType = {
15
15
  type: Exclude<FieldType, "array" | "object">;
16
+ children?: never;
16
17
  };
17
18
  type FieldStringArrayType = {
18
- type: Exclude<FieldType, "array" | "object">[];
19
+ type: Array<Exclude<FieldType, "array" | "object">>;
20
+ children?: never;
19
21
  };
20
22
  type FieldArrayType = {
21
23
  type: "array";
22
24
  children: Exclude<FieldType, "array"> | Exclude<FieldType, "array">[] | Schema;
23
25
  };
24
26
  type FieldArrayArrayType = {
25
- type: ["array", ...Exclude<FieldType, "array" | "object">[]];
27
+ type: Array<"array" | Exclude<FieldType, "array" | "object">>;
26
28
  children: Exclude<FieldType, "array" | "object"> | Exclude<FieldType, "array" | "object">[];
27
29
  };
28
30
  type FieldObjectType = {
29
31
  type: "object";
30
32
  children: Schema;
31
33
  };
32
- export type Field = FieldDefault & (FieldStringType | FieldStringArrayType | FieldObjectType | FieldArrayType | FieldArrayArrayType);
34
+ export type Field = FieldDefault & (FieldStringType | (FieldStringArrayType & FieldArrayArrayType) | FieldObjectType | FieldArrayType);
33
35
  export type Schema = Field[];
34
36
  export interface Options {
35
37
  page?: number;
@@ -66,19 +68,18 @@ export default class Inibase {
66
68
  private throwError;
67
69
  setTableSchema(tableName: string, schema: Schema): Promise<void>;
68
70
  getTableSchema(tableName: string): Promise<Schema | undefined>;
69
- getField(keyPath: string, schema: Schema): Field | null;
71
+ getField(keyPath: string, schema: Schema): (FieldDefault & FieldStringType) | (FieldDefault & FieldObjectType) | (FieldDefault & FieldArrayType) | null;
70
72
  private validateData;
71
73
  private formatField;
72
74
  private formatData;
73
75
  private getDefaultValue;
74
76
  private joinPathesContents;
75
77
  private getItemsFromSchema;
76
- private FormatObjectCriteriaValue;
77
78
  private applyCriteria;
78
79
  private _filterSchemaByColumns;
79
80
  get(tableName: string, where?: string | number | (string | number)[] | Criteria | undefined, options?: Options | undefined, onlyOne?: true, onlyLinesNumbers?: undefined, tableSchema?: Schema): Promise<Data | null>;
80
81
  get(tableName: string, where?: string | number | (string | number)[] | Criteria | undefined, options?: Options | undefined, onlyOne?: boolean | undefined, onlyLinesNumbers?: true, tableSchema?: Schema): Promise<number[] | null>;
81
- post(tableName: string, data: Data | Data[], options?: Options, returnPostedData?: false): Promise<void | null>;
82
+ post(tableName: string, data: Data | Data[], options?: Options, returnPostedData?: boolean): Promise<void | null>;
82
83
  post(tableName: string, data: Data, options: Options | undefined, returnPostedData: true): Promise<Data | null>;
83
84
  post(tableName: string, data: Data[], options: Options | undefined, returnPostedData: true): Promise<Data[] | null>;
84
85
  put(tableName: string, data: Data | Data[], where?: number | string | (number | string)[] | Criteria, options?: Options, returnPostedData?: false): Promise<void | null>;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { unlink, rename, mkdir, readdir } from "node:fs/promises";
1
+ import { unlink, rename, mkdir, readdir, open } 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";
@@ -87,9 +87,10 @@ export default class Inibase {
87
87
  return RETURN;
88
88
  }, replaceOldPathes = Utils.findChangedProperties(schemaToIdsPath((await this.getTableSchema(tableName)) ?? []), schemaToIdsPath(schema));
89
89
  if (replaceOldPathes)
90
- for await (const [oldPath, newPath] of Object.entries(replaceOldPathes))
90
+ await Promise.all(Object.entries(replaceOldPathes).map(async ([oldPath, newPath]) => {
91
91
  if (await File.isExists(join(TablePath, oldPath)))
92
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
96
  }
@@ -302,8 +303,10 @@ export default class Inibase {
302
303
  async getItemsFromSchema(tableName, schema, linesNumber, options, prefix) {
303
304
  const path = join(this.folder, this.database, tableName);
304
305
  let RETURN = {};
305
- for await (const field of schema) {
306
- if (field.type === "array" && field.children) {
306
+ await Promise.all(schema.map(async (field) => {
307
+ if ((field.type === "array" ||
308
+ (Array.isArray(field.type) && field.type.includes("array"))) &&
309
+ field.children) {
307
310
  if (Utils.isArrayOfObjects(field.children)) {
308
311
  if (field.children.filter((children) => children.type === "array" &&
309
312
  Utils.isArrayOfObjects(children.children)).length) {
@@ -320,14 +323,17 @@ export default class Inibase {
320
323
  if (Utils.isObject(item[child_field.key])) {
321
324
  Object.entries(item[child_field.key]).forEach(([key, value]) => {
322
325
  if (!Utils.isArrayOfArrays(value))
323
- value = value.map((_value) => child_field.type === "array" ? [[_value]] : [_value]);
326
+ value = value.map((_value) => child_field.type === "array"
327
+ ? [[_value]]
328
+ : [_value]);
324
329
  for (let _i = 0; _i < value.length; _i++) {
325
330
  if (Utils.isArrayOfNulls(value[_i]))
326
331
  continue;
327
332
  if (!RETURN[index][field.key][_i])
328
333
  RETURN[index][field.key][_i] = {};
329
334
  if (!RETURN[index][field.key][_i][child_field.key])
330
- RETURN[index][field.key][_i][child_field.key] = [];
335
+ RETURN[index][field.key][_i][child_field.key] =
336
+ [];
331
337
  value[_i].forEach((_element, _index) => {
332
338
  if (!RETURN[index][field.key][_i][child_field.key][_index])
333
339
  RETURN[index][field.key][_i][child_field.key][_index] = {};
@@ -380,6 +386,7 @@ export default class Inibase {
380
386
  });
381
387
  }
382
388
  else if (field.children === "table" ||
389
+ (Array.isArray(field.type) && field.type.includes("table")) ||
383
390
  (Array.isArray(field.children) && field.children.includes("table"))) {
384
391
  if (options.columns)
385
392
  options.columns = options.columns
@@ -387,13 +394,13 @@ export default class Inibase {
387
394
  .map((column) => column.replace(`${field.key}.`, ""));
388
395
  const items = await File.get(join(path, (prefix ?? "") + field.key + ".inib"), linesNumber, field.type, field.children, this.salt);
389
396
  if (items)
390
- for await (const [index, item] of Object.entries(items)) {
397
+ await Promise.all(Object.entries(items).map(async ([index, item]) => {
391
398
  if (!RETURN[index])
392
399
  RETURN[index] = {};
393
400
  RETURN[index][field.key] = item
394
401
  ? await this.get(field.key, item, options)
395
402
  : this.getDefaultValue(field);
396
- }
403
+ }));
397
404
  }
398
405
  else if (await File.isExists(join(path, (prefix ?? "") + field.key + ".inib"))) {
399
406
  const items = await File.get(join(path, (prefix ?? "") + field.key + ".inib"), linesNumber, field.type, field?.children, this.salt);
@@ -452,63 +459,9 @@ export default class Inibase {
452
459
  { ...data, [field.key]: this.getDefaultValue(field) },
453
460
  ]));
454
461
  }
455
- }
462
+ }));
456
463
  return RETURN;
457
464
  }
458
- FormatObjectCriteriaValue(value, isParentArray = false) {
459
- switch (value[0]) {
460
- case ">":
461
- case "<":
462
- return value[1] === "="
463
- ? [
464
- value.slice(0, 2),
465
- value.slice(2),
466
- ]
467
- : [
468
- value.slice(0, 1),
469
- value.slice(1),
470
- ];
471
- case "[":
472
- return value[1] === "]"
473
- ? [
474
- value.slice(0, 2),
475
- value.slice(2).toString().split(","),
476
- ]
477
- : ["[]", value.slice(1)];
478
- case "!":
479
- return ["=", "*"].includes(value[1])
480
- ? [
481
- value.slice(0, 2),
482
- value.slice(2),
483
- ]
484
- : value[1] === "["
485
- ? [
486
- value.slice(0, 3),
487
- value.slice(3),
488
- ]
489
- : [
490
- (value.slice(0, 1) + "="),
491
- value.slice(1),
492
- ];
493
- case "=":
494
- return isParentArray
495
- ? [
496
- value.slice(0, 1),
497
- value.slice(1),
498
- ]
499
- : [
500
- value.slice(0, 1),
501
- (value.slice(1) + ","),
502
- ];
503
- case "*":
504
- return [
505
- value.slice(0, 1),
506
- value.slice(1),
507
- ];
508
- default:
509
- return ["=", value];
510
- }
511
- }
512
465
  async applyCriteria(tableName, schema, options, criteria, allTrue) {
513
466
  let RETURN = {}, RETURN_LineNumbers = null;
514
467
  if (!criteria)
@@ -544,7 +497,7 @@ export default class Inibase {
544
497
  Array.isArray(value?.or)) {
545
498
  const searchCriteria = (value?.or)
546
499
  .map((single_or) => typeof single_or === "string"
547
- ? this.FormatObjectCriteriaValue(single_or)
500
+ ? Utils.FormatObjectCriteriaValue(single_or)
548
501
  : ["=", single_or])
549
502
  .filter((a) => a);
550
503
  if (searchCriteria.length > 0) {
@@ -558,7 +511,7 @@ export default class Inibase {
558
511
  Array.isArray(value?.and)) {
559
512
  const searchCriteria = (value?.and)
560
513
  .map((single_and) => typeof single_and === "string"
561
- ? this.FormatObjectCriteriaValue(single_and)
514
+ ? Utils.FormatObjectCriteriaValue(single_and)
562
515
  : ["=", single_and])
563
516
  .filter((a) => a);
564
517
  if (searchCriteria.length > 0) {
@@ -572,7 +525,7 @@ export default class Inibase {
572
525
  else if (Array.isArray(value)) {
573
526
  const searchCriteria = value
574
527
  .map((single) => typeof single === "string"
575
- ? this.FormatObjectCriteriaValue(single)
528
+ ? Utils.FormatObjectCriteriaValue(single)
576
529
  : ["=", single])
577
530
  .filter((a) => a);
578
531
  if (searchCriteria.length > 0) {
@@ -582,7 +535,7 @@ export default class Inibase {
582
535
  }
583
536
  }
584
537
  else if (typeof value === "string") {
585
- const ComparisonOperatorValue = this.FormatObjectCriteriaValue(value);
538
+ const ComparisonOperatorValue = Utils.FormatObjectCriteriaValue(value);
586
539
  if (ComparisonOperatorValue) {
587
540
  searchOperator = ComparisonOperatorValue[0];
588
541
  searchComparedAtValue = ComparisonOperatorValue[1];
@@ -731,7 +684,7 @@ export default class Inibase {
731
684
  if (onlyLinesNumbers)
732
685
  return Object.keys(RETURN).map(Number);
733
686
  const alreadyExistsColumns = Object.keys(Object.values(RETURN)[0]);
734
- RETURN = Object.values(Utils.deepMerge(RETURN, await this.getItemsFromSchema(tableName, schema.filter((field) => !alreadyExistsColumns.includes(field.key)), Object.keys(RETURN).map(Number), options)));
687
+ RETURN = Object.values(Utils.deepMerge(RETURN, await this.getItemsFromSchema(tableName, schema.filter(({ key }) => !alreadyExistsColumns.includes(key)), Object.keys(RETURN).map(Number), options)));
735
688
  if (Config.isCacheEnabled)
736
689
  await File.write(cachedFilePath, Array.from(linesNumbers).join(","), true);
737
690
  }
@@ -766,6 +719,17 @@ export default class Inibase {
766
719
  if (!schema)
767
720
  throw this.throwError("NO_SCHEMA", tableName);
768
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
+ }
769
733
  let lastId = 0, totalItems = 0, lastIdObj;
770
734
  if (await File.isExists(idFilePath)) {
771
735
  if (await File.isExists(join(cashFolderPath, "lastId.inib"))) {
@@ -797,11 +761,8 @@ export default class Inibase {
797
761
  throw this.throwError("NO_DATA");
798
762
  RETURN = this.formatData(RETURN, schema);
799
763
  const pathesContents = this.joinPathesContents(join(this.folder, this.database, tableName), Array.isArray(RETURN) ? RETURN.toReversed() : RETURN);
800
- const renameList = [];
801
- for await (const [path, content] of Object.entries(pathesContents))
802
- renameList.push(await File.append(path, content));
803
- for await (const [tempPath, filePath] of renameList)
804
- await rename(tempPath, filePath);
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)));
805
766
  await File.write(join(cashFolderPath, "lastId.inib"), lastId.toString(), true);
806
767
  await File.write(join(cashFolderPath, "totalItems.inib"), String(totalItems + (Array.isArray(RETURN) ? RETURN.length : 1)), true);
807
768
  if (returnPostedData)
@@ -844,13 +805,23 @@ export default class Inibase {
844
805
  ...(({ id, ...restOfData }) => restOfData)(data),
845
806
  updatedAt: Date.now(),
846
807
  });
847
- for await (const [path, content] of Object.entries(pathesContents))
848
- await File.replace(path, content);
808
+ let testFileHandle;
809
+ 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));
815
+ }
816
+ finally {
817
+ await testFileHandle?.close();
818
+ }
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)));
849
821
  if (Config.isCacheEnabled) {
850
822
  const cacheFiles = (await readdir(join(this.folder, this.database, tableName, ".tmp")))?.filter((fileName) => !["lastId.inib", "totalItems.inib"].includes(fileName));
851
823
  if (cacheFiles.length)
852
- for await (const file of cacheFiles)
853
- await unlink(join(this.folder, this.database, tableName, ".tmp", file));
824
+ await Promise.all(cacheFiles.map(async (file) => unlink(join(this.folder, this.database, tableName, ".tmp", file))));
854
825
  }
855
826
  if (returnPostedData)
856
827
  return this.get(tableName, where, options, undefined, undefined, schema);
@@ -880,16 +851,23 @@ export default class Inibase {
880
851
  [lineNum]: Array.isArray(content) ? content[index] : content,
881
852
  }), {}),
882
853
  ]));
883
- const renameList = [];
884
- for await (const [path, content] of Object.entries(pathesContents))
885
- renameList.push(await File.replace(path, content));
886
- for await (const [tempPath, filePath] of renameList)
887
- await rename(tempPath, filePath);
854
+ let testFileHandle;
855
+ 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));
861
+ }
862
+ finally {
863
+ await testFileHandle?.close();
864
+ }
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)));
888
867
  if (Config.isCacheEnabled) {
889
868
  const cacheFiles = (await readdir(join(this.folder, this.database, tableName, ".tmp")))?.filter((fileName) => !["lastId.inib", "totalItems.inib"].includes(fileName));
890
869
  if (cacheFiles.length)
891
- for await (const file of cacheFiles)
892
- await unlink(join(this.folder, this.database, tableName, ".tmp", file));
870
+ await Promise.all(cacheFiles.map(async (file) => unlink(join(this.folder, this.database, tableName, ".tmp", file))));
893
871
  }
894
872
  if (returnPostedData)
895
873
  return this.get(tableName, where, options, !Array.isArray(where), undefined, schema);
@@ -914,13 +892,11 @@ export default class Inibase {
914
892
  if (!where) {
915
893
  const files = (await readdir(join(this.folder, this.database, tableName)))?.filter((fileName) => fileName.endsWith(".inib"));
916
894
  if (files.length)
917
- for await (const file of files)
918
- await unlink(join(this.folder, this.database, tableName, file));
895
+ await Promise.all(files.map(async (file) => unlink(join(this.folder, this.database, tableName, file))));
919
896
  if (Config.isCacheEnabled) {
920
897
  const cacheFiles = (await readdir(join(this.folder, this.database, tableName, ".tmp")))?.filter((fileName) => !["lastId.inib", "totalItems.inib"].includes(fileName));
921
898
  if (cacheFiles.length)
922
- for await (const file of cacheFiles)
923
- await unlink(join(this.folder, this.database, tableName, ".tmp", file));
899
+ await Promise.all(cacheFiles.map(async (file) => unlink(join(this.folder, this.database, tableName, ".tmp", file))));
924
900
  }
925
901
  return "*";
926
902
  }
@@ -944,16 +920,23 @@ export default class Inibase {
944
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));
945
921
  if (!_id.length)
946
922
  throw this.throwError("NO_ITEMS", tableName);
947
- const renameList = [];
948
- for await (const file of files)
949
- renameList.push(await File.remove(join(this.folder, this.database, tableName, file), where));
950
- for await (const [tempPath, filePath] of renameList)
951
- await rename(tempPath, filePath);
923
+ let testFileHandle;
924
+ 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));
930
+ }
931
+ finally {
932
+ await testFileHandle?.close();
933
+ }
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)));
952
936
  if (Config.isCacheEnabled) {
953
937
  const cacheFiles = (await readdir(join(this.folder, this.database, tableName, ".tmp")))?.filter((fileName) => !["lastId.inib", "totalItems.inib"].includes(fileName));
954
938
  if (cacheFiles.length)
955
- for await (const file of cacheFiles)
956
- await unlink(join(this.folder, this.database, tableName, ".tmp", file));
939
+ await Promise.all(cacheFiles.map(async (file) => unlink(join(this.folder, this.database, tableName, ".tmp", file))));
957
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")))
958
941
  ? Number(await File.read(join(this.folder, this.database, tableName, ".tmp", "totalItems.inib"), true))
959
942
  : await File.count(join(this.folder, this.database, tableName, "id.inib"))) - (Array.isArray(where) ? where.length : 1)), true);
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type FieldType } from "./index.js";
1
+ import { type FieldType, ComparisonOperator } from "./index.js";
2
2
  /**
3
3
  * Type guard function to check if the input is an array of objects.
4
4
  *
@@ -174,6 +174,10 @@ export declare const validateFieldType: (value: any, fieldType: FieldType | Fiel
174
174
  * @returns A flattened object using dot notation for keys.
175
175
  */
176
176
  export declare const objectToDotNotation: (input: Record<string, any>) => Record<string, string | number | (string | number)[]>;
177
+ export declare function FormatObjectCriteriaValue(value: string, isParentArray?: boolean): [
178
+ ComparisonOperator,
179
+ string | number | boolean | null | (string | number | null)[]
180
+ ];
177
181
  export default class Utils {
178
182
  static isNumber: (input: any) => input is number;
179
183
  static isObject: (obj: any) => obj is Record<any, any>;
@@ -195,4 +199,5 @@ export default class Utils {
195
199
  static validateFieldType: (value: any, fieldType: FieldType | FieldType[], fieldChildrenType?: FieldType | FieldType[] | undefined) => boolean;
196
200
  static objectToDotNotation: (input: Record<string, any>) => Record<string, string | number | (string | number)[]>;
197
201
  static isArrayOfNulls: (input: any) => input is null[] | null[][];
202
+ static FormatObjectCriteriaValue: typeof FormatObjectCriteriaValue;
198
203
  }
package/dist/utils.js CHANGED
@@ -363,6 +363,60 @@ export const objectToDotNotation = (input) => {
363
363
  }
364
364
  return result;
365
365
  };
366
+ export function FormatObjectCriteriaValue(value, isParentArray = false) {
367
+ switch (value[0]) {
368
+ case ">":
369
+ case "<":
370
+ return value[1] === "="
371
+ ? [
372
+ value.slice(0, 2),
373
+ value.slice(2),
374
+ ]
375
+ : [
376
+ value.slice(0, 1),
377
+ value.slice(1),
378
+ ];
379
+ case "[":
380
+ return value[1] === "]"
381
+ ? [
382
+ value.slice(0, 2),
383
+ value.slice(2).toString().split(","),
384
+ ]
385
+ : ["[]", value.slice(1)];
386
+ case "!":
387
+ return ["=", "*"].includes(value[1])
388
+ ? [
389
+ value.slice(0, 2),
390
+ value.slice(2),
391
+ ]
392
+ : value[1] === "["
393
+ ? [
394
+ value.slice(0, 3),
395
+ value.slice(3),
396
+ ]
397
+ : [
398
+ (value.slice(0, 1) + "="),
399
+ value.slice(1),
400
+ ];
401
+ case "=":
402
+ return isParentArray
403
+ ? [
404
+ value.slice(0, 1),
405
+ value.slice(1),
406
+ ]
407
+ : [
408
+ value.slice(0, 1),
409
+ (value.slice(1) + ","),
410
+ ];
411
+ case "*":
412
+ return [
413
+ value.slice(0, 1),
414
+ value.slice(1),
415
+ ];
416
+ default:
417
+ return ["=", value];
418
+ }
419
+ }
366
420
  export default class Utils {
367
421
  static isNumber = isNumber;
368
422
  static isObject = isObject;
@@ -384,4 +438,5 @@ export default class Utils {
384
438
  static validateFieldType = validateFieldType;
385
439
  static objectToDotNotation = objectToDotNotation;
386
440
  static isArrayOfNulls = isArrayOfNulls;
441
+ static FormatObjectCriteriaValue = FormatObjectCriteriaValue;
387
442
  }
@@ -47,7 +47,28 @@ export declare const findLastIdNumber: (schema: Schema, secretKeyOrSalt: string
47
47
  * @param secretKeyOrSalt - The secret key or salt for encoding IDs, can be a string, number, or Buffer.
48
48
  * @returns The updated schema with encoded IDs.
49
49
  */
50
- export declare const addIdToSchema: (schema: Schema, oldIndex: number | undefined, secretKeyOrSalt: string | number | Buffer) => import("./index.js").Field[];
50
+ export declare const addIdToSchema: (schema: Schema, oldIndex: number | undefined, secretKeyOrSalt: string | number | Buffer) => (({
51
+ id?: string | number | undefined;
52
+ key: string;
53
+ required?: boolean | undefined;
54
+ } & {
55
+ type: "string" | "number" | "boolean" | "id" | "url" | "html" | "table" | "email" | "date" | "password" | "ip";
56
+ children?: undefined;
57
+ }) | ({
58
+ id?: string | number | undefined;
59
+ key: string;
60
+ required?: boolean | undefined;
61
+ } & {
62
+ type: "object";
63
+ children: Schema;
64
+ }) | ({
65
+ id?: string | number | undefined;
66
+ key: string;
67
+ required?: boolean | undefined;
68
+ } & {
69
+ type: "array";
70
+ children: "string" | "number" | "boolean" | "object" | "id" | "url" | "html" | "table" | "email" | "date" | "password" | "ip" | Schema | ("string" | "number" | "boolean" | "object" | "id" | "url" | "html" | "table" | "email" | "date" | "password" | "ip")[];
71
+ }))[];
51
72
  export declare const hashObject: (obj: any) => string;
52
73
  export default class UtilsServer {
53
74
  static encodeID: (id: string | number, secretKeyOrSalt: string | number | Buffer) => string;
@@ -55,6 +76,27 @@ export default class UtilsServer {
55
76
  static hashPassword: (password: string) => string;
56
77
  static comparePassword: (hashedPassword: string, inputPassword: string) => boolean;
57
78
  static findLastIdNumber: (schema: Schema, secretKeyOrSalt: string | number | Buffer) => number;
58
- static addIdToSchema: (schema: Schema, oldIndex: number | undefined, secretKeyOrSalt: string | number | Buffer) => import("./index.js").Field[];
79
+ static addIdToSchema: (schema: Schema, oldIndex: number | undefined, secretKeyOrSalt: string | number | Buffer) => (({
80
+ id?: string | number | undefined;
81
+ key: string;
82
+ required?: boolean | undefined;
83
+ } & {
84
+ type: "string" | "number" | "boolean" | "id" | "url" | "html" | "table" | "email" | "date" | "password" | "ip";
85
+ children?: undefined;
86
+ }) | ({
87
+ id?: string | number | undefined;
88
+ key: string;
89
+ required?: boolean | undefined;
90
+ } & {
91
+ type: "object";
92
+ children: Schema;
93
+ }) | ({
94
+ id?: string | number | undefined;
95
+ key: string;
96
+ required?: boolean | undefined;
97
+ } & {
98
+ type: "array";
99
+ children: "string" | "number" | "boolean" | "object" | "id" | "url" | "html" | "table" | "email" | "date" | "password" | "ip" | Schema | ("string" | "number" | "boolean" | "object" | "id" | "url" | "html" | "table" | "email" | "date" | "password" | "ip")[];
100
+ }))[];
59
101
  static hashObject: (obj: any) => string;
60
102
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inibase",
3
- "version": "1.0.0-rc.27",
3
+ "version": "1.0.0-rc.29",
4
4
  "author": {
5
5
  "name": "Karim Amahtil",
6
6
  "email": "karim.amahtil@gmail.com"
@@ -10,6 +10,7 @@
10
10
  "exports": {
11
11
  ".": "./dist/index.js",
12
12
  "./file": "./dist/file.js",
13
+ "./config": "./dist/config.js",
13
14
  "./utils": "./dist/utils.js",
14
15
  "./utils.server": "./dist/utils.server.js"
15
16
  },
@@ -53,13 +54,16 @@
53
54
  "utils": [
54
55
  "./dist/utils.d.ts"
55
56
  ],
57
+ "config": [
58
+ "./dist/config.d.ts"
59
+ ],
56
60
  "utils.server": [
57
61
  "./dist/utils.server.d.ts"
58
62
  ]
59
63
  }
60
64
  },
61
65
  "devDependencies": {
62
- "@types/node": "^20.10.4",
66
+ "@types/node": "^20.10.6",
63
67
  "typescript": "^5.3.3"
64
68
  },
65
69
  "scripts": {