inibase 1.0.0-rc.4 → 1.0.0-rc.42
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 +161 -91
- package/dist/config.d.ts +5 -0
- package/dist/config.js +5 -0
- package/dist/file.d.ts +176 -0
- package/dist/file.js +625 -0
- package/dist/file.thread.d.ts +1 -0
- package/dist/file.thread.js +5 -0
- package/dist/index.d.ts +111 -0
- package/dist/index.js +1178 -0
- package/dist/index.thread.d.ts +1 -0
- package/dist/index.thread.js +6 -0
- package/dist/utils.d.ts +206 -0
- package/dist/utils.js +418 -0
- package/dist/utils.server.d.ts +107 -0
- package/dist/utils.server.js +254 -0
- package/package.json +75 -18
- package/file.ts +0 -327
- package/index.ts +0 -1280
- package/tsconfig.json +0 -6
- package/utils.ts +0 -110
package/index.ts
DELETED
|
@@ -1,1280 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
readFileSync,
|
|
3
|
-
writeFileSync,
|
|
4
|
-
mkdirSync,
|
|
5
|
-
existsSync,
|
|
6
|
-
appendFileSync,
|
|
7
|
-
readdirSync,
|
|
8
|
-
unlinkSync,
|
|
9
|
-
renameSync,
|
|
10
|
-
} from "fs";
|
|
11
|
-
import { join, parse } from "path";
|
|
12
|
-
import { createDecipheriv, createCipheriv, scryptSync } from "crypto";
|
|
13
|
-
import Utils from "./utils";
|
|
14
|
-
import File from "./file";
|
|
15
|
-
|
|
16
|
-
export { File, Utils };
|
|
17
|
-
|
|
18
|
-
export type Data = {
|
|
19
|
-
id?: number | string;
|
|
20
|
-
[key: string]: any;
|
|
21
|
-
created_at?: Date;
|
|
22
|
-
updated_at?: Date;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export type FieldType =
|
|
26
|
-
| "string"
|
|
27
|
-
| "number"
|
|
28
|
-
| "boolean"
|
|
29
|
-
| "date"
|
|
30
|
-
| "email"
|
|
31
|
-
| "url"
|
|
32
|
-
| "table"
|
|
33
|
-
| "object"
|
|
34
|
-
| "array"
|
|
35
|
-
| "password";
|
|
36
|
-
type Field = {
|
|
37
|
-
id?: string | number | null | undefined;
|
|
38
|
-
key: string;
|
|
39
|
-
required?: boolean;
|
|
40
|
-
} & (
|
|
41
|
-
| {
|
|
42
|
-
type: Exclude<FieldType, "array" | "object">;
|
|
43
|
-
required?: boolean;
|
|
44
|
-
}
|
|
45
|
-
| {
|
|
46
|
-
type: "array";
|
|
47
|
-
children: FieldType | FieldType[] | Schema;
|
|
48
|
-
}
|
|
49
|
-
| {
|
|
50
|
-
type: "object";
|
|
51
|
-
children: Schema;
|
|
52
|
-
}
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
export type Schema = Field[];
|
|
56
|
-
|
|
57
|
-
export interface Options {
|
|
58
|
-
page?: number;
|
|
59
|
-
per_page?: number;
|
|
60
|
-
columns?: string[];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export type ComparisonOperator =
|
|
64
|
-
| "="
|
|
65
|
-
| "!="
|
|
66
|
-
| ">"
|
|
67
|
-
| "<"
|
|
68
|
-
| ">="
|
|
69
|
-
| "<="
|
|
70
|
-
| "*"
|
|
71
|
-
| "!*"
|
|
72
|
-
| "[]"
|
|
73
|
-
| "![]";
|
|
74
|
-
|
|
75
|
-
type pageInfo = {
|
|
76
|
-
total_items?: number;
|
|
77
|
-
total_pages?: number;
|
|
78
|
-
} & Options;
|
|
79
|
-
|
|
80
|
-
export type Criteria =
|
|
81
|
-
| {
|
|
82
|
-
[logic in "and" | "or"]?: Criteria | (string | number | boolean | null)[];
|
|
83
|
-
}
|
|
84
|
-
| {
|
|
85
|
-
[key: string]: string | number | boolean | Criteria;
|
|
86
|
-
}
|
|
87
|
-
| null;
|
|
88
|
-
|
|
89
|
-
declare global {
|
|
90
|
-
type Entries<T> = {
|
|
91
|
-
[K in keyof T]: [K, T[K]];
|
|
92
|
-
}[keyof T][];
|
|
93
|
-
|
|
94
|
-
interface ObjectConstructor {
|
|
95
|
-
entries<T extends object>(o: T): Entries<T>;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export default class Inibase {
|
|
100
|
-
public database: string;
|
|
101
|
-
public databasePath: string;
|
|
102
|
-
public cache: Map<string, string>;
|
|
103
|
-
public pageInfoArray: Record<string, Record<string, number>>;
|
|
104
|
-
public pageInfo: pageInfo;
|
|
105
|
-
|
|
106
|
-
constructor(databaseName: string, mainFolder: string = "/") {
|
|
107
|
-
this.database = databaseName;
|
|
108
|
-
this.databasePath = join(mainFolder, databaseName);
|
|
109
|
-
this.cache = new Map<string, any>();
|
|
110
|
-
this.pageInfoArray = {};
|
|
111
|
-
this.pageInfo = { page: 1, per_page: 15 };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
private throwError(
|
|
115
|
-
code: string,
|
|
116
|
-
variable?:
|
|
117
|
-
| string
|
|
118
|
-
| number
|
|
119
|
-
| (string | number)[]
|
|
120
|
-
| Record<string, string | number>,
|
|
121
|
-
language: string = "en"
|
|
122
|
-
): Error {
|
|
123
|
-
const errorMessages: Record<string, Record<string, string>> = {
|
|
124
|
-
en: {
|
|
125
|
-
NO_SCHEMA: "NO_SCHEMA: {variable}",
|
|
126
|
-
NO_ITEMS: "NO_ITEMS: {variable}",
|
|
127
|
-
INVALID_ID: "INVALID_ID: {variable}",
|
|
128
|
-
INVALID_TYPE: "INVALID_TYPE: {variable}",
|
|
129
|
-
REQUIRED: "REQUIRED: {variable}",
|
|
130
|
-
NO_DATA: "NO_DATA: {variable}",
|
|
131
|
-
INVALID_OPERATOR: "INVALID_OPERATOR: {variable}",
|
|
132
|
-
PARAMETERS: "PARAMETERS: {variable}",
|
|
133
|
-
},
|
|
134
|
-
// Add more languages and error messages as needed
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
let errorMessage = errorMessages[language][code] || code;
|
|
138
|
-
if (variable) {
|
|
139
|
-
if (
|
|
140
|
-
typeof variable === "string" ||
|
|
141
|
-
typeof variable === "number" ||
|
|
142
|
-
Array.isArray(variable)
|
|
143
|
-
)
|
|
144
|
-
errorMessage = errorMessage.replaceAll(
|
|
145
|
-
`{variable}`,
|
|
146
|
-
Array.isArray(variable) ? variable.join(", ") : (variable as string)
|
|
147
|
-
);
|
|
148
|
-
else
|
|
149
|
-
Object.keys(variable).forEach(
|
|
150
|
-
(variableKey) =>
|
|
151
|
-
(errorMessage = errorMessage.replaceAll(
|
|
152
|
-
`{${variableKey}}`,
|
|
153
|
-
variable[variableKey].toString()
|
|
154
|
-
))
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
return new Error(errorMessage);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
public encodeID(id: number, secretKey?: string | number): string {
|
|
161
|
-
if (!secretKey) secretKey = this.databasePath;
|
|
162
|
-
|
|
163
|
-
const salt = scryptSync(secretKey.toString(), "salt", 32),
|
|
164
|
-
cipher = createCipheriv("aes-256-cbc", salt, salt.subarray(0, 16));
|
|
165
|
-
|
|
166
|
-
return cipher.update(id.toString(), "utf8", "hex") + cipher.final("hex");
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
public decodeID(input: string, secretKey?: string | number): number {
|
|
170
|
-
if (!secretKey) secretKey = this.databasePath;
|
|
171
|
-
const salt = scryptSync(secretKey.toString(), "salt", 32),
|
|
172
|
-
decipher = createDecipheriv("aes-256-cbc", salt, salt.subarray(0, 16));
|
|
173
|
-
return Number(
|
|
174
|
-
decipher.update(input as string, "hex", "utf8") + decipher.final("utf8")
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
public isValidID(input: any): boolean {
|
|
179
|
-
return Array.isArray(input)
|
|
180
|
-
? input.every(this.isValidID)
|
|
181
|
-
: typeof input === "string" && input.length === 32;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
public validateData(
|
|
185
|
-
data: Data | Data[],
|
|
186
|
-
schema: Schema,
|
|
187
|
-
skipRequiredField: boolean = false
|
|
188
|
-
): void {
|
|
189
|
-
if (Utils.isArrayOfObjects(data))
|
|
190
|
-
for (const single_data of data as Data[])
|
|
191
|
-
this.validateData(single_data, schema, skipRequiredField);
|
|
192
|
-
else if (!Array.isArray(data)) {
|
|
193
|
-
const validateFieldType = (
|
|
194
|
-
value: any,
|
|
195
|
-
field: Field | FieldType | FieldType[]
|
|
196
|
-
): boolean => {
|
|
197
|
-
if (Array.isArray(field))
|
|
198
|
-
return field.some((item) => validateFieldType(value, item));
|
|
199
|
-
switch (typeof field === "string" ? field : field.type) {
|
|
200
|
-
case "string":
|
|
201
|
-
return value === null || typeof value === "string";
|
|
202
|
-
case "number":
|
|
203
|
-
return value === null || typeof value === "number";
|
|
204
|
-
case "boolean":
|
|
205
|
-
return (
|
|
206
|
-
value === null ||
|
|
207
|
-
typeof value === "boolean" ||
|
|
208
|
-
value === "true" ||
|
|
209
|
-
value === "false"
|
|
210
|
-
);
|
|
211
|
-
case "date":
|
|
212
|
-
return value === null || value instanceof Date;
|
|
213
|
-
case "object":
|
|
214
|
-
return (
|
|
215
|
-
value === null ||
|
|
216
|
-
(typeof value === "object" &&
|
|
217
|
-
!Array.isArray(value) &&
|
|
218
|
-
value !== null)
|
|
219
|
-
);
|
|
220
|
-
case "array":
|
|
221
|
-
return (
|
|
222
|
-
value === null ||
|
|
223
|
-
(Array.isArray(value) &&
|
|
224
|
-
value.every((item) => validateFieldType(item, field)))
|
|
225
|
-
);
|
|
226
|
-
case "email":
|
|
227
|
-
return (
|
|
228
|
-
value === null ||
|
|
229
|
-
(typeof value === "string" &&
|
|
230
|
-
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
|
|
231
|
-
);
|
|
232
|
-
case "url":
|
|
233
|
-
return (
|
|
234
|
-
value === null ||
|
|
235
|
-
(typeof value === "string" &&
|
|
236
|
-
(value[0] === "#" ||
|
|
237
|
-
/^((https?|www):\/\/)?[a-z0-9-]+(\.[a-z0-9-]+)*\.[a-z]+(\/[^\s]*)?$/.test(
|
|
238
|
-
value
|
|
239
|
-
)))
|
|
240
|
-
);
|
|
241
|
-
case "table":
|
|
242
|
-
// feat: check if id exists
|
|
243
|
-
if (Array.isArray(value))
|
|
244
|
-
return (
|
|
245
|
-
typeof field !== "string" &&
|
|
246
|
-
field.type === "table" &&
|
|
247
|
-
((Utils.isArrayOfObjects(value) &&
|
|
248
|
-
value.every(
|
|
249
|
-
(element) =>
|
|
250
|
-
element.hasOwnProperty("id") &&
|
|
251
|
-
this.isValidID((element as Data).id)
|
|
252
|
-
)) ||
|
|
253
|
-
value.every(Utils.isNumber) ||
|
|
254
|
-
this.isValidID(value))
|
|
255
|
-
);
|
|
256
|
-
else if (Utils.isObject(value))
|
|
257
|
-
return (
|
|
258
|
-
value.hasOwnProperty("id") && this.isValidID((value as Data).id)
|
|
259
|
-
);
|
|
260
|
-
else return Utils.isNumber(value) || this.isValidID(value);
|
|
261
|
-
default:
|
|
262
|
-
return true;
|
|
263
|
-
}
|
|
264
|
-
};
|
|
265
|
-
for (const field of schema) {
|
|
266
|
-
if (data.hasOwnProperty(field.key)) {
|
|
267
|
-
if (!validateFieldType(data[field.key], field))
|
|
268
|
-
throw this.throwError("INVALID_TYPE", field.key);
|
|
269
|
-
if (
|
|
270
|
-
(field.type === "array" || field.type === "object") &&
|
|
271
|
-
field.children &&
|
|
272
|
-
Utils.isArrayOfObjects(field.children)
|
|
273
|
-
)
|
|
274
|
-
this.validateData(
|
|
275
|
-
data[field.key],
|
|
276
|
-
field.children as Schema,
|
|
277
|
-
skipRequiredField
|
|
278
|
-
);
|
|
279
|
-
} else if (field.required && !skipRequiredField)
|
|
280
|
-
throw this.throwError("REQUIRED", field.key);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
public setTableSchema(tableName: string, schema: Schema): void {
|
|
286
|
-
const encodeSchema = (schema: Schema) => {
|
|
287
|
-
let RETURN: any[][] = [],
|
|
288
|
-
index = 0;
|
|
289
|
-
for (const field of schema) {
|
|
290
|
-
if (!RETURN[index]) RETURN[index] = [];
|
|
291
|
-
RETURN[index].push(
|
|
292
|
-
field.id ? this.decodeID(field.id as string) : null
|
|
293
|
-
);
|
|
294
|
-
RETURN[index].push(field.key ?? null);
|
|
295
|
-
RETURN[index].push(field.required ?? null);
|
|
296
|
-
RETURN[index].push(field.type ?? null);
|
|
297
|
-
RETURN[index].push(
|
|
298
|
-
(field.type === "array" || field.type === "object") &&
|
|
299
|
-
field.children &&
|
|
300
|
-
Utils.isArrayOfObjects(field.children)
|
|
301
|
-
? encodeSchema(field.children as Schema) ?? null
|
|
302
|
-
: null
|
|
303
|
-
);
|
|
304
|
-
index++;
|
|
305
|
-
}
|
|
306
|
-
return RETURN;
|
|
307
|
-
},
|
|
308
|
-
addIdToSchema = (schema: Schema, oldIndex: number = 0) =>
|
|
309
|
-
schema.map((field) => {
|
|
310
|
-
if (
|
|
311
|
-
(field.type === "array" || field.type === "object") &&
|
|
312
|
-
Utils.isArrayOfObjects(field.children)
|
|
313
|
-
) {
|
|
314
|
-
if (!field.id) {
|
|
315
|
-
oldIndex++;
|
|
316
|
-
field = { ...field, id: this.encodeID(oldIndex) };
|
|
317
|
-
} else oldIndex = this.decodeID(field.id as string);
|
|
318
|
-
field.children = addIdToSchema(field.children as Schema, oldIndex);
|
|
319
|
-
oldIndex += field.children.length;
|
|
320
|
-
} else if (field.id) oldIndex = this.decodeID(field.id as string);
|
|
321
|
-
else {
|
|
322
|
-
oldIndex++;
|
|
323
|
-
field = { ...field, id: this.encodeID(oldIndex) };
|
|
324
|
-
}
|
|
325
|
-
return field;
|
|
326
|
-
}),
|
|
327
|
-
findLastIdNumber = (schema: Schema): number => {
|
|
328
|
-
const lastField = schema[schema.length - 1];
|
|
329
|
-
if (lastField) {
|
|
330
|
-
if (
|
|
331
|
-
(lastField.type === "array" || lastField.type === "object") &&
|
|
332
|
-
Utils.isArrayOfObjects(lastField.children)
|
|
333
|
-
)
|
|
334
|
-
return findLastIdNumber(lastField.children as Schema);
|
|
335
|
-
else return this.decodeID(lastField.id as string);
|
|
336
|
-
} else return 0;
|
|
337
|
-
};
|
|
338
|
-
|
|
339
|
-
// remove id from schema
|
|
340
|
-
schema = schema.filter((field) => field.key !== "id");
|
|
341
|
-
schema = addIdToSchema(schema, findLastIdNumber(schema));
|
|
342
|
-
const TablePath = join(this.databasePath, tableName),
|
|
343
|
-
TableSchemaPath = join(TablePath, "schema.inib");
|
|
344
|
-
if (!existsSync(TablePath)) mkdirSync(TablePath, { recursive: true });
|
|
345
|
-
if (existsSync(TableSchemaPath)) {
|
|
346
|
-
// update columns files names based on field id
|
|
347
|
-
const schemaToIdsPath = (schema: any, prefix = "") => {
|
|
348
|
-
let RETURN: any = {};
|
|
349
|
-
for (const field of schema) {
|
|
350
|
-
if (field.children && Utils.isArrayOfObjects(field.children)) {
|
|
351
|
-
Utils.deepMerge(
|
|
352
|
-
RETURN,
|
|
353
|
-
schemaToIdsPath(
|
|
354
|
-
field.children,
|
|
355
|
-
(prefix ?? "") +
|
|
356
|
-
field.key +
|
|
357
|
-
(field.type === "array" ? ".*." : ".")
|
|
358
|
-
)
|
|
359
|
-
);
|
|
360
|
-
} else if (this.isValidID(field.id))
|
|
361
|
-
RETURN[this.decodeID(field.id)] = File.encodeFileName(
|
|
362
|
-
(prefix ?? "") + field.key,
|
|
363
|
-
"inib"
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
return RETURN;
|
|
367
|
-
},
|
|
368
|
-
findChangedProperties = (
|
|
369
|
-
obj1: Record<string, string>,
|
|
370
|
-
obj2: Record<string, string>
|
|
371
|
-
): Record<string, string> | null => {
|
|
372
|
-
const result: Record<string, string> = {};
|
|
373
|
-
|
|
374
|
-
for (const key1 in obj1) {
|
|
375
|
-
if (obj2.hasOwnProperty(key1) && obj1[key1] !== obj2[key1]) {
|
|
376
|
-
result[obj1[key1]] = obj2[key1];
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
return Object.keys(result).length ? result : null;
|
|
381
|
-
},
|
|
382
|
-
replaceOldPathes = findChangedProperties(
|
|
383
|
-
schemaToIdsPath(this.getTableSchema(tableName)),
|
|
384
|
-
schemaToIdsPath(schema)
|
|
385
|
-
);
|
|
386
|
-
if (replaceOldPathes) {
|
|
387
|
-
for (const [oldPath, newPath] of Object.entries(replaceOldPathes))
|
|
388
|
-
if (existsSync(join(TablePath, oldPath)))
|
|
389
|
-
renameSync(join(TablePath, oldPath), join(TablePath, newPath));
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
writeFileSync(
|
|
394
|
-
join(TablePath, "schema.inib"),
|
|
395
|
-
JSON.stringify(encodeSchema(schema))
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
public getTableSchema(tableName: string): Schema | undefined {
|
|
400
|
-
const decodeSchema = (encodedSchema: any) => {
|
|
401
|
-
return encodedSchema.map((field: any) =>
|
|
402
|
-
Array.isArray(field[0])
|
|
403
|
-
? decodeSchema(field)
|
|
404
|
-
: Object.fromEntries(
|
|
405
|
-
Object.entries({
|
|
406
|
-
id: this.encodeID(field[0]),
|
|
407
|
-
key: field[1],
|
|
408
|
-
required: field[2],
|
|
409
|
-
type: field[3],
|
|
410
|
-
children: field[4]
|
|
411
|
-
? Array.isArray(field[4])
|
|
412
|
-
? decodeSchema(field[4])
|
|
413
|
-
: field[4]
|
|
414
|
-
: null,
|
|
415
|
-
}).filter(([_, v]) => v != null)
|
|
416
|
-
)
|
|
417
|
-
);
|
|
418
|
-
},
|
|
419
|
-
TableSchemaPath = join(this.databasePath, tableName, "schema.inib");
|
|
420
|
-
if (!existsSync(TableSchemaPath)) return undefined;
|
|
421
|
-
if (!this.cache.has(TableSchemaPath)) {
|
|
422
|
-
const TableSchemaPathContent = readFileSync(TableSchemaPath);
|
|
423
|
-
this.cache.set(
|
|
424
|
-
TableSchemaPath,
|
|
425
|
-
TableSchemaPathContent
|
|
426
|
-
? decodeSchema(JSON.parse(TableSchemaPathContent.toString()))
|
|
427
|
-
: ""
|
|
428
|
-
);
|
|
429
|
-
}
|
|
430
|
-
return [
|
|
431
|
-
{ id: this.encodeID(0), key: "id", type: "number", required: true },
|
|
432
|
-
...(this.cache.get(TableSchemaPath) as unknown as Schema),
|
|
433
|
-
];
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
public getField(keyPath: string, schema: Schema | Field): Field | null {
|
|
437
|
-
for (const key of keyPath.split(".")) {
|
|
438
|
-
if (key === "*") continue;
|
|
439
|
-
const foundItem = (schema as Schema).find((item) => item.key === key);
|
|
440
|
-
if (!foundItem) return null;
|
|
441
|
-
schema =
|
|
442
|
-
(foundItem.type === "array" || foundItem.type === "object") &&
|
|
443
|
-
foundItem.children &&
|
|
444
|
-
Utils.isArrayOfObjects(foundItem.children)
|
|
445
|
-
? (foundItem.children as Schema)
|
|
446
|
-
: foundItem;
|
|
447
|
-
}
|
|
448
|
-
return schema as Field;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
public formatData(
|
|
452
|
-
data: Data | Data[],
|
|
453
|
-
schema: Schema,
|
|
454
|
-
formatOnlyAvailiableKeys?: boolean
|
|
455
|
-
): Data | Data[] {
|
|
456
|
-
if (Utils.isArrayOfObjects(data)) {
|
|
457
|
-
return data.map((single_data: Data) =>
|
|
458
|
-
this.formatData(single_data, schema)
|
|
459
|
-
);
|
|
460
|
-
} else if (!Array.isArray(data)) {
|
|
461
|
-
let RETURN: Data = {};
|
|
462
|
-
for (const field of schema) {
|
|
463
|
-
if (!data.hasOwnProperty(field.key)) {
|
|
464
|
-
RETURN[field.key] = this.getDefaultValue(field);
|
|
465
|
-
continue;
|
|
466
|
-
}
|
|
467
|
-
if (formatOnlyAvailiableKeys && !data.hasOwnProperty(field.key))
|
|
468
|
-
continue;
|
|
469
|
-
|
|
470
|
-
if (field.type === "array" || field.type === "object") {
|
|
471
|
-
if (field.children)
|
|
472
|
-
if (typeof field.children === "string") {
|
|
473
|
-
if (field.type === "array" && field.children === "table") {
|
|
474
|
-
if (Array.isArray(data[field.key])) {
|
|
475
|
-
if (Utils.isArrayOfObjects(data[field.key])) {
|
|
476
|
-
if (
|
|
477
|
-
data[field.key].every(
|
|
478
|
-
(item: any) =>
|
|
479
|
-
item.hasOwnProperty("id") &&
|
|
480
|
-
(this.isValidID(item.id) || Utils.isNumber(item.id))
|
|
481
|
-
)
|
|
482
|
-
)
|
|
483
|
-
data[field.key].map((item: any) =>
|
|
484
|
-
Utils.isNumber(item.id)
|
|
485
|
-
? parseFloat(item.id)
|
|
486
|
-
: this.decodeID(item.id)
|
|
487
|
-
);
|
|
488
|
-
} else if (
|
|
489
|
-
this.isValidID(data[field.key]) ||
|
|
490
|
-
Utils.isNumber(data[field.key])
|
|
491
|
-
)
|
|
492
|
-
RETURN[field.key] = data[field.key].map(
|
|
493
|
-
(item: number | string) =>
|
|
494
|
-
Utils.isNumber(item)
|
|
495
|
-
? parseFloat(item as string)
|
|
496
|
-
: this.decodeID(item as string)
|
|
497
|
-
);
|
|
498
|
-
} else if (this.isValidID(data[field.key]))
|
|
499
|
-
RETURN[field.key] = [this.decodeID(data[field.key])];
|
|
500
|
-
else if (Utils.isNumber(data[field.key]))
|
|
501
|
-
RETURN[field.key] = [parseFloat(data[field.key])];
|
|
502
|
-
} else if (data.hasOwnProperty(field.key))
|
|
503
|
-
RETURN[field.key] = data[field.key];
|
|
504
|
-
} else if (Utils.isArrayOfObjects(field.children))
|
|
505
|
-
RETURN[field.key] = this.formatData(
|
|
506
|
-
data[field.key],
|
|
507
|
-
field.children as Schema,
|
|
508
|
-
formatOnlyAvailiableKeys
|
|
509
|
-
);
|
|
510
|
-
} else if (field.type === "table") {
|
|
511
|
-
if (Utils.isObject(data[field.key])) {
|
|
512
|
-
if (
|
|
513
|
-
data[field.key].hasOwnProperty("id") &&
|
|
514
|
-
(this.isValidID(data[field.key].id) ||
|
|
515
|
-
Utils.isNumber(data[field.key]))
|
|
516
|
-
)
|
|
517
|
-
RETURN[field.key] = Utils.isNumber(data[field.key].id)
|
|
518
|
-
? parseFloat(data[field.key].id)
|
|
519
|
-
: this.decodeID(data[field.key].id);
|
|
520
|
-
} else if (
|
|
521
|
-
this.isValidID(data[field.key]) ||
|
|
522
|
-
Utils.isNumber(data[field.key])
|
|
523
|
-
)
|
|
524
|
-
RETURN[field.key] = Utils.isNumber(data[field.key])
|
|
525
|
-
? parseFloat(data[field.key])
|
|
526
|
-
: this.decodeID(data[field.key]);
|
|
527
|
-
} else if (field.type === "password")
|
|
528
|
-
RETURN[field.key] =
|
|
529
|
-
data[field.key].length === 161
|
|
530
|
-
? data[field.key]
|
|
531
|
-
: Utils.hashPassword(data[field.key]);
|
|
532
|
-
else RETURN[field.key] = data[field.key];
|
|
533
|
-
}
|
|
534
|
-
return RETURN;
|
|
535
|
-
} else return [];
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
private getDefaultValue(field: Field): any {
|
|
539
|
-
switch (field.type) {
|
|
540
|
-
case "array":
|
|
541
|
-
return Utils.isArrayOfObjects(field.children)
|
|
542
|
-
? [
|
|
543
|
-
this.getDefaultValue({
|
|
544
|
-
...field,
|
|
545
|
-
type: "object",
|
|
546
|
-
children: field.children as Schema,
|
|
547
|
-
}),
|
|
548
|
-
]
|
|
549
|
-
: [];
|
|
550
|
-
case "object":
|
|
551
|
-
return Utils.combineObjects(
|
|
552
|
-
field.children.map((f) => ({ [f.key]: this.getDefaultValue(f) }))
|
|
553
|
-
);
|
|
554
|
-
case "boolean":
|
|
555
|
-
return false;
|
|
556
|
-
default:
|
|
557
|
-
return null;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
public joinPathesContents(
|
|
562
|
-
mainPath: string,
|
|
563
|
-
data: Data | Data[]
|
|
564
|
-
): { [key: string]: string[] } {
|
|
565
|
-
const CombineData = (data: Data | Data[], prefix?: string) => {
|
|
566
|
-
let RETURN: Record<
|
|
567
|
-
string,
|
|
568
|
-
string | boolean | number | null | (string | boolean | number | null)[]
|
|
569
|
-
> = {};
|
|
570
|
-
|
|
571
|
-
if (Utils.isArrayOfObjects(data))
|
|
572
|
-
RETURN = Utils.combineObjects(
|
|
573
|
-
(data as Data[]).map((single_data) => CombineData(single_data))
|
|
574
|
-
);
|
|
575
|
-
else {
|
|
576
|
-
for (const [key, value] of Object.entries(data as Data)) {
|
|
577
|
-
if (Utils.isObject(value))
|
|
578
|
-
Object.assign(RETURN, CombineData(value, `${key}.`));
|
|
579
|
-
else if (Array.isArray(value)) {
|
|
580
|
-
if (Utils.isArrayOfObjects(value))
|
|
581
|
-
Object.assign(
|
|
582
|
-
RETURN,
|
|
583
|
-
CombineData(
|
|
584
|
-
Utils.combineObjects(value),
|
|
585
|
-
(prefix ?? "") + key + ".*."
|
|
586
|
-
)
|
|
587
|
-
);
|
|
588
|
-
else
|
|
589
|
-
RETURN[(prefix ?? "") + key] = Utils.encode(value) as
|
|
590
|
-
| boolean
|
|
591
|
-
| number
|
|
592
|
-
| string
|
|
593
|
-
| null;
|
|
594
|
-
} else
|
|
595
|
-
RETURN[(prefix ?? "") + key] = Utils.encode(value) as
|
|
596
|
-
| boolean
|
|
597
|
-
| number
|
|
598
|
-
| string
|
|
599
|
-
| null;
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
return RETURN;
|
|
603
|
-
};
|
|
604
|
-
const addPathToKeys = (obj: Record<string, any>, path: string) => {
|
|
605
|
-
const newObject: Record<string, any> = {};
|
|
606
|
-
|
|
607
|
-
for (const key in obj)
|
|
608
|
-
newObject[join(path, File.encodeFileName(key, "inib"))] = obj[key];
|
|
609
|
-
|
|
610
|
-
return newObject;
|
|
611
|
-
};
|
|
612
|
-
return addPathToKeys(CombineData(data), mainPath);
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
public async get(
|
|
616
|
-
tableName: string,
|
|
617
|
-
where?: string | number | (string | number)[] | Criteria,
|
|
618
|
-
options: Options = {
|
|
619
|
-
page: 1,
|
|
620
|
-
per_page: 15,
|
|
621
|
-
},
|
|
622
|
-
onlyLinesNumbers?: boolean
|
|
623
|
-
): Promise<Data | Data[] | number[] | null> {
|
|
624
|
-
if (!options.columns) options.columns = [];
|
|
625
|
-
else if (
|
|
626
|
-
options.columns.length &&
|
|
627
|
-
!(options.columns as string[]).includes("id")
|
|
628
|
-
)
|
|
629
|
-
options.columns.push("id");
|
|
630
|
-
if (!options.page) options.page = 1;
|
|
631
|
-
if (!options.per_page) options.per_page = 15;
|
|
632
|
-
let RETURN!: Data | Data[] | null;
|
|
633
|
-
let schema = this.getTableSchema(tableName);
|
|
634
|
-
if (!schema) throw this.throwError("NO_SCHEMA", tableName);
|
|
635
|
-
const filterSchemaByColumns = (schema: Schema, columns: string[]): Schema =>
|
|
636
|
-
schema
|
|
637
|
-
.map((field) => {
|
|
638
|
-
if (columns.includes(field.key)) return field;
|
|
639
|
-
if (
|
|
640
|
-
(field.type === "array" || field.type === "object") &&
|
|
641
|
-
Utils.isArrayOfObjects(field.children) &&
|
|
642
|
-
columns.filter((column) =>
|
|
643
|
-
column.startsWith(
|
|
644
|
-
field.key + (field.type === "array" ? ".*." : ".")
|
|
645
|
-
)
|
|
646
|
-
).length
|
|
647
|
-
) {
|
|
648
|
-
field.children = filterSchemaByColumns(
|
|
649
|
-
field.children as Schema,
|
|
650
|
-
columns
|
|
651
|
-
.filter((column) =>
|
|
652
|
-
column.startsWith(
|
|
653
|
-
field.key + (field.type === "array" ? ".*." : ".")
|
|
654
|
-
)
|
|
655
|
-
)
|
|
656
|
-
.map((column) =>
|
|
657
|
-
column.replace(
|
|
658
|
-
field.key + (field.type === "array" ? ".*." : "."),
|
|
659
|
-
""
|
|
660
|
-
)
|
|
661
|
-
)
|
|
662
|
-
);
|
|
663
|
-
return field;
|
|
664
|
-
}
|
|
665
|
-
return null;
|
|
666
|
-
})
|
|
667
|
-
.filter((i) => i) as Schema;
|
|
668
|
-
if (options.columns.length)
|
|
669
|
-
schema = filterSchemaByColumns(schema, options.columns);
|
|
670
|
-
|
|
671
|
-
const getItemsFromSchema = async (
|
|
672
|
-
path: string,
|
|
673
|
-
schema: Schema,
|
|
674
|
-
linesNumber: number[],
|
|
675
|
-
prefix?: string
|
|
676
|
-
) => {
|
|
677
|
-
let RETURN: Record<number, Data> = {};
|
|
678
|
-
for (const field of schema) {
|
|
679
|
-
if (
|
|
680
|
-
(field.type === "array" || field.type === "object") &&
|
|
681
|
-
field.children
|
|
682
|
-
) {
|
|
683
|
-
if (field.children === "table") {
|
|
684
|
-
if (options.columns)
|
|
685
|
-
options.columns = (options.columns as string[])
|
|
686
|
-
.filter((column) => column.includes(`${field.key}.*.`))
|
|
687
|
-
.map((column) => column.replace(`${field.key}.*.`, ""));
|
|
688
|
-
for await (const [index, value] of Object.entries(
|
|
689
|
-
(await File.get(
|
|
690
|
-
join(
|
|
691
|
-
path,
|
|
692
|
-
File.encodeFileName((prefix ?? "") + field.key, "inib")
|
|
693
|
-
),
|
|
694
|
-
field.type,
|
|
695
|
-
linesNumber
|
|
696
|
-
)) ?? {}
|
|
697
|
-
)) {
|
|
698
|
-
if (!RETURN[index]) RETURN[index] = {};
|
|
699
|
-
RETURN[index][field.key] = value
|
|
700
|
-
? await this.get(field.key, value as number, options)
|
|
701
|
-
: this.getDefaultValue(field);
|
|
702
|
-
}
|
|
703
|
-
} else if (Utils.isArrayOfObjects(field.children)) {
|
|
704
|
-
Object.entries(
|
|
705
|
-
(await getItemsFromSchema(
|
|
706
|
-
path,
|
|
707
|
-
field.children as Schema,
|
|
708
|
-
linesNumber,
|
|
709
|
-
(prefix ?? "") +
|
|
710
|
-
field.key +
|
|
711
|
-
(field.type === "array" ? ".*." : ".")
|
|
712
|
-
)) ?? {}
|
|
713
|
-
).forEach(([index, item]) => {
|
|
714
|
-
if (!RETURN[index]) RETURN[index] = {};
|
|
715
|
-
RETURN[index][field.key] =
|
|
716
|
-
field.type === "array" &&
|
|
717
|
-
Utils.isObject(item) &&
|
|
718
|
-
Object.values(item).every((i) => i === null)
|
|
719
|
-
? []
|
|
720
|
-
: item;
|
|
721
|
-
});
|
|
722
|
-
}
|
|
723
|
-
} else if (field.type === "table") {
|
|
724
|
-
if (
|
|
725
|
-
existsSync(join(this.databasePath, field.key)) &&
|
|
726
|
-
existsSync(
|
|
727
|
-
join(
|
|
728
|
-
path,
|
|
729
|
-
File.encodeFileName((prefix ?? "") + field.key, "inib")
|
|
730
|
-
)
|
|
731
|
-
)
|
|
732
|
-
) {
|
|
733
|
-
if (options.columns)
|
|
734
|
-
options.columns = (options.columns as string[])
|
|
735
|
-
.filter(
|
|
736
|
-
(column) =>
|
|
737
|
-
column.includes(`${field.key}.`) &&
|
|
738
|
-
!column.includes(`${field.key}.*.`)
|
|
739
|
-
)
|
|
740
|
-
.map((column) => column.replace(`${field.key}.`, ""));
|
|
741
|
-
for await (const [index, value] of Object.entries(
|
|
742
|
-
(await File.get(
|
|
743
|
-
join(
|
|
744
|
-
path,
|
|
745
|
-
File.encodeFileName((prefix ?? "") + field.key, "inib")
|
|
746
|
-
),
|
|
747
|
-
"number",
|
|
748
|
-
linesNumber
|
|
749
|
-
)) ?? {}
|
|
750
|
-
)) {
|
|
751
|
-
if (!RETURN[index]) RETURN[index] = {};
|
|
752
|
-
RETURN[index][field.key] = value
|
|
753
|
-
? await this.get(field.key, value as number, options)
|
|
754
|
-
: this.getDefaultValue(field);
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
} else if (
|
|
758
|
-
existsSync(
|
|
759
|
-
join(path, File.encodeFileName((prefix ?? "") + field.key, "inib"))
|
|
760
|
-
)
|
|
761
|
-
) {
|
|
762
|
-
Object.entries(
|
|
763
|
-
(await File.get(
|
|
764
|
-
join(
|
|
765
|
-
path,
|
|
766
|
-
File.encodeFileName((prefix ?? "") + field.key, "inib")
|
|
767
|
-
),
|
|
768
|
-
field.type,
|
|
769
|
-
linesNumber
|
|
770
|
-
)) ?? {}
|
|
771
|
-
).forEach(([index, item]) => {
|
|
772
|
-
if (!RETURN[index]) RETURN[index] = {};
|
|
773
|
-
RETURN[index][field.key] = item ?? this.getDefaultValue(field);
|
|
774
|
-
});
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
return RETURN;
|
|
778
|
-
};
|
|
779
|
-
if (!where) {
|
|
780
|
-
// Display all data
|
|
781
|
-
RETURN = Object.values(
|
|
782
|
-
await getItemsFromSchema(
|
|
783
|
-
join(this.databasePath, tableName),
|
|
784
|
-
schema,
|
|
785
|
-
Array.from(
|
|
786
|
-
{ length: options.per_page },
|
|
787
|
-
(_, index) =>
|
|
788
|
-
((options.page as number) - 1) * (options.per_page as number) +
|
|
789
|
-
index +
|
|
790
|
-
1
|
|
791
|
-
)
|
|
792
|
-
)
|
|
793
|
-
);
|
|
794
|
-
} else if (this.isValidID(where) || Utils.isNumber(where)) {
|
|
795
|
-
let Ids = where as string | number | (string | number)[];
|
|
796
|
-
if (!Array.isArray(Ids)) Ids = [Ids];
|
|
797
|
-
const idFilePath = join(this.databasePath, tableName, "id.inib");
|
|
798
|
-
if (!existsSync(idFilePath)) throw this.throwError("NO_ITEMS", tableName);
|
|
799
|
-
const [lineNumbers, countItems] = await File.search(
|
|
800
|
-
idFilePath,
|
|
801
|
-
"number",
|
|
802
|
-
"[]",
|
|
803
|
-
Utils.isNumber(Ids)
|
|
804
|
-
? Ids.map((id) => parseFloat(id as string))
|
|
805
|
-
: Ids.map((id) => this.decodeID(id as string)),
|
|
806
|
-
undefined,
|
|
807
|
-
Ids.length
|
|
808
|
-
);
|
|
809
|
-
if (!lineNumbers || !Object.keys(lineNumbers).length)
|
|
810
|
-
throw this.throwError(
|
|
811
|
-
"INVALID_ID",
|
|
812
|
-
where as number | string | (number | string)[]
|
|
813
|
-
);
|
|
814
|
-
RETURN = Object.values(
|
|
815
|
-
(await getItemsFromSchema(
|
|
816
|
-
join(this.databasePath, tableName),
|
|
817
|
-
schema,
|
|
818
|
-
Object.keys(lineNumbers).map(Number)
|
|
819
|
-
)) ?? {}
|
|
820
|
-
);
|
|
821
|
-
if (RETURN.length && !Array.isArray(where)) RETURN = RETURN[0];
|
|
822
|
-
} else if (Utils.isObject(where)) {
|
|
823
|
-
// Criteria
|
|
824
|
-
const FormatObjectCriteriaValue = (
|
|
825
|
-
value: string
|
|
826
|
-
): [ComparisonOperator, string | number | boolean | null] => {
|
|
827
|
-
switch (value[0]) {
|
|
828
|
-
case ">":
|
|
829
|
-
case "<":
|
|
830
|
-
case "[":
|
|
831
|
-
return ["=", "]", "*"].includes(value[1])
|
|
832
|
-
? [
|
|
833
|
-
value.slice(0, 2) as ComparisonOperator,
|
|
834
|
-
value.slice(2) as string | number,
|
|
835
|
-
]
|
|
836
|
-
: [
|
|
837
|
-
value.slice(0, 1) as ComparisonOperator,
|
|
838
|
-
value.slice(1) as string | number,
|
|
839
|
-
];
|
|
840
|
-
case "!":
|
|
841
|
-
return ["=", "*"].includes(value[1])
|
|
842
|
-
? [
|
|
843
|
-
value.slice(0, 2) as ComparisonOperator,
|
|
844
|
-
value.slice(2) as string | number,
|
|
845
|
-
]
|
|
846
|
-
: value[1] === "["
|
|
847
|
-
? [
|
|
848
|
-
value.slice(0, 3) as ComparisonOperator,
|
|
849
|
-
value.slice(3) as string | number,
|
|
850
|
-
]
|
|
851
|
-
: [
|
|
852
|
-
value.slice(0, 1) as ComparisonOperator,
|
|
853
|
-
value.slice(1) as string | number,
|
|
854
|
-
];
|
|
855
|
-
case "=":
|
|
856
|
-
case "*":
|
|
857
|
-
return [
|
|
858
|
-
value.slice(0, 1) as ComparisonOperator,
|
|
859
|
-
value.slice(1) as string | number,
|
|
860
|
-
];
|
|
861
|
-
default:
|
|
862
|
-
return ["=", value];
|
|
863
|
-
}
|
|
864
|
-
};
|
|
865
|
-
|
|
866
|
-
const applyCriteria = async (
|
|
867
|
-
criteria?: Criteria,
|
|
868
|
-
allTrue?: boolean
|
|
869
|
-
): Promise<Record<number, Data> | null> => {
|
|
870
|
-
let RETURN: Record<number, Data> = {};
|
|
871
|
-
if (!criteria) return null;
|
|
872
|
-
if (criteria.and && Utils.isObject(criteria.and)) {
|
|
873
|
-
const searchResult = await applyCriteria(
|
|
874
|
-
criteria.and as Criteria,
|
|
875
|
-
true
|
|
876
|
-
);
|
|
877
|
-
if (searchResult) {
|
|
878
|
-
RETURN = Utils.deepMerge(
|
|
879
|
-
RETURN,
|
|
880
|
-
Object.fromEntries(
|
|
881
|
-
Object.entries(searchResult).filter(
|
|
882
|
-
([_k, v], _i) =>
|
|
883
|
-
Object.keys(v).length ===
|
|
884
|
-
Object.keys(criteria.and ?? {}).length
|
|
885
|
-
)
|
|
886
|
-
)
|
|
887
|
-
);
|
|
888
|
-
delete criteria.and;
|
|
889
|
-
} else return null;
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
if (criteria.or && Utils.isObject(criteria.or)) {
|
|
893
|
-
const searchResult = await applyCriteria(criteria.or as Criteria);
|
|
894
|
-
delete criteria.or;
|
|
895
|
-
if (searchResult) RETURN = Utils.deepMerge(RETURN, searchResult);
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
if (Object.keys(criteria).length > 0) {
|
|
899
|
-
allTrue = true;
|
|
900
|
-
let index = -1;
|
|
901
|
-
for (const [key, value] of Object.entries(criteria)) {
|
|
902
|
-
index++;
|
|
903
|
-
let searchOperator:
|
|
904
|
-
| ComparisonOperator
|
|
905
|
-
| ComparisonOperator[]
|
|
906
|
-
| undefined = undefined,
|
|
907
|
-
searchComparedAtValue:
|
|
908
|
-
| string
|
|
909
|
-
| number
|
|
910
|
-
| boolean
|
|
911
|
-
| null
|
|
912
|
-
| (string | number | boolean | null)[]
|
|
913
|
-
| undefined = undefined,
|
|
914
|
-
searchLogicalOperator: "and" | "or" | undefined = undefined;
|
|
915
|
-
if (Utils.isObject(value)) {
|
|
916
|
-
if (
|
|
917
|
-
(value as Criteria)?.or &&
|
|
918
|
-
Array.isArray((value as Criteria).or)
|
|
919
|
-
) {
|
|
920
|
-
const searchCriteria = (
|
|
921
|
-
(value as Criteria).or as (string | number | boolean)[]
|
|
922
|
-
)
|
|
923
|
-
.map(
|
|
924
|
-
(
|
|
925
|
-
single_or
|
|
926
|
-
): [ComparisonOperator, string | number | boolean | null] =>
|
|
927
|
-
typeof single_or === "string"
|
|
928
|
-
? FormatObjectCriteriaValue(single_or)
|
|
929
|
-
: ["=", single_or]
|
|
930
|
-
)
|
|
931
|
-
.filter((a) => a) as [ComparisonOperator, string | number][];
|
|
932
|
-
if (searchCriteria.length > 0) {
|
|
933
|
-
searchOperator = searchCriteria.map(
|
|
934
|
-
(single_or) => single_or[0]
|
|
935
|
-
);
|
|
936
|
-
searchComparedAtValue = searchCriteria.map(
|
|
937
|
-
(single_or) => single_or[1]
|
|
938
|
-
);
|
|
939
|
-
searchLogicalOperator = "or";
|
|
940
|
-
}
|
|
941
|
-
delete (value as Criteria).or;
|
|
942
|
-
}
|
|
943
|
-
if (
|
|
944
|
-
(value as Criteria)?.and &&
|
|
945
|
-
Array.isArray((value as Criteria).and)
|
|
946
|
-
) {
|
|
947
|
-
const searchCriteria = (
|
|
948
|
-
(value as Criteria).and as (string | number | boolean)[]
|
|
949
|
-
)
|
|
950
|
-
.map(
|
|
951
|
-
(
|
|
952
|
-
single_and
|
|
953
|
-
): [ComparisonOperator, string | number | boolean | null] =>
|
|
954
|
-
typeof single_and === "string"
|
|
955
|
-
? FormatObjectCriteriaValue(single_and)
|
|
956
|
-
: ["=", single_and]
|
|
957
|
-
)
|
|
958
|
-
.filter((a) => a) as [ComparisonOperator, string | number][];
|
|
959
|
-
if (searchCriteria.length > 0) {
|
|
960
|
-
searchOperator = searchCriteria.map(
|
|
961
|
-
(single_and) => single_and[0]
|
|
962
|
-
);
|
|
963
|
-
searchComparedAtValue = searchCriteria.map(
|
|
964
|
-
(single_and) => single_and[1]
|
|
965
|
-
);
|
|
966
|
-
searchLogicalOperator = "and";
|
|
967
|
-
}
|
|
968
|
-
delete (value as Criteria).and;
|
|
969
|
-
}
|
|
970
|
-
} else if (Array.isArray(value)) {
|
|
971
|
-
const searchCriteria = value
|
|
972
|
-
.map(
|
|
973
|
-
(
|
|
974
|
-
single
|
|
975
|
-
): [ComparisonOperator, string | number | boolean | null] =>
|
|
976
|
-
typeof single === "string"
|
|
977
|
-
? FormatObjectCriteriaValue(single)
|
|
978
|
-
: ["=", single]
|
|
979
|
-
)
|
|
980
|
-
.filter((a) => a) as [ComparisonOperator, string | number][];
|
|
981
|
-
if (searchCriteria.length > 0) {
|
|
982
|
-
searchOperator = searchCriteria.map((single) => single[0]);
|
|
983
|
-
searchComparedAtValue = searchCriteria.map(
|
|
984
|
-
(single) => single[1]
|
|
985
|
-
);
|
|
986
|
-
searchLogicalOperator = "and";
|
|
987
|
-
}
|
|
988
|
-
} else if (typeof value === "string") {
|
|
989
|
-
const ComparisonOperatorValue = FormatObjectCriteriaValue(value);
|
|
990
|
-
if (ComparisonOperatorValue) {
|
|
991
|
-
searchOperator = ComparisonOperatorValue[0];
|
|
992
|
-
searchComparedAtValue = ComparisonOperatorValue[1];
|
|
993
|
-
}
|
|
994
|
-
} else {
|
|
995
|
-
searchOperator = "=";
|
|
996
|
-
searchComparedAtValue = value as number | boolean;
|
|
997
|
-
}
|
|
998
|
-
const [searchResult, totlaItems] = await File.search(
|
|
999
|
-
join(
|
|
1000
|
-
this.databasePath,
|
|
1001
|
-
tableName,
|
|
1002
|
-
File.encodeFileName(key, "inib")
|
|
1003
|
-
),
|
|
1004
|
-
this.getField(key, schema as Schema)?.type ?? "string",
|
|
1005
|
-
searchOperator,
|
|
1006
|
-
searchComparedAtValue,
|
|
1007
|
-
searchLogicalOperator,
|
|
1008
|
-
options.per_page,
|
|
1009
|
-
(options.page as number) - 1 * (options.per_page as number) + 1,
|
|
1010
|
-
true
|
|
1011
|
-
);
|
|
1012
|
-
if (searchResult) {
|
|
1013
|
-
RETURN = Utils.deepMerge(RETURN, searchResult);
|
|
1014
|
-
if (!this.pageInfoArray[key]) this.pageInfoArray[key] = {};
|
|
1015
|
-
this.pageInfoArray[key].total_items = totlaItems;
|
|
1016
|
-
}
|
|
1017
|
-
if (allTrue && index > 0) {
|
|
1018
|
-
if (!Object.keys(RETURN).length) RETURN = {};
|
|
1019
|
-
RETURN = Object.fromEntries(
|
|
1020
|
-
Object.entries(RETURN).filter(
|
|
1021
|
-
([_index, item]) => Object.keys(item).length > index
|
|
1022
|
-
)
|
|
1023
|
-
);
|
|
1024
|
-
if (!Object.keys(RETURN).length) RETURN = {};
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
return Object.keys(RETURN).length ? RETURN : null;
|
|
1029
|
-
};
|
|
1030
|
-
RETURN = await applyCriteria(where as Criteria);
|
|
1031
|
-
if (RETURN) {
|
|
1032
|
-
if (onlyLinesNumbers) return Object.keys(RETURN).map(Number);
|
|
1033
|
-
const alreadyExistsColumns = Object.keys(Object.values(RETURN)[0]).map(
|
|
1034
|
-
(key) => File.decodeFileName(parse(key).name)
|
|
1035
|
-
),
|
|
1036
|
-
greatestColumnTotalItems = alreadyExistsColumns.reduce(
|
|
1037
|
-
(maxItem: string, currentItem: string) =>
|
|
1038
|
-
this.pageInfoArray[currentItem]?.total_items ||
|
|
1039
|
-
(0 > (this.pageInfoArray[maxItem]?.total_items || 0) &&
|
|
1040
|
-
this.pageInfoArray[currentItem].total_items)
|
|
1041
|
-
? currentItem
|
|
1042
|
-
: maxItem,
|
|
1043
|
-
""
|
|
1044
|
-
);
|
|
1045
|
-
if (greatestColumnTotalItems)
|
|
1046
|
-
this.pageInfo = {
|
|
1047
|
-
...(({ columns, ...restOFOptions }) => restOFOptions)(options),
|
|
1048
|
-
...this.pageInfoArray[greatestColumnTotalItems],
|
|
1049
|
-
total_pages: Math.ceil(
|
|
1050
|
-
this.pageInfoArray[greatestColumnTotalItems].total_items /
|
|
1051
|
-
options.per_page
|
|
1052
|
-
),
|
|
1053
|
-
};
|
|
1054
|
-
RETURN = Object.values(
|
|
1055
|
-
Utils.deepMerge(
|
|
1056
|
-
await getItemsFromSchema(
|
|
1057
|
-
join(this.databasePath, tableName),
|
|
1058
|
-
schema.filter(
|
|
1059
|
-
(field) => !alreadyExistsColumns.includes(field.key)
|
|
1060
|
-
),
|
|
1061
|
-
Object.keys(RETURN).map(Number)
|
|
1062
|
-
),
|
|
1063
|
-
RETURN
|
|
1064
|
-
)
|
|
1065
|
-
);
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
if (
|
|
1069
|
-
!RETURN ||
|
|
1070
|
-
(Utils.isObject(RETURN) && !Object.keys(RETURN).length) ||
|
|
1071
|
-
(Array.isArray(RETURN) && !RETURN.length)
|
|
1072
|
-
)
|
|
1073
|
-
return null;
|
|
1074
|
-
return Utils.isArrayOfObjects(RETURN)
|
|
1075
|
-
? (RETURN as Data[]).map((data: Data) => {
|
|
1076
|
-
data.id = this.encodeID(data.id as number);
|
|
1077
|
-
return data;
|
|
1078
|
-
})
|
|
1079
|
-
: {
|
|
1080
|
-
...(RETURN as Data),
|
|
1081
|
-
id: this.encodeID((RETURN as Data).id as number),
|
|
1082
|
-
};
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
public async post(
|
|
1086
|
-
tableName: string,
|
|
1087
|
-
data: Data | Data[],
|
|
1088
|
-
options: Options = {
|
|
1089
|
-
page: 1,
|
|
1090
|
-
per_page: 15,
|
|
1091
|
-
}
|
|
1092
|
-
): Promise<Data | Data[] | null> {
|
|
1093
|
-
const schema = this.getTableSchema(tableName);
|
|
1094
|
-
let RETURN: Data | Data[] | null | undefined;
|
|
1095
|
-
if (!schema) throw this.throwError("NO_SCHEMA", tableName);
|
|
1096
|
-
const idFilePath = join(this.databasePath, tableName, "id.inib");
|
|
1097
|
-
let last_id = existsSync(idFilePath)
|
|
1098
|
-
? Number(Object.values(await File.get(idFilePath, "number", -1))[0])
|
|
1099
|
-
: 0;
|
|
1100
|
-
if (Utils.isArrayOfObjects(data))
|
|
1101
|
-
(data as Data[]).forEach((single_data, index) => {
|
|
1102
|
-
if (!RETURN) RETURN = [];
|
|
1103
|
-
RETURN[index] = (({ id, updated_at, created_at, ...rest }) => rest)(
|
|
1104
|
-
single_data
|
|
1105
|
-
);
|
|
1106
|
-
RETURN[index].id = ++last_id;
|
|
1107
|
-
RETURN[index].created_at = new Date();
|
|
1108
|
-
});
|
|
1109
|
-
else {
|
|
1110
|
-
RETURN = (({ id, updated_at, created_at, ...rest }) => rest)(
|
|
1111
|
-
data as Data
|
|
1112
|
-
);
|
|
1113
|
-
RETURN.id = ++last_id;
|
|
1114
|
-
RETURN.created_at = new Date();
|
|
1115
|
-
}
|
|
1116
|
-
if (!RETURN) throw this.throwError("NO_DATA");
|
|
1117
|
-
this.validateData(RETURN, schema);
|
|
1118
|
-
RETURN = this.formatData(RETURN, schema);
|
|
1119
|
-
const pathesContents = this.joinPathesContents(
|
|
1120
|
-
join(this.databasePath, tableName),
|
|
1121
|
-
RETURN
|
|
1122
|
-
);
|
|
1123
|
-
for (const [path, content] of Object.entries(pathesContents))
|
|
1124
|
-
appendFileSync(
|
|
1125
|
-
path,
|
|
1126
|
-
(Array.isArray(content) ? content.join("\n") : content ?? "") + "\n",
|
|
1127
|
-
"utf8"
|
|
1128
|
-
);
|
|
1129
|
-
|
|
1130
|
-
return this.get(
|
|
1131
|
-
tableName,
|
|
1132
|
-
Utils.isArrayOfObjects(RETURN)
|
|
1133
|
-
? RETURN.map((data: Data) => data.id)
|
|
1134
|
-
: ((RETURN as Data).id as number),
|
|
1135
|
-
options
|
|
1136
|
-
);
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
public async put(
|
|
1140
|
-
tableName: string,
|
|
1141
|
-
data: Data | Data[],
|
|
1142
|
-
where?: number | string | (number | string)[] | Criteria
|
|
1143
|
-
) {
|
|
1144
|
-
const schema = this.getTableSchema(tableName);
|
|
1145
|
-
if (!schema) throw this.throwError("NO_SCHEMA", tableName);
|
|
1146
|
-
this.validateData(data, schema, true);
|
|
1147
|
-
data = this.formatData(data, schema, true);
|
|
1148
|
-
if (!where) {
|
|
1149
|
-
if (Utils.isArrayOfObjects(data)) {
|
|
1150
|
-
if (
|
|
1151
|
-
!(data as Data[]).every(
|
|
1152
|
-
(item) => item.hasOwnProperty("id") && this.isValidID(item.id)
|
|
1153
|
-
)
|
|
1154
|
-
)
|
|
1155
|
-
throw this.throwError("INVALID_ID");
|
|
1156
|
-
await this.put(
|
|
1157
|
-
tableName,
|
|
1158
|
-
data,
|
|
1159
|
-
(data as Data[]).map((item) => item.id)
|
|
1160
|
-
);
|
|
1161
|
-
} else if (data.hasOwnProperty("id")) {
|
|
1162
|
-
if (!this.isValidID((data as Data).id))
|
|
1163
|
-
throw this.throwError("INVALID_ID", (data as Data).id);
|
|
1164
|
-
await this.put(
|
|
1165
|
-
tableName,
|
|
1166
|
-
data,
|
|
1167
|
-
this.decodeID((data as Data).id as string)
|
|
1168
|
-
);
|
|
1169
|
-
} else {
|
|
1170
|
-
const pathesContents = this.joinPathesContents(
|
|
1171
|
-
join(this.databasePath, tableName),
|
|
1172
|
-
Utils.isArrayOfObjects(data)
|
|
1173
|
-
? (data as Data[]).map((item) => ({
|
|
1174
|
-
...item,
|
|
1175
|
-
updated_at: new Date(),
|
|
1176
|
-
}))
|
|
1177
|
-
: { ...data, updated_at: new Date() }
|
|
1178
|
-
);
|
|
1179
|
-
for (const [path, content] of Object.entries(pathesContents))
|
|
1180
|
-
await File.replace(path, content);
|
|
1181
|
-
}
|
|
1182
|
-
} else if (this.isValidID(where)) {
|
|
1183
|
-
let Ids = where as string | string[];
|
|
1184
|
-
if (!Array.isArray(Ids)) Ids = [Ids];
|
|
1185
|
-
const idFilePath = join(this.databasePath, tableName, "id.inib");
|
|
1186
|
-
if (!existsSync(idFilePath)) throw this.throwError("NO_ITEMS", tableName);
|
|
1187
|
-
const [lineNumbers, countItems] = await File.search(
|
|
1188
|
-
idFilePath,
|
|
1189
|
-
"number",
|
|
1190
|
-
"[]",
|
|
1191
|
-
Ids.map((id) => this.decodeID(id)),
|
|
1192
|
-
undefined,
|
|
1193
|
-
Ids.length
|
|
1194
|
-
);
|
|
1195
|
-
if (!lineNumbers || !Object.keys(lineNumbers).length)
|
|
1196
|
-
throw this.throwError("INVALID_ID");
|
|
1197
|
-
await this.put(tableName, data, Object.keys(lineNumbers).map(Number));
|
|
1198
|
-
} else if (Utils.isNumber(where)) {
|
|
1199
|
-
// where in this case, is the line(s) number(s) and not id(s)
|
|
1200
|
-
const pathesContents = Object.fromEntries(
|
|
1201
|
-
Object.entries(
|
|
1202
|
-
this.joinPathesContents(
|
|
1203
|
-
join(this.databasePath, tableName),
|
|
1204
|
-
Utils.isArrayOfObjects(data)
|
|
1205
|
-
? (data as Data[]).map((item) => ({
|
|
1206
|
-
...item,
|
|
1207
|
-
updated_at: new Date(),
|
|
1208
|
-
}))
|
|
1209
|
-
: { ...data, updated_at: new Date() }
|
|
1210
|
-
)
|
|
1211
|
-
).map(([key, value]) => [
|
|
1212
|
-
key,
|
|
1213
|
-
([...(Array.isArray(where) ? where : [where])] as number[]).reduce(
|
|
1214
|
-
(obj, key, index) => ({
|
|
1215
|
-
...obj,
|
|
1216
|
-
[key]: Array.isArray(value) ? value[index] : value,
|
|
1217
|
-
}),
|
|
1218
|
-
{}
|
|
1219
|
-
),
|
|
1220
|
-
])
|
|
1221
|
-
);
|
|
1222
|
-
for (const [path, content] of Object.entries(pathesContents))
|
|
1223
|
-
await File.replace(path, content);
|
|
1224
|
-
} else if (typeof where === "object" && !Array.isArray(where)) {
|
|
1225
|
-
const lineNumbers = this.get(tableName, where, undefined, true);
|
|
1226
|
-
if (!lineNumbers || !Array.isArray(lineNumbers) || !lineNumbers.length)
|
|
1227
|
-
throw this.throwError("NO_ITEMS", tableName);
|
|
1228
|
-
await this.put(tableName, data, lineNumbers);
|
|
1229
|
-
} else throw this.throwError("PARAMETERS", tableName);
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
public async delete(
|
|
1233
|
-
tableName: string,
|
|
1234
|
-
where?: number | string | (number | string)[] | Criteria
|
|
1235
|
-
) {
|
|
1236
|
-
const schema = this.getTableSchema(tableName);
|
|
1237
|
-
if (!schema) throw this.throwError("NO_SCHEMA", tableName);
|
|
1238
|
-
if (!where) {
|
|
1239
|
-
const files = readdirSync(join(this.databasePath, tableName));
|
|
1240
|
-
if (files.length) {
|
|
1241
|
-
for (const file in files.filter(
|
|
1242
|
-
(fileName: string) => fileName !== "schema.inib"
|
|
1243
|
-
))
|
|
1244
|
-
unlinkSync(join(this.databasePath, tableName, file));
|
|
1245
|
-
}
|
|
1246
|
-
} else if (this.isValidID(where)) {
|
|
1247
|
-
let Ids = where as string | string[];
|
|
1248
|
-
if (!Array.isArray(Ids)) Ids = [Ids];
|
|
1249
|
-
const idFilePath = join(this.databasePath, tableName, "id.inib");
|
|
1250
|
-
if (!existsSync(idFilePath)) throw this.throwError("NO_ITEMS", tableName);
|
|
1251
|
-
const [lineNumbers, countItems] = await File.search(
|
|
1252
|
-
idFilePath,
|
|
1253
|
-
"number",
|
|
1254
|
-
"[]",
|
|
1255
|
-
Ids.map((id) => this.decodeID(id)),
|
|
1256
|
-
undefined,
|
|
1257
|
-
Ids.length
|
|
1258
|
-
);
|
|
1259
|
-
if (!lineNumbers || !Object.keys(lineNumbers).length)
|
|
1260
|
-
throw this.throwError("INVALID_ID");
|
|
1261
|
-
await this.delete(tableName, Object.keys(lineNumbers).map(Number));
|
|
1262
|
-
} else if (Utils.isNumber(where)) {
|
|
1263
|
-
const files = readdirSync(join(this.databasePath, tableName));
|
|
1264
|
-
if (files.length) {
|
|
1265
|
-
for (const file in files.filter(
|
|
1266
|
-
(fileName: string) => fileName !== "schema.inib"
|
|
1267
|
-
))
|
|
1268
|
-
await File.remove(
|
|
1269
|
-
join(this.databasePath, tableName, file),
|
|
1270
|
-
where as number | number[]
|
|
1271
|
-
);
|
|
1272
|
-
}
|
|
1273
|
-
} else if (typeof where === "object" && !Array.isArray(where)) {
|
|
1274
|
-
const lineNumbers = this.get(tableName, where, undefined, true);
|
|
1275
|
-
if (!lineNumbers || !Array.isArray(lineNumbers) || !lineNumbers.length)
|
|
1276
|
-
throw this.throwError("NO_ITEMS", tableName);
|
|
1277
|
-
await this.delete(tableName, lineNumbers);
|
|
1278
|
-
} else throw this.throwError("PARAMETERS", tableName);
|
|
1279
|
-
}
|
|
1280
|
-
}
|