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