inibase 1.0.0-rc.3 → 1.0.0-rc.30

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