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