inibase 1.0.0-rc.99 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,85 +1,93 @@
1
1
  import "dotenv/config";
2
2
  import { randomBytes, scryptSync } from "node:crypto";
3
3
  import { appendFileSync, existsSync, readFileSync } from "node:fs";
4
- import { mkdir, readFile, readdir, rename, stat, unlink, writeFile, } from "node:fs/promises";
4
+ import { glob, mkdir, readFile, readdir, rename, rm, unlink, writeFile, } from "node:fs/promises";
5
5
  import { join, parse } from "node:path";
6
6
  import { inspect } from "node:util";
7
7
  import Inison from "inison";
8
8
  import * as File from "./file.js";
9
9
  import * as Utils from "./utils.js";
10
10
  import * as UtilsServer from "./utils.server.js";
11
+ // hide ExperimentalWarning glob()
12
+ process.removeAllListeners("warning");
11
13
  export default class Inibase {
12
14
  pageInfo;
13
15
  salt;
14
16
  databasePath;
15
- tables;
16
17
  fileExtension = ".txt";
17
- checkIFunique;
18
+ tablesMap;
19
+ uniqueMap;
18
20
  totalItems;
19
21
  constructor(database, mainFolder = ".") {
20
22
  this.databasePath = join(mainFolder, database);
21
- this.tables = {};
22
- this.totalItems = {};
23
- this.pageInfo = {};
24
- this.checkIFunique = {};
23
+ this.clear();
25
24
  if (!process.env.INIBASE_SECRET) {
26
25
  if (existsSync(".env") &&
27
26
  readFileSync(".env").includes("INIBASE_SECRET="))
28
- throw this.Error("NO_ENV");
27
+ throw this.createError("NO_ENV");
29
28
  this.salt = scryptSync(randomBytes(16), randomBytes(16), 32);
30
29
  appendFileSync(".env", `\nINIBASE_SECRET=${this.salt.toString("hex")}\n`);
31
30
  }
32
31
  else
33
32
  this.salt = Buffer.from(process.env.INIBASE_SECRET, "hex");
34
33
  }
35
- Error(code, variable, language = "en") {
36
- const errorMessages = {
37
- en: {
38
- FIELD_UNIQUE: "Field {variable} should be unique, got {variable} instead",
39
- FIELD_REQUIRED: "Field {variable} is required",
40
- TABLE_EXISTS: "Table {variable} already exists",
41
- TABLE_NOT_EXISTS: "Table {variable} doesn't exist",
42
- NO_SCHEMA: "Table {variable} does't have a schema",
43
- NO_ITEMS: "Table {variable} is empty",
44
- INVALID_ID: "The given ID(s) is/are not valid(s)",
45
- INVALID_TYPE: "Expect {variable} to be {variable}, got {variable} instead",
46
- INVALID_PARAMETERS: "The given parameters are not valid",
47
- NO_ENV: Number(process.versions.node.split(".").reduce((a, b) => a + b)) >= 26
48
- ? "please run with '--env-file=.env'"
49
- : "please use dotenv",
50
- },
51
- // Add more languages and error messages as needed
52
- };
53
- const errorMessage = errorMessages[language][code];
34
+ static errorMessages = {
35
+ en: {
36
+ TABLE_EMPTY: "Table {variable} is empty",
37
+ TABLE_EXISTS: "Table {variable} already exists",
38
+ TABLE_NOT_EXISTS: "Table {variable} doesn't exist",
39
+ NO_SCHEMA: "Table {variable} does't have a schema",
40
+ FIELD_UNIQUE: "Field {variable} should be unique, got {variable} instead",
41
+ FIELD_REQUIRED: "Field {variable} is required",
42
+ INVALID_ID: "The given ID(s) is/are not valid(s)",
43
+ INVALID_TYPE: "Expect {variable} to be {variable}, got {variable} instead",
44
+ INVALID_PARAMETERS: "The given parameters are not valid",
45
+ INVALID_REGEX_MATCH: "Field {variable} does not match the expected pattern",
46
+ NO_ENV: Number(process.versions.node.split(".").reduce((a, b) => a + b)) >= 26
47
+ ? "please run with '--env-file=.env'"
48
+ : "please use dotenv",
49
+ },
50
+ };
51
+ createError(name, variable, language = "en") {
52
+ const errorMessage = Inibase.errorMessages[language]?.[name];
54
53
  if (!errorMessage)
55
54
  return new Error("ERR");
56
- return new Error(variable
55
+ const error = new Error(variable
57
56
  ? Array.isArray(variable)
58
57
  ? errorMessage.replace(/\{variable\}/g, () => variable.shift()?.toString() ?? "")
59
58
  : errorMessage.replaceAll("{variable}", `'${variable.toString()}'`)
60
59
  : errorMessage.replaceAll("{variable}", ""));
60
+ error.name = name;
61
+ return error;
62
+ }
63
+ clear() {
64
+ this.tablesMap = new Map();
65
+ this.totalItems = new Map();
66
+ this.pageInfo = {};
67
+ this.uniqueMap = new Map();
61
68
  }
62
- getFileExtension = (tableName) => {
69
+ getFileExtension(tableName) {
63
70
  let mainExtension = this.fileExtension;
64
71
  // TODO: ADD ENCRYPTION
65
- // if(this.tables[tableName].config.encryption)
72
+ // if(this.tablesMap.get(tableName).config.encryption)
66
73
  // mainExtension += ".enc"
67
- if (this.tables[tableName].config.compression)
74
+ if (this.tablesMap.get(tableName).config.compression)
68
75
  mainExtension += ".gz";
69
76
  return mainExtension;
70
- };
71
- _schemaToIdsPath = (tableName, schema, prefix = "") => {
77
+ }
78
+ _schemaToIdsPath(tableName, schema, prefix = "") {
72
79
  const RETURN = {};
73
80
  for (const field of schema)
74
81
  if ((field.type === "array" || field.type === "object") &&
75
82
  field.children &&
76
- Utils.isArrayOfObjects(field.children)) {
83
+ Utils.isArrayOfObjects(field.children))
77
84
  Utils.deepMerge(RETURN, this._schemaToIdsPath(tableName, field.children, `${(prefix ?? "") + field.key}.`));
78
- }
79
85
  else if (field.id)
80
- RETURN[field.id] = `${(prefix ?? "") + field.key}${this.getFileExtension(tableName)}`;
86
+ RETURN[Utils.isValidID(field.id)
87
+ ? UtilsServer.decodeID(field.id, this.salt)
88
+ : field.id] = `${(prefix ?? "") + field.key}${this.getFileExtension(tableName)}`;
81
89
  return RETURN;
82
- };
90
+ }
83
91
  /**
84
92
  * Create a new table inside database, with predefined schema and config
85
93
  *
@@ -90,7 +98,7 @@ export default class Inibase {
90
98
  async createTable(tableName, schema, config) {
91
99
  const tablePath = join(this.databasePath, tableName);
92
100
  if (await File.isExists(tablePath))
93
- throw this.Error("TABLE_EXISTS", tableName);
101
+ throw this.createError("TABLE_EXISTS", tableName);
94
102
  await mkdir(join(tablePath, ".tmp"), { recursive: true });
95
103
  await mkdir(join(tablePath, ".cache"));
96
104
  // if config not set => load default global env config
@@ -108,8 +116,14 @@ export default class Inibase {
108
116
  if (config.prepend)
109
117
  await writeFile(join(tablePath, ".prepend.config"), "");
110
118
  }
111
- if (schema)
112
- await writeFile(join(tablePath, "schema.json"), JSON.stringify(UtilsServer.addIdToSchema(schema, 0, this.salt), null, 2));
119
+ if (schema) {
120
+ const lastSchemaID = { value: 0 };
121
+ await writeFile(join(tablePath, "schema.json"), JSON.stringify(UtilsServer.addIdToSchema(schema, lastSchemaID, this.salt), null, 2));
122
+ await writeFile(join(tablePath, `${lastSchemaID.value}.schema`), "");
123
+ }
124
+ else
125
+ await writeFile(join(tablePath, "0.schema"), "");
126
+ await writeFile(join(tablePath, "0-0.pagination"), "");
113
127
  }
114
128
  // Function to replace the string in one schema.json file
115
129
  async replaceStringInFile(filePath, targetString, replaceString) {
@@ -119,20 +133,6 @@ export default class Inibase {
119
133
  await writeFile(filePath, updatedContent, "utf8");
120
134
  }
121
135
  }
122
- // Function to process schema files one by one (sequentially)
123
- async replaceStringInSchemas(directoryPath, targetString, replaceString) {
124
- const files = await readdir(directoryPath);
125
- for (const file of files) {
126
- const fullPath = join(directoryPath, file);
127
- const fileStat = await stat(fullPath);
128
- if (fileStat.isDirectory()) {
129
- await this.replaceStringInSchemas(fullPath, targetString, replaceString);
130
- }
131
- else if (file === "schema.json") {
132
- await this.replaceStringInFile(fullPath, targetString, replaceString);
133
- }
134
- }
135
- }
136
136
  /**
137
137
  * Update table schema or config
138
138
  *
@@ -141,15 +141,20 @@ export default class Inibase {
141
141
  * @param {(Config&{name?: string})} [config]
142
142
  */
143
143
  async updateTable(tableName, schema, config) {
144
- const table = await this.getTable(tableName), tablePath = join(this.databasePath, tableName);
144
+ const table = await this.getTable(tableName);
145
+ const tablePath = join(this.databasePath, tableName);
145
146
  if (schema) {
146
147
  // remove id from schema
147
148
  schema = schema.filter(({ key }) => !["id", "createdAt", "updatedAt"].includes(key));
149
+ let schemaIdFilePath;
150
+ for await (const fileName of glob("*.schema", { cwd: tablePath }))
151
+ schemaIdFilePath = join(tablePath, fileName);
152
+ const lastSchemaID = {
153
+ value: schemaIdFilePath ? Number(parse(schemaIdFilePath).name) : 0,
154
+ };
148
155
  if (await File.isExists(join(tablePath, "schema.json"))) {
149
156
  // update columns files names based on field id
150
- schema = UtilsServer.addIdToSchema(schema, table.schema?.length
151
- ? UtilsServer.findLastIdNumber(table.schema, this.salt)
152
- : 0, this.salt);
157
+ schema = UtilsServer.addIdToSchema(schema, lastSchemaID, this.salt);
153
158
  if (table.schema?.length) {
154
159
  const replaceOldPathes = Utils.findChangedProperties(this._schemaToIdsPath(tableName, table.schema), this._schemaToIdsPath(tableName, schema));
155
160
  if (replaceOldPathes)
@@ -160,8 +165,12 @@ export default class Inibase {
160
165
  }
161
166
  }
162
167
  else
163
- schema = UtilsServer.addIdToSchema(schema, 0, this.salt);
168
+ schema = UtilsServer.addIdToSchema(schema, lastSchemaID, this.salt);
164
169
  await writeFile(join(tablePath, "schema.json"), JSON.stringify(schema, null, 2));
170
+ if (schemaIdFilePath)
171
+ await rename(schemaIdFilePath, join(tablePath, `${lastSchemaID.value}.schema`));
172
+ else
173
+ await writeFile(join(tablePath, `${lastSchemaID.value}.schema`), "");
165
174
  }
166
175
  if (config) {
167
176
  if (config.compression !== undefined &&
@@ -186,8 +195,10 @@ export default class Inibase {
186
195
  if (config.cache !== undefined && config.cache !== table.config.cache) {
187
196
  if (config.cache)
188
197
  await writeFile(join(tablePath, ".cache.config"), "");
189
- else
198
+ else {
199
+ await this.clearCache(tableName);
190
200
  await unlink(join(tablePath, ".cache.config"));
201
+ }
191
202
  }
192
203
  if (config.prepend !== undefined &&
193
204
  config.prepend !== table.config.prepend) {
@@ -213,11 +224,15 @@ export default class Inibase {
213
224
  await unlink(join(tablePath, ".prepend.config"));
214
225
  }
215
226
  if (config.name) {
216
- await this.replaceStringInSchemas(this.databasePath, `"table": "${tableName}"`, `"table": "${config.name}"`);
217
227
  await rename(tablePath, join(this.databasePath, config.name));
228
+ // replace table name in other linked tables (relationship)
229
+ for await (const schemaPath of glob("**/schema.json", {
230
+ cwd: this.databasePath,
231
+ }))
232
+ await this.replaceStringInFile(schemaPath, `"table": "${tableName}"`, `"table": "${config.name}"`);
218
233
  }
219
234
  }
220
- delete this.tables[tableName];
235
+ this.tablesMap.delete(tableName);
221
236
  }
222
237
  /**
223
238
  * Get table schema and config
@@ -225,30 +240,29 @@ export default class Inibase {
225
240
  * @param {string} tableName
226
241
  * @return {*} {Promise<TableObject>}
227
242
  */
228
- async getTable(tableName) {
243
+ async getTable(tableName, encodeIDs = true) {
229
244
  const tablePath = join(this.databasePath, tableName);
230
245
  if (!(await File.isExists(tablePath)))
231
- throw this.Error("TABLE_NOT_EXISTS", tableName);
232
- if (!this.tables[tableName])
233
- this.tables[tableName] = {
234
- schema: await this.getTableSchema(tableName),
246
+ throw this.createError("TABLE_NOT_EXISTS", tableName);
247
+ if (!this.tablesMap.has(tableName))
248
+ this.tablesMap.set(tableName, {
249
+ schema: await this.getTableSchema(tableName, encodeIDs),
235
250
  config: {
236
251
  compression: await File.isExists(join(tablePath, ".compression.config")),
237
252
  cache: await File.isExists(join(tablePath, ".cache.config")),
238
253
  prepend: await File.isExists(join(tablePath, ".prepend.config")),
239
254
  },
240
- };
241
- return this.tables[tableName];
255
+ });
256
+ return this.tablesMap.get(tableName);
242
257
  }
243
258
  async getTableSchema(tableName, encodeIDs = true) {
244
- const tableSchemaPath = join(this.databasePath, tableName, "schema.json");
245
- if (!(await File.isExists(tableSchemaPath)))
259
+ const tablePath = join(this.databasePath, tableName);
260
+ if (!(await File.isExists(join(tablePath, "schema.json"))))
246
261
  return undefined;
247
- const schemaFile = await readFile(tableSchemaPath, "utf8");
262
+ const schemaFile = await readFile(join(tablePath, "schema.json"), "utf8");
248
263
  if (!schemaFile)
249
264
  return undefined;
250
265
  let schema = JSON.parse(schemaFile);
251
- const lastIdNumber = UtilsServer.findLastIdNumber(schema, this.salt);
252
266
  schema = [
253
267
  {
254
268
  id: 0,
@@ -258,16 +272,15 @@ export default class Inibase {
258
272
  },
259
273
  ...schema,
260
274
  {
261
- id: lastIdNumber + 1,
275
+ id: -1,
262
276
  key: "createdAt",
263
277
  type: "date",
264
278
  required: true,
265
279
  },
266
280
  {
267
- id: lastIdNumber + 2,
281
+ id: -2,
268
282
  key: "updatedAt",
269
283
  type: "date",
270
- required: false,
271
284
  },
272
285
  ];
273
286
  if (!encodeIDs)
@@ -275,49 +288,88 @@ export default class Inibase {
275
288
  return UtilsServer.encodeSchemaID(schema, this.salt);
276
289
  }
277
290
  async throwErrorIfTableEmpty(tableName) {
278
- const table = await this.getTable(tableName);
291
+ const table = await this.getTable(tableName, false);
279
292
  if (!table.schema)
280
- throw this.Error("NO_SCHEMA", tableName);
293
+ throw this.createError("NO_SCHEMA", tableName);
281
294
  if (!(await File.isExists(join(this.databasePath, tableName, `id${this.getFileExtension(tableName)}`))))
282
- throw this.Error("NO_ITEMS", tableName);
283
- return table;
295
+ throw this.createError("TABLE_EMPTY", tableName);
284
296
  }
285
- validateData(data, schema, skipRequiredField = false) {
286
- if (Utils.isArrayOfObjects(data))
297
+ _validateData(data, schema, skipRequiredField = false) {
298
+ if (Utils.isArrayOfObjects(data)) {
287
299
  for (const single_data of data)
288
- this.validateData(single_data, schema, skipRequiredField);
289
- else if (Utils.isObject(data)) {
300
+ this._validateData(single_data, schema, skipRequiredField);
301
+ return;
302
+ }
303
+ if (Utils.isObject(data)) {
290
304
  for (const field of schema) {
291
305
  if (!Object.hasOwn(data, field.key) ||
292
306
  data[field.key] === null ||
293
307
  data[field.key] === undefined ||
294
308
  data[field.key] === "") {
295
309
  if (field.required && !skipRequiredField)
296
- throw this.Error("FIELD_REQUIRED", field.key);
297
- return;
310
+ throw this.createError("FIELD_REQUIRED", field.key);
311
+ continue;
298
312
  }
299
313
  if (!Utils.validateFieldType(data[field.key], field.type, (field.type === "array" || field.type === "object") &&
300
314
  field.children &&
301
315
  !Utils.isArrayOfObjects(field.children)
302
316
  ? field.children
303
317
  : undefined))
304
- throw this.Error("INVALID_TYPE", [
318
+ throw this.createError("INVALID_TYPE", [
305
319
  field.key,
306
- Array.isArray(field.type) ? field.type.join(", ") : field.type,
320
+ (Array.isArray(field.type) ? field.type.join(", ") : field.type) +
321
+ (field.children
322
+ ? Array.isArray(field.children)
323
+ ? Utils.isArrayOfObjects(field.children)
324
+ ? "[object]"
325
+ : `[${field.children.join("|")}]`
326
+ : `[${field.children}]`
327
+ : ""),
307
328
  data[field.key],
308
329
  ]);
309
330
  if ((field.type === "array" || field.type === "object") &&
310
331
  field.children &&
311
332
  Utils.isArrayOfObjects(field.children))
312
- this.validateData(data[field.key], field.children, skipRequiredField);
313
- else if (field.unique) {
314
- if (!this.checkIFunique[field.key])
315
- this.checkIFunique[field.key] = [];
316
- this.checkIFunique[`${field.key}`].push(data[field.key]);
333
+ this._validateData(data[field.key], field.children, skipRequiredField);
334
+ else {
335
+ if (field.regex) {
336
+ const regex = UtilsServer.getCachedRegex(field.regex);
337
+ if (!regex.test(data[field.key]))
338
+ throw this.createError("INVALID_REGEX_MATCH", [field.key]);
339
+ }
340
+ if (field.unique) {
341
+ let uniqueKey;
342
+ if (typeof field.unique === "boolean")
343
+ uniqueKey = field.id;
344
+ else
345
+ uniqueKey = field.unique;
346
+ if (!this.uniqueMap.has(uniqueKey))
347
+ this.uniqueMap.set(uniqueKey, {
348
+ exclude: new Set(),
349
+ columnsValues: new Map(),
350
+ });
351
+ if (!this.uniqueMap
352
+ .get(uniqueKey)
353
+ .columnsValues.has(field.id))
354
+ this.uniqueMap
355
+ .get(uniqueKey)
356
+ .columnsValues.set(field.id, new Set());
357
+ if (data.id)
358
+ this.uniqueMap.get(uniqueKey).exclude.add(-data.id);
359
+ this.uniqueMap
360
+ .get(uniqueKey)
361
+ .columnsValues.get(field.id)
362
+ .add(data[field.key]);
363
+ }
317
364
  }
318
365
  }
319
366
  }
320
367
  }
368
+ async validateData(tableName, data, skipRequiredField = false) {
369
+ // Skip ID and (created|updated)At
370
+ this._validateData(data, this.tablesMap.get(tableName).schema.slice(1, -2), skipRequiredField);
371
+ await this.checkUnique(tableName);
372
+ }
321
373
  cleanObject(obj) {
322
374
  const cleanedObject = Object.entries(obj).reduce((acc, [key, value]) => {
323
375
  if (value !== undefined && value !== null && value !== "")
@@ -339,8 +391,9 @@ export default class Inibase {
339
391
  return null;
340
392
  if (!Array.isArray(value))
341
393
  value = [value];
342
- if (Utils.isArrayOfObjects(fieldChildrenType))
394
+ if (Utils.isArrayOfObjects(fieldChildrenType)) {
343
395
  return this.formatData(value, fieldChildrenType, _formatOnlyAvailiableKeys);
396
+ }
344
397
  if (!value.length)
345
398
  return null;
346
399
  return value.map((_value) => this.formatField(_value, fieldChildrenType));
@@ -389,34 +442,63 @@ export default class Inibase {
389
442
  }
390
443
  return null;
391
444
  }
392
- async checkUnique(tableName, schema) {
445
+ async checkUnique(tableName) {
393
446
  const tablePath = join(this.databasePath, tableName);
394
- for await (const [key, values] of Object.entries(this.checkIFunique)) {
395
- const field = Utils.getField(key, schema);
396
- if (!field)
447
+ const flattenSchema = Utils.flattenSchema(this.tablesMap.get(tableName).schema);
448
+ function hasDuplicates(setA, setB) {
449
+ for (const value of setA)
450
+ if (setB.has(value))
451
+ return true; // Stop and return true if a duplicate is found
452
+ return false; // No duplicates found
453
+ }
454
+ for await (const [_uniqueID, valueObject] of this.uniqueMap) {
455
+ let index = 0;
456
+ let shouldContinueParent = false; // Flag to manage parent loop continuation
457
+ const mergedLineNumbers = new Set();
458
+ for await (const [columnID, values] of valueObject.columnsValues) {
459
+ index++;
460
+ const field = flattenSchema.find(({ id }) => id === columnID);
461
+ const [_, totalLines, lineNumbers] = await File.search(join(tablePath, `${field.key}${this.getFileExtension(tableName)}`), "[]", Array.from(values), undefined, valueObject.exclude, field.type, field.children, 1, undefined, false, this.salt);
462
+ if (totalLines > 0) {
463
+ if (valueObject.columnsValues.size === 1 ||
464
+ hasDuplicates(lineNumbers, mergedLineNumbers)) {
465
+ this.uniqueMap = new Map();
466
+ throw this.createError("FIELD_UNIQUE", [
467
+ field.key,
468
+ Array.from(values).join(", "),
469
+ ]);
470
+ }
471
+ lineNumbers.forEach(mergedLineNumbers.add, mergedLineNumbers);
472
+ }
473
+ else {
474
+ shouldContinueParent = true; // Flag to skip the rest of this inner loop
475
+ break; // Exit the inner loop
476
+ }
477
+ }
478
+ if (shouldContinueParent)
397
479
  continue;
398
- const [searchResult, totalLines] = await File.search(join(tablePath, `${key}${this.getFileExtension(tableName)}`), Array.isArray(values) ? "=" : "[]", values, undefined, field.type, field.children, 1, undefined, false, this.salt);
399
- if (searchResult && totalLines > 0)
400
- throw this.Error("FIELD_UNIQUE", [
401
- field.key,
402
- Array.isArray(values) ? values.join(", ") : values,
403
- ]);
404
480
  }
405
- this.checkIFunique = {};
481
+ this.uniqueMap = new Map();
406
482
  }
407
483
  formatData(data, schema, formatOnlyAvailiableKeys) {
408
- if (Utils.isArrayOfObjects(data))
409
- return data.map((single_data) => this.formatData(single_data, schema, formatOnlyAvailiableKeys));
410
- if (Utils.isObject(data)) {
484
+ const clonedData = structuredClone(data);
485
+ if (Utils.isArrayOfObjects(clonedData))
486
+ return clonedData.map((singleData) => this.formatData(singleData, schema, formatOnlyAvailiableKeys));
487
+ if (Utils.isObject(clonedData)) {
411
488
  const RETURN = {};
412
489
  for (const field of schema) {
413
- if (!Object.hasOwn(data, field.key)) {
490
+ if (!Object.hasOwn(clonedData, field.key)) {
414
491
  if (formatOnlyAvailiableKeys)
415
492
  continue;
416
493
  RETURN[field.key] = this.getDefaultValue(field);
417
494
  continue;
418
495
  }
419
- RETURN[field.key] = this.formatField(data[field.key], field.type, field.children, formatOnlyAvailiableKeys);
496
+ if (Array.isArray(clonedData[field.key]) &&
497
+ !clonedData[field.key].length) {
498
+ RETURN[field.key] = this.getDefaultValue(field);
499
+ continue;
500
+ }
501
+ RETURN[field.key] = this.formatField(clonedData[field.key], field.type, field.children, formatOnlyAvailiableKeys);
420
502
  }
421
503
  return RETURN;
422
504
  }
@@ -432,15 +514,6 @@ export default class Inibase {
432
514
  });
433
515
  switch (field.type) {
434
516
  case "array":
435
- return Utils.isArrayOfObjects(field.children)
436
- ? [
437
- this.getDefaultValue({
438
- ...field,
439
- type: "object",
440
- children: field.children,
441
- }),
442
- ]
443
- : null;
444
517
  case "object": {
445
518
  if (!field.children || !Utils.isArrayOfObjects(field.children))
446
519
  return null;
@@ -455,15 +528,17 @@ export default class Inibase {
455
528
  return null;
456
529
  }
457
530
  }
458
- _combineObjectsToArray = (input) => input.reduce((result, current) => {
459
- for (const [key, value] of Object.entries(current))
460
- if (Object.hasOwn(result, key) && Array.isArray(result[key]))
461
- result[key].push(value);
462
- else
463
- result[key] = [value];
464
- return result;
465
- }, {});
466
- _CombineData = (data, prefix) => {
531
+ _combineObjectsToArray(input) {
532
+ return input.reduce((result, current) => {
533
+ for (const [key, value] of Object.entries(current))
534
+ if (Object.hasOwn(result, key) && Array.isArray(result[key]))
535
+ result[key].push(value);
536
+ else
537
+ result[key] = [value];
538
+ return result;
539
+ }, {});
540
+ }
541
+ _CombineData(data, prefix) {
467
542
  if (Utils.isArrayOfObjects(data))
468
543
  return this._combineObjectsToArray(data.map((single_data) => this._CombineData(single_data)));
469
544
  const RETURN = {};
@@ -480,7 +555,7 @@ export default class Inibase {
480
555
  RETURN[(prefix ?? "") + key] = File.encode(value);
481
556
  }
482
557
  return RETURN;
483
- };
558
+ }
484
559
  joinPathesContents(tableName, data) {
485
560
  const tablePath = join(this.databasePath, tableName), combinedData = this._CombineData(data);
486
561
  const newCombinedData = {};
@@ -488,19 +563,21 @@ export default class Inibase {
488
563
  newCombinedData[join(tablePath, `${key}${this.getFileExtension(tableName)}`)] = value;
489
564
  return newCombinedData;
490
565
  }
491
- _getItemsFromSchemaHelper(RETURN, item, index, field) {
566
+ _processSchemaDataHelper(RETURN, item, index, field) {
567
+ // If the item is an object, we need to process its children
492
568
  if (Utils.isObject(item)) {
493
569
  if (!RETURN[index])
494
- RETURN[index] = {};
570
+ RETURN[index] = {}; // Ensure the index exists
495
571
  if (!RETURN[index][field.key])
496
572
  RETURN[index][field.key] = [];
573
+ // Process children fields (recursive if needed)
497
574
  for (const child_field of field.children.filter((children) => children.type === "array" &&
498
575
  Utils.isArrayOfObjects(children.children))) {
499
576
  if (Utils.isObject(item[child_field.key])) {
500
577
  for (const [key, value] of Object.entries(item[child_field.key])) {
501
578
  for (let _i = 0; _i < value.length; _i++) {
502
- if ((Array.isArray(value[_i]) && Utils.isArrayOfNulls(value[_i])) ||
503
- value[_i] === null)
579
+ if (value[_i] === null ||
580
+ (Array.isArray(value[_i]) && Utils.isArrayOfNulls(value[_i])))
504
581
  continue;
505
582
  if (!RETURN[index][field.key][_i])
506
583
  RETURN[index][field.key][_i] = {};
@@ -513,15 +590,18 @@ export default class Inibase {
513
590
  value[_i];
514
591
  }
515
592
  else {
516
- value[_i].forEach((_element, _index) => {
517
- // Recursive call
518
- this._getItemsFromSchemaHelper(RETURN[index][field.key][_i][child_field.key][_index], _element, _index, child_field);
593
+ for (let _index = 0; _index < value[_i].length; _index++) {
594
+ const element = value[_i][_index];
595
+ if (element === null)
596
+ continue;
597
+ // Recursive call to handle nested structure
598
+ this._processSchemaDataHelper(RETURN, element, _index, child_field);
519
599
  // Perform property assignments
520
600
  if (!RETURN[index][field.key][_i][child_field.key][_index])
521
601
  RETURN[index][field.key][_i][child_field.key][_index] = {};
522
602
  RETURN[index][field.key][_i][child_field.key][_index][key] =
523
- _element;
524
- });
603
+ element;
604
+ }
525
605
  }
526
606
  }
527
607
  }
@@ -529,206 +609,258 @@ export default class Inibase {
529
609
  }
530
610
  }
531
611
  }
532
- async getItemsFromSchema(tableName, schema, linesNumber, options, prefix) {
533
- const tablePath = join(this.databasePath, tableName);
612
+ async processSchemaData(tableName, schema, linesNumber, options, prefix) {
534
613
  const RETURN = {};
535
- for await (const field of schema) {
536
- if ((field.type === "array" ||
537
- (Array.isArray(field.type) && field.type.includes("array"))) &&
538
- field.children) {
539
- if (Utils.isArrayOfObjects(field.children)) {
540
- if (field.children.filter((children) => children.type === "array" &&
541
- Utils.isArrayOfObjects(children.children)).length) {
542
- // one of children has array field type and has children array of object = Schema
543
- const childItems = await this.getItemsFromSchema(tableName, field.children.filter((children) => children.type === "array" &&
544
- Utils.isArrayOfObjects(children.children)), linesNumber, options, `${(prefix ?? "") + field.key}.`);
545
- if (childItems)
546
- for (const [index, item] of Object.entries(childItems))
547
- this._getItemsFromSchemaHelper(RETURN, item, index, field);
548
- field.children = field.children.filter((children) => children.type !== "array" ||
549
- !Utils.isArrayOfObjects(children.children));
550
- }
551
- const fieldItems = await this.getItemsFromSchema(tableName, field.children, linesNumber, options, `${(prefix ?? "") + field.key}.`);
552
- if (fieldItems)
553
- for (const [index, item] of Object.entries(fieldItems)) {
554
- if (!RETURN[index])
555
- RETURN[index] = {};
556
- if (Utils.isObject(item)) {
557
- if (!Utils.isArrayOfNulls(Object.values(item))) {
558
- if (RETURN[index][field.key])
559
- Object.entries(item).forEach(([key, value], _index) => {
560
- for (let _index = 0; _index < value.length; _index++)
561
- if (RETURN[index][field.key][_index])
562
- Object.assign(RETURN[index][field.key][_index], {
563
- [key]: value[_index],
564
- });
565
- else
566
- RETURN[index][field.key][_index] = {
567
- [key]: value[_index],
568
- };
569
- });
570
- else if (Object.values(item).every((_i) => Utils.isArrayOfArrays(_i) || Array.isArray(_i)) &&
571
- prefix)
572
- RETURN[index][field.key] = item;
573
- else {
574
- RETURN[index][field.key] = [];
575
- Object.entries(item).forEach(([key, value], _ind) => {
576
- if (!Array.isArray(value)) {
577
- RETURN[index][field.key][_ind] = {};
578
- RETURN[index][field.key][_ind][key] = value;
579
- }
614
+ for (const field of schema) {
615
+ // If the field is of simple type (non-recursive), process it directly
616
+ if (this.isSimpleField(field.type)) {
617
+ await this.processSimpleField(tableName, field, linesNumber, RETURN, options, prefix);
618
+ }
619
+ else if (this.isArrayField(field.type)) {
620
+ // Process array fields (recursive if needed)
621
+ await this.processArrayField(tableName, field, linesNumber, RETURN, options, prefix);
622
+ }
623
+ else if (this.isObjectField(field.type)) {
624
+ // Process object fields (recursive if needed)
625
+ await this.processObjectField(tableName, field, linesNumber, RETURN, options, prefix);
626
+ }
627
+ else if (this.isTableField(field.type)) {
628
+ // Process table reference fields
629
+ await this.processTableField(tableName, field, linesNumber, RETURN, options, prefix);
630
+ }
631
+ }
632
+ return RETURN;
633
+ }
634
+ // Helper function to determine if a field is simple
635
+ isSimpleField(fieldType) {
636
+ const complexTypes = ["array", "object", "table"];
637
+ if (Array.isArray(fieldType))
638
+ return fieldType.every((type) => typeof type === "string" && !complexTypes.includes(type));
639
+ return !complexTypes.includes(fieldType);
640
+ }
641
+ // Process a simple field (non-recursive)
642
+ async processSimpleField(tableName, field, linesNumber, RETURN, _options, prefix) {
643
+ const fieldPath = join(this.databasePath, tableName, `${prefix ?? ""}${field.key}${this.getFileExtension(tableName)}`);
644
+ if (await File.isExists(fieldPath)) {
645
+ const items = await File.get(fieldPath, linesNumber, field.type, field.children, this.salt);
646
+ if (items) {
647
+ for (const [index, item] of Object.entries(items)) {
648
+ if (typeof item === "undefined")
649
+ continue; // Skip undefined items
650
+ if (!RETURN[index])
651
+ RETURN[index] = {}; // Ensure the index exists
652
+ RETURN[index][field.key] = item; // Assign item to the RETURN object
653
+ }
654
+ }
655
+ }
656
+ }
657
+ // Helper function to check if the field type is array
658
+ isArrayField(fieldType) {
659
+ return ((Array.isArray(fieldType) &&
660
+ fieldType.every((type) => typeof type === "string") &&
661
+ fieldType.includes("array")) ||
662
+ fieldType === "array");
663
+ }
664
+ // Process array fields (recursive if needed)
665
+ async processArrayField(tableName, field, linesNumber, RETURN, options, prefix) {
666
+ if (Array.isArray(field.children)) {
667
+ if (this.isSimpleField(field.children)) {
668
+ await this.processSimpleField(tableName, field, linesNumber, RETURN, options, prefix);
669
+ }
670
+ else if (this.isTableField(field.children)) {
671
+ await this.processTableField(tableName, field, linesNumber, RETURN, options, prefix);
672
+ }
673
+ else {
674
+ // Handling array of objects and filtering nested arrays
675
+ const nestedArrayFields = field.children.filter((children) => children.type === "array" &&
676
+ Utils.isArrayOfObjects(children.children));
677
+ if (nestedArrayFields.length > 0) {
678
+ // one of children has array field type and has children array of object = Schema
679
+ const childItems = await this.processSchemaData(tableName, nestedArrayFields, linesNumber, options, `${(prefix ?? "") + field.key}.`);
680
+ if (childItems)
681
+ for (const [index, item] of Object.entries(childItems))
682
+ this._processSchemaDataHelper(RETURN, item, index, field);
683
+ // Remove nested arrays after processing
684
+ field.children = field.children.filter((children) => !nestedArrayFields.map(({ key }) => key).includes(children.key));
685
+ }
686
+ // Process remaining items for the field's children
687
+ const items = await this.processSchemaData(tableName, field.children, linesNumber, options, `${(prefix ?? "") + field.key}.`);
688
+ // Process the items after retrieval
689
+ if (items) {
690
+ for (const [index, item] of Object.entries(items)) {
691
+ if (typeof item === "undefined")
692
+ continue; // Skip undefined items
693
+ if (!RETURN[index])
694
+ RETURN[index] = {};
695
+ if (Utils.isObject(item)) {
696
+ const itemEntries = Object.entries(item);
697
+ const itemValues = itemEntries.map(([_key, value]) => value);
698
+ if (!Utils.isArrayOfNulls(itemValues)) {
699
+ if (RETURN[index][field.key])
700
+ for (let _index = 0; _index < itemEntries.length; _index++) {
701
+ const [key, value] = itemEntries[_index];
702
+ for (let _index = 0; _index < value.length; _index++) {
703
+ if (value[_index] === null)
704
+ continue;
705
+ if (RETURN[index][field.key][_index])
706
+ Object.assign(RETURN[index][field.key][_index], {
707
+ [key]: value[_index],
708
+ });
580
709
  else
581
- for (let _i = 0; _i < value.length; _i++) {
582
- if (value[_i] === null ||
583
- (Array.isArray(value[_i]) &&
584
- Utils.isArrayOfNulls(value[_i])))
585
- continue;
586
- if (!RETURN[index][field.key][_i])
587
- RETURN[index][field.key][_i] = {};
588
- RETURN[index][field.key][_i][key] = value[_i];
589
- }
590
- });
710
+ RETURN[index][field.key][_index] = {
711
+ [key]: value[_index],
712
+ };
713
+ }
591
714
  }
592
- }
593
- else
594
- RETURN[index][field.key] = null;
595
- }
596
- else
597
- RETURN[index][field.key] = item;
598
- }
599
- }
600
- else if (field.children === "table" ||
601
- (Array.isArray(field.type) && field.type.includes("table")) ||
602
- (Array.isArray(field.children) && field.children.includes("table"))) {
603
- if (field.table &&
604
- (await File.isExists(join(this.databasePath, field.table))) &&
605
- (await File.isExists(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension(tableName)}`)))) {
606
- const itemsIDs = await File.get(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension(tableName)}`), linesNumber, field.type, field.children, this.salt);
607
- if (itemsIDs) {
608
- const searchableIDs = new Map();
609
- for (const [lineNumber, lineContent] of Object.entries(itemsIDs)) {
610
- if (!RETURN[lineNumber])
611
- RETURN[lineNumber] = {};
612
- if (lineContent !== null && lineContent !== undefined)
613
- searchableIDs.set(lineNumber, lineContent);
614
- }
615
- if (searchableIDs.size) {
616
- const items = await this.get(field.table, Array.from(new Set(Array.from(searchableIDs.values()).flat())), {
617
- ...options,
618
- perPage: Number.POSITIVE_INFINITY,
619
- columns: options.columns
620
- ?.filter((column) => column.includes(`${field.key}.`))
621
- .map((column) => column.replace(`${field.key}.`, "")),
622
- });
623
- if (items) {
624
- for (const [lineNumber, lineContent,] of searchableIDs.entries()) {
625
- const foundedItems = items.filter(({ id }) => lineContent.includes(id));
626
- if (foundedItems)
627
- RETURN[lineNumber][field.key] = foundedItems;
715
+ else if (itemValues.every((_i) => Utils.isArrayOfArrays(_i)) &&
716
+ prefix)
717
+ RETURN[index][field.key] = item;
718
+ else {
719
+ RETURN[index][field.key] = [];
720
+ for (let _index = 0; _index < itemEntries.length; _index++) {
721
+ const [key, value] = itemEntries[_index];
722
+ if (!Array.isArray(value)) {
723
+ RETURN[index][field.key][_index] = {};
724
+ RETURN[index][field.key][_index][key] = value;
725
+ }
726
+ else
727
+ for (let _i = 0; _i < value.length; _i++) {
728
+ if (value[_i] === null ||
729
+ (Array.isArray(value[_i]) &&
730
+ Utils.isArrayOfNulls(value[_i])))
731
+ continue;
732
+ if (!RETURN[index][field.key][_i])
733
+ RETURN[index][field.key][_i] = {};
734
+ RETURN[index][field.key][_i][key] = value[_i];
735
+ }
628
736
  }
629
737
  }
630
738
  }
631
739
  }
740
+ else
741
+ RETURN[index][field.key] = item;
632
742
  }
633
743
  }
634
- else if (await File.isExists(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension(tableName)}`))) {
635
- const items = await File.get(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension(tableName)}`), linesNumber, field.type, field.children, this.salt);
636
- if (items)
637
- for (const [index, item] of Object.entries(items)) {
638
- if (!RETURN[index])
639
- RETURN[index] = {};
640
- if (item !== null && item !== undefined)
641
- RETURN[index][field.key] = item;
642
- }
744
+ }
745
+ }
746
+ else if (this.isSimpleField(field.children)) {
747
+ // If `children` is FieldType, handle it as an array of simple types (no recursion needed here)
748
+ await this.processSimpleField(tableName, field, linesNumber, RETURN, options, prefix);
749
+ }
750
+ else if (this.isTableField(field.children)) {
751
+ await this.processTableField(tableName, field, linesNumber, RETURN, options, prefix);
752
+ }
753
+ }
754
+ // Helper function to check if the field type is object
755
+ isObjectField(fieldType) {
756
+ return (fieldType === "object" ||
757
+ (Array.isArray(fieldType) &&
758
+ fieldType.every((type) => typeof type === "string") &&
759
+ fieldType.includes("object")));
760
+ }
761
+ // Process object fields (recursive if needed)
762
+ async processObjectField(tableName, field, linesNumber, RETURN, options, prefix) {
763
+ if (Array.isArray(field.children)) {
764
+ // If `children` is a Schema (array of Field objects), recurse
765
+ const items = await this.processSchemaData(tableName, field.children, linesNumber, options, `${prefix ?? ""}${field.key}.`);
766
+ for (const [index, item] of Object.entries(items)) {
767
+ if (typeof item === "undefined")
768
+ continue; // Skip undefined items
769
+ if (!RETURN[index])
770
+ RETURN[index] = {};
771
+ if (Utils.isObject(item)) {
772
+ if (!Object.values(item).every((i) => i === null))
773
+ RETURN[index][field.key] = item;
643
774
  }
644
775
  }
645
- else if (field.type === "object") {
646
- const fieldItems = await this.getItemsFromSchema(tableName, field.children, linesNumber, options, `${(prefix ?? "") + field.key}.`);
647
- if (fieldItems)
648
- for (const [index, item] of Object.entries(fieldItems)) {
649
- if (!RETURN[index])
650
- RETURN[index] = {};
651
- if (Utils.isObject(item)) {
652
- if (!Object.values(item).every((i) => i === null))
653
- RETURN[index][field.key] = item;
654
- }
776
+ }
777
+ }
778
+ // Helper function to check if the field type is table
779
+ isTableField(fieldType) {
780
+ return (fieldType === "table" ||
781
+ (Array.isArray(fieldType) &&
782
+ fieldType.every((type) => typeof type === "string") &&
783
+ fieldType.includes("table")));
784
+ }
785
+ // Process table reference fields
786
+ async processTableField(tableName, field, linesNumber, RETURN, options, prefix) {
787
+ if (field.table &&
788
+ (await File.isExists(join(this.databasePath, field.table)))) {
789
+ const fieldPath = join(this.databasePath, tableName, `${prefix ?? ""}${field.key}${this.getFileExtension(tableName)}`);
790
+ if (await File.isExists(fieldPath)) {
791
+ const itemsIDs = await File.get(fieldPath, linesNumber, field.type, field.children, this.salt);
792
+ const isArrayField = this.isArrayField(field.type);
793
+ if (itemsIDs) {
794
+ const searchableIDs = new Map();
795
+ for (const [lineNumber, lineContent] of Object.entries(itemsIDs)) {
796
+ if (typeof lineContent === "undefined")
797
+ continue; // Skip undefined items
798
+ if (!RETURN[lineNumber])
799
+ RETURN[lineNumber] = {};
800
+ if (lineContent !== null && lineContent !== undefined)
801
+ searchableIDs.set(lineNumber, lineContent);
655
802
  }
656
- }
657
- else if (field.type === "table") {
658
- if (field.table &&
659
- (await File.isExists(join(this.databasePath, field.table))) &&
660
- (await File.isExists(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension(tableName)}`)))) {
661
- const itemsIDs = await File.get(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension(tableName)}`), linesNumber, field.type, field.children, this.salt);
662
- if (itemsIDs) {
663
- const searchableIDs = new Map();
664
- for (const [lineNumber, lineContent] of Object.entries(itemsIDs)) {
665
- if (!RETURN[lineNumber])
666
- RETURN[lineNumber] = {};
667
- if (lineContent !== null && lineContent !== undefined)
668
- searchableIDs.set(lineNumber, lineContent);
669
- }
670
- if (searchableIDs.size) {
671
- const items = await this.get(field.table, Array.from(new Set(searchableIDs.values())), {
672
- ...options,
673
- perPage: Number.POSITIVE_INFINITY,
674
- columns: options.columns
675
- ?.filter((column) => column.includes(`${field.key}.`))
676
- .map((column) => column.replace(`${field.key}.`, "")),
677
- });
678
- if (items) {
679
- for (const [lineNumber, lineContent,] of searchableIDs.entries()) {
680
- const foundedItem = items.find(({ id }) => id === lineContent);
681
- if (foundedItem)
682
- RETURN[lineNumber][field.key] = foundedItem;
683
- }
803
+ if (searchableIDs.size) {
804
+ const items = await this.get(field.table, isArrayField
805
+ ? Array.from(new Set(Array.from(searchableIDs.values()).flat()))
806
+ : Array.from(new Set(searchableIDs.values())), {
807
+ ...options,
808
+ perPage: Number.POSITIVE_INFINITY,
809
+ columns: options.columns
810
+ ?.filter((column) => column.includes(`${field.key}.`))
811
+ .map((column) => column.replace(`${field.key}.`, "")),
812
+ });
813
+ if (items) {
814
+ for (const [lineNumber, lineContent] of searchableIDs.entries()) {
815
+ const foundedItem = isArrayField
816
+ ? items.filter(({ id }) => lineContent.includes(id))
817
+ : items.find(({ id }) => id === lineContent);
818
+ if (foundedItem)
819
+ RETURN[lineNumber][field.key] = foundedItem;
684
820
  }
685
821
  }
686
822
  }
687
823
  }
688
824
  }
689
- else if (await File.isExists(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension(tableName)}`))) {
690
- const items = await File.get(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension(tableName)}`), linesNumber, field.type, field.children, this.salt);
691
- if (items)
692
- for (const [index, item] of Object.entries(items)) {
693
- if (!RETURN[index])
694
- RETURN[index] = {};
695
- if (item !== null && item !== undefined)
696
- RETURN[index][field.key] = item;
697
- }
698
- }
699
825
  }
700
- return RETURN;
701
826
  }
702
- async applyCriteria(tableName, schema, options, criteria, allTrue) {
827
+ _setNestedKey(obj, path, value) {
828
+ const keys = path.split(".");
829
+ let current = obj;
830
+ keys.forEach((key, index) => {
831
+ if (index === keys.length - 1) {
832
+ current[key] = value; // Set the value at the last key
833
+ }
834
+ else {
835
+ current[key] = current[key] || {}; // Ensure the object structure exists
836
+ current = current[key];
837
+ }
838
+ });
839
+ }
840
+ async applyCriteria(tableName, options, criteria, allTrue, searchIn) {
703
841
  const tablePath = join(this.databasePath, tableName);
704
- let RETURN = {}, RETURN_LineNumbers = null;
842
+ let RETURN = {};
705
843
  if (!criteria)
706
844
  return [null, null];
707
845
  if (criteria.and && Utils.isObject(criteria.and)) {
708
- const [searchResult, lineNumbers] = await this.applyCriteria(tableName, schema, options, criteria.and, true);
846
+ const [searchResult, lineNumbers] = await this.applyCriteria(tableName, options, criteria.and, true, searchIn);
709
847
  if (searchResult) {
710
- RETURN = Utils.deepMerge(RETURN, Object.fromEntries(Object.entries(searchResult).filter(([_k, v], _i) => Object.keys(v).length ===
711
- Object.keys(criteria.and ?? {}).length)));
848
+ RETURN = Utils.deepMerge(RETURN, Object.fromEntries(Object.entries(searchResult).filter(([_k, v], _i) => Object.keys(v).filter((key) => Object.keys(criteria.and).includes(key)).length)));
712
849
  delete criteria.and;
713
- RETURN_LineNumbers = lineNumbers;
850
+ searchIn = lineNumbers;
714
851
  }
715
852
  else
716
853
  return [null, null];
717
854
  }
718
- if (criteria.or && Utils.isObject(criteria.or)) {
719
- const [searchResult, lineNumbers] = await this.applyCriteria(tableName, schema, options, criteria.or, false);
855
+ const criteriaOR = criteria.or;
856
+ if (criteriaOR)
720
857
  delete criteria.or;
721
- if (searchResult) {
722
- RETURN = Utils.deepMerge(RETURN, searchResult);
723
- RETURN_LineNumbers = lineNumbers;
724
- }
725
- }
726
858
  if (Object.keys(criteria).length > 0) {
727
859
  if (allTrue === undefined)
728
860
  allTrue = true;
729
861
  let index = -1;
730
862
  for await (const [key, value] of Object.entries(criteria)) {
731
- const field = Utils.getField(key, schema);
863
+ const field = Utils.getField(key, this.tablesMap.get(tableName).schema);
732
864
  index++;
733
865
  let searchOperator = undefined, searchComparedAtValue = undefined, searchLogicalOperator = undefined;
734
866
  if (Utils.isObject(value)) {
@@ -784,27 +916,40 @@ export default class Inibase {
784
916
  searchOperator = "=";
785
917
  searchComparedAtValue = value;
786
918
  }
787
- const [searchResult, totalLines, linesNumbers] = await File.search(join(tablePath, `${key}${this.getFileExtension(tableName)}`), searchOperator ?? "=", searchComparedAtValue ?? null, searchLogicalOperator, field?.type, field?.children, options.perPage, (options.page - 1) * options.perPage + 1, true, this.salt);
919
+ const [searchResult, totalLines, linesNumbers] = await File.search(join(tablePath, `${key}${this.getFileExtension(tableName)}`), searchOperator ?? "=", searchComparedAtValue ?? null, searchLogicalOperator, allTrue ? searchIn : undefined, field?.type, field?.children, options.perPage, (options.page - 1) * options.perPage + 1, true, this.salt);
788
920
  if (searchResult) {
789
- RETURN = Utils.deepMerge(RETURN, Object.fromEntries(Object.entries(searchResult).map(([id, value]) => [
790
- id,
791
- {
792
- [key]: value,
793
- },
794
- ])));
795
- this.totalItems[`${tableName}-${key}`] = totalLines;
796
- RETURN_LineNumbers = linesNumbers;
797
- }
798
- if (allTrue && index > 0) {
799
- if (!Object.keys(RETURN).length)
800
- RETURN = {};
801
- RETURN = Object.fromEntries(Object.entries(RETURN).filter(([_index, item]) => Object.keys(item).length > index));
802
- if (!Object.keys(RETURN).length)
803
- RETURN = {};
921
+ RETURN = Utils.deepMerge(RETURN, Object.fromEntries(Object.entries(searchResult).map(([id, value]) => {
922
+ const nestedObj = {};
923
+ this._setNestedKey(nestedObj, key, value);
924
+ return [id, nestedObj];
925
+ })));
926
+ this.totalItems.set(`${tableName}-${key}`, totalLines);
927
+ if (linesNumbers?.size) {
928
+ if (searchIn) {
929
+ for (const lineNumber of linesNumbers)
930
+ searchIn.add(lineNumber);
931
+ }
932
+ else
933
+ searchIn = linesNumbers;
934
+ }
804
935
  }
936
+ else if (allTrue)
937
+ return [null, null];
938
+ }
939
+ }
940
+ if (criteriaOR && Utils.isObject(criteriaOR)) {
941
+ const [searchResult, lineNumbers] = await this.applyCriteria(tableName, options, criteriaOR, false, searchIn);
942
+ if (searchResult) {
943
+ RETURN = Utils.deepMerge(RETURN, searchResult);
944
+ if (!Object.keys(RETURN).length)
945
+ RETURN = {};
946
+ RETURN = Object.fromEntries(Object.entries(RETURN).filter(([_index, item]) => Object.keys(item).filter((key) => Object.keys(criteriaOR).includes(key)).length));
947
+ if (!Object.keys(RETURN).length)
948
+ RETURN = {};
949
+ searchIn = lineNumbers;
805
950
  }
806
951
  }
807
- return [Object.keys(RETURN).length ? RETURN : null, RETURN_LineNumbers];
952
+ return [Object.keys(RETURN).length ? RETURN : null, searchIn];
808
953
  }
809
954
  _filterSchemaByColumns(schema, columns) {
810
955
  return schema
@@ -834,9 +979,8 @@ export default class Inibase {
834
979
  */
835
980
  async clearCache(tableName) {
836
981
  const cacheFolderPath = join(this.databasePath, tableName, ".cache");
837
- await Promise.all((await readdir(cacheFolderPath))
838
- .filter((file) => file !== ".pagination")
839
- .map((file) => unlink(join(cacheFolderPath, file))));
982
+ await rm(cacheFolderPath, { recursive: true, force: true });
983
+ await mkdir(cacheFolderPath);
840
984
  }
841
985
  async get(tableName, where, options = {
842
986
  page: 1,
@@ -855,10 +999,15 @@ export default class Inibase {
855
999
  options.page = options.page || 1;
856
1000
  options.perPage = options.perPage || 15;
857
1001
  let RETURN;
858
- let schema = (await this.getTable(tableName)).schema;
1002
+ let schema = structuredClone((await this.getTable(tableName)).schema);
859
1003
  if (!schema)
860
- throw this.Error("NO_SCHEMA", tableName);
861
- if (!(await File.isExists(join(tablePath, `id${this.getFileExtension(tableName)}`))))
1004
+ throw this.createError("NO_SCHEMA", tableName);
1005
+ let pagination;
1006
+ for await (const paginationFileName of glob("*.pagination", {
1007
+ cwd: tablePath,
1008
+ }))
1009
+ pagination = parse(paginationFileName).name.split("-").map(Number);
1010
+ if (!pagination[1])
862
1011
  return null;
863
1012
  if (options.columns?.length)
864
1013
  schema = this._filterSchemaByColumns(schema, options.columns);
@@ -881,7 +1030,7 @@ export default class Inibase {
881
1030
  .map((column) => [column, true]);
882
1031
  let cacheKey = "";
883
1032
  // Criteria
884
- if (this.tables[tableName].config.cache)
1033
+ if (this.tablesMap.get(tableName).config.cache)
885
1034
  cacheKey = UtilsServer.hashString(inspect(sortArray, { sorted: true }));
886
1035
  if (where) {
887
1036
  const lineNumbers = await this.get(tableName, where, undefined, undefined, true);
@@ -901,7 +1050,7 @@ export default class Inibase {
901
1050
  if (!(await File.isExists(path)))
902
1051
  return null;
903
1052
  // Construct the paste command to merge files and filter lines by IDs
904
- const pasteCommand = `paste ${filesPathes.join(" ")}`;
1053
+ const pasteCommand = `paste '${filesPathes.join("' '")}'`;
905
1054
  // Construct the sort command dynamically based on the number of files for sorting
906
1055
  const index = 2;
907
1056
  const sortColumns = sortArray
@@ -914,15 +1063,15 @@ export default class Inibase {
914
1063
  return "";
915
1064
  })
916
1065
  .join(" ");
917
- const sortCommand = `sort ${sortColumns} -T=${join(tablePath, ".tmp")}`;
1066
+ const sortCommand = `sort ${sortColumns} -T='${join(tablePath, ".tmp")}'`;
918
1067
  try {
919
1068
  if (cacheKey)
920
1069
  await File.lock(join(tablePath, ".tmp"), cacheKey);
921
1070
  // Combine && Execute the commands synchronously
922
- let lines = (await UtilsServer.exec(this.tables[tableName].config.cache
1071
+ let lines = (await UtilsServer.exec(this.tablesMap.get(tableName).config.cache
923
1072
  ? (await File.isExists(join(tablePath, ".cache", `${cacheKey}${this.fileExtension}`)))
924
- ? `${awkCommand} ${join(tablePath, ".cache", `${cacheKey}${this.fileExtension}`)}`
925
- : `${pasteCommand} | ${sortCommand} -o ${join(tablePath, ".cache", `${cacheKey}${this.fileExtension}`)} && ${awkCommand} ${join(tablePath, ".cache", `${cacheKey}${this.fileExtension}`)}`
1073
+ ? `${awkCommand} '${join(tablePath, ".cache", `${cacheKey}${this.fileExtension}`)}'`
1074
+ : `${pasteCommand} | ${sortCommand} -o '${join(tablePath, ".cache", `${cacheKey}${this.fileExtension}`)}' && ${awkCommand} '${join(tablePath, ".cache", `${cacheKey}${this.fileExtension}`)}'`
926
1075
  : `${pasteCommand} | ${sortCommand} | ${awkCommand}`, {
927
1076
  encoding: "utf-8",
928
1077
  })).stdout
@@ -930,8 +1079,8 @@ export default class Inibase {
930
1079
  .split("\n");
931
1080
  if (where)
932
1081
  lines = lines.slice((options.page - 1) * options.perPage, options.page * options.perPage);
933
- else if (!this.totalItems[`${tableName}-*`])
934
- this.totalItems[`${tableName}-*`] = await File.count(join(tablePath, `id${this.getFileExtension(tableName)}`));
1082
+ else if (!this.totalItems.has(`${tableName}-*`))
1083
+ this.totalItems.set(`${tableName}-*`, pagination[1]);
935
1084
  if (!lines.length)
936
1085
  return null;
937
1086
  // Parse the result and extract the specified lines
@@ -961,19 +1110,11 @@ export default class Inibase {
961
1110
  }
962
1111
  if (!where) {
963
1112
  // Display all data
964
- RETURN = Object.values(await this.getItemsFromSchema(tableName, schema, Array.from({ length: options.perPage }, (_, index) => (options.page - 1) * options.perPage +
1113
+ RETURN = Object.values(await this.processSchemaData(tableName, schema, Array.from({ length: options.perPage }, (_, index) => (options.page - 1) * options.perPage +
965
1114
  index +
966
1115
  1), options));
967
- if (await File.isExists(join(tablePath, ".cache", ".pagination"))) {
968
- if (!this.totalItems[`${tableName}-*`])
969
- this.totalItems[`${tableName}-*`] = Number((await readFile(join(tablePath, ".cache", ".pagination"), "utf8")).split(",")[1]);
970
- }
971
- else {
972
- const lastId = Number(Object.keys((await File.get(join(tablePath, `id${this.getFileExtension(tableName)}`), -1, "number", undefined, this.salt, true))?.[0] ?? 0));
973
- if (!this.totalItems[`${tableName}-*`])
974
- this.totalItems[`${tableName}-*`] = await File.count(join(tablePath, `id${this.getFileExtension(tableName)}`));
975
- await writeFile(join(tablePath, ".cache", ".pagination"), `${lastId},${this.totalItems[`${tableName}-*`]}`);
976
- }
1116
+ if (!this.totalItems.has(`${tableName}-*`))
1117
+ this.totalItems.set(`${tableName}-*`, pagination[1]);
977
1118
  }
978
1119
  else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
979
1120
  Utils.isNumber(where)) {
@@ -981,12 +1122,12 @@ export default class Inibase {
981
1122
  let lineNumbers = where;
982
1123
  if (!Array.isArray(lineNumbers))
983
1124
  lineNumbers = [lineNumbers];
984
- if (!this.totalItems[`${tableName}-*`])
985
- this.totalItems[`${tableName}-*`] = lineNumbers.length;
1125
+ if (!this.totalItems.has(`${tableName}-*`))
1126
+ this.totalItems.set(`${tableName}-*`, lineNumbers.length);
986
1127
  // useless
987
1128
  if (onlyLinesNumbers)
988
1129
  return lineNumbers;
989
- RETURN = Object.values((await this.getItemsFromSchema(tableName, schema, lineNumbers, options)) ?? {});
1130
+ RETURN = Object.values((await this.processSchemaData(tableName, schema, lineNumbers, options)) ?? {});
990
1131
  if (RETURN?.length && !Array.isArray(where))
991
1132
  RETURN = RETURN[0];
992
1133
  }
@@ -995,11 +1136,11 @@ export default class Inibase {
995
1136
  let Ids = where;
996
1137
  if (!Array.isArray(Ids))
997
1138
  Ids = [Ids];
998
- const [lineNumbers, countItems] = await File.search(join(tablePath, `id${this.getFileExtension(tableName)}`), "[]", Ids.map((id) => Utils.isNumber(id) ? Number(id) : UtilsServer.decodeID(id, this.salt)), undefined, "number", undefined, Ids.length, 0, !this.totalItems[`${tableName}-*`], this.salt);
1139
+ const [lineNumbers, countItems] = await File.search(join(tablePath, `id${this.getFileExtension(tableName)}`), "[]", Ids.map((id) => Utils.isNumber(id) ? Number(id) : UtilsServer.decodeID(id, this.salt)), undefined, undefined, "number", undefined, Ids.length, 0, !this.totalItems.has(`${tableName}-*`), this.salt);
999
1140
  if (!lineNumbers)
1000
1141
  return null;
1001
- if (!this.totalItems[`${tableName}-*`])
1002
- this.totalItems[`${tableName}-*`] = countItems;
1142
+ if (!this.totalItems.has(`${tableName}-*`))
1143
+ this.totalItems.set(`${tableName}-*`, countItems);
1003
1144
  if (onlyLinesNumbers)
1004
1145
  return Object.keys(lineNumbers).length
1005
1146
  ? Object.keys(lineNumbers).map(Number)
@@ -1009,41 +1150,40 @@ export default class Inibase {
1009
1150
  if (!options.columns?.length)
1010
1151
  options.columns = undefined;
1011
1152
  }
1012
- RETURN = Object.values((await this.getItemsFromSchema(tableName, schema, Object.keys(lineNumbers).map(Number), options)) ?? {});
1153
+ RETURN = Object.values((await this.processSchemaData(tableName, schema, Object.keys(lineNumbers).map(Number), options)) ?? {});
1013
1154
  if (RETURN?.length && !Array.isArray(where))
1014
1155
  RETURN = RETURN[0];
1015
1156
  }
1016
1157
  else if (Utils.isObject(where)) {
1017
1158
  let cachedFilePath = "";
1018
1159
  // Criteria
1019
- if (this.tables[tableName].config.cache)
1160
+ if (this.tablesMap.get(tableName).config.cache)
1020
1161
  cachedFilePath = join(tablePath, ".cache", `${UtilsServer.hashString(inspect(where, { sorted: true }))}${this.fileExtension}`);
1021
- if (this.tables[tableName].config.cache &&
1162
+ if (this.tablesMap.get(tableName).config.cache &&
1022
1163
  (await File.isExists(cachedFilePath))) {
1023
1164
  const cachedItems = (await readFile(cachedFilePath, "utf8")).split(",");
1024
- if (!this.totalItems[`${tableName}-*`])
1025
- this.totalItems[`${tableName}-*`] = cachedItems.length;
1165
+ if (!this.totalItems.has(`${tableName}-*`))
1166
+ this.totalItems.set(`${tableName}-*`, cachedItems.length);
1026
1167
  if (onlyLinesNumbers)
1027
1168
  return onlyOne ? Number(cachedItems[0]) : cachedItems.map(Number);
1028
1169
  return this.get(tableName, cachedItems
1029
1170
  .slice((options.page - 1) * options.perPage, options.page * options.perPage)
1030
1171
  .map(Number), options, onlyOne);
1031
1172
  }
1032
- let linesNumbers = null;
1033
- [RETURN, linesNumbers] = await this.applyCriteria(tableName, schema, options, where);
1034
- if (RETURN && linesNumbers?.size) {
1035
- if (!this.totalItems[`${tableName}-*`])
1036
- this.totalItems[`${tableName}-*`] = linesNumbers.size;
1173
+ const [LineNumberDataMap, linesNumbers] = await this.applyCriteria(tableName, options, where);
1174
+ if (LineNumberDataMap && linesNumbers?.size) {
1175
+ if (!this.totalItems.has(`${tableName}-*`))
1176
+ this.totalItems.set(`${tableName}-*`, linesNumbers.size);
1037
1177
  if (onlyLinesNumbers)
1038
1178
  return onlyOne
1039
1179
  ? linesNumbers.values().next().value
1040
1180
  : Array.from(linesNumbers);
1041
- const alreadyExistsColumns = Object.keys(Object.values(RETURN)[0]), alreadyExistsColumnsIDs = Utils.flattenSchema(schema)
1181
+ const alreadyExistsColumns = Object.keys(Object.values(LineNumberDataMap)[0]), alreadyExistsColumnsIDs = Utils.flattenSchema(schema)
1042
1182
  .filter(({ key }) => alreadyExistsColumns.includes(key))
1043
1183
  .map(({ id }) => id);
1044
- RETURN = Object.values(Utils.deepMerge(RETURN, await this.getItemsFromSchema(tableName, Utils.filterSchema(schema, ({ id, type, children }) => !alreadyExistsColumnsIDs.includes(id) ||
1045
- Utils.isFieldType("table", type, children)), Object.keys(RETURN).map(Number), options)));
1046
- if (this.tables[tableName].config.cache)
1184
+ RETURN = Object.values(Utils.deepMerge(LineNumberDataMap, await this.processSchemaData(tableName, Utils.filterSchema(schema, ({ id, type, children }) => !alreadyExistsColumnsIDs.includes(id) ||
1185
+ Utils.isFieldType("table", type, children)), Object.keys(LineNumberDataMap).map(Number), options)));
1186
+ if (this.tablesMap.get(tableName).config.cache)
1047
1187
  await writeFile(cachedFilePath, Array.from(linesNumbers).join(","));
1048
1188
  }
1049
1189
  }
@@ -1051,8 +1191,9 @@ export default class Inibase {
1051
1191
  (Utils.isObject(RETURN) && !Object.keys(RETURN).length) ||
1052
1192
  (Array.isArray(RETURN) && !RETURN.length))
1053
1193
  return null;
1054
- const greatestTotalItems = this.totalItems[`${tableName}-*`] ??
1055
- Math.max(...Object.entries(this.totalItems)
1194
+ const greatestTotalItems = this.totalItems.has(`${tableName}-*`)
1195
+ ? this.totalItems.get(`${tableName}-*`)
1196
+ : Math.max(...[...this.totalItems.entries()]
1056
1197
  .filter(([k]) => k.startsWith(`${tableName}-`))
1057
1198
  .map(([, v]) => v));
1058
1199
  this.pageInfo[tableName] = {
@@ -1069,70 +1210,64 @@ export default class Inibase {
1069
1210
  page: 1,
1070
1211
  perPage: 15,
1071
1212
  };
1072
- const tablePath = join(this.databasePath, tableName), schema = (await this.getTable(tableName)).schema;
1073
- if (!schema)
1074
- throw this.Error("NO_SCHEMA", tableName);
1213
+ const tablePath = join(this.databasePath, tableName);
1214
+ await this.getTable(tableName);
1215
+ if (!this.tablesMap.get(tableName).schema)
1216
+ throw this.createError("NO_SCHEMA", tableName);
1075
1217
  if (!returnPostedData)
1076
1218
  returnPostedData = false;
1077
- const keys = UtilsServer.hashString(Object.keys(Array.isArray(data) ? data[0] : data).join("."));
1078
- // Skip Id and (created|updated)At
1079
- this.validateData(data, schema.slice(1, -2));
1080
- let lastId = 0, renameList = [];
1219
+ let clonedData = structuredClone(data);
1220
+ const keys = UtilsServer.hashString(Object.keys(Array.isArray(clonedData) ? clonedData[0] : clonedData).join("."));
1221
+ await this.validateData(tableName, clonedData);
1222
+ const renameList = [];
1081
1223
  try {
1082
1224
  await File.lock(join(tablePath, ".tmp"), keys);
1083
- if (await File.isExists(join(tablePath, `id${this.getFileExtension(tableName)}`))) {
1084
- if (await File.isExists(join(tablePath, ".cache", ".pagination")))
1085
- [lastId, this.totalItems[`${tableName}-*`]] = (await readFile(join(tablePath, ".cache", ".pagination"), "utf8"))
1086
- .split(",")
1087
- .map(Number);
1088
- else {
1089
- lastId = Number(Object.keys((await File.get(join(tablePath, `id${this.getFileExtension(tableName)}`), -1, "number", undefined, this.salt, true))?.[0] ?? 0));
1090
- if (!this.totalItems[`${tableName}-*`])
1091
- this.totalItems[`${tableName}-*`] = await File.count(join(tablePath, `id${this.getFileExtension(tableName)}`));
1092
- }
1093
- }
1094
- else
1095
- this.totalItems[`${tableName}-*`] = 0;
1096
- if (Utils.isArrayOfObjects(data))
1097
- for (let index = 0; index < data.length; index++) {
1098
- const element = data[index];
1225
+ let paginationFilePath;
1226
+ for await (const fileName of glob("*.pagination", { cwd: tablePath }))
1227
+ paginationFilePath = join(tablePath, fileName);
1228
+ let [lastId, _totalItems] = parse(paginationFilePath)
1229
+ .name.split("-")
1230
+ .map(Number);
1231
+ this.totalItems.set(`${tableName}-*`, _totalItems);
1232
+ if (Utils.isArrayOfObjects(clonedData))
1233
+ for (let index = 0; index < clonedData.length; index++) {
1234
+ const element = clonedData[index];
1099
1235
  element.id = ++lastId;
1100
1236
  element.createdAt = Date.now();
1101
1237
  element.updatedAt = undefined;
1102
1238
  }
1103
1239
  else {
1104
- data.id = ++lastId;
1105
- data.createdAt = Date.now();
1106
- data.updatedAt = undefined;
1240
+ clonedData.id = ++lastId;
1241
+ clonedData.createdAt = Date.now();
1242
+ clonedData.updatedAt = undefined;
1107
1243
  }
1108
- await this.checkUnique(tableName, schema);
1109
- data = this.formatData(data, schema);
1110
- const pathesContents = this.joinPathesContents(tableName, this.tables[tableName].config.prepend
1111
- ? Array.isArray(data)
1112
- ? data.toReversed()
1113
- : data
1114
- : data);
1115
- await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(this.tables[tableName].config.prepend
1244
+ clonedData = this.formatData(clonedData, this.tablesMap.get(tableName).schema, false);
1245
+ const pathesContents = this.joinPathesContents(tableName, this.tablesMap.get(tableName).config.prepend
1246
+ ? Array.isArray(clonedData)
1247
+ ? clonedData.toReversed()
1248
+ : clonedData
1249
+ : clonedData);
1250
+ await Promise.allSettled(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(this.tablesMap.get(tableName).config.prepend
1116
1251
  ? await File.prepend(path, content)
1117
1252
  : await File.append(path, content))));
1118
- await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
1119
- renameList = [];
1120
- if (this.tables[tableName].config.cache)
1253
+ await Promise.allSettled(renameList
1254
+ .filter(([_, filePath]) => filePath)
1255
+ .map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
1256
+ if (this.tablesMap.get(tableName).config.cache)
1121
1257
  await this.clearCache(tableName);
1122
- this.totalItems[`${tableName}-*`] += Array.isArray(data)
1123
- ? data.length
1124
- : 1;
1125
- await writeFile(join(tablePath, ".cache", ".pagination"), `${lastId},${this.totalItems[`${tableName}-*`]}`);
1258
+ const currentValue = this.totalItems.get(`${tableName}-*`) || 0;
1259
+ this.totalItems.set(`${tableName}-*`, currentValue + (Array.isArray(data) ? data.length : 1));
1260
+ await rename(paginationFilePath, join(tablePath, `${lastId}-${this.totalItems.get(`${tableName}-*`)}.pagination`));
1126
1261
  if (returnPostedData)
1127
- return this.get(tableName, this.tables[tableName].config.prepend
1128
- ? Array.isArray(data)
1129
- ? data.map((_, index) => index + 1).toReversed()
1262
+ return this.get(tableName, this.tablesMap.get(tableName).config.prepend
1263
+ ? Array.isArray(clonedData)
1264
+ ? clonedData.map((_, index) => index + 1).toReversed()
1130
1265
  : 1
1131
- : Array.isArray(data)
1132
- ? data
1133
- .map((_, index) => this.totalItems[`${tableName}-*`] - index)
1266
+ : Array.isArray(clonedData)
1267
+ ? clonedData
1268
+ .map((_, index) => this.totalItems.get(`${tableName}-*`) - index)
1134
1269
  .toReversed()
1135
- : this.totalItems[`${tableName}-*`], options, !Utils.isArrayOfObjects(data));
1270
+ : this.totalItems.get(`${tableName}-*`), options, !Utils.isArrayOfObjects(clonedData));
1136
1271
  }
1137
1272
  finally {
1138
1273
  if (renameList.length)
@@ -1144,45 +1279,38 @@ export default class Inibase {
1144
1279
  page: 1,
1145
1280
  perPage: 15,
1146
1281
  }, returnUpdatedData) {
1147
- let renameList = [];
1282
+ const renameList = [];
1148
1283
  const tablePath = join(this.databasePath, tableName);
1149
- const schema = (await this.throwErrorIfTableEmpty(tableName))
1150
- .schema;
1284
+ await this.throwErrorIfTableEmpty(tableName);
1285
+ let clonedData = structuredClone(data);
1151
1286
  if (!where) {
1152
- if (Utils.isArrayOfObjects(data)) {
1153
- if (!data.every((item) => Object.hasOwn(item, "id") && Utils.isValidID(item.id)))
1154
- throw this.Error("INVALID_ID");
1155
- return this.put(tableName, data, data.map(({ id }) => id), options, returnUpdatedData || undefined);
1287
+ if (Utils.isArrayOfObjects(clonedData)) {
1288
+ if (!clonedData.every((item) => Object.hasOwn(item, "id") && Utils.isValidID(item.id)))
1289
+ throw this.createError("INVALID_ID");
1290
+ return this.put(tableName, clonedData, clonedData.map(({ id }) => id), options, returnUpdatedData);
1156
1291
  }
1157
- if (Object.hasOwn(data, "id")) {
1158
- if (!Utils.isValidID(data.id))
1159
- throw this.Error("INVALID_ID", data.id);
1160
- return this.put(tableName, data, data.id, options, returnUpdatedData || undefined);
1292
+ if (Object.hasOwn(clonedData, "id")) {
1293
+ if (!Utils.isValidID(clonedData.id))
1294
+ throw this.createError("INVALID_ID", clonedData.id);
1295
+ return this.put(tableName, clonedData, clonedData.id, options, returnUpdatedData);
1161
1296
  }
1162
- let totalItems;
1163
- if (await File.isExists(join(tablePath, ".cache", ".pagination")))
1164
- totalItems = (await readFile(join(tablePath, ".cache", ".pagination"), "utf8"))
1165
- .split(",")
1166
- .map(Number)[1];
1167
- else
1168
- totalItems = await File.count(join(tablePath, `id${this.getFileExtension(tableName)}`));
1169
- this.validateData(data, schema, true);
1170
- await this.checkUnique(tableName, schema);
1171
- data = this.formatData(data, schema, true);
1297
+ await this.validateData(tableName, clonedData, true);
1298
+ clonedData = this.formatData(clonedData, this.tablesMap.get(tableName).schema, true);
1172
1299
  const pathesContents = this.joinPathesContents(tableName, {
1173
- ...(({ id, ...restOfData }) => restOfData)(data),
1300
+ ...(({ id, ...restOfData }) => restOfData)(clonedData),
1174
1301
  updatedAt: Date.now(),
1175
1302
  });
1176
1303
  try {
1177
1304
  await File.lock(join(tablePath, ".tmp"));
1178
- await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => {
1179
- const replacementObject = {};
1180
- for (const index of [...Array(totalItems)].keys())
1181
- replacementObject[`${index + 1}`] = content;
1182
- renameList.push(await File.replace(path, replacementObject));
1183
- }));
1184
- await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
1185
- if (this.tables[tableName].config.cache)
1305
+ for await (const paginationFileName of glob("*.pagination", {
1306
+ cwd: tablePath,
1307
+ }))
1308
+ this.totalItems.set(`${tableName}-*`, parse(paginationFileName).name.split("-").map(Number)[1]);
1309
+ await Promise.allSettled(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(await File.replace(path, content, this.totalItems.get(`${tableName}-*`)))));
1310
+ await Promise.allSettled(renameList
1311
+ .filter(([_, filePath]) => filePath)
1312
+ .map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
1313
+ if (this.tablesMap.get(tableName).config.cache)
1186
1314
  await this.clearCache(join(tablePath, ".cache"));
1187
1315
  if (returnUpdatedData)
1188
1316
  return await this.get(tableName, undefined, options);
@@ -1196,20 +1324,19 @@ export default class Inibase {
1196
1324
  else if ((Array.isArray(where) && where.every(Utils.isValidID)) ||
1197
1325
  Utils.isValidID(where)) {
1198
1326
  const lineNumbers = await this.get(tableName, where, undefined, undefined, true);
1199
- return this.put(tableName, data, lineNumbers, options, returnUpdatedData || undefined);
1327
+ return this.put(tableName, clonedData, lineNumbers, options, false);
1200
1328
  }
1201
1329
  else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
1202
1330
  Utils.isNumber(where)) {
1203
1331
  // "where" in this case, is the line(s) number(s) and not id(s)
1204
- this.validateData(data, schema, true);
1205
- await this.checkUnique(tableName, schema);
1206
- data = this.formatData(data, schema, true);
1207
- const pathesContents = Object.fromEntries(Object.entries(this.joinPathesContents(tableName, Utils.isArrayOfObjects(data)
1208
- ? data.map((item) => ({
1332
+ await this.validateData(tableName, clonedData, true);
1333
+ clonedData = this.formatData(clonedData, this.tablesMap.get(tableName).schema, true);
1334
+ const pathesContents = Object.fromEntries(Object.entries(this.joinPathesContents(tableName, Utils.isArrayOfObjects(clonedData)
1335
+ ? clonedData.map((item) => ({
1209
1336
  ...item,
1210
1337
  updatedAt: Date.now(),
1211
1338
  }))
1212
- : { ...data, updatedAt: Date.now() })).map(([path, content]) => [
1339
+ : { ...clonedData, updatedAt: Date.now() })).map(([path, content]) => [
1213
1340
  path,
1214
1341
  [...(Array.isArray(where) ? where : [where])].reduce((obj, lineNum, index) => Object.assign(obj, {
1215
1342
  [lineNum]: Array.isArray(content) ? content[index] : content,
@@ -1220,10 +1347,11 @@ export default class Inibase {
1220
1347
  .join("."));
1221
1348
  try {
1222
1349
  await File.lock(join(tablePath, ".tmp"), keys);
1223
- await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(await File.replace(path, content))));
1224
- await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
1225
- renameList = [];
1226
- if (this.tables[tableName].config.cache)
1350
+ await Promise.allSettled(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(await File.replace(path, content))));
1351
+ await Promise.allSettled(renameList
1352
+ .filter(([_, filePath]) => filePath)
1353
+ .map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
1354
+ if (this.tablesMap.get(tableName).config.cache)
1227
1355
  await this.clearCache(tableName);
1228
1356
  if (returnUpdatedData)
1229
1357
  return this.get(tableName, where, options, !Array.isArray(where));
@@ -1236,10 +1364,11 @@ export default class Inibase {
1236
1364
  }
1237
1365
  else if (Utils.isObject(where)) {
1238
1366
  const lineNumbers = await this.get(tableName, where, undefined, undefined, true);
1239
- return this.put(tableName, data, lineNumbers, options, returnUpdatedData || undefined);
1367
+ if (lineNumbers)
1368
+ return this.put(tableName, clonedData, lineNumbers, options, returnUpdatedData);
1240
1369
  }
1241
1370
  else
1242
- throw this.Error("INVALID_PARAMETERS");
1371
+ throw this.createError("INVALID_PARAMETERS");
1243
1372
  }
1244
1373
  /**
1245
1374
  * Delete item(s) in a table
@@ -1249,17 +1378,27 @@ export default class Inibase {
1249
1378
  * @return {boolean | null} {(Promise<boolean | null>)}
1250
1379
  */
1251
1380
  async delete(tableName, where, _id) {
1252
- const renameList = [];
1253
1381
  const tablePath = join(this.databasePath, tableName);
1254
1382
  await this.throwErrorIfTableEmpty(tableName);
1255
1383
  if (!where) {
1256
1384
  try {
1257
1385
  await File.lock(join(tablePath, ".tmp"));
1386
+ let paginationFilePath;
1387
+ let pagination;
1388
+ for await (const paginationFileName of glob("*.pagination", {
1389
+ cwd: tablePath,
1390
+ })) {
1391
+ paginationFilePath = join(tablePath, paginationFileName);
1392
+ pagination = parse(paginationFileName)
1393
+ .name.split("-")
1394
+ .map(Number);
1395
+ }
1258
1396
  await Promise.all((await readdir(tablePath))
1259
1397
  ?.filter((fileName) => fileName.endsWith(this.getFileExtension(tableName)))
1260
1398
  .map(async (file) => unlink(join(tablePath, file))));
1261
- if (this.tables[tableName].config.cache)
1399
+ if (this.tablesMap.get(tableName).config.cache)
1262
1400
  await this.clearCache(tableName);
1401
+ await rename(paginationFilePath, join(tablePath, `${pagination[0]}-0.pagination`));
1263
1402
  return true;
1264
1403
  }
1265
1404
  finally {
@@ -1276,18 +1415,33 @@ export default class Inibase {
1276
1415
  // "where" in this case, is the line(s) number(s) and not id(s)
1277
1416
  const files = (await readdir(tablePath))?.filter((fileName) => fileName.endsWith(this.getFileExtension(tableName)));
1278
1417
  if (files.length) {
1418
+ const renameList = [];
1279
1419
  try {
1280
1420
  await File.lock(join(tablePath, ".tmp"));
1281
- await Promise.all(files.map(async (file) => renameList.push(await File.remove(join(tablePath, file), where))));
1282
- await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
1283
- if (this.tables[tableName].config.cache)
1284
- await this.clearCache(tableName);
1285
- if (await File.isExists(join(tablePath, ".cache", ".pagination"))) {
1286
- const [lastId, totalItems] = (await readFile(join(tablePath, ".cache", ".pagination"), "utf8"))
1287
- .split(",")
1421
+ let paginationFilePath;
1422
+ let pagination;
1423
+ for await (const paginationFileName of glob("*.pagination", {
1424
+ cwd: tablePath,
1425
+ })) {
1426
+ paginationFilePath = join(tablePath, paginationFileName);
1427
+ pagination = parse(paginationFileName)
1428
+ .name.split("-")
1288
1429
  .map(Number);
1289
- await writeFile(join(tablePath, ".cache", ".pagination"), `${lastId},${totalItems - (Array.isArray(where) ? where.length : 1)}`);
1290
1430
  }
1431
+ if (pagination[1] &&
1432
+ pagination[1] - (Array.isArray(where) ? where.length : 1) > 0) {
1433
+ await Promise.all(files.map(async (file) => renameList.push(await File.remove(join(tablePath, file), where))));
1434
+ await Promise.all(renameList
1435
+ .filter(([_, filePath]) => filePath)
1436
+ .map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
1437
+ }
1438
+ else
1439
+ await Promise.all((await readdir(tablePath))
1440
+ ?.filter((fileName) => fileName.endsWith(this.getFileExtension(tableName)))
1441
+ .map(async (file) => unlink(join(tablePath, file))));
1442
+ if (this.tablesMap.get(tableName).config.cache)
1443
+ await this.clearCache(tableName);
1444
+ await rename(paginationFilePath, join(tablePath, `${pagination[0]}-${pagination[1] - (Array.isArray(where) ? where.length : 1)}.pagination`));
1291
1445
  return true;
1292
1446
  }
1293
1447
  finally {
@@ -1299,10 +1453,11 @@ export default class Inibase {
1299
1453
  }
1300
1454
  else if (Utils.isObject(where)) {
1301
1455
  const lineNumbers = await this.get(tableName, where, undefined, undefined, true);
1302
- return this.delete(tableName, lineNumbers);
1456
+ if (lineNumbers)
1457
+ return this.delete(tableName, lineNumbers);
1303
1458
  }
1304
1459
  else
1305
- throw this.Error("INVALID_PARAMETERS");
1460
+ throw this.createError("INVALID_PARAMETERS");
1306
1461
  return false;
1307
1462
  }
1308
1463
  async sum(tableName, columns, where) {