inibase 1.0.0-rc.4 → 1.0.0-rc.40

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 ADDED
@@ -0,0 +1,1106 @@
1
+ import { unlink, rename, mkdir, readdir } from "node:fs/promises";
2
+ import { existsSync, appendFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { scryptSync, randomBytes } from "node:crypto";
5
+ import { Worker } from "node:worker_threads";
6
+ import File from "./file.js";
7
+ import Utils from "./utils.js";
8
+ import UtilsServer from "./utils.server.js";
9
+ import Config from "./config.js";
10
+ import { inspect } from "node:util";
11
+ export default class Inibase {
12
+ folder;
13
+ database;
14
+ table;
15
+ pageInfo;
16
+ isThreadEnabled = false;
17
+ totalItems;
18
+ salt;
19
+ constructor(database, mainFolder = ".", _table = null, _totalItems = {}, _pageInfo = {}, _isThreadEnabled = false) {
20
+ this.database = database;
21
+ this.folder = mainFolder;
22
+ this.table = _table;
23
+ this.totalItems = _totalItems;
24
+ this.pageInfo = _pageInfo;
25
+ this.isThreadEnabled = _isThreadEnabled;
26
+ if (!existsSync(".env") || !process.env.INIBASE_SECRET) {
27
+ this.salt = scryptSync(randomBytes(16), randomBytes(16), 32);
28
+ appendFileSync(".env", `\nINIBASE_SECRET=${this.salt.toString("hex")}\n`);
29
+ }
30
+ else
31
+ this.salt = Buffer.from(process.env.INIBASE_SECRET, "hex");
32
+ }
33
+ throwError(code, variable, language = "en") {
34
+ const errorMessages = {
35
+ en: {
36
+ FIELD_REQUIRED: "Field {variable} is required",
37
+ NO_SCHEMA: "Table {variable} does't have a schema",
38
+ NO_ITEMS: "Table {variable} is empty",
39
+ NO_RESULTS: "No results found for table {variable}",
40
+ INVALID_ID: "The given ID(s) is/are not valid(s)",
41
+ INVALID_TYPE: "Expect {variable} to be {variable}, got {variable} instead",
42
+ INVALID_PARAMETERS: "The given parameters are not valid",
43
+ },
44
+ // Add more languages and error messages as needed
45
+ };
46
+ let errorMessage = errorMessages[language][code];
47
+ if (!errorMessage)
48
+ return new Error("ERR");
49
+ return new Error(variable
50
+ ? Array.isArray(variable)
51
+ ? errorMessage.replace(/\{variable\}/g, () => variable.shift()?.toString() ?? "")
52
+ : errorMessage.replaceAll(`{variable}`, `'${variable.toString()}'`)
53
+ : errorMessage.replaceAll(`{variable}`, ""));
54
+ }
55
+ async createWorker(functionName, arg) {
56
+ return new Promise((resolve, reject) => {
57
+ const worker = new Worker("./dist/index.thread.js", {
58
+ workerData: {
59
+ _constructor: [
60
+ this.database,
61
+ this.folder,
62
+ this.table,
63
+ this.totalItems,
64
+ this.pageInfo,
65
+ true, // enable Thread
66
+ ],
67
+ functionName,
68
+ arg,
69
+ },
70
+ });
71
+ worker.on("message", resolve);
72
+ worker.on("error", reject);
73
+ });
74
+ }
75
+ _decodeIdFromSchema = (schema) => schema.map((field) => {
76
+ if ((field.type === "array" || field.type === "object") &&
77
+ field.children &&
78
+ Utils.isArrayOfObjects(field.children))
79
+ field.children = this._decodeIdFromSchema(field.children);
80
+ if (field.id && !Utils.isNumber(field.id))
81
+ field.id = UtilsServer.decodeID(field.id, this.salt);
82
+ return field;
83
+ });
84
+ _schemaToIdsPath = (schema, prefix = "") => {
85
+ let RETURN = {};
86
+ for (const field of schema)
87
+ if ((field.type === "array" || field.type === "object") &&
88
+ field.children &&
89
+ Utils.isArrayOfObjects(field.children)) {
90
+ Utils.deepMerge(RETURN, this._schemaToIdsPath(field.children, (prefix ?? "") + field.key + "."));
91
+ }
92
+ else if (Utils.isValidID(field.id))
93
+ RETURN[UtilsServer.decodeID(field.id, this.salt)] =
94
+ (prefix ?? "") + field.key + ".inib";
95
+ return RETURN;
96
+ };
97
+ async setTableSchema(tableName, schema) {
98
+ const tablePath = join(this.folder, this.database, tableName), tableSchemaPath = join(tablePath, "schema.json"), isTablePathExists = await File.isExists(tablePath);
99
+ // remove id from schema
100
+ schema = schema.filter(({ key }) => !["id", "createdAt", "updatedAt"].includes(key));
101
+ schema = UtilsServer.addIdToSchema(schema, UtilsServer.findLastIdNumber(schema, this.salt), this.salt, isTablePathExists);
102
+ if (!isTablePathExists)
103
+ await mkdir(tablePath, { recursive: true });
104
+ if (!(await File.isExists(join(tablePath, ".tmp"))))
105
+ await mkdir(join(tablePath, ".tmp"));
106
+ if (!(await File.isExists(join(tablePath, ".cache"))))
107
+ await mkdir(join(tablePath, ".cache"));
108
+ if (await File.isExists(tableSchemaPath)) {
109
+ // update columns files names based on field id
110
+ const replaceOldPathes = Utils.findChangedProperties(this._schemaToIdsPath((await this.getTableSchema(tableName)) ?? []), this._schemaToIdsPath(schema));
111
+ if (replaceOldPathes)
112
+ await Promise.all(Object.entries(replaceOldPathes).map(async ([oldPath, newPath]) => {
113
+ if (await File.isExists(join(tablePath, oldPath)))
114
+ await rename(join(tablePath, oldPath), join(tablePath, newPath));
115
+ }));
116
+ }
117
+ await File.write(join(tablePath, "schema.json"), JSON.stringify(isTablePathExists ? this._decodeIdFromSchema(schema) : schema, null, 2), true);
118
+ }
119
+ async getTableSchema(tableName) {
120
+ const tableSchemaPath = join(this.folder, this.database, tableName, "schema.json");
121
+ if (!(await File.isExists(tableSchemaPath)))
122
+ return undefined;
123
+ const schemaFile = await File.read(tableSchemaPath, true);
124
+ if (!schemaFile)
125
+ return undefined;
126
+ const schema = JSON.parse(schemaFile), lastIdNumber = UtilsServer.findLastIdNumber(schema, this.salt);
127
+ return [
128
+ {
129
+ id: UtilsServer.encodeID(0, this.salt),
130
+ key: "id",
131
+ type: "id",
132
+ required: true,
133
+ },
134
+ ...UtilsServer.addIdToSchema(schema, lastIdNumber, this.salt),
135
+ {
136
+ id: UtilsServer.encodeID(lastIdNumber + 1, this.salt),
137
+ key: "createdAt",
138
+ type: "date",
139
+ required: true,
140
+ },
141
+ {
142
+ id: UtilsServer.encodeID(lastIdNumber + 2, this.salt),
143
+ key: "updatedAt",
144
+ type: "date",
145
+ required: false,
146
+ },
147
+ ];
148
+ }
149
+ getField(keyPath, schema) {
150
+ let RETURN = null;
151
+ const keyPathSplited = keyPath.split(".");
152
+ for (const [index, key] of keyPathSplited.entries()) {
153
+ const foundItem = schema.find((item) => item.key === key);
154
+ if (!foundItem)
155
+ return null;
156
+ if (index === keyPathSplited.length - 1)
157
+ RETURN = foundItem;
158
+ if ((foundItem.type === "array" || foundItem.type === "object") &&
159
+ foundItem.children &&
160
+ Utils.isArrayOfObjects(foundItem.children))
161
+ RETURN = foundItem.children;
162
+ }
163
+ if (!RETURN)
164
+ return null;
165
+ return Utils.isArrayOfObjects(RETURN) ? RETURN[0] : RETURN;
166
+ }
167
+ validateData(data, schema, skipRequiredField = false) {
168
+ if (Utils.isArrayOfObjects(data))
169
+ for (const single_data of data)
170
+ this.validateData(single_data, schema, skipRequiredField);
171
+ else if (Utils.isObject(data)) {
172
+ for (const field of schema) {
173
+ if (!data.hasOwnProperty(field.key) &&
174
+ field.required &&
175
+ !skipRequiredField)
176
+ throw this.throwError("FIELD_REQUIRED", field.key);
177
+ if (data.hasOwnProperty(field.key) &&
178
+ !Utils.validateFieldType(data[field.key], field.type, (field.type === "array" || field.type === "object") &&
179
+ field.children &&
180
+ !Utils.isArrayOfObjects(field.children)
181
+ ? field.children
182
+ : undefined))
183
+ throw this.throwError("INVALID_TYPE", [
184
+ field.key,
185
+ field.type,
186
+ typeof data[field.key],
187
+ ]);
188
+ if ((field.type === "array" || field.type === "object") &&
189
+ field.children &&
190
+ Utils.isArrayOfObjects(field.children))
191
+ this.validateData(data[field.key], field.children, skipRequiredField);
192
+ }
193
+ }
194
+ }
195
+ formatField(value, field, formatOnlyAvailiableKeys) {
196
+ if (Array.isArray(field.type))
197
+ field.type = Utils.detectFieldType(value, field.type) ?? field.type[0];
198
+ switch (field.type) {
199
+ case "array":
200
+ if (typeof field.children === "string") {
201
+ if (field.children === "table") {
202
+ if (Array.isArray(value)) {
203
+ if (Utils.isArrayOfObjects(value)) {
204
+ if (value.every((item) => item.hasOwnProperty("id") &&
205
+ (Utils.isValidID(item.id) || Utils.isNumber(item.id))))
206
+ value.map((item) => item.id
207
+ ? Utils.isNumber(item.id)
208
+ ? Number(item.id)
209
+ : UtilsServer.decodeID(item.id, this.salt)
210
+ : null);
211
+ }
212
+ else if (value.every(Utils.isValidID) ||
213
+ value.every(Utils.isNumber))
214
+ return value.map((item) => Utils.isNumber(item)
215
+ ? Number(item)
216
+ : UtilsServer.decodeID(item, this.salt));
217
+ }
218
+ else if (Utils.isValidID(value))
219
+ return [UtilsServer.decodeID(value, this.salt)];
220
+ else if (Utils.isNumber(value))
221
+ return [Number(value)];
222
+ }
223
+ else
224
+ return Array.isArray(value) ? value : [value];
225
+ }
226
+ else if (Utils.isArrayOfObjects(field.children))
227
+ return this.formatData(value, field.children, formatOnlyAvailiableKeys);
228
+ else if (Array.isArray(field.children))
229
+ return Array.isArray(value) ? value : [value];
230
+ break;
231
+ case "object":
232
+ if (Utils.isArrayOfObjects(field.children))
233
+ return this.formatData(value, field.children, formatOnlyAvailiableKeys);
234
+ break;
235
+ case "table":
236
+ if (Array.isArray(value))
237
+ value = value[0];
238
+ if (Utils.isObject(value)) {
239
+ if (value.hasOwnProperty("id") &&
240
+ (Utils.isValidID(value.id) ||
241
+ Utils.isNumber(value.id)))
242
+ return Utils.isNumber(value.id)
243
+ ? Number(value.id)
244
+ : UtilsServer.decodeID(value.id, this.salt);
245
+ }
246
+ else if (Utils.isValidID(value) || Utils.isNumber(value))
247
+ return Utils.isNumber(value)
248
+ ? Number(value)
249
+ : UtilsServer.decodeID(value, this.salt);
250
+ break;
251
+ case "password":
252
+ if (Array.isArray(value))
253
+ value = value[0];
254
+ return Utils.isPassword(value)
255
+ ? value
256
+ : UtilsServer.hashPassword(String(value));
257
+ case "number":
258
+ if (Array.isArray(value))
259
+ value = value[0];
260
+ return Utils.isNumber(value) ? Number(value) : null;
261
+ case "id":
262
+ if (Array.isArray(value))
263
+ value = value[0];
264
+ return Utils.isNumber(value)
265
+ ? value
266
+ : UtilsServer.decodeID(value, this.salt);
267
+ case "json":
268
+ return JSON.stringify(value);
269
+ default:
270
+ return value;
271
+ }
272
+ return null;
273
+ }
274
+ formatData(data, schema, formatOnlyAvailiableKeys) {
275
+ this.validateData(data, schema, formatOnlyAvailiableKeys);
276
+ if (Utils.isArrayOfObjects(data))
277
+ return data.map((single_data) => this.formatData(single_data, schema, formatOnlyAvailiableKeys));
278
+ else if (Utils.isObject(data)) {
279
+ let RETURN = {};
280
+ for (const field of schema) {
281
+ if (!data.hasOwnProperty(field.key)) {
282
+ if (formatOnlyAvailiableKeys || !field.required)
283
+ continue;
284
+ RETURN[field.key] = this.getDefaultValue(field);
285
+ continue;
286
+ }
287
+ RETURN[field.key] = this.formatField(data[field.key], field, formatOnlyAvailiableKeys);
288
+ }
289
+ return RETURN;
290
+ }
291
+ else
292
+ return [];
293
+ }
294
+ getDefaultValue(field) {
295
+ if (Array.isArray(field.type))
296
+ return this.getDefaultValue({
297
+ ...field,
298
+ type: field.type.sort((a, b) => Number(b === "array") - Number(a === "array") ||
299
+ Number(a === "string") - Number(b === "string") ||
300
+ Number(a === "number") - Number(b === "number"))[0],
301
+ });
302
+ switch (field.type) {
303
+ case "array":
304
+ return Utils.isArrayOfObjects(field.children)
305
+ ? [
306
+ this.getDefaultValue({
307
+ ...field,
308
+ type: "object",
309
+ children: field.children,
310
+ }),
311
+ ]
312
+ : [];
313
+ case "object":
314
+ return Utils.combineObjects(field.children.map((f) => ({
315
+ [f.key]: this.getDefaultValue(f),
316
+ })));
317
+ case "boolean":
318
+ return false;
319
+ default:
320
+ return null;
321
+ }
322
+ }
323
+ _combineObjectsToArray = (input) => input.reduce((result, current) => {
324
+ for (const [key, value] of Object.entries(current))
325
+ if (!result[key])
326
+ result[key] = [value];
327
+ else
328
+ result[key].push(value);
329
+ return result;
330
+ }, {});
331
+ _CombineData = (_data, prefix) => {
332
+ let RETURN = {};
333
+ if (Utils.isArrayOfObjects(_data))
334
+ RETURN = this._combineObjectsToArray(_data.map((single_data) => this._CombineData(single_data)));
335
+ else
336
+ for (const [key, value] of Object.entries(_data)) {
337
+ if (Utils.isObject(value))
338
+ Object.assign(RETURN, this._CombineData(value, `${key}.`));
339
+ else if (Utils.isArrayOfObjects(value)) {
340
+ Object.assign(RETURN, this._CombineData(this._combineObjectsToArray(value), (prefix ?? "") + key + "."));
341
+ }
342
+ else if (Utils.isArrayOfArrays(value) &&
343
+ value.every(Utils.isArrayOfObjects))
344
+ Object.assign(RETURN, this._CombineData(this._combineObjectsToArray(value.map(this._combineObjectsToArray)), (prefix ?? "") + key + "."));
345
+ else
346
+ RETURN[(prefix ?? "") + key] = File.encode(value);
347
+ }
348
+ return RETURN;
349
+ };
350
+ _addPathToKeys = (obj, path) => {
351
+ const newObject = {};
352
+ for (const key in obj)
353
+ newObject[join(path, key + ".inib")] = obj[key];
354
+ return newObject;
355
+ };
356
+ joinPathesContents(mainPath, data) {
357
+ return this._addPathToKeys(this._CombineData(data), mainPath);
358
+ }
359
+ async getItemsFromSchema(tableName, schema, linesNumber, options, prefix) {
360
+ const tablePath = join(this.folder, this.database, tableName);
361
+ let RETURN = {};
362
+ await Promise.all(schema.map(async (field) => {
363
+ if ((field.type === "array" ||
364
+ (Array.isArray(field.type) && field.type.includes("array"))) &&
365
+ field.children) {
366
+ if (Utils.isArrayOfObjects(field.children)) {
367
+ if (field.children.filter((children) => children.type === "array" &&
368
+ Utils.isArrayOfObjects(children.children)).length) {
369
+ // one of children has array field type and has children array of object = Schema
370
+ Object.entries((await this.getItemsFromSchema(tableName, field.children.filter((children) => children.type === "array" &&
371
+ Utils.isArrayOfObjects(children.children)), linesNumber, options, (prefix ?? "") + field.key + ".")) ?? {}).forEach(([index, item]) => {
372
+ if (Utils.isObject(item)) {
373
+ if (!RETURN[index])
374
+ RETURN[index] = {};
375
+ if (!RETURN[index][field.key])
376
+ RETURN[index][field.key] = [];
377
+ for (const child_field of field.children.filter((children) => children.type === "array" &&
378
+ Utils.isArrayOfObjects(children.children))) {
379
+ if (Utils.isObject(item[child_field.key])) {
380
+ Object.entries(item[child_field.key]).forEach(([key, value]) => {
381
+ if (!Utils.isArrayOfArrays(value))
382
+ value = value.map((_value) => child_field.type === "array"
383
+ ? [[_value]]
384
+ : [_value]);
385
+ for (let _i = 0; _i < value.length; _i++) {
386
+ if (Utils.isArrayOfNulls(value[_i]))
387
+ continue;
388
+ if (!RETURN[index][field.key][_i])
389
+ RETURN[index][field.key][_i] = {};
390
+ if (!RETURN[index][field.key][_i][child_field.key])
391
+ RETURN[index][field.key][_i][child_field.key] =
392
+ [];
393
+ value[_i].forEach((_element, _index) => {
394
+ if (!RETURN[index][field.key][_i][child_field.key][_index])
395
+ RETURN[index][field.key][_i][child_field.key][_index] = {};
396
+ RETURN[index][field.key][_i][child_field.key][_index][key] = _element;
397
+ });
398
+ }
399
+ });
400
+ }
401
+ }
402
+ }
403
+ });
404
+ field.children = field.children.filter((children) => children.type !== "array" ||
405
+ !Utils.isArrayOfObjects(children.children));
406
+ }
407
+ Object.entries((await this.getItemsFromSchema(tableName, field.children, linesNumber, options, (prefix ?? "") + field.key + ".")) ?? {}).forEach(([index, item]) => {
408
+ if (!RETURN[index])
409
+ RETURN[index] = {};
410
+ if (Utils.isObject(item)) {
411
+ if (!Object.values(item).every((i) => i === null)) {
412
+ if (RETURN[index][field.key]) {
413
+ Object.entries(item).forEach(([key, value], _index) => {
414
+ RETURN[index][field.key] = RETURN[index][field.key].map((_obj, _i) => ({
415
+ ..._obj,
416
+ [key]: value[_i],
417
+ }));
418
+ });
419
+ }
420
+ else if (Object.values(item).every((_i) => Utils.isArrayOfArrays(_i) || Array.isArray(_i)))
421
+ RETURN[index][field.key] = item;
422
+ else {
423
+ RETURN[index][field.key] = [];
424
+ Object.entries(item).forEach(([key, value]) => {
425
+ for (let _i = 0; _i < value.length; _i++) {
426
+ if (value[_i] === null ||
427
+ (Array.isArray(value[_i]) &&
428
+ value[_i].every((_item) => _item === null)))
429
+ continue;
430
+ if (!RETURN[index][field.key][_i])
431
+ RETURN[index][field.key][_i] = {};
432
+ RETURN[index][field.key][_i][key] = value[_i];
433
+ }
434
+ });
435
+ }
436
+ }
437
+ else
438
+ RETURN[index][field.key] = null;
439
+ }
440
+ else
441
+ RETURN[index][field.key] = item;
442
+ });
443
+ }
444
+ else if (field.children === "table" ||
445
+ (Array.isArray(field.type) && field.type.includes("table")) ||
446
+ (Array.isArray(field.children) && field.children.includes("table"))) {
447
+ if (options.columns)
448
+ options.columns = options.columns
449
+ .filter((column) => column.includes(`${field.key}.`))
450
+ .map((column) => column.replace(`${field.key}.`, ""));
451
+ const items = await File.get(join(tablePath, (prefix ?? "") + field.key + ".inib"), linesNumber, field.type, field.children, this.salt);
452
+ if (items)
453
+ await Promise.all(Object.entries(items).map(async ([index, item]) => {
454
+ if (!RETURN[index])
455
+ RETURN[index] = {};
456
+ RETURN[index][field.key] = item
457
+ ? await this.get(field.key, item, options)
458
+ : this.getDefaultValue(field);
459
+ }));
460
+ }
461
+ else if (await File.isExists(join(tablePath, (prefix ?? "") + field.key + ".inib"))) {
462
+ const items = await File.get(join(tablePath, (prefix ?? "") + field.key + ".inib"), linesNumber, field.type, field?.children, this.salt);
463
+ if (items)
464
+ for (const [index, item] of Object.entries(items)) {
465
+ if (!RETURN[index])
466
+ RETURN[index] = {};
467
+ RETURN[index][field.key] = item ?? this.getDefaultValue(field);
468
+ }
469
+ }
470
+ }
471
+ else if (field.type === "object") {
472
+ for await (const [index, item] of Object.entries((await this.getItemsFromSchema(tableName, field.children, linesNumber, options, (prefix ?? "") + field.key + ".")) ?? {})) {
473
+ if (!RETURN[index])
474
+ RETURN[index] = {};
475
+ if (Utils.isObject(item)) {
476
+ if (!Object.values(item).every((i) => i === null))
477
+ RETURN[index][field.key] = item;
478
+ else
479
+ RETURN[index][field.key] = null;
480
+ }
481
+ else
482
+ RETURN[index][field.key] = null;
483
+ }
484
+ }
485
+ else if (field.type === "table") {
486
+ if ((await File.isExists(join(this.folder, this.database, field.key))) &&
487
+ (await File.isExists(join(tablePath, (prefix ?? "") + field.key + ".inib")))) {
488
+ if (options.columns)
489
+ options.columns = options.columns
490
+ .filter((column) => column.includes(`${field.key}.`) &&
491
+ !column.includes(`${field.key}.`))
492
+ .map((column) => column.replace(`${field.key}.`, ""));
493
+ const items = await File.get(join(tablePath, (prefix ?? "") + field.key + ".inib"), linesNumber, "number", undefined, this.salt);
494
+ if (items)
495
+ for await (const [index, item] of Object.entries(items)) {
496
+ if (!RETURN[index])
497
+ RETURN[index] = {};
498
+ RETURN[index][field.key] = item
499
+ ? await this.get(field.key, item, options)
500
+ : this.getDefaultValue(field);
501
+ }
502
+ }
503
+ }
504
+ else if (await File.isExists(join(tablePath, (prefix ?? "") + field.key + ".inib"))) {
505
+ const items = await File.get(join(tablePath, (prefix ?? "") + field.key + ".inib"), linesNumber, field.type, field?.children, this.salt);
506
+ if (items)
507
+ for (const [index, item] of Object.entries(items)) {
508
+ if (!RETURN[index])
509
+ RETURN[index] = {};
510
+ RETURN[index][field.key] = item ?? this.getDefaultValue(field);
511
+ }
512
+ else
513
+ RETURN = Object.fromEntries(Object.entries(RETURN).map(([index, data]) => [
514
+ index,
515
+ { ...data, [field.key]: this.getDefaultValue(field) },
516
+ ]));
517
+ }
518
+ }));
519
+ return RETURN;
520
+ }
521
+ async applyCriteria(tableName, schema, options, criteria, allTrue) {
522
+ const tablePath = join(this.folder, this.database, tableName);
523
+ let RETURN = {}, RETURN_LineNumbers = null;
524
+ if (!criteria)
525
+ return [null, null];
526
+ if (criteria.and && Utils.isObject(criteria.and)) {
527
+ const [searchResult, lineNumbers] = await this.applyCriteria(tableName, schema, options, criteria.and, true);
528
+ if (searchResult) {
529
+ RETURN = Utils.deepMerge(RETURN, Object.fromEntries(Object.entries(searchResult).filter(([_k, v], _i) => Object.keys(v).length === Object.keys(criteria.and ?? {}).length)));
530
+ delete criteria.and;
531
+ RETURN_LineNumbers = lineNumbers;
532
+ }
533
+ else
534
+ return [null, null];
535
+ }
536
+ if (criteria.or && Utils.isObject(criteria.or)) {
537
+ const [searchResult, lineNumbers] = await this.applyCriteria(tableName, schema, options, criteria.or, false);
538
+ delete criteria.or;
539
+ if (searchResult) {
540
+ RETURN = Utils.deepMerge(RETURN, searchResult);
541
+ RETURN_LineNumbers = lineNumbers;
542
+ }
543
+ }
544
+ if (Object.keys(criteria).length > 0) {
545
+ if (allTrue === undefined)
546
+ allTrue = true;
547
+ let index = -1;
548
+ for await (const [key, value] of Object.entries(criteria)) {
549
+ const field = this.getField(key, schema);
550
+ index++;
551
+ let searchOperator = undefined, searchComparedAtValue = undefined, searchLogicalOperator = undefined;
552
+ if (Utils.isObject(value)) {
553
+ if (value?.or &&
554
+ Array.isArray(value?.or)) {
555
+ const searchCriteria = (value?.or)
556
+ .map((single_or) => typeof single_or === "string"
557
+ ? Utils.FormatObjectCriteriaValue(single_or)
558
+ : ["=", single_or])
559
+ .filter((a) => a);
560
+ if (searchCriteria.length > 0) {
561
+ searchOperator = searchCriteria.map((single_or) => single_or[0]);
562
+ searchComparedAtValue = searchCriteria.map((single_or) => single_or[1]);
563
+ searchLogicalOperator = "or";
564
+ }
565
+ delete value?.or;
566
+ }
567
+ if (value?.and &&
568
+ Array.isArray(value?.and)) {
569
+ const searchCriteria = (value?.and)
570
+ .map((single_and) => typeof single_and === "string"
571
+ ? Utils.FormatObjectCriteriaValue(single_and)
572
+ : ["=", single_and])
573
+ .filter((a) => a);
574
+ if (searchCriteria.length > 0) {
575
+ searchOperator = searchCriteria.map((single_and) => single_and[0]);
576
+ searchComparedAtValue = searchCriteria.map((single_and) => single_and[1]);
577
+ searchLogicalOperator = "and";
578
+ }
579
+ delete value?.and;
580
+ }
581
+ }
582
+ else if (Array.isArray(value)) {
583
+ const searchCriteria = value
584
+ .map((single) => typeof single === "string"
585
+ ? Utils.FormatObjectCriteriaValue(single)
586
+ : ["=", single])
587
+ .filter((a) => a);
588
+ if (searchCriteria.length > 0) {
589
+ searchOperator = searchCriteria.map((single) => single[0]);
590
+ searchComparedAtValue = searchCriteria.map((single) => single[1]);
591
+ searchLogicalOperator = "and";
592
+ }
593
+ }
594
+ else if (typeof value === "string") {
595
+ const ComparisonOperatorValue = Utils.FormatObjectCriteriaValue(value);
596
+ if (ComparisonOperatorValue) {
597
+ searchOperator = ComparisonOperatorValue[0];
598
+ searchComparedAtValue = ComparisonOperatorValue[1];
599
+ }
600
+ }
601
+ else {
602
+ searchOperator = "=";
603
+ searchComparedAtValue = value;
604
+ }
605
+ const [searchResult, totalLines, linesNumbers] = await File.search(join(tablePath, key + ".inib"), searchOperator ?? "=", searchComparedAtValue ?? null, searchLogicalOperator, field?.type, field?.children, options.perPage, options.page - 1 * options.perPage + 1, true, this.salt);
606
+ if (searchResult) {
607
+ RETURN = Utils.deepMerge(RETURN, Object.fromEntries(Object.entries(searchResult).map(([id, value]) => [
608
+ id,
609
+ {
610
+ [key]: value,
611
+ },
612
+ ])));
613
+ this.totalItems[tableName + "-" + key] = totalLines;
614
+ RETURN_LineNumbers = linesNumbers;
615
+ }
616
+ if (allTrue && index > 0) {
617
+ if (!Object.keys(RETURN).length)
618
+ RETURN = {};
619
+ RETURN = Object.fromEntries(Object.entries(RETURN).filter(([_index, item]) => Object.keys(item).length > index));
620
+ if (!Object.keys(RETURN).length)
621
+ RETURN = {};
622
+ }
623
+ }
624
+ }
625
+ return [Object.keys(RETURN).length ? RETURN : null, RETURN_LineNumbers];
626
+ }
627
+ _filterSchemaByColumns(schema, columns) {
628
+ return schema
629
+ .map((field) => {
630
+ if (columns.some((column) => column.startsWith("!")))
631
+ return columns.includes("!" + field.key) ? null : field;
632
+ if (columns.includes(field.key) || columns.includes("*"))
633
+ return field;
634
+ if ((field.type === "array" || field.type === "object") &&
635
+ Utils.isArrayOfObjects(field.children) &&
636
+ columns.filter((column) => column.startsWith(field.key + ".") ||
637
+ column.startsWith("!" + field.key + ".")).length) {
638
+ field.children = this._filterSchemaByColumns(field.children, columns
639
+ .filter((column) => column.startsWith(field.key + ".") ||
640
+ column.startsWith("!" + field.key + "."))
641
+ .map((column) => column.replace(field.key + ".", "")));
642
+ return field;
643
+ }
644
+ return null;
645
+ })
646
+ .filter((i) => i);
647
+ }
648
+ async clearCache(tablePath) {
649
+ await Promise.all((await readdir(join(tablePath, ".cache")))
650
+ ?.filter((fileName) => fileName !== "pagination.inib")
651
+ .map(async (file) => unlink(join(tablePath, ".cache", file))));
652
+ }
653
+ async get(tableName, where, options = {
654
+ page: 1,
655
+ perPage: 15,
656
+ }, onlyOne, onlyLinesNumbers, tableSchema) {
657
+ const tablePath = join(this.folder, this.database, tableName);
658
+ // Ensure options.columns is an array
659
+ if (options.columns) {
660
+ options.columns = Array.isArray(options.columns)
661
+ ? options.columns
662
+ : [options.columns];
663
+ if (options.columns.length && !options.columns.includes("id"))
664
+ options.columns.push("id");
665
+ }
666
+ // Default values for page and perPage
667
+ options.page = options.page || 1;
668
+ options.perPage = options.perPage || 15;
669
+ let RETURN;
670
+ let schema = tableSchema ?? (await this.getTableSchema(tableName));
671
+ if (!schema)
672
+ throw this.throwError("NO_SCHEMA", tableName);
673
+ if (!(await File.isExists(join(tablePath, "id.inib"))))
674
+ return null;
675
+ if (options.columns && options.columns.length)
676
+ schema = this._filterSchemaByColumns(schema, options.columns);
677
+ if (!where) {
678
+ // Display all data
679
+ RETURN = Object.values(await this.getItemsFromSchema(tableName, schema, Array.from({ length: options.perPage }, (_, index) => (options.page - 1) * options.perPage +
680
+ index +
681
+ 1), options));
682
+ if (Config.isCacheEnabled &&
683
+ (await File.isExists(join(tablePath, ".cache", "pagination.inib"))))
684
+ this.totalItems[tableName + "-*"] = Number((await File.read(join(tablePath, ".cache", "pagination.inib"), true)).split(",")[1]);
685
+ else {
686
+ let [lastId, totalItems] = await File.get(join(tablePath, "id.inib"), -1, "number", undefined, this.salt, true);
687
+ if (lastId)
688
+ lastId = Number(Object.keys(lastId)[0] ?? 0);
689
+ this.totalItems[tableName + "-*"] = totalItems;
690
+ if (Config.isCacheEnabled)
691
+ await File.write(join(tablePath, ".cache", "pagination.inib"), `${lastId},${totalItems}`, true);
692
+ }
693
+ }
694
+ else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
695
+ (Utils.isNumber(where) && !onlyLinesNumbers)) {
696
+ // "where" in this case, is the lineNumbers instead of IDs
697
+ let lineNumbers = where;
698
+ if (!Array.isArray(lineNumbers))
699
+ lineNumbers = [lineNumbers];
700
+ RETURN = Object.values((await this.getItemsFromSchema(tableName, schema, lineNumbers, options)) ?? {});
701
+ if (!this.totalItems[tableName + "-*"])
702
+ this.totalItems[tableName + "-*"] = lineNumbers.length;
703
+ if (RETURN && RETURN.length && !Array.isArray(where))
704
+ RETURN = RETURN[0];
705
+ }
706
+ else if ((Array.isArray(where) &&
707
+ (where.every(Utils.isValidID) || where.every(Utils.isNumber))) ||
708
+ Utils.isValidID(where) ||
709
+ Utils.isNumber(where)) {
710
+ let Ids = where;
711
+ if (!Array.isArray(Ids))
712
+ Ids = [Ids];
713
+ const [lineNumbers, countItems] = await File.search(join(tablePath, "id.inib"), "[]", Ids.map((id) => Utils.isNumber(id) ? Number(id) : UtilsServer.decodeID(id, this.salt)), undefined, "number", undefined, Ids.length, 0, !this.totalItems[tableName + "-*"], this.salt);
714
+ if (!lineNumbers)
715
+ throw this.throwError("NO_RESULTS", tableName);
716
+ if (onlyLinesNumbers)
717
+ return Object.keys(lineNumbers).length
718
+ ? Object.keys(lineNumbers).map(Number)
719
+ : null;
720
+ RETURN = Object.values((await this.getItemsFromSchema(tableName, schema, Object.keys(lineNumbers).map(Number), options)) ?? {});
721
+ if (!this.totalItems[tableName + "-*"])
722
+ this.totalItems[tableName + "-*"] = countItems;
723
+ if (RETURN && RETURN.length && !Array.isArray(where))
724
+ RETURN = RETURN[0];
725
+ }
726
+ else if (Utils.isObject(where)) {
727
+ let cachedFilePath = "";
728
+ // Criteria
729
+ if (Config.isCacheEnabled)
730
+ cachedFilePath = join(tablePath, ".cache", `${UtilsServer.hashString(inspect(where, { sorted: true }))}.inib`);
731
+ if (Config.isCacheEnabled && (await File.isExists(cachedFilePath))) {
732
+ const cachedItems = (await File.read(cachedFilePath, true)).split(",");
733
+ this.totalItems[tableName + "-*"] = cachedItems.length;
734
+ if (onlyLinesNumbers)
735
+ return cachedItems.map(Number);
736
+ return this.get(tableName, cachedItems
737
+ .slice((options.page - 1) * options.perPage, options.page * options.perPage)
738
+ .map(Number), options, undefined, undefined, schema);
739
+ }
740
+ else {
741
+ let linesNumbers;
742
+ [RETURN, linesNumbers] = await this.applyCriteria(tableName, schema, options, where);
743
+ if (RETURN && linesNumbers) {
744
+ if (onlyLinesNumbers)
745
+ return Object.keys(RETURN).map(Number);
746
+ const alreadyExistsColumns = Object.keys(Object.values(RETURN)[0]);
747
+ RETURN = Object.values(Utils.deepMerge(RETURN, await this.getItemsFromSchema(tableName, schema.filter(({ key }) => !alreadyExistsColumns.includes(key)), Object.keys(RETURN).map(Number), options)));
748
+ if (Config.isCacheEnabled)
749
+ await File.write(cachedFilePath, Array.from(linesNumbers).join(","), true);
750
+ }
751
+ }
752
+ }
753
+ if (!RETURN ||
754
+ (Utils.isObject(RETURN) && !Object.keys(RETURN).length) ||
755
+ (Array.isArray(RETURN) && !RETURN.length))
756
+ return null;
757
+ const greatestTotalItems = this.totalItems[tableName + "-*"] ??
758
+ Math.max(...Object.entries(this.totalItems)
759
+ .filter(([k]) => k.startsWith(tableName + "-"))
760
+ .map(([, v]) => v));
761
+ this.pageInfo[tableName] = {
762
+ ...(({ columns, ...restOfOptions }) => restOfOptions)(options),
763
+ perPage: Array.isArray(RETURN) ? RETURN.length : 1,
764
+ totalPages: Math.ceil(greatestTotalItems / options.perPage),
765
+ total: greatestTotalItems,
766
+ };
767
+ return onlyOne && Array.isArray(RETURN) ? RETURN[0] : RETURN;
768
+ }
769
+ async post(tableName, data, options, returnPostedData) {
770
+ if (!options)
771
+ options = {
772
+ page: 1,
773
+ perPage: 15,
774
+ };
775
+ const tablePath = join(this.folder, this.database, tableName);
776
+ if (!returnPostedData)
777
+ returnPostedData = false;
778
+ const schema = await this.getTableSchema(tableName);
779
+ let RETURN;
780
+ if (!schema)
781
+ throw this.throwError("NO_SCHEMA", tableName);
782
+ const keys = UtilsServer.hashString(Object.keys(Array.isArray(data) ? data[0] : data).join("."));
783
+ let lastId = 0, totalItems = 0, renameList = [];
784
+ try {
785
+ await File.lock(join(tablePath, ".tmp"), keys);
786
+ if (await File.isExists(join(tablePath, "id.inib"))) {
787
+ if (Config.isCacheEnabled &&
788
+ (await File.isExists(join(tablePath, ".cache", "pagination.inib"))))
789
+ [lastId, totalItems] = (await File.read(join(tablePath, ".cache", "pagination.inib"), true))
790
+ .split(",")
791
+ .map(Number);
792
+ else {
793
+ let lastIdObj;
794
+ [lastIdObj, totalItems] = await File.get(join(tablePath, "id.inib"), -1, "number", undefined, this.salt, true);
795
+ if (lastIdObj)
796
+ lastId = Number(Object.keys(lastIdObj)[0] ?? 0);
797
+ }
798
+ }
799
+ if (Utils.isArrayOfObjects(data))
800
+ RETURN = data.map(({ id, updatedAt, createdAt, ...rest }) => ({
801
+ id: ++lastId,
802
+ ...rest,
803
+ createdAt: Date.now(),
804
+ }));
805
+ else
806
+ RETURN = (({ id, updatedAt, createdAt, ...rest }) => ({
807
+ id: ++lastId,
808
+ ...rest,
809
+ createdAt: Date.now(),
810
+ }))(data);
811
+ RETURN = this.formatData(RETURN, schema);
812
+ const pathesContents = this.joinPathesContents(tablePath, Config.isReverseEnabled
813
+ ? Array.isArray(RETURN)
814
+ ? RETURN.toReversed()
815
+ : RETURN
816
+ : RETURN);
817
+ await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(this.isThreadEnabled
818
+ ? await File.createWorker("append", [path, content])
819
+ : await File.append(path, content))));
820
+ await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
821
+ renameList = [];
822
+ if (Config.isCacheEnabled) {
823
+ await this.clearCache(tablePath);
824
+ await File.write(join(tablePath, ".cache", "pagination.inib"), `${lastId},${totalItems + (Array.isArray(RETURN) ? RETURN.length : 1)}`, true);
825
+ }
826
+ if (returnPostedData)
827
+ return this.get(tableName, Array.isArray(RETURN) ? RETURN.map((_, index) => index + 1) : 1, options, !Utils.isArrayOfObjects(data), // return only one item if data is not array of objects
828
+ undefined, schema);
829
+ }
830
+ finally {
831
+ if (renameList.length)
832
+ await Promise.allSettled(renameList.map(async ([tempPath, _]) => unlink(tempPath)));
833
+ await File.unlock(join(tablePath, ".tmp"), keys);
834
+ }
835
+ }
836
+ async put(tableName, data, where, options = {
837
+ page: 1,
838
+ perPage: 15,
839
+ }, returnPostedData) {
840
+ let renameList = [];
841
+ const tablePath = join(this.folder, this.database, tableName);
842
+ const schema = await this.getTableSchema(tableName);
843
+ if (!schema)
844
+ throw this.throwError("NO_SCHEMA", tableName);
845
+ if (!(await File.isExists(join(tablePath, "id.inib"))))
846
+ throw this.throwError("NO_ITEMS", tableName);
847
+ data = this.formatData(data, schema, true);
848
+ if (!where) {
849
+ if (Utils.isArrayOfObjects(data)) {
850
+ if (!data.every((item) => item.hasOwnProperty("id") && Utils.isValidID(item.id)))
851
+ throw this.throwError("INVALID_ID");
852
+ return this.put(tableName, data, data
853
+ .filter((item) => item.id !== undefined)
854
+ .map((item) => item.id));
855
+ }
856
+ else if (data.hasOwnProperty("id")) {
857
+ if (!Utils.isValidID(data.id))
858
+ throw this.throwError("INVALID_ID", data.id);
859
+ return this.put(tableName, data, UtilsServer.decodeID(data.id, this.salt));
860
+ }
861
+ else {
862
+ let totalItems;
863
+ if (Config.isCacheEnabled &&
864
+ (await File.isExists(join(tablePath, ".cache", "pagination.inib"))))
865
+ totalItems = (await File.read(join(tablePath, ".cache", "pagination.inib"), true))
866
+ .split(",")
867
+ .map(Number)[1];
868
+ else
869
+ totalItems = await File.count(join(tablePath, "id.inib"));
870
+ const pathesContents = this.joinPathesContents(tablePath, {
871
+ ...(({ id, ...restOfData }) => restOfData)(data),
872
+ updatedAt: Date.now(),
873
+ });
874
+ try {
875
+ await File.lock(join(tablePath, ".tmp"));
876
+ await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(this.isThreadEnabled
877
+ ? await File.createWorker("replace", [
878
+ path,
879
+ Utils.combineObjects([...Array(totalItems)].map((_, i) => ({
880
+ [`${i + 1}`]: content,
881
+ }))),
882
+ ])
883
+ : await File.replace(path, Utils.combineObjects([...Array(totalItems)].map((_, i) => ({
884
+ [`${i + 1}`]: content,
885
+ })))))));
886
+ await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
887
+ if (Config.isCacheEnabled)
888
+ await this.clearCache(join(tablePath, ".cache"));
889
+ if (returnPostedData)
890
+ return this.get(tableName, where, options, undefined, undefined, schema);
891
+ }
892
+ finally {
893
+ if (renameList.length)
894
+ await Promise.allSettled(renameList.map(async ([tempPath, _]) => unlink(tempPath)));
895
+ await File.unlock(join(tablePath, ".tmp"));
896
+ }
897
+ }
898
+ }
899
+ else if ((Array.isArray(where) &&
900
+ (where.every(Utils.isValidID) || where.every(Utils.isNumber))) ||
901
+ Utils.isValidID(where) ||
902
+ Utils.isNumber(where)) {
903
+ if ((Array.isArray(where) && where.every(Utils.isValidID)) ||
904
+ Utils.isValidID(where)) {
905
+ const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
906
+ return this.put(tableName, data, lineNumbers);
907
+ }
908
+ else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
909
+ Utils.isNumber(where)) {
910
+ // "where" in this case, is the line(s) number(s) and not id(s)
911
+ const pathesContents = Object.fromEntries(Object.entries(this.joinPathesContents(tablePath, Utils.isArrayOfObjects(data)
912
+ ? data.map((item) => ({
913
+ ...item,
914
+ updatedAt: Date.now(),
915
+ }))
916
+ : { ...data, updatedAt: Date.now() })).map(([path, content]) => [
917
+ path,
918
+ [...(Array.isArray(where) ? where : [where])].reduce((obj, lineNum, index) => ({
919
+ ...obj,
920
+ [lineNum]: Array.isArray(content) ? content[index] : content,
921
+ }), {}),
922
+ ]));
923
+ const keys = UtilsServer.hashString(Object.keys(pathesContents)
924
+ .map((path) => path.replaceAll(".inib", ""))
925
+ .join("."));
926
+ try {
927
+ await File.lock(join(tablePath, ".tmp"), keys);
928
+ await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(this.isThreadEnabled
929
+ ? await File.createWorker("replace", [path, content])
930
+ : await File.replace(path, content))));
931
+ await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
932
+ renameList = [];
933
+ if (Config.isCacheEnabled)
934
+ await this.clearCache(tablePath);
935
+ if (returnPostedData)
936
+ return this.get(tableName, where, options, !Array.isArray(where), undefined, schema);
937
+ }
938
+ finally {
939
+ if (renameList.length)
940
+ await Promise.allSettled(renameList.map(async ([tempPath, _]) => unlink(tempPath)));
941
+ await File.unlock(join(tablePath, ".tmp"), keys);
942
+ }
943
+ }
944
+ }
945
+ else if (Utils.isObject(where)) {
946
+ const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
947
+ if (!lineNumbers || !lineNumbers.length)
948
+ throw this.throwError("NO_RESULTS", tableName);
949
+ return this.put(tableName, data, lineNumbers);
950
+ }
951
+ else
952
+ throw this.throwError("INVALID_PARAMETERS");
953
+ }
954
+ async delete(tableName, where, _id) {
955
+ let renameList = [];
956
+ const tablePath = join(this.folder, this.database, tableName);
957
+ const schema = await this.getTableSchema(tableName);
958
+ if (!schema)
959
+ throw this.throwError("NO_SCHEMA", tableName);
960
+ if (!(await File.isExists(join(tablePath, "id.inib"))))
961
+ throw this.throwError("NO_ITEMS", tableName);
962
+ if (!where) {
963
+ try {
964
+ await File.lock(join(tablePath, ".tmp"));
965
+ await Promise.all((await readdir(tablePath))
966
+ ?.filter((fileName) => fileName.endsWith(".inib"))
967
+ .map(async (file) => unlink(join(tablePath, file))));
968
+ if (Config.isCacheEnabled)
969
+ await this.clearCache(tablePath);
970
+ }
971
+ finally {
972
+ await File.unlock(join(tablePath, ".tmp"));
973
+ }
974
+ return "*";
975
+ }
976
+ else if ((Array.isArray(where) &&
977
+ (where.every(Utils.isValidID) || where.every(Utils.isNumber))) ||
978
+ Utils.isValidID(where) ||
979
+ Utils.isNumber(where)) {
980
+ if ((Array.isArray(where) && where.every(Utils.isValidID)) ||
981
+ Utils.isValidID(where)) {
982
+ const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
983
+ if (!lineNumbers)
984
+ return null;
985
+ return this.delete(tableName, lineNumbers, where);
986
+ }
987
+ else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
988
+ Utils.isNumber(where)) {
989
+ // "where" in this case, is the line(s) number(s) and not id(s)
990
+ const files = (await readdir(tablePath))?.filter((fileName) => fileName.endsWith(".inib"));
991
+ if (files.length) {
992
+ if (!_id)
993
+ _id = Object.entries((await File.get(join(tablePath, "id.inib"), where, "number", undefined, this.salt)) ?? {}).map(([_key, id]) => UtilsServer.encodeID(Number(id), this.salt));
994
+ if (!_id.length)
995
+ throw this.throwError("NO_RESULTS", tableName);
996
+ try {
997
+ await File.lock(join(tablePath, ".tmp"));
998
+ await Promise.all(files.map(async (file) => renameList.push(this.isThreadEnabled
999
+ ? await File.createWorker("remove", [
1000
+ join(tablePath, file),
1001
+ where,
1002
+ ])
1003
+ : await File.remove(join(tablePath, file), where))));
1004
+ await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
1005
+ if (Config.isCacheEnabled) {
1006
+ await this.clearCache(tablePath);
1007
+ if (await File.isExists(join(tablePath, ".cache", "pagination.inib"))) {
1008
+ let [lastId, totalItems] = (await File.read(join(tablePath, ".cache", "pagination.inib"), true))
1009
+ .split(",")
1010
+ .map(Number);
1011
+ await File.write(join(tablePath, ".cache", "pagination.inib"), `${lastId},${totalItems - (Array.isArray(where) ? where.length : 1)}`, true);
1012
+ }
1013
+ }
1014
+ return Array.isArray(_id) && _id.length === 1 ? _id[0] : _id;
1015
+ }
1016
+ finally {
1017
+ if (renameList.length)
1018
+ await Promise.allSettled(renameList.map(async ([tempPath, _]) => unlink(tempPath)));
1019
+ await File.unlock(join(tablePath, ".tmp"));
1020
+ }
1021
+ }
1022
+ }
1023
+ }
1024
+ else if (Utils.isObject(where)) {
1025
+ const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
1026
+ if (!lineNumbers || !lineNumbers.length)
1027
+ throw this.throwError("NO_RESULTS", tableName);
1028
+ return this.delete(tableName, lineNumbers);
1029
+ }
1030
+ else
1031
+ throw this.throwError("INVALID_PARAMETERS");
1032
+ return null;
1033
+ }
1034
+ async sum(tableName, columns, where) {
1035
+ let RETURN = {};
1036
+ const tablePath = join(this.folder, this.database, tableName), schema = await this.getTableSchema(tableName);
1037
+ if (!schema)
1038
+ throw this.throwError("NO_SCHEMA", tableName);
1039
+ if (!(await File.isExists(join(tablePath, "id.inib"))))
1040
+ throw this.throwError("NO_ITEMS", tableName);
1041
+ if (!Array.isArray(columns))
1042
+ columns = [columns];
1043
+ for await (const column of columns) {
1044
+ const columnPath = join(tablePath, column + ".inib");
1045
+ if (await File.isExists(columnPath)) {
1046
+ if (where) {
1047
+ const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
1048
+ RETURN[column] = lineNumbers
1049
+ ? await File.sum(columnPath, lineNumbers)
1050
+ : 0;
1051
+ }
1052
+ else
1053
+ RETURN[column] = await File.sum(columnPath);
1054
+ }
1055
+ }
1056
+ return Array.isArray(columns) ? RETURN : Object.values(RETURN)[0];
1057
+ }
1058
+ async max(tableName, columns, where) {
1059
+ let RETURN = {};
1060
+ const tablePath = join(this.folder, this.database, tableName), schema = await this.getTableSchema(tableName);
1061
+ if (!schema)
1062
+ throw this.throwError("NO_SCHEMA", tableName);
1063
+ if (!(await File.isExists(join(tablePath, "id.inib"))))
1064
+ throw this.throwError("NO_ITEMS", tableName);
1065
+ if (!Array.isArray(columns))
1066
+ columns = [columns];
1067
+ for await (const column of columns) {
1068
+ const columnPath = join(tablePath, column + ".inib");
1069
+ if (await File.isExists(columnPath)) {
1070
+ if (where) {
1071
+ const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
1072
+ RETURN[column] = lineNumbers
1073
+ ? await File.max(columnPath, lineNumbers)
1074
+ : 0;
1075
+ }
1076
+ else
1077
+ RETURN[column] = await File.max(columnPath);
1078
+ }
1079
+ }
1080
+ return RETURN;
1081
+ }
1082
+ async min(tableName, columns, where) {
1083
+ let RETURN = {};
1084
+ const tablePath = join(this.folder, this.database, tableName), schema = await this.getTableSchema(tableName);
1085
+ if (!schema)
1086
+ throw this.throwError("NO_SCHEMA", tableName);
1087
+ if (!(await File.isExists(join(tablePath, "id.inib"))))
1088
+ throw this.throwError("NO_ITEMS", tableName);
1089
+ if (!Array.isArray(columns))
1090
+ columns = [columns];
1091
+ for await (const column of columns) {
1092
+ const columnPath = join(tablePath, column + ".inib");
1093
+ if (await File.isExists(columnPath)) {
1094
+ if (where) {
1095
+ const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
1096
+ RETURN[column] = lineNumbers
1097
+ ? await File.min(columnPath, lineNumbers)
1098
+ : 0;
1099
+ }
1100
+ else
1101
+ RETURN[column] = await File.min(columnPath);
1102
+ }
1103
+ }
1104
+ return RETURN;
1105
+ }
1106
+ }