inibase 1.0.0-rc.4 → 1.0.0-rc.5
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 +15 -5
- package/file.ts +225 -75
- package/index.test.ts +248 -0
- package/index.ts +594 -333
- package/package.json +3 -2
- package/tsconfig.json +2 -1
- package/utils.ts +99 -44
package/index.ts
CHANGED
|
@@ -9,12 +9,9 @@ import {
|
|
|
9
9
|
renameSync,
|
|
10
10
|
} from "fs";
|
|
11
11
|
import { join, parse } from "path";
|
|
12
|
-
import { createDecipheriv, createCipheriv, scryptSync } from "crypto";
|
|
13
12
|
import Utils from "./utils";
|
|
14
13
|
import File from "./file";
|
|
15
14
|
|
|
16
|
-
export { File, Utils };
|
|
17
|
-
|
|
18
15
|
export type Data = {
|
|
19
16
|
id?: number | string;
|
|
20
17
|
[key: string]: any;
|
|
@@ -33,31 +30,45 @@ export type FieldType =
|
|
|
33
30
|
| "object"
|
|
34
31
|
| "array"
|
|
35
32
|
| "password";
|
|
36
|
-
type
|
|
33
|
+
type FieldDefault = {
|
|
37
34
|
id?: string | number | null | undefined;
|
|
38
35
|
key: string;
|
|
39
36
|
required?: boolean;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
37
|
+
};
|
|
38
|
+
type FieldStringType = {
|
|
39
|
+
type: Exclude<FieldType, "array" | "object">;
|
|
40
|
+
};
|
|
41
|
+
type FieldStringArrayType = {
|
|
42
|
+
type: Exclude<FieldType, "array" | "object">[];
|
|
43
|
+
};
|
|
44
|
+
type FieldArrayType = {
|
|
45
|
+
type: "array";
|
|
46
|
+
children: FieldType | FieldType[] | Schema;
|
|
47
|
+
};
|
|
48
|
+
type FieldArrayArrayType = {
|
|
49
|
+
type: ["array", ...FieldType[]];
|
|
50
|
+
children: FieldType | FieldType[];
|
|
51
|
+
};
|
|
52
|
+
type FieldObjectType = {
|
|
53
|
+
type: "object";
|
|
54
|
+
children: Schema;
|
|
55
|
+
};
|
|
56
|
+
// if "type" is array, make "array" at first place, and "number" & "string" at last place of the array
|
|
57
|
+
type Field = FieldDefault &
|
|
58
|
+
(
|
|
59
|
+
| FieldStringType
|
|
60
|
+
| FieldStringArrayType
|
|
61
|
+
| FieldObjectType
|
|
62
|
+
| FieldArrayType
|
|
63
|
+
| FieldArrayArrayType
|
|
64
|
+
);
|
|
54
65
|
|
|
55
66
|
export type Schema = Field[];
|
|
56
67
|
|
|
57
68
|
export interface Options {
|
|
58
69
|
page?: number;
|
|
59
70
|
per_page?: number;
|
|
60
|
-
columns?: string[];
|
|
71
|
+
columns?: string[] | string;
|
|
61
72
|
}
|
|
62
73
|
|
|
63
74
|
export type ComparisonOperator =
|
|
@@ -103,7 +114,7 @@ export default class Inibase {
|
|
|
103
114
|
public pageInfoArray: Record<string, Record<string, number>>;
|
|
104
115
|
public pageInfo: pageInfo;
|
|
105
116
|
|
|
106
|
-
constructor(databaseName: string, mainFolder: string = "
|
|
117
|
+
constructor(databaseName: string, mainFolder: string = ".") {
|
|
107
118
|
this.database = databaseName;
|
|
108
119
|
this.databasePath = join(mainFolder, databaseName);
|
|
109
120
|
this.cache = new Map<string, any>();
|
|
@@ -122,14 +133,14 @@ export default class Inibase {
|
|
|
122
133
|
): Error {
|
|
123
134
|
const errorMessages: Record<string, Record<string, string>> = {
|
|
124
135
|
en: {
|
|
136
|
+
FIELD_REQUIRED: "REQUIRED: {variable}",
|
|
125
137
|
NO_SCHEMA: "NO_SCHEMA: {variable}",
|
|
126
138
|
NO_ITEMS: "NO_ITEMS: {variable}",
|
|
139
|
+
NO_DATA: "NO_DATA: {variable}",
|
|
127
140
|
INVALID_ID: "INVALID_ID: {variable}",
|
|
128
141
|
INVALID_TYPE: "INVALID_TYPE: {variable}",
|
|
129
|
-
REQUIRED: "REQUIRED: {variable}",
|
|
130
|
-
NO_DATA: "NO_DATA: {variable}",
|
|
131
142
|
INVALID_OPERATOR: "INVALID_OPERATOR: {variable}",
|
|
132
|
-
|
|
143
|
+
INVALID_PARAMETERS: "PARAMETERS: {variable}",
|
|
133
144
|
},
|
|
134
145
|
// Add more languages and error messages as needed
|
|
135
146
|
};
|
|
@@ -157,131 +168,6 @@ export default class Inibase {
|
|
|
157
168
|
return new Error(errorMessage);
|
|
158
169
|
}
|
|
159
170
|
|
|
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
171
|
public setTableSchema(tableName: string, schema: Schema): void {
|
|
286
172
|
const encodeSchema = (schema: Schema) => {
|
|
287
173
|
let RETURN: any[][] = [],
|
|
@@ -289,7 +175,9 @@ export default class Inibase {
|
|
|
289
175
|
for (const field of schema) {
|
|
290
176
|
if (!RETURN[index]) RETURN[index] = [];
|
|
291
177
|
RETURN[index].push(
|
|
292
|
-
field.id
|
|
178
|
+
field.id
|
|
179
|
+
? Utils.decodeID(field.id as string, this.databasePath)
|
|
180
|
+
: null
|
|
293
181
|
);
|
|
294
182
|
RETURN[index].push(field.key ?? null);
|
|
295
183
|
RETURN[index].push(field.required ?? null);
|
|
@@ -313,14 +201,22 @@ export default class Inibase {
|
|
|
313
201
|
) {
|
|
314
202
|
if (!field.id) {
|
|
315
203
|
oldIndex++;
|
|
316
|
-
field = {
|
|
317
|
-
|
|
204
|
+
field = {
|
|
205
|
+
...field,
|
|
206
|
+
id: Utils.encodeID(oldIndex, this.databasePath),
|
|
207
|
+
};
|
|
208
|
+
} else
|
|
209
|
+
oldIndex = Utils.decodeID(field.id as string, this.databasePath);
|
|
318
210
|
field.children = addIdToSchema(field.children as Schema, oldIndex);
|
|
319
211
|
oldIndex += field.children.length;
|
|
320
|
-
} else if (field.id)
|
|
212
|
+
} else if (field.id)
|
|
213
|
+
oldIndex = Utils.decodeID(field.id as string, this.databasePath);
|
|
321
214
|
else {
|
|
322
215
|
oldIndex++;
|
|
323
|
-
field = {
|
|
216
|
+
field = {
|
|
217
|
+
...field,
|
|
218
|
+
id: Utils.encodeID(oldIndex, this.databasePath),
|
|
219
|
+
};
|
|
324
220
|
}
|
|
325
221
|
return field;
|
|
326
222
|
}),
|
|
@@ -332,8 +228,10 @@ export default class Inibase {
|
|
|
332
228
|
Utils.isArrayOfObjects(lastField.children)
|
|
333
229
|
)
|
|
334
230
|
return findLastIdNumber(lastField.children as Schema);
|
|
335
|
-
else
|
|
336
|
-
|
|
231
|
+
else if (lastField.id && Utils.isValidID(lastField.id))
|
|
232
|
+
return Utils.decodeID(lastField.id as string, this.databasePath);
|
|
233
|
+
}
|
|
234
|
+
return 0;
|
|
337
235
|
};
|
|
338
236
|
|
|
339
237
|
// remove id from schema
|
|
@@ -346,7 +244,7 @@ export default class Inibase {
|
|
|
346
244
|
// update columns files names based on field id
|
|
347
245
|
const schemaToIdsPath = (schema: any, prefix = "") => {
|
|
348
246
|
let RETURN: any = {};
|
|
349
|
-
for (const field of schema)
|
|
247
|
+
for (const field of schema)
|
|
350
248
|
if (field.children && Utils.isArrayOfObjects(field.children)) {
|
|
351
249
|
Utils.deepMerge(
|
|
352
250
|
RETURN,
|
|
@@ -357,37 +255,20 @@ export default class Inibase {
|
|
|
357
255
|
(field.type === "array" ? ".*." : ".")
|
|
358
256
|
)
|
|
359
257
|
);
|
|
360
|
-
} else if (
|
|
361
|
-
RETURN[
|
|
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> = {};
|
|
258
|
+
} else if (Utils.isValidID(field.id))
|
|
259
|
+
RETURN[Utils.decodeID(field.id, this.databasePath)] =
|
|
260
|
+
File.encodeFileName((prefix ?? "") + field.key, "inib");
|
|
373
261
|
|
|
374
|
-
|
|
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;
|
|
262
|
+
return RETURN;
|
|
381
263
|
},
|
|
382
|
-
replaceOldPathes = findChangedProperties(
|
|
264
|
+
replaceOldPathes = Utils.findChangedProperties(
|
|
383
265
|
schemaToIdsPath(this.getTableSchema(tableName)),
|
|
384
266
|
schemaToIdsPath(schema)
|
|
385
267
|
);
|
|
386
|
-
if (replaceOldPathes)
|
|
268
|
+
if (replaceOldPathes)
|
|
387
269
|
for (const [oldPath, newPath] of Object.entries(replaceOldPathes))
|
|
388
270
|
if (existsSync(join(TablePath, oldPath)))
|
|
389
271
|
renameSync(join(TablePath, oldPath), join(TablePath, newPath));
|
|
390
|
-
}
|
|
391
272
|
}
|
|
392
273
|
|
|
393
274
|
writeFileSync(
|
|
@@ -403,7 +284,7 @@ export default class Inibase {
|
|
|
403
284
|
? decodeSchema(field)
|
|
404
285
|
: Object.fromEntries(
|
|
405
286
|
Object.entries({
|
|
406
|
-
id:
|
|
287
|
+
id: Utils.encodeID(field[0], this.databasePath),
|
|
407
288
|
key: field[1],
|
|
408
289
|
required: field[2],
|
|
409
290
|
type: field[3],
|
|
@@ -428,24 +309,156 @@ export default class Inibase {
|
|
|
428
309
|
);
|
|
429
310
|
}
|
|
430
311
|
return [
|
|
431
|
-
{
|
|
312
|
+
{
|
|
313
|
+
id: Utils.encodeID(0, this.databasePath),
|
|
314
|
+
key: "id",
|
|
315
|
+
type: "number",
|
|
316
|
+
required: true,
|
|
317
|
+
},
|
|
432
318
|
...(this.cache.get(TableSchemaPath) as unknown as Schema),
|
|
433
319
|
];
|
|
434
320
|
}
|
|
435
321
|
|
|
436
|
-
public getField
|
|
437
|
-
|
|
322
|
+
public getField<Property extends keyof Field | "children">(
|
|
323
|
+
keyPath: string,
|
|
324
|
+
schema: Schema | Field,
|
|
325
|
+
property?: Property
|
|
326
|
+
) {
|
|
327
|
+
const keyPathSplited = keyPath.split(".");
|
|
328
|
+
for (const [index, key] of keyPathSplited.entries()) {
|
|
438
329
|
if (key === "*") continue;
|
|
439
330
|
const foundItem = (schema as Schema).find((item) => item.key === key);
|
|
440
331
|
if (!foundItem) return null;
|
|
441
|
-
schema =
|
|
332
|
+
if (index === keyPathSplited.length - 1) schema = foundItem;
|
|
333
|
+
if (
|
|
442
334
|
(foundItem.type === "array" || foundItem.type === "object") &&
|
|
443
335
|
foundItem.children &&
|
|
444
336
|
Utils.isArrayOfObjects(foundItem.children)
|
|
445
|
-
|
|
446
|
-
|
|
337
|
+
)
|
|
338
|
+
schema = foundItem.children as Schema;
|
|
339
|
+
}
|
|
340
|
+
if (property) {
|
|
341
|
+
switch (property) {
|
|
342
|
+
case "type":
|
|
343
|
+
return (schema as Field).type;
|
|
344
|
+
case "children":
|
|
345
|
+
return (
|
|
346
|
+
schema as
|
|
347
|
+
| (Field & FieldObjectType)
|
|
348
|
+
| FieldArrayType
|
|
349
|
+
| FieldArrayArrayType
|
|
350
|
+
).children;
|
|
351
|
+
|
|
352
|
+
default:
|
|
353
|
+
return (schema as Field)[property as keyof Field];
|
|
354
|
+
}
|
|
355
|
+
} else return schema as Field;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
public validateData(
|
|
359
|
+
data: Data | Data[],
|
|
360
|
+
schema: Schema,
|
|
361
|
+
skipRequiredField: boolean = false
|
|
362
|
+
): void {
|
|
363
|
+
const validateFieldType = (
|
|
364
|
+
value: any,
|
|
365
|
+
fieldType: FieldType | FieldType[],
|
|
366
|
+
fieldChildrenType?: FieldType | FieldType[]
|
|
367
|
+
): boolean => {
|
|
368
|
+
if (value === null) return true;
|
|
369
|
+
if (Array.isArray(fieldType))
|
|
370
|
+
return Utils.detectFieldType(value, fieldType) !== undefined;
|
|
371
|
+
if (fieldType === "array" && fieldChildrenType && Array.isArray(value))
|
|
372
|
+
return value.some(
|
|
373
|
+
(v) =>
|
|
374
|
+
Utils.detectFieldType(
|
|
375
|
+
v,
|
|
376
|
+
Array.isArray(fieldChildrenType)
|
|
377
|
+
? fieldChildrenType
|
|
378
|
+
: [fieldChildrenType]
|
|
379
|
+
) !== undefined
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
switch (fieldType) {
|
|
383
|
+
case "string":
|
|
384
|
+
// TO-DO: and not email, url, password ...
|
|
385
|
+
return !Utils.isNumber(value);
|
|
386
|
+
case "password":
|
|
387
|
+
return !Utils.isNumber(value) && Utils.isPassword(value);
|
|
388
|
+
case "number":
|
|
389
|
+
return Utils.isNumber(value);
|
|
390
|
+
case "boolean":
|
|
391
|
+
return (
|
|
392
|
+
typeof value === "boolean" || value === "true" || value === "false"
|
|
393
|
+
);
|
|
394
|
+
case "date":
|
|
395
|
+
return Utils.isDate(value);
|
|
396
|
+
case "object":
|
|
397
|
+
return Utils.isObject(value);
|
|
398
|
+
case "array":
|
|
399
|
+
return Array.isArray(value);
|
|
400
|
+
case "email":
|
|
401
|
+
return Utils.isEmail(value);
|
|
402
|
+
case "url":
|
|
403
|
+
return Utils.isURL(value);
|
|
404
|
+
case "table":
|
|
405
|
+
// feat: check if id exists
|
|
406
|
+
if (Array.isArray(value))
|
|
407
|
+
return (
|
|
408
|
+
(Utils.isArrayOfObjects(value) &&
|
|
409
|
+
value.every(
|
|
410
|
+
(element: Data) =>
|
|
411
|
+
element.hasOwnProperty("id") &&
|
|
412
|
+
(Utils.isValidID(element.id) || Utils.isNumber(element.id))
|
|
413
|
+
)) ||
|
|
414
|
+
value.every(Utils.isNumber) ||
|
|
415
|
+
Utils.isValidID(value)
|
|
416
|
+
);
|
|
417
|
+
else if (Utils.isObject(value))
|
|
418
|
+
return (
|
|
419
|
+
value.hasOwnProperty("id") &&
|
|
420
|
+
(Utils.isValidID((value as Data).id) ||
|
|
421
|
+
Utils.isNumber((value as Data).id))
|
|
422
|
+
);
|
|
423
|
+
else return Utils.isNumber(value) || Utils.isValidID(value);
|
|
424
|
+
default:
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
if (Utils.isArrayOfObjects(data))
|
|
429
|
+
for (const single_data of data as Data[])
|
|
430
|
+
this.validateData(single_data, schema, skipRequiredField);
|
|
431
|
+
else if (Utils.isObject(data)) {
|
|
432
|
+
for (const field of schema) {
|
|
433
|
+
if (
|
|
434
|
+
!data.hasOwnProperty(field.key) &&
|
|
435
|
+
field.required &&
|
|
436
|
+
!skipRequiredField
|
|
437
|
+
)
|
|
438
|
+
throw this.throwError("FIELD_REQUIRED", field.key);
|
|
439
|
+
if (
|
|
440
|
+
!validateFieldType(
|
|
441
|
+
data[field.key],
|
|
442
|
+
field.type,
|
|
443
|
+
(field as any)?.children &&
|
|
444
|
+
!Utils.isArrayOfObjects((field as any)?.children)
|
|
445
|
+
? (field as any)?.children
|
|
446
|
+
: undefined
|
|
447
|
+
)
|
|
448
|
+
)
|
|
449
|
+
throw this.throwError("INVALID_TYPE", field.key);
|
|
450
|
+
if (
|
|
451
|
+
(field.type === "array" || field.type === "object") &&
|
|
452
|
+
field.children &&
|
|
453
|
+
Utils.isArrayOfObjects(field.children)
|
|
454
|
+
)
|
|
455
|
+
this.validateData(
|
|
456
|
+
data[field.key],
|
|
457
|
+
field.children as Schema,
|
|
458
|
+
skipRequiredField
|
|
459
|
+
);
|
|
460
|
+
}
|
|
447
461
|
}
|
|
448
|
-
return schema as Field;
|
|
449
462
|
}
|
|
450
463
|
|
|
451
464
|
public formatData(
|
|
@@ -453,89 +466,111 @@ export default class Inibase {
|
|
|
453
466
|
schema: Schema,
|
|
454
467
|
formatOnlyAvailiableKeys?: boolean
|
|
455
468
|
): Data | Data[] {
|
|
456
|
-
|
|
469
|
+
const formatField = (
|
|
470
|
+
value: any,
|
|
471
|
+
field: Field
|
|
472
|
+
): Data | Data[] | number | string => {
|
|
473
|
+
if (Array.isArray(field.type))
|
|
474
|
+
field.type = Utils.detectFieldType(value, field.type);
|
|
475
|
+
|
|
476
|
+
switch (field.type) {
|
|
477
|
+
case "array":
|
|
478
|
+
if (typeof field.children === "string") {
|
|
479
|
+
if (field.type === "array" && field.children === "table") {
|
|
480
|
+
if (Array.isArray(data[field.key])) {
|
|
481
|
+
if (Utils.isArrayOfObjects(data[field.key])) {
|
|
482
|
+
if (
|
|
483
|
+
value.every(
|
|
484
|
+
(item: any) =>
|
|
485
|
+
item.hasOwnProperty("id") &&
|
|
486
|
+
(Utils.isValidID(item.id) || Utils.isNumber(item.id))
|
|
487
|
+
)
|
|
488
|
+
)
|
|
489
|
+
value.map((item: any) =>
|
|
490
|
+
Utils.isNumber(item.id)
|
|
491
|
+
? Number(item.id)
|
|
492
|
+
: Utils.decodeID(item.id, this.databasePath)
|
|
493
|
+
);
|
|
494
|
+
} else if (Utils.isValidID(value) || Utils.isNumber(value))
|
|
495
|
+
return value.map((item: number | string) =>
|
|
496
|
+
Utils.isNumber(item)
|
|
497
|
+
? Number(item as string)
|
|
498
|
+
: Utils.decodeID(item as string, this.databasePath)
|
|
499
|
+
);
|
|
500
|
+
} else if (Utils.isValidID(value))
|
|
501
|
+
return [Utils.decodeID(value, this.databasePath)];
|
|
502
|
+
else if (Utils.isNumber(value)) return [Number(value)];
|
|
503
|
+
} else if (data.hasOwnProperty(field.key)) return value;
|
|
504
|
+
} else if (Utils.isArrayOfObjects(field.children))
|
|
505
|
+
return this.formatData(
|
|
506
|
+
value,
|
|
507
|
+
field.children as Schema,
|
|
508
|
+
formatOnlyAvailiableKeys
|
|
509
|
+
);
|
|
510
|
+
else if (Array.isArray(field.children))
|
|
511
|
+
return Array.isArray(value) ? value : [value];
|
|
512
|
+
break;
|
|
513
|
+
case "object":
|
|
514
|
+
if (Utils.isArrayOfObjects(field.children))
|
|
515
|
+
return this.formatData(
|
|
516
|
+
value,
|
|
517
|
+
field.children,
|
|
518
|
+
formatOnlyAvailiableKeys
|
|
519
|
+
);
|
|
520
|
+
break;
|
|
521
|
+
case "table":
|
|
522
|
+
if (Utils.isObject(value)) {
|
|
523
|
+
if (
|
|
524
|
+
value.hasOwnProperty("id") &&
|
|
525
|
+
(Utils.isValidID(value.id) || Utils.isNumber(value))
|
|
526
|
+
)
|
|
527
|
+
return Utils.isNumber(value.id)
|
|
528
|
+
? Number(value.id)
|
|
529
|
+
: Utils.decodeID(value.id, this.databasePath);
|
|
530
|
+
} else if (Utils.isValidID(value) || Utils.isNumber(value))
|
|
531
|
+
return Utils.isNumber(value)
|
|
532
|
+
? Number(value)
|
|
533
|
+
: Utils.decodeID(value, this.databasePath);
|
|
534
|
+
break;
|
|
535
|
+
case "password":
|
|
536
|
+
return value.length === 161 ? value : Utils.hashPassword(value);
|
|
537
|
+
case "number":
|
|
538
|
+
return Utils.isNumber(value) ? Number(value) : null;
|
|
539
|
+
default:
|
|
540
|
+
return value;
|
|
541
|
+
}
|
|
542
|
+
return null;
|
|
543
|
+
};
|
|
544
|
+
if (Utils.isArrayOfObjects(data))
|
|
457
545
|
return data.map((single_data: Data) =>
|
|
458
|
-
this.formatData(single_data, schema)
|
|
546
|
+
this.formatData(single_data, schema, formatOnlyAvailiableKeys)
|
|
459
547
|
);
|
|
460
|
-
|
|
548
|
+
else if (Utils.isObject(data)) {
|
|
461
549
|
let RETURN: Data = {};
|
|
462
550
|
for (const field of schema) {
|
|
463
551
|
if (!data.hasOwnProperty(field.key)) {
|
|
552
|
+
if (formatOnlyAvailiableKeys) continue;
|
|
464
553
|
RETURN[field.key] = this.getDefaultValue(field);
|
|
465
554
|
continue;
|
|
466
555
|
}
|
|
467
|
-
|
|
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];
|
|
556
|
+
RETURN[field.key] = formatField(data[field.key], field);
|
|
533
557
|
}
|
|
534
558
|
return RETURN;
|
|
535
559
|
} else return [];
|
|
536
560
|
}
|
|
537
561
|
|
|
538
562
|
private getDefaultValue(field: Field): any {
|
|
563
|
+
if (Array.isArray(field.type))
|
|
564
|
+
return this.getDefaultValue({
|
|
565
|
+
...field,
|
|
566
|
+
type: field.type.sort(
|
|
567
|
+
(a: FieldType, b: FieldType) =>
|
|
568
|
+
Number(b === "array") - Number(a === "array") ||
|
|
569
|
+
Number(a === "string") - Number(b === "string") ||
|
|
570
|
+
Number(a === "number") - Number(b === "number")
|
|
571
|
+
)[0],
|
|
572
|
+
} as Field);
|
|
573
|
+
|
|
539
574
|
switch (field.type) {
|
|
540
575
|
case "array":
|
|
541
576
|
return Utils.isArrayOfObjects(field.children)
|
|
@@ -562,37 +597,54 @@ export default class Inibase {
|
|
|
562
597
|
mainPath: string,
|
|
563
598
|
data: Data | Data[]
|
|
564
599
|
): { [key: string]: string[] } {
|
|
565
|
-
const CombineData = (
|
|
600
|
+
const CombineData = (_data: Data | Data[], prefix?: string) => {
|
|
566
601
|
let RETURN: Record<
|
|
567
602
|
string,
|
|
568
603
|
string | boolean | number | null | (string | boolean | number | null)[]
|
|
569
604
|
> = {};
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
605
|
+
const combineObjectsToArray = (input: any[]) =>
|
|
606
|
+
input.reduce(
|
|
607
|
+
(r, c) => (
|
|
608
|
+
Object.keys(c).map((k) => (r[k] = [...(r[k] || []), c[k]])), r
|
|
609
|
+
),
|
|
610
|
+
{}
|
|
611
|
+
);
|
|
612
|
+
if (Utils.isArrayOfObjects(_data))
|
|
613
|
+
RETURN = combineObjectsToArray(
|
|
614
|
+
(_data as Data[]).map((single_data) => CombineData(single_data))
|
|
574
615
|
);
|
|
575
616
|
else {
|
|
576
|
-
for (const [key, value] of Object.entries(
|
|
617
|
+
for (const [key, value] of Object.entries(_data as Data)) {
|
|
577
618
|
if (Utils.isObject(value))
|
|
578
619
|
Object.assign(RETURN, CombineData(value, `${key}.`));
|
|
579
620
|
else if (Array.isArray(value)) {
|
|
580
|
-
if (Utils.isArrayOfObjects(value))
|
|
621
|
+
if (Utils.isArrayOfObjects(value)) {
|
|
622
|
+
Object.assign(
|
|
623
|
+
RETURN,
|
|
624
|
+
CombineData(
|
|
625
|
+
combineObjectsToArray(value),
|
|
626
|
+
(prefix ?? "") + key + ".*."
|
|
627
|
+
)
|
|
628
|
+
);
|
|
629
|
+
} else if (
|
|
630
|
+
Utils.isArrayOfArrays(value) &&
|
|
631
|
+
value.every(Utils.isArrayOfObjects)
|
|
632
|
+
)
|
|
581
633
|
Object.assign(
|
|
582
634
|
RETURN,
|
|
583
635
|
CombineData(
|
|
584
|
-
|
|
636
|
+
combineObjectsToArray(value.map(combineObjectsToArray)),
|
|
585
637
|
(prefix ?? "") + key + ".*."
|
|
586
638
|
)
|
|
587
639
|
);
|
|
588
640
|
else
|
|
589
|
-
RETURN[(prefix ?? "") + key] =
|
|
641
|
+
RETURN[(prefix ?? "") + key] = File.encode(value) as
|
|
590
642
|
| boolean
|
|
591
643
|
| number
|
|
592
644
|
| string
|
|
593
645
|
| null;
|
|
594
646
|
} else
|
|
595
|
-
RETURN[(prefix ?? "") + key] =
|
|
647
|
+
RETURN[(prefix ?? "") + key] = File.encode(value) as
|
|
596
648
|
| boolean
|
|
597
649
|
| number
|
|
598
650
|
| string
|
|
@@ -622,6 +674,8 @@ export default class Inibase {
|
|
|
622
674
|
onlyLinesNumbers?: boolean
|
|
623
675
|
): Promise<Data | Data[] | number[] | null> {
|
|
624
676
|
if (!options.columns) options.columns = [];
|
|
677
|
+
else if (!Array.isArray(options.columns))
|
|
678
|
+
options.columns = [options.columns];
|
|
625
679
|
else if (
|
|
626
680
|
options.columns.length &&
|
|
627
681
|
!(options.columns as string[]).includes("id")
|
|
@@ -635,23 +689,35 @@ export default class Inibase {
|
|
|
635
689
|
const filterSchemaByColumns = (schema: Schema, columns: string[]): Schema =>
|
|
636
690
|
schema
|
|
637
691
|
.map((field) => {
|
|
638
|
-
if (columns.
|
|
692
|
+
if (columns.some((column) => column.startsWith("!")))
|
|
693
|
+
return columns.includes("!" + field.key) ? null : field;
|
|
694
|
+
if (columns.includes(field.key) || columns.includes("*"))
|
|
695
|
+
return field;
|
|
696
|
+
|
|
639
697
|
if (
|
|
640
698
|
(field.type === "array" || field.type === "object") &&
|
|
641
699
|
Utils.isArrayOfObjects(field.children) &&
|
|
642
|
-
columns.filter(
|
|
643
|
-
column
|
|
644
|
-
|
|
645
|
-
|
|
700
|
+
columns.filter(
|
|
701
|
+
(column) =>
|
|
702
|
+
column.startsWith(
|
|
703
|
+
field.key + (field.type === "array" ? ".*." : ".")
|
|
704
|
+
) ||
|
|
705
|
+
column.startsWith(
|
|
706
|
+
"!" + field.key + (field.type === "array" ? ".*." : ".")
|
|
707
|
+
)
|
|
646
708
|
).length
|
|
647
709
|
) {
|
|
648
710
|
field.children = filterSchemaByColumns(
|
|
649
711
|
field.children as Schema,
|
|
650
712
|
columns
|
|
651
|
-
.filter(
|
|
652
|
-
column
|
|
653
|
-
|
|
654
|
-
|
|
713
|
+
.filter(
|
|
714
|
+
(column) =>
|
|
715
|
+
column.startsWith(
|
|
716
|
+
field.key + (field.type === "array" ? ".*." : ".")
|
|
717
|
+
) ||
|
|
718
|
+
column.startsWith(
|
|
719
|
+
"!" + field.key + (field.type === "array" ? ".*." : ".")
|
|
720
|
+
)
|
|
655
721
|
)
|
|
656
722
|
.map((column) =>
|
|
657
723
|
column.replace(
|
|
@@ -677,10 +743,144 @@ export default class Inibase {
|
|
|
677
743
|
let RETURN: Record<number, Data> = {};
|
|
678
744
|
for (const field of schema) {
|
|
679
745
|
if (
|
|
680
|
-
(field.type === "array" ||
|
|
681
|
-
|
|
746
|
+
(field.type === "array" ||
|
|
747
|
+
(Array.isArray(field.type) &&
|
|
748
|
+
(field.type as any).includes("array"))) &&
|
|
749
|
+
(field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
|
|
750
|
+
.children
|
|
682
751
|
) {
|
|
683
|
-
if (
|
|
752
|
+
if (
|
|
753
|
+
Utils.isArrayOfObjects(
|
|
754
|
+
(field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
|
|
755
|
+
.children
|
|
756
|
+
)
|
|
757
|
+
) {
|
|
758
|
+
if (
|
|
759
|
+
(
|
|
760
|
+
(field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
|
|
761
|
+
.children as Schema
|
|
762
|
+
).filter(
|
|
763
|
+
(children) =>
|
|
764
|
+
children.type === "array" &&
|
|
765
|
+
Utils.isArrayOfObjects(children.children)
|
|
766
|
+
).length
|
|
767
|
+
) {
|
|
768
|
+
// one of children has array field type and has children array of object = Schema
|
|
769
|
+
Object.entries(
|
|
770
|
+
(await getItemsFromSchema(
|
|
771
|
+
path,
|
|
772
|
+
(
|
|
773
|
+
(
|
|
774
|
+
field as FieldDefault &
|
|
775
|
+
(FieldArrayType | FieldArrayArrayType)
|
|
776
|
+
).children as Schema
|
|
777
|
+
).filter(
|
|
778
|
+
(children) =>
|
|
779
|
+
children.type === "array" &&
|
|
780
|
+
Utils.isArrayOfObjects(children.children)
|
|
781
|
+
),
|
|
782
|
+
linesNumber,
|
|
783
|
+
(prefix ?? "") + field.key + ".*."
|
|
784
|
+
)) ?? {}
|
|
785
|
+
).forEach(([index, item]) => {
|
|
786
|
+
if (Utils.isObject(item)) {
|
|
787
|
+
if (!RETURN[index]) RETURN[index] = {};
|
|
788
|
+
if (!RETURN[index][field.key]) RETURN[index][field.key] = [];
|
|
789
|
+
for (const child_field of (
|
|
790
|
+
(
|
|
791
|
+
field as FieldDefault &
|
|
792
|
+
(FieldArrayType | FieldArrayArrayType)
|
|
793
|
+
).children as Schema
|
|
794
|
+
).filter(
|
|
795
|
+
(children) =>
|
|
796
|
+
children.type === "array" &&
|
|
797
|
+
Utils.isArrayOfObjects(children.children)
|
|
798
|
+
)) {
|
|
799
|
+
if (Utils.isObject(item[child_field.key])) {
|
|
800
|
+
Object.entries(item[child_field.key]).forEach(
|
|
801
|
+
([key, value]) => {
|
|
802
|
+
for (let _i = 0; _i < value.length; _i++) {
|
|
803
|
+
if (!RETURN[index][field.key][_i])
|
|
804
|
+
RETURN[index][field.key][_i] = {};
|
|
805
|
+
if (!RETURN[index][field.key][_i][child_field.key])
|
|
806
|
+
RETURN[index][field.key][_i][child_field.key] =
|
|
807
|
+
[];
|
|
808
|
+
value[_i].forEach((_element, _index) => {
|
|
809
|
+
if (
|
|
810
|
+
!RETURN[index][field.key][_i][child_field.key][
|
|
811
|
+
_index
|
|
812
|
+
]
|
|
813
|
+
)
|
|
814
|
+
RETURN[index][field.key][_i][child_field.key][
|
|
815
|
+
_index
|
|
816
|
+
] = {};
|
|
817
|
+
RETURN[index][field.key][_i][child_field.key][
|
|
818
|
+
_index
|
|
819
|
+
][key] = _element;
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
(
|
|
829
|
+
field as FieldDefault & (FieldArrayType | FieldArrayArrayType)
|
|
830
|
+
).children = (
|
|
831
|
+
(field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
|
|
832
|
+
.children as Schema
|
|
833
|
+
).filter(
|
|
834
|
+
(children) =>
|
|
835
|
+
children.type !== "array" ||
|
|
836
|
+
!Utils.isArrayOfObjects(children.children)
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
Object.entries(
|
|
840
|
+
(await getItemsFromSchema(
|
|
841
|
+
path,
|
|
842
|
+
(field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
|
|
843
|
+
.children as Schema,
|
|
844
|
+
linesNumber,
|
|
845
|
+
(prefix ?? "") + field.key + ".*."
|
|
846
|
+
)) ?? {}
|
|
847
|
+
).forEach(([index, item]) => {
|
|
848
|
+
if (!RETURN[index]) RETURN[index] = {};
|
|
849
|
+
if (Utils.isObject(item)) {
|
|
850
|
+
if (!Object.values(item).every((i) => i === null)) {
|
|
851
|
+
if (RETURN[index][field.key])
|
|
852
|
+
Object.entries(item).forEach(([key, value], _index) => {
|
|
853
|
+
RETURN[index][field.key] = RETURN[index][field.key].map(
|
|
854
|
+
(_obj, _i) => ({ ..._obj, [key]: value[_index] })
|
|
855
|
+
);
|
|
856
|
+
});
|
|
857
|
+
else if (Object.values(item).every(Utils.isArrayOfArrays))
|
|
858
|
+
RETURN[index][field.key] = item;
|
|
859
|
+
else {
|
|
860
|
+
RETURN[index][field.key] = [];
|
|
861
|
+
Object.entries(item).forEach(([key, value]) => {
|
|
862
|
+
for (let _i = 0; _i < value.length; _i++) {
|
|
863
|
+
if (!RETURN[index][field.key][_i])
|
|
864
|
+
RETURN[index][field.key][_i] = {};
|
|
865
|
+
RETURN[index][field.key][_i][key] = value[_i];
|
|
866
|
+
}
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
} else RETURN[index][field.key] = null;
|
|
870
|
+
} else RETURN[index][field.key] = item;
|
|
871
|
+
});
|
|
872
|
+
} else if (
|
|
873
|
+
(field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
|
|
874
|
+
.children === "table" ||
|
|
875
|
+
(Array.isArray(
|
|
876
|
+
(field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
|
|
877
|
+
.children
|
|
878
|
+
) &&
|
|
879
|
+
(
|
|
880
|
+
(field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
|
|
881
|
+
.children as FieldType[]
|
|
882
|
+
).includes("table"))
|
|
883
|
+
) {
|
|
684
884
|
if (options.columns)
|
|
685
885
|
options.columns = (options.columns as string[])
|
|
686
886
|
.filter((column) => column.includes(`${field.key}.*.`))
|
|
@@ -691,8 +891,10 @@ export default class Inibase {
|
|
|
691
891
|
path,
|
|
692
892
|
File.encodeFileName((prefix ?? "") + field.key, "inib")
|
|
693
893
|
),
|
|
894
|
+
linesNumber,
|
|
694
895
|
field.type,
|
|
695
|
-
|
|
896
|
+
(field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
|
|
897
|
+
.children as FieldType | FieldType[]
|
|
696
898
|
)) ?? {}
|
|
697
899
|
)) {
|
|
698
900
|
if (!RETURN[index]) RETURN[index] = {};
|
|
@@ -700,26 +902,44 @@ export default class Inibase {
|
|
|
700
902
|
? await this.get(field.key, value as number, options)
|
|
701
903
|
: this.getDefaultValue(field);
|
|
702
904
|
}
|
|
703
|
-
} else if (
|
|
704
|
-
|
|
705
|
-
(
|
|
905
|
+
} else if (
|
|
906
|
+
existsSync(
|
|
907
|
+
join(
|
|
706
908
|
path,
|
|
707
|
-
|
|
909
|
+
File.encodeFileName((prefix ?? "") + field.key, "inib")
|
|
910
|
+
)
|
|
911
|
+
)
|
|
912
|
+
)
|
|
913
|
+
Object.entries(
|
|
914
|
+
(await File.get(
|
|
915
|
+
join(
|
|
916
|
+
path,
|
|
917
|
+
File.encodeFileName((prefix ?? "") + field.key, "inib")
|
|
918
|
+
),
|
|
708
919
|
linesNumber,
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
(field.type === "array" ? ".*." : ".")
|
|
920
|
+
field.type,
|
|
921
|
+
(field as any)?.children
|
|
712
922
|
)) ?? {}
|
|
713
923
|
).forEach(([index, item]) => {
|
|
714
924
|
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;
|
|
925
|
+
RETURN[index][field.key] = item ?? this.getDefaultValue(field);
|
|
721
926
|
});
|
|
722
|
-
|
|
927
|
+
} else if (field.type === "object") {
|
|
928
|
+
Object.entries(
|
|
929
|
+
(await getItemsFromSchema(
|
|
930
|
+
path,
|
|
931
|
+
field.children as Schema,
|
|
932
|
+
linesNumber,
|
|
933
|
+
(prefix ?? "") + field.key + "."
|
|
934
|
+
)) ?? {}
|
|
935
|
+
).forEach(([index, item]) => {
|
|
936
|
+
if (!RETURN[index]) RETURN[index] = {};
|
|
937
|
+
if (Utils.isObject(item)) {
|
|
938
|
+
if (!Object.values(item).every((i) => i === null))
|
|
939
|
+
RETURN[index][field.key] = item;
|
|
940
|
+
else RETURN[index][field.key] = null;
|
|
941
|
+
} else RETURN[index][field.key] = null;
|
|
942
|
+
});
|
|
723
943
|
} else if (field.type === "table") {
|
|
724
944
|
if (
|
|
725
945
|
existsSync(join(this.databasePath, field.key)) &&
|
|
@@ -744,8 +964,8 @@ export default class Inibase {
|
|
|
744
964
|
path,
|
|
745
965
|
File.encodeFileName((prefix ?? "") + field.key, "inib")
|
|
746
966
|
),
|
|
747
|
-
|
|
748
|
-
|
|
967
|
+
linesNumber,
|
|
968
|
+
"number"
|
|
749
969
|
)) ?? {}
|
|
750
970
|
)) {
|
|
751
971
|
if (!RETURN[index]) RETURN[index] = {};
|
|
@@ -758,21 +978,21 @@ export default class Inibase {
|
|
|
758
978
|
existsSync(
|
|
759
979
|
join(path, File.encodeFileName((prefix ?? "") + field.key, "inib"))
|
|
760
980
|
)
|
|
761
|
-
)
|
|
981
|
+
)
|
|
762
982
|
Object.entries(
|
|
763
983
|
(await File.get(
|
|
764
984
|
join(
|
|
765
985
|
path,
|
|
766
986
|
File.encodeFileName((prefix ?? "") + field.key, "inib")
|
|
767
987
|
),
|
|
988
|
+
linesNumber,
|
|
768
989
|
field.type,
|
|
769
|
-
|
|
990
|
+
(field as any)?.children
|
|
770
991
|
)) ?? {}
|
|
771
992
|
).forEach(([index, item]) => {
|
|
772
993
|
if (!RETURN[index]) RETURN[index] = {};
|
|
773
994
|
RETURN[index][field.key] = item ?? this.getDefaultValue(field);
|
|
774
995
|
});
|
|
775
|
-
}
|
|
776
996
|
}
|
|
777
997
|
return RETURN;
|
|
778
998
|
};
|
|
@@ -791,18 +1011,19 @@ export default class Inibase {
|
|
|
791
1011
|
)
|
|
792
1012
|
)
|
|
793
1013
|
);
|
|
794
|
-
} else if (
|
|
1014
|
+
} else if (Utils.isValidID(where) || Utils.isNumber(where)) {
|
|
795
1015
|
let Ids = where as string | number | (string | number)[];
|
|
796
1016
|
if (!Array.isArray(Ids)) Ids = [Ids];
|
|
797
1017
|
const idFilePath = join(this.databasePath, tableName, "id.inib");
|
|
798
1018
|
if (!existsSync(idFilePath)) throw this.throwError("NO_ITEMS", tableName);
|
|
799
1019
|
const [lineNumbers, countItems] = await File.search(
|
|
800
1020
|
idFilePath,
|
|
801
|
-
"number",
|
|
802
1021
|
"[]",
|
|
803
1022
|
Utils.isNumber(Ids)
|
|
804
|
-
? Ids.map((id) =>
|
|
805
|
-
: Ids.map((id) =>
|
|
1023
|
+
? Ids.map((id) => Number(id as string))
|
|
1024
|
+
: Ids.map((id) => Utils.decodeID(id as string, this.databasePath)),
|
|
1025
|
+
undefined,
|
|
1026
|
+
"number",
|
|
806
1027
|
undefined,
|
|
807
1028
|
Ids.length
|
|
808
1029
|
);
|
|
@@ -822,7 +1043,8 @@ export default class Inibase {
|
|
|
822
1043
|
} else if (Utils.isObject(where)) {
|
|
823
1044
|
// Criteria
|
|
824
1045
|
const FormatObjectCriteriaValue = (
|
|
825
|
-
value: string
|
|
1046
|
+
value: string,
|
|
1047
|
+
isParentArray: boolean = false
|
|
826
1048
|
): [ComparisonOperator, string | number | boolean | null] => {
|
|
827
1049
|
switch (value[0]) {
|
|
828
1050
|
case ">":
|
|
@@ -849,10 +1071,19 @@ export default class Inibase {
|
|
|
849
1071
|
value.slice(3) as string | number,
|
|
850
1072
|
]
|
|
851
1073
|
: [
|
|
852
|
-
value.slice(0, 1) as ComparisonOperator,
|
|
1074
|
+
(value.slice(0, 1) + "=") as ComparisonOperator,
|
|
853
1075
|
value.slice(1) as string | number,
|
|
854
1076
|
];
|
|
855
1077
|
case "=":
|
|
1078
|
+
return isParentArray
|
|
1079
|
+
? [
|
|
1080
|
+
value.slice(0, 1) as ComparisonOperator,
|
|
1081
|
+
value.slice(1) as string | number,
|
|
1082
|
+
]
|
|
1083
|
+
: [
|
|
1084
|
+
value.slice(0, 1) as ComparisonOperator,
|
|
1085
|
+
(value.slice(1) + ",") as string,
|
|
1086
|
+
];
|
|
856
1087
|
case "*":
|
|
857
1088
|
return [
|
|
858
1089
|
value.slice(0, 1) as ComparisonOperator,
|
|
@@ -899,6 +1130,7 @@ export default class Inibase {
|
|
|
899
1130
|
allTrue = true;
|
|
900
1131
|
let index = -1;
|
|
901
1132
|
for (const [key, value] of Object.entries(criteria)) {
|
|
1133
|
+
const field = this.getField(key, schema as Schema) as Field;
|
|
902
1134
|
index++;
|
|
903
1135
|
let searchOperator:
|
|
904
1136
|
| ComparisonOperator
|
|
@@ -1001,10 +1233,11 @@ export default class Inibase {
|
|
|
1001
1233
|
tableName,
|
|
1002
1234
|
File.encodeFileName(key, "inib")
|
|
1003
1235
|
),
|
|
1004
|
-
this.getField(key, schema as Schema)?.type ?? "string",
|
|
1005
1236
|
searchOperator,
|
|
1006
1237
|
searchComparedAtValue,
|
|
1007
1238
|
searchLogicalOperator,
|
|
1239
|
+
field?.type,
|
|
1240
|
+
(field as any)?.children,
|
|
1008
1241
|
options.per_page,
|
|
1009
1242
|
(options.page as number) - 1 * (options.per_page as number) + 1,
|
|
1010
1243
|
true
|
|
@@ -1044,7 +1277,7 @@ export default class Inibase {
|
|
|
1044
1277
|
);
|
|
1045
1278
|
if (greatestColumnTotalItems)
|
|
1046
1279
|
this.pageInfo = {
|
|
1047
|
-
...(({ columns, ...
|
|
1280
|
+
...(({ columns, ...restOfOptions }) => restOfOptions)(options),
|
|
1048
1281
|
...this.pageInfoArray[greatestColumnTotalItems],
|
|
1049
1282
|
total_pages: Math.ceil(
|
|
1050
1283
|
this.pageInfoArray[greatestColumnTotalItems].total_items /
|
|
@@ -1073,12 +1306,12 @@ export default class Inibase {
|
|
|
1073
1306
|
return null;
|
|
1074
1307
|
return Utils.isArrayOfObjects(RETURN)
|
|
1075
1308
|
? (RETURN as Data[]).map((data: Data) => {
|
|
1076
|
-
data.id =
|
|
1309
|
+
data.id = Utils.encodeID(data.id as number, this.databasePath);
|
|
1077
1310
|
return data;
|
|
1078
1311
|
})
|
|
1079
1312
|
: {
|
|
1080
1313
|
...(RETURN as Data),
|
|
1081
|
-
id:
|
|
1314
|
+
id: Utils.encodeID((RETURN as Data).id as number, this.databasePath),
|
|
1082
1315
|
};
|
|
1083
1316
|
}
|
|
1084
1317
|
|
|
@@ -1095,7 +1328,7 @@ export default class Inibase {
|
|
|
1095
1328
|
if (!schema) throw this.throwError("NO_SCHEMA", tableName);
|
|
1096
1329
|
const idFilePath = join(this.databasePath, tableName, "id.inib");
|
|
1097
1330
|
let last_id = existsSync(idFilePath)
|
|
1098
|
-
? Number(Object.values(await File.get(idFilePath, "number"
|
|
1331
|
+
? Number(Object.values(await File.get(idFilePath, -1, "number"))[0])
|
|
1099
1332
|
: 0;
|
|
1100
1333
|
if (Utils.isArrayOfObjects(data))
|
|
1101
1334
|
(data as Data[]).forEach((single_data, index) => {
|
|
@@ -1139,8 +1372,12 @@ export default class Inibase {
|
|
|
1139
1372
|
public async put(
|
|
1140
1373
|
tableName: string,
|
|
1141
1374
|
data: Data | Data[],
|
|
1142
|
-
where?: number | string | (number | string)[] | Criteria
|
|
1143
|
-
|
|
1375
|
+
where?: number | string | (number | string)[] | Criteria,
|
|
1376
|
+
options: Options = {
|
|
1377
|
+
page: 1,
|
|
1378
|
+
per_page: 15,
|
|
1379
|
+
}
|
|
1380
|
+
): Promise<Data | Data[] | null> {
|
|
1144
1381
|
const schema = this.getTableSchema(tableName);
|
|
1145
1382
|
if (!schema) throw this.throwError("NO_SCHEMA", tableName);
|
|
1146
1383
|
this.validateData(data, schema, true);
|
|
@@ -1149,54 +1386,59 @@ export default class Inibase {
|
|
|
1149
1386
|
if (Utils.isArrayOfObjects(data)) {
|
|
1150
1387
|
if (
|
|
1151
1388
|
!(data as Data[]).every(
|
|
1152
|
-
(item) => item.hasOwnProperty("id") &&
|
|
1389
|
+
(item) => item.hasOwnProperty("id") && Utils.isValidID(item.id)
|
|
1153
1390
|
)
|
|
1154
1391
|
)
|
|
1155
1392
|
throw this.throwError("INVALID_ID");
|
|
1156
|
-
|
|
1393
|
+
return this.put(
|
|
1157
1394
|
tableName,
|
|
1158
1395
|
data,
|
|
1159
1396
|
(data as Data[]).map((item) => item.id)
|
|
1160
1397
|
);
|
|
1161
1398
|
} else if (data.hasOwnProperty("id")) {
|
|
1162
|
-
if (!
|
|
1399
|
+
if (!Utils.isValidID((data as Data).id))
|
|
1163
1400
|
throw this.throwError("INVALID_ID", (data as Data).id);
|
|
1164
|
-
|
|
1401
|
+
return this.put(
|
|
1165
1402
|
tableName,
|
|
1166
1403
|
data,
|
|
1167
|
-
|
|
1404
|
+
Utils.decodeID((data as Data).id as string, this.databasePath)
|
|
1168
1405
|
);
|
|
1169
1406
|
} else {
|
|
1170
1407
|
const pathesContents = this.joinPathesContents(
|
|
1171
1408
|
join(this.databasePath, tableName),
|
|
1172
1409
|
Utils.isArrayOfObjects(data)
|
|
1173
1410
|
? (data as Data[]).map((item) => ({
|
|
1174
|
-
...item,
|
|
1411
|
+
...(({ id, ...restOfData }) => restOfData)(item),
|
|
1175
1412
|
updated_at: new Date(),
|
|
1176
1413
|
}))
|
|
1177
|
-
: {
|
|
1414
|
+
: {
|
|
1415
|
+
...(({ id, ...restOfData }) => restOfData)(data as Data),
|
|
1416
|
+
updated_at: new Date(),
|
|
1417
|
+
}
|
|
1178
1418
|
);
|
|
1179
1419
|
for (const [path, content] of Object.entries(pathesContents))
|
|
1180
1420
|
await File.replace(path, content);
|
|
1421
|
+
return this.get(tableName, where, options);
|
|
1181
1422
|
}
|
|
1182
|
-
} else if (
|
|
1423
|
+
} else if (Utils.isValidID(where)) {
|
|
1183
1424
|
let Ids = where as string | string[];
|
|
1184
1425
|
if (!Array.isArray(Ids)) Ids = [Ids];
|
|
1185
1426
|
const idFilePath = join(this.databasePath, tableName, "id.inib");
|
|
1186
1427
|
if (!existsSync(idFilePath)) throw this.throwError("NO_ITEMS", tableName);
|
|
1187
1428
|
const [lineNumbers, countItems] = await File.search(
|
|
1188
1429
|
idFilePath,
|
|
1189
|
-
"number",
|
|
1190
1430
|
"[]",
|
|
1191
|
-
Ids.map((id) =>
|
|
1431
|
+
Ids.map((id) => Utils.decodeID(id, this.databasePath)),
|
|
1432
|
+
undefined,
|
|
1433
|
+
"number",
|
|
1192
1434
|
undefined,
|
|
1193
1435
|
Ids.length
|
|
1194
1436
|
);
|
|
1195
1437
|
if (!lineNumbers || !Object.keys(lineNumbers).length)
|
|
1196
1438
|
throw this.throwError("INVALID_ID");
|
|
1197
|
-
|
|
1439
|
+
return this.put(tableName, data, Object.keys(lineNumbers).map(Number));
|
|
1198
1440
|
} else if (Utils.isNumber(where)) {
|
|
1199
|
-
// where in this case, is the line(s) number(s) and not id(s)
|
|
1441
|
+
// "where" in this case, is the line(s) number(s) and not id(s)
|
|
1200
1442
|
const pathesContents = Object.fromEntries(
|
|
1201
1443
|
Object.entries(
|
|
1202
1444
|
this.joinPathesContents(
|
|
@@ -1221,18 +1463,20 @@ export default class Inibase {
|
|
|
1221
1463
|
);
|
|
1222
1464
|
for (const [path, content] of Object.entries(pathesContents))
|
|
1223
1465
|
await File.replace(path, content);
|
|
1466
|
+
return this.get(tableName, where, options);
|
|
1224
1467
|
} else if (typeof where === "object" && !Array.isArray(where)) {
|
|
1225
1468
|
const lineNumbers = this.get(tableName, where, undefined, true);
|
|
1226
1469
|
if (!lineNumbers || !Array.isArray(lineNumbers) || !lineNumbers.length)
|
|
1227
1470
|
throw this.throwError("NO_ITEMS", tableName);
|
|
1228
|
-
|
|
1229
|
-
} else throw this.throwError("
|
|
1471
|
+
return this.put(tableName, data, lineNumbers);
|
|
1472
|
+
} else throw this.throwError("INVALID_PARAMETERS", tableName);
|
|
1230
1473
|
}
|
|
1231
1474
|
|
|
1232
1475
|
public async delete(
|
|
1233
1476
|
tableName: string,
|
|
1234
|
-
where?: number | string | (number | string)[] | Criteria
|
|
1235
|
-
|
|
1477
|
+
where?: number | string | (number | string)[] | Criteria,
|
|
1478
|
+
_id?: string | string[]
|
|
1479
|
+
): Promise<string | string[]> {
|
|
1236
1480
|
const schema = this.getTableSchema(tableName);
|
|
1237
1481
|
if (!schema) throw this.throwError("NO_SCHEMA", tableName);
|
|
1238
1482
|
if (!where) {
|
|
@@ -1243,25 +1487,41 @@ export default class Inibase {
|
|
|
1243
1487
|
))
|
|
1244
1488
|
unlinkSync(join(this.databasePath, tableName, file));
|
|
1245
1489
|
}
|
|
1246
|
-
|
|
1490
|
+
return "*";
|
|
1491
|
+
} else if (Utils.isValidID(where)) {
|
|
1247
1492
|
let Ids = where as string | string[];
|
|
1248
1493
|
if (!Array.isArray(Ids)) Ids = [Ids];
|
|
1249
1494
|
const idFilePath = join(this.databasePath, tableName, "id.inib");
|
|
1250
1495
|
if (!existsSync(idFilePath)) throw this.throwError("NO_ITEMS", tableName);
|
|
1251
1496
|
const [lineNumbers, countItems] = await File.search(
|
|
1252
1497
|
idFilePath,
|
|
1253
|
-
"number",
|
|
1254
1498
|
"[]",
|
|
1255
|
-
Ids.map((id) =>
|
|
1499
|
+
Ids.map((id) => Utils.decodeID(id, this.databasePath)),
|
|
1500
|
+
undefined,
|
|
1501
|
+
"number",
|
|
1256
1502
|
undefined,
|
|
1257
1503
|
Ids.length
|
|
1258
1504
|
);
|
|
1259
1505
|
if (!lineNumbers || !Object.keys(lineNumbers).length)
|
|
1260
1506
|
throw this.throwError("INVALID_ID");
|
|
1261
|
-
|
|
1507
|
+
return this.delete(
|
|
1508
|
+
tableName,
|
|
1509
|
+
Object.keys(lineNumbers).map(Number),
|
|
1510
|
+
where as string | string[]
|
|
1511
|
+
);
|
|
1262
1512
|
} else if (Utils.isNumber(where)) {
|
|
1263
1513
|
const files = readdirSync(join(this.databasePath, tableName));
|
|
1264
1514
|
if (files.length) {
|
|
1515
|
+
if (!_id)
|
|
1516
|
+
_id = Object.values(
|
|
1517
|
+
await File.get(
|
|
1518
|
+
join(this.databasePath, tableName, "id.inib"),
|
|
1519
|
+
where as number | number[],
|
|
1520
|
+
"number"
|
|
1521
|
+
)
|
|
1522
|
+
)
|
|
1523
|
+
.map(Number)
|
|
1524
|
+
.map((id) => Utils.encodeID(id, this.databasePath));
|
|
1265
1525
|
for (const file in files.filter(
|
|
1266
1526
|
(fileName: string) => fileName !== "schema.inib"
|
|
1267
1527
|
))
|
|
@@ -1269,12 +1529,13 @@ export default class Inibase {
|
|
|
1269
1529
|
join(this.databasePath, tableName, file),
|
|
1270
1530
|
where as number | number[]
|
|
1271
1531
|
);
|
|
1532
|
+
return Array.isArray(_id) && _id.length === 1 ? _id[0] : _id;
|
|
1272
1533
|
}
|
|
1273
1534
|
} else if (typeof where === "object" && !Array.isArray(where)) {
|
|
1274
1535
|
const lineNumbers = this.get(tableName, where, undefined, true);
|
|
1275
1536
|
if (!lineNumbers || !Array.isArray(lineNumbers) || !lineNumbers.length)
|
|
1276
1537
|
throw this.throwError("NO_ITEMS", tableName);
|
|
1277
|
-
|
|
1278
|
-
} else throw this.throwError("
|
|
1538
|
+
return this.delete(tableName, lineNumbers);
|
|
1539
|
+
} else throw this.throwError("INVALID_PARAMETERS", tableName);
|
|
1279
1540
|
}
|
|
1280
1541
|
}
|