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/README.md +129 -92
- package/dist/config.d.ts +4 -0
- package/dist/config.js +4 -0
- package/dist/file.d.ts +171 -0
- package/dist/file.js +764 -0
- package/dist/index.d.ts +97 -0
- package/dist/index.js +1030 -0
- package/dist/utils.d.ts +203 -0
- package/dist/utils.js +442 -0
- package/dist/utils.server.d.ts +102 -0
- package/dist/utils.server.js +149 -0
- package/package.json +59 -18
- package/file.ts +0 -322
- package/index.ts +0 -1251
- package/tsconfig.json +0 -6
- package/utils.ts +0 -110
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
|
+
}
|