inibase 1.0.0-rc.4 → 1.0.0-rc.6
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 +17 -11
- package/file.ts +230 -84
- package/index.test.ts +204 -0
- package/index.ts +715 -417
- package/package.json +4 -3
- package/tsconfig.json +2 -1
- package/utils.ts +156 -45
package/index.ts
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} from "
|
|
11
|
-
import { join, parse } from "path";
|
|
12
|
-
import { createDecipheriv, createCipheriv, scryptSync } from "crypto";
|
|
2
|
+
unlink,
|
|
3
|
+
rename,
|
|
4
|
+
readFile,
|
|
5
|
+
writeFile,
|
|
6
|
+
appendFile,
|
|
7
|
+
mkdir,
|
|
8
|
+
readdir,
|
|
9
|
+
} from "node:fs/promises";
|
|
10
|
+
import { join, parse } from "node:path";
|
|
13
11
|
import Utils from "./utils";
|
|
14
12
|
import File from "./file";
|
|
15
|
-
|
|
16
|
-
export { File, Utils };
|
|
13
|
+
import { scryptSync } from "node:crypto";
|
|
17
14
|
|
|
18
15
|
export type Data = {
|
|
19
16
|
id?: number | string;
|
|
@@ -32,32 +29,50 @@ export type FieldType =
|
|
|
32
29
|
| "table"
|
|
33
30
|
| "object"
|
|
34
31
|
| "array"
|
|
35
|
-
| "password"
|
|
36
|
-
|
|
32
|
+
| "password"
|
|
33
|
+
| "html"
|
|
34
|
+
| "ip"
|
|
35
|
+
| "id";
|
|
36
|
+
type FieldDefault = {
|
|
37
37
|
id?: string | number | null | undefined;
|
|
38
38
|
key: string;
|
|
39
39
|
required?: boolean;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
40
|
+
children?: any;
|
|
41
|
+
};
|
|
42
|
+
type FieldStringType = {
|
|
43
|
+
type: Exclude<FieldType, "array" | "object">;
|
|
44
|
+
};
|
|
45
|
+
type FieldStringArrayType = {
|
|
46
|
+
type: Exclude<FieldType, "array" | "object">[];
|
|
47
|
+
};
|
|
48
|
+
type FieldArrayType = {
|
|
49
|
+
type: "array";
|
|
50
|
+
children: FieldType | FieldType[] | Schema;
|
|
51
|
+
};
|
|
52
|
+
type FieldArrayArrayType = {
|
|
53
|
+
type: ["array", ...FieldType[]];
|
|
54
|
+
children: FieldType | FieldType[];
|
|
55
|
+
};
|
|
56
|
+
type FieldObjectType = {
|
|
57
|
+
type: "object";
|
|
58
|
+
children: Schema;
|
|
59
|
+
};
|
|
60
|
+
// if "type" is array, make "array" at first place, and "number" & "string" at last place of the array
|
|
61
|
+
export type Field = FieldDefault &
|
|
62
|
+
(
|
|
63
|
+
| FieldStringType
|
|
64
|
+
| FieldStringArrayType
|
|
65
|
+
| FieldObjectType
|
|
66
|
+
| FieldArrayType
|
|
67
|
+
| FieldArrayArrayType
|
|
68
|
+
);
|
|
54
69
|
|
|
55
70
|
export type Schema = Field[];
|
|
56
71
|
|
|
57
72
|
export interface Options {
|
|
58
73
|
page?: number;
|
|
59
74
|
per_page?: number;
|
|
60
|
-
columns?: string[];
|
|
75
|
+
columns?: string[] | string;
|
|
61
76
|
}
|
|
62
77
|
|
|
63
78
|
export type ComparisonOperator =
|
|
@@ -102,13 +117,15 @@ export default class Inibase {
|
|
|
102
117
|
public cache: Map<string, string>;
|
|
103
118
|
public pageInfoArray: Record<string, Record<string, number>>;
|
|
104
119
|
public pageInfo: pageInfo;
|
|
120
|
+
private salt: Buffer;
|
|
105
121
|
|
|
106
|
-
constructor(databaseName: string, mainFolder: string = "
|
|
122
|
+
constructor(databaseName: string, mainFolder: string = ".") {
|
|
107
123
|
this.database = databaseName;
|
|
108
124
|
this.databasePath = join(mainFolder, databaseName);
|
|
109
125
|
this.cache = new Map<string, any>();
|
|
110
126
|
this.pageInfoArray = {};
|
|
111
127
|
this.pageInfo = { page: 1, per_page: 15 };
|
|
128
|
+
this.salt = scryptSync(this.databasePath, "salt", 32);
|
|
112
129
|
}
|
|
113
130
|
|
|
114
131
|
private throwError(
|
|
@@ -122,14 +139,14 @@ export default class Inibase {
|
|
|
122
139
|
): Error {
|
|
123
140
|
const errorMessages: Record<string, Record<string, string>> = {
|
|
124
141
|
en: {
|
|
142
|
+
FIELD_REQUIRED: "REQUIRED: {variable}",
|
|
125
143
|
NO_SCHEMA: "NO_SCHEMA: {variable}",
|
|
126
144
|
NO_ITEMS: "NO_ITEMS: {variable}",
|
|
145
|
+
NO_DATA: "NO_DATA: {variable}",
|
|
127
146
|
INVALID_ID: "INVALID_ID: {variable}",
|
|
128
147
|
INVALID_TYPE: "INVALID_TYPE: {variable}",
|
|
129
|
-
REQUIRED: "REQUIRED: {variable}",
|
|
130
|
-
NO_DATA: "NO_DATA: {variable}",
|
|
131
148
|
INVALID_OPERATOR: "INVALID_OPERATOR: {variable}",
|
|
132
|
-
|
|
149
|
+
INVALID_PARAMETERS: "PARAMETERS: {variable}",
|
|
133
150
|
},
|
|
134
151
|
// Add more languages and error messages as needed
|
|
135
152
|
};
|
|
@@ -157,148 +174,40 @@ export default class Inibase {
|
|
|
157
174
|
return new Error(errorMessage);
|
|
158
175
|
}
|
|
159
176
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
}
|
|
177
|
+
private findLastIdNumber(schema: Schema): number {
|
|
178
|
+
const lastField = schema[schema.length - 1];
|
|
179
|
+
if (lastField) {
|
|
180
|
+
if (
|
|
181
|
+
(lastField.type === "array" || lastField.type === "object") &&
|
|
182
|
+
Utils.isArrayOfObjects(lastField.children)
|
|
183
|
+
)
|
|
184
|
+
return this.findLastIdNumber(lastField.children as Schema);
|
|
185
|
+
else if (lastField.id && Utils.isValidID(lastField.id))
|
|
186
|
+
return Utils.decodeID(lastField.id as string, this.salt);
|
|
282
187
|
}
|
|
188
|
+
return 0;
|
|
283
189
|
}
|
|
284
190
|
|
|
285
|
-
public setTableSchema(
|
|
191
|
+
public async setTableSchema(
|
|
192
|
+
tableName: string,
|
|
193
|
+
schema: Schema
|
|
194
|
+
): Promise<void> {
|
|
286
195
|
const encodeSchema = (schema: Schema) => {
|
|
287
196
|
let RETURN: any[][] = [],
|
|
288
197
|
index = 0;
|
|
289
198
|
for (const field of schema) {
|
|
290
199
|
if (!RETURN[index]) RETURN[index] = [];
|
|
291
200
|
RETURN[index].push(
|
|
292
|
-
field.id ?
|
|
201
|
+
field.id ? Utils.decodeID(field.id as string, this.salt) : null
|
|
293
202
|
);
|
|
294
203
|
RETURN[index].push(field.key ?? null);
|
|
295
204
|
RETURN[index].push(field.required ?? null);
|
|
296
205
|
RETURN[index].push(field.type ?? null);
|
|
297
206
|
RETURN[index].push(
|
|
298
|
-
(field
|
|
299
|
-
field.children
|
|
300
|
-
|
|
301
|
-
|
|
207
|
+
(field as any).children
|
|
208
|
+
? Utils.isArrayOfObjects((field as any).children)
|
|
209
|
+
? encodeSchema((field as any).children as Schema) ?? null
|
|
210
|
+
: (field as any).children
|
|
302
211
|
: null
|
|
303
212
|
);
|
|
304
213
|
index++;
|
|
@@ -313,40 +222,39 @@ export default class Inibase {
|
|
|
313
222
|
) {
|
|
314
223
|
if (!field.id) {
|
|
315
224
|
oldIndex++;
|
|
316
|
-
field = {
|
|
317
|
-
|
|
225
|
+
field = {
|
|
226
|
+
...field,
|
|
227
|
+
id: Utils.encodeID(oldIndex, this.salt),
|
|
228
|
+
};
|
|
229
|
+
} else oldIndex = Utils.decodeID(field.id as string, this.salt);
|
|
318
230
|
field.children = addIdToSchema(field.children as Schema, oldIndex);
|
|
319
231
|
oldIndex += field.children.length;
|
|
320
|
-
} else if (field.id)
|
|
232
|
+
} else if (field.id)
|
|
233
|
+
oldIndex = Utils.decodeID(field.id as string, this.salt);
|
|
321
234
|
else {
|
|
322
235
|
oldIndex++;
|
|
323
|
-
field = {
|
|
236
|
+
field = {
|
|
237
|
+
...field,
|
|
238
|
+
id: Utils.encodeID(oldIndex, this.salt),
|
|
239
|
+
};
|
|
324
240
|
}
|
|
325
241
|
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
|
-
};
|
|
242
|
+
});
|
|
338
243
|
|
|
339
244
|
// remove id from schema
|
|
340
|
-
schema = schema.filter(
|
|
341
|
-
|
|
245
|
+
schema = schema.filter(
|
|
246
|
+
(field) => !["id", "created_at", "updated_at"].includes(field.key)
|
|
247
|
+
);
|
|
248
|
+
schema = addIdToSchema(schema, this.findLastIdNumber(schema));
|
|
342
249
|
const TablePath = join(this.databasePath, tableName),
|
|
343
250
|
TableSchemaPath = join(TablePath, "schema.inib");
|
|
344
|
-
if (!
|
|
345
|
-
|
|
251
|
+
if (!(await File.isExists(TablePath)))
|
|
252
|
+
await mkdir(TablePath, { recursive: true });
|
|
253
|
+
if (await File.isExists(TableSchemaPath)) {
|
|
346
254
|
// update columns files names based on field id
|
|
347
255
|
const schemaToIdsPath = (schema: any, prefix = "") => {
|
|
348
256
|
let RETURN: any = {};
|
|
349
|
-
for (const field of schema)
|
|
257
|
+
for (const field of schema)
|
|
350
258
|
if (field.children && Utils.isArrayOfObjects(field.children)) {
|
|
351
259
|
Utils.deepMerge(
|
|
352
260
|
RETURN,
|
|
@@ -357,53 +265,38 @@ export default class Inibase {
|
|
|
357
265
|
(field.type === "array" ? ".*." : ".")
|
|
358
266
|
)
|
|
359
267
|
);
|
|
360
|
-
} else if (
|
|
361
|
-
RETURN[
|
|
268
|
+
} else if (Utils.isValidID(field.id))
|
|
269
|
+
RETURN[Utils.decodeID(field.id, this.salt)] = File.encodeFileName(
|
|
362
270
|
(prefix ?? "") + field.key,
|
|
363
271
|
"inib"
|
|
364
272
|
);
|
|
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
273
|
|
|
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;
|
|
274
|
+
return RETURN;
|
|
381
275
|
},
|
|
382
|
-
replaceOldPathes = findChangedProperties(
|
|
383
|
-
schemaToIdsPath(this.getTableSchema(tableName)),
|
|
276
|
+
replaceOldPathes = Utils.findChangedProperties(
|
|
277
|
+
schemaToIdsPath(await this.getTableSchema(tableName)),
|
|
384
278
|
schemaToIdsPath(schema)
|
|
385
279
|
);
|
|
386
|
-
if (replaceOldPathes)
|
|
280
|
+
if (replaceOldPathes)
|
|
387
281
|
for (const [oldPath, newPath] of Object.entries(replaceOldPathes))
|
|
388
|
-
if (
|
|
389
|
-
|
|
390
|
-
}
|
|
282
|
+
if (await File.isExists(join(TablePath, oldPath)))
|
|
283
|
+
await rename(join(TablePath, oldPath), join(TablePath, newPath));
|
|
391
284
|
}
|
|
392
285
|
|
|
393
|
-
|
|
286
|
+
await writeFile(
|
|
394
287
|
join(TablePath, "schema.inib"),
|
|
395
288
|
JSON.stringify(encodeSchema(schema))
|
|
396
289
|
);
|
|
397
290
|
}
|
|
398
291
|
|
|
399
|
-
public getTableSchema(tableName: string): Schema | undefined {
|
|
292
|
+
public async getTableSchema(tableName: string): Promise<Schema | undefined> {
|
|
400
293
|
const decodeSchema = (encodedSchema: any) => {
|
|
401
294
|
return encodedSchema.map((field: any) =>
|
|
402
295
|
Array.isArray(field[0])
|
|
403
296
|
? decodeSchema(field)
|
|
404
297
|
: Object.fromEntries(
|
|
405
298
|
Object.entries({
|
|
406
|
-
id:
|
|
299
|
+
id: Utils.encodeID(field[0], this.salt),
|
|
407
300
|
key: field[1],
|
|
408
301
|
required: field[2],
|
|
409
302
|
type: field[3],
|
|
@@ -417,9 +310,11 @@ export default class Inibase {
|
|
|
417
310
|
);
|
|
418
311
|
},
|
|
419
312
|
TableSchemaPath = join(this.databasePath, tableName, "schema.inib");
|
|
420
|
-
if (!
|
|
313
|
+
if (!(await File.isExists(TableSchemaPath))) return undefined;
|
|
421
314
|
if (!this.cache.has(TableSchemaPath)) {
|
|
422
|
-
const TableSchemaPathContent =
|
|
315
|
+
const TableSchemaPathContent = await readFile(TableSchemaPath, {
|
|
316
|
+
encoding: "utf8",
|
|
317
|
+
});
|
|
423
318
|
this.cache.set(
|
|
424
319
|
TableSchemaPath,
|
|
425
320
|
TableSchemaPathContent
|
|
@@ -427,25 +322,180 @@ export default class Inibase {
|
|
|
427
322
|
: ""
|
|
428
323
|
);
|
|
429
324
|
}
|
|
325
|
+
const schema = this.cache.get(TableSchemaPath) as unknown as Schema,
|
|
326
|
+
lastIdNumber = this.findLastIdNumber(schema);
|
|
430
327
|
return [
|
|
431
|
-
{
|
|
432
|
-
|
|
328
|
+
{
|
|
329
|
+
id: Utils.encodeID(0, this.salt),
|
|
330
|
+
key: "id",
|
|
331
|
+
type: "number",
|
|
332
|
+
required: true,
|
|
333
|
+
},
|
|
334
|
+
...schema,
|
|
335
|
+
{
|
|
336
|
+
id: Utils.encodeID(lastIdNumber, this.salt),
|
|
337
|
+
key: "created_at",
|
|
338
|
+
type: "date",
|
|
339
|
+
required: true,
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
id: Utils.encodeID(lastIdNumber + 1, this.salt),
|
|
343
|
+
key: "updated_at",
|
|
344
|
+
type: "date",
|
|
345
|
+
required: false,
|
|
346
|
+
},
|
|
433
347
|
];
|
|
434
348
|
}
|
|
435
349
|
|
|
436
|
-
public getField
|
|
437
|
-
|
|
350
|
+
public getField<Property extends keyof Field | "children">(
|
|
351
|
+
keyPath: string,
|
|
352
|
+
schema: Schema | Field,
|
|
353
|
+
property?: Property
|
|
354
|
+
) {
|
|
355
|
+
const keyPathSplited = keyPath.split(".");
|
|
356
|
+
for (const [index, key] of keyPathSplited.entries()) {
|
|
438
357
|
if (key === "*") continue;
|
|
439
358
|
const foundItem = (schema as Schema).find((item) => item.key === key);
|
|
440
359
|
if (!foundItem) return null;
|
|
441
|
-
schema =
|
|
360
|
+
if (index === keyPathSplited.length - 1) schema = foundItem;
|
|
361
|
+
if (
|
|
442
362
|
(foundItem.type === "array" || foundItem.type === "object") &&
|
|
443
363
|
foundItem.children &&
|
|
444
364
|
Utils.isArrayOfObjects(foundItem.children)
|
|
445
|
-
|
|
446
|
-
|
|
365
|
+
)
|
|
366
|
+
schema = foundItem.children as Schema;
|
|
367
|
+
}
|
|
368
|
+
if (property) {
|
|
369
|
+
switch (property) {
|
|
370
|
+
case "type":
|
|
371
|
+
return (schema as Field).type;
|
|
372
|
+
case "children":
|
|
373
|
+
return (
|
|
374
|
+
schema as
|
|
375
|
+
| (Field & FieldObjectType)
|
|
376
|
+
| FieldArrayType
|
|
377
|
+
| FieldArrayArrayType
|
|
378
|
+
).children;
|
|
379
|
+
|
|
380
|
+
default:
|
|
381
|
+
return (schema as Field)[property as keyof Field];
|
|
382
|
+
}
|
|
383
|
+
} else return schema as Field;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
public validateData(
|
|
387
|
+
data: Data | Data[],
|
|
388
|
+
schema: Schema,
|
|
389
|
+
skipRequiredField: boolean = false
|
|
390
|
+
): void {
|
|
391
|
+
const validateFieldType = (
|
|
392
|
+
value: any,
|
|
393
|
+
fieldType: FieldType | FieldType[],
|
|
394
|
+
fieldChildrenType?: FieldType | FieldType[]
|
|
395
|
+
): boolean => {
|
|
396
|
+
if (value === null) return true;
|
|
397
|
+
if (Array.isArray(fieldType))
|
|
398
|
+
return Utils.detectFieldType(value, fieldType) !== undefined;
|
|
399
|
+
if (fieldType === "array" && fieldChildrenType && Array.isArray(value))
|
|
400
|
+
return value.some(
|
|
401
|
+
(v) =>
|
|
402
|
+
Utils.detectFieldType(
|
|
403
|
+
v,
|
|
404
|
+
Array.isArray(fieldChildrenType)
|
|
405
|
+
? fieldChildrenType
|
|
406
|
+
: [fieldChildrenType]
|
|
407
|
+
) !== undefined
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
switch (fieldType) {
|
|
411
|
+
case "string":
|
|
412
|
+
return Utils.isString(value);
|
|
413
|
+
case "password":
|
|
414
|
+
return (
|
|
415
|
+
Utils.isNumber(value) ||
|
|
416
|
+
Utils.isString(value) ||
|
|
417
|
+
Utils.isPassword(value)
|
|
418
|
+
);
|
|
419
|
+
case "number":
|
|
420
|
+
return Utils.isNumber(value);
|
|
421
|
+
case "html":
|
|
422
|
+
return Utils.isHTML(value);
|
|
423
|
+
case "ip":
|
|
424
|
+
return Utils.isIP(value);
|
|
425
|
+
case "boolean":
|
|
426
|
+
return Utils.isBoolean(value);
|
|
427
|
+
case "date":
|
|
428
|
+
return Utils.isDate(value);
|
|
429
|
+
case "object":
|
|
430
|
+
return Utils.isObject(value);
|
|
431
|
+
case "array":
|
|
432
|
+
return Array.isArray(value);
|
|
433
|
+
case "email":
|
|
434
|
+
return Utils.isEmail(value);
|
|
435
|
+
case "url":
|
|
436
|
+
return Utils.isURL(value);
|
|
437
|
+
case "table":
|
|
438
|
+
// feat: check if id exists
|
|
439
|
+
if (Array.isArray(value))
|
|
440
|
+
return (
|
|
441
|
+
(Utils.isArrayOfObjects(value) &&
|
|
442
|
+
value.every(
|
|
443
|
+
(element: Data) =>
|
|
444
|
+
element.hasOwnProperty("id") &&
|
|
445
|
+
(Utils.isValidID(element.id) || Utils.isNumber(element.id))
|
|
446
|
+
)) ||
|
|
447
|
+
value.every(Utils.isNumber) ||
|
|
448
|
+
Utils.isValidID(value)
|
|
449
|
+
);
|
|
450
|
+
else if (Utils.isObject(value))
|
|
451
|
+
return (
|
|
452
|
+
value.hasOwnProperty("id") &&
|
|
453
|
+
(Utils.isValidID((value as Data).id) ||
|
|
454
|
+
Utils.isNumber((value as Data).id))
|
|
455
|
+
);
|
|
456
|
+
else return Utils.isNumber(value) || Utils.isValidID(value);
|
|
457
|
+
case "id":
|
|
458
|
+
return Utils.isNumber(value) || Utils.isValidID(value);
|
|
459
|
+
default:
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
if (Utils.isArrayOfObjects(data))
|
|
465
|
+
for (const single_data of data as Data[])
|
|
466
|
+
this.validateData(single_data, schema, skipRequiredField);
|
|
467
|
+
else if (Utils.isObject(data)) {
|
|
468
|
+
for (const field of schema) {
|
|
469
|
+
if (
|
|
470
|
+
!data.hasOwnProperty(field.key) &&
|
|
471
|
+
field.required &&
|
|
472
|
+
!skipRequiredField
|
|
473
|
+
)
|
|
474
|
+
throw this.throwError("FIELD_REQUIRED", field.key);
|
|
475
|
+
if (
|
|
476
|
+
data.hasOwnProperty(field.key) &&
|
|
477
|
+
!validateFieldType(
|
|
478
|
+
data[field.key],
|
|
479
|
+
field.type,
|
|
480
|
+
(field as any)?.children &&
|
|
481
|
+
!Utils.isArrayOfObjects((field as any)?.children)
|
|
482
|
+
? (field as any)?.children
|
|
483
|
+
: undefined
|
|
484
|
+
)
|
|
485
|
+
)
|
|
486
|
+
throw this.throwError("INVALID_TYPE", field.key);
|
|
487
|
+
if (
|
|
488
|
+
(field.type === "array" || field.type === "object") &&
|
|
489
|
+
field.children &&
|
|
490
|
+
Utils.isArrayOfObjects(field.children)
|
|
491
|
+
)
|
|
492
|
+
this.validateData(
|
|
493
|
+
data[field.key],
|
|
494
|
+
field.children as Schema,
|
|
495
|
+
skipRequiredField
|
|
496
|
+
);
|
|
497
|
+
}
|
|
447
498
|
}
|
|
448
|
-
return schema as Field;
|
|
449
499
|
}
|
|
450
500
|
|
|
451
501
|
public formatData(
|
|
@@ -453,89 +503,117 @@ export default class Inibase {
|
|
|
453
503
|
schema: Schema,
|
|
454
504
|
formatOnlyAvailiableKeys?: boolean
|
|
455
505
|
): Data | Data[] {
|
|
456
|
-
|
|
506
|
+
const formatField = (
|
|
507
|
+
value: any,
|
|
508
|
+
field: Field
|
|
509
|
+
): Data | Data[] | number | string => {
|
|
510
|
+
if (Array.isArray(field.type))
|
|
511
|
+
field.type = Utils.detectFieldType(value, field.type);
|
|
512
|
+
switch (field.type) {
|
|
513
|
+
case "array":
|
|
514
|
+
if (typeof field.children === "string") {
|
|
515
|
+
if (field.type === "array" && field.children === "table") {
|
|
516
|
+
if (Array.isArray(data[field.key])) {
|
|
517
|
+
if (Utils.isArrayOfObjects(data[field.key])) {
|
|
518
|
+
if (
|
|
519
|
+
value.every(
|
|
520
|
+
(item: any) =>
|
|
521
|
+
item.hasOwnProperty("id") &&
|
|
522
|
+
(Utils.isValidID(item.id) || Utils.isNumber(item.id))
|
|
523
|
+
)
|
|
524
|
+
)
|
|
525
|
+
value.map((item: any) =>
|
|
526
|
+
Utils.isNumber(item.id)
|
|
527
|
+
? Number(item.id)
|
|
528
|
+
: Utils.decodeID(item.id, this.salt)
|
|
529
|
+
);
|
|
530
|
+
} else if (Utils.isValidID(value) || Utils.isNumber(value))
|
|
531
|
+
return value.map((item: number | string) =>
|
|
532
|
+
Utils.isNumber(item)
|
|
533
|
+
? Number(item as string)
|
|
534
|
+
: Utils.decodeID(item as string, this.salt)
|
|
535
|
+
);
|
|
536
|
+
} else if (Utils.isValidID(value))
|
|
537
|
+
return [Utils.decodeID(value, this.salt)];
|
|
538
|
+
else if (Utils.isNumber(value)) return [Number(value)];
|
|
539
|
+
} else if (data.hasOwnProperty(field.key)) return value;
|
|
540
|
+
} else if (Utils.isArrayOfObjects(field.children))
|
|
541
|
+
return this.formatData(
|
|
542
|
+
value,
|
|
543
|
+
field.children as Schema,
|
|
544
|
+
formatOnlyAvailiableKeys
|
|
545
|
+
);
|
|
546
|
+
else if (Array.isArray(field.children))
|
|
547
|
+
return Array.isArray(value) ? value : [value];
|
|
548
|
+
break;
|
|
549
|
+
case "object":
|
|
550
|
+
if (Utils.isArrayOfObjects(field.children))
|
|
551
|
+
return this.formatData(
|
|
552
|
+
value,
|
|
553
|
+
field.children,
|
|
554
|
+
formatOnlyAvailiableKeys
|
|
555
|
+
);
|
|
556
|
+
break;
|
|
557
|
+
case "table":
|
|
558
|
+
if (Utils.isObject(value)) {
|
|
559
|
+
if (
|
|
560
|
+
value.hasOwnProperty("id") &&
|
|
561
|
+
(Utils.isValidID(value.id) || Utils.isNumber(value))
|
|
562
|
+
)
|
|
563
|
+
return Utils.isNumber(value.id)
|
|
564
|
+
? Number(value.id)
|
|
565
|
+
: Utils.decodeID(value.id, this.salt);
|
|
566
|
+
} else if (Utils.isValidID(value) || Utils.isNumber(value))
|
|
567
|
+
return Utils.isNumber(value)
|
|
568
|
+
? Number(value)
|
|
569
|
+
: Utils.decodeID(value, this.salt);
|
|
570
|
+
break;
|
|
571
|
+
case "password":
|
|
572
|
+
return value.length === 161 ? value : Utils.hashPassword(value);
|
|
573
|
+
case "number":
|
|
574
|
+
return Utils.isNumber(value) ? Number(value) : null;
|
|
575
|
+
case "id":
|
|
576
|
+
return Utils.isNumber(value)
|
|
577
|
+
? Utils.encodeID(value, this.salt)
|
|
578
|
+
: value;
|
|
579
|
+
default:
|
|
580
|
+
return value;
|
|
581
|
+
}
|
|
582
|
+
return null;
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
this.validateData(data, schema, formatOnlyAvailiableKeys);
|
|
586
|
+
|
|
587
|
+
if (Utils.isArrayOfObjects(data))
|
|
457
588
|
return data.map((single_data: Data) =>
|
|
458
|
-
this.formatData(single_data, schema)
|
|
589
|
+
this.formatData(single_data, schema, formatOnlyAvailiableKeys)
|
|
459
590
|
);
|
|
460
|
-
|
|
591
|
+
else if (Utils.isObject(data)) {
|
|
461
592
|
let RETURN: Data = {};
|
|
462
593
|
for (const field of schema) {
|
|
463
594
|
if (!data.hasOwnProperty(field.key)) {
|
|
595
|
+
if (formatOnlyAvailiableKeys || !field.required) continue;
|
|
464
596
|
RETURN[field.key] = this.getDefaultValue(field);
|
|
465
597
|
continue;
|
|
466
598
|
}
|
|
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];
|
|
599
|
+
RETURN[field.key] = formatField(data[field.key], field);
|
|
533
600
|
}
|
|
534
601
|
return RETURN;
|
|
535
602
|
} else return [];
|
|
536
603
|
}
|
|
537
604
|
|
|
538
605
|
private getDefaultValue(field: Field): any {
|
|
606
|
+
if (Array.isArray(field.type))
|
|
607
|
+
return this.getDefaultValue({
|
|
608
|
+
...field,
|
|
609
|
+
type: field.type.sort(
|
|
610
|
+
(a: FieldType, b: FieldType) =>
|
|
611
|
+
Number(b === "array") - Number(a === "array") ||
|
|
612
|
+
Number(a === "string") - Number(b === "string") ||
|
|
613
|
+
Number(a === "number") - Number(b === "number")
|
|
614
|
+
)[0],
|
|
615
|
+
} as Field);
|
|
616
|
+
|
|
539
617
|
switch (field.type) {
|
|
540
618
|
case "array":
|
|
541
619
|
return Utils.isArrayOfObjects(field.children)
|
|
@@ -562,37 +640,54 @@ export default class Inibase {
|
|
|
562
640
|
mainPath: string,
|
|
563
641
|
data: Data | Data[]
|
|
564
642
|
): { [key: string]: string[] } {
|
|
565
|
-
const CombineData = (
|
|
643
|
+
const CombineData = (_data: Data | Data[], prefix?: string) => {
|
|
566
644
|
let RETURN: Record<
|
|
567
645
|
string,
|
|
568
646
|
string | boolean | number | null | (string | boolean | number | null)[]
|
|
569
647
|
> = {};
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
648
|
+
const combineObjectsToArray = (input: any[]) =>
|
|
649
|
+
input.reduce(
|
|
650
|
+
(r, c) => (
|
|
651
|
+
Object.keys(c).map((k) => (r[k] = [...(r[k] || []), c[k]])), r
|
|
652
|
+
),
|
|
653
|
+
{}
|
|
654
|
+
);
|
|
655
|
+
if (Utils.isArrayOfObjects(_data))
|
|
656
|
+
RETURN = combineObjectsToArray(
|
|
657
|
+
(_data as Data[]).map((single_data) => CombineData(single_data))
|
|
574
658
|
);
|
|
575
659
|
else {
|
|
576
|
-
for (const [key, value] of Object.entries(
|
|
660
|
+
for (const [key, value] of Object.entries(_data as Data)) {
|
|
577
661
|
if (Utils.isObject(value))
|
|
578
662
|
Object.assign(RETURN, CombineData(value, `${key}.`));
|
|
579
663
|
else if (Array.isArray(value)) {
|
|
580
|
-
if (Utils.isArrayOfObjects(value))
|
|
664
|
+
if (Utils.isArrayOfObjects(value)) {
|
|
665
|
+
Object.assign(
|
|
666
|
+
RETURN,
|
|
667
|
+
CombineData(
|
|
668
|
+
combineObjectsToArray(value),
|
|
669
|
+
(prefix ?? "") + key + ".*."
|
|
670
|
+
)
|
|
671
|
+
);
|
|
672
|
+
} else if (
|
|
673
|
+
Utils.isArrayOfArrays(value) &&
|
|
674
|
+
value.every(Utils.isArrayOfObjects)
|
|
675
|
+
)
|
|
581
676
|
Object.assign(
|
|
582
677
|
RETURN,
|
|
583
678
|
CombineData(
|
|
584
|
-
|
|
679
|
+
combineObjectsToArray(value.map(combineObjectsToArray)),
|
|
585
680
|
(prefix ?? "") + key + ".*."
|
|
586
681
|
)
|
|
587
682
|
);
|
|
588
683
|
else
|
|
589
|
-
RETURN[(prefix ?? "") + key] =
|
|
684
|
+
RETURN[(prefix ?? "") + key] = File.encode(value) as
|
|
590
685
|
| boolean
|
|
591
686
|
| number
|
|
592
687
|
| string
|
|
593
688
|
| null;
|
|
594
689
|
} else
|
|
595
|
-
RETURN[(prefix ?? "") + key] =
|
|
690
|
+
RETURN[(prefix ?? "") + key] = File.encode(value) as
|
|
596
691
|
| boolean
|
|
597
692
|
| number
|
|
598
693
|
| string
|
|
@@ -622,43 +717,43 @@ export default class Inibase {
|
|
|
622
717
|
onlyLinesNumbers?: boolean
|
|
623
718
|
): Promise<Data | Data[] | number[] | null> {
|
|
624
719
|
if (!options.columns) options.columns = [];
|
|
625
|
-
else if (
|
|
626
|
-
options.columns.
|
|
627
|
-
|
|
628
|
-
)
|
|
720
|
+
else if (!Array.isArray(options.columns))
|
|
721
|
+
options.columns = [options.columns];
|
|
722
|
+
if (options.columns.length && !(options.columns as string[]).includes("id"))
|
|
629
723
|
options.columns.push("id");
|
|
630
724
|
if (!options.page) options.page = 1;
|
|
631
725
|
if (!options.per_page) options.per_page = 15;
|
|
632
726
|
let RETURN!: Data | Data[] | null;
|
|
633
|
-
let schema = this.getTableSchema(tableName);
|
|
727
|
+
let schema = await this.getTableSchema(tableName);
|
|
634
728
|
if (!schema) throw this.throwError("NO_SCHEMA", tableName);
|
|
729
|
+
const idFilePath = join(this.databasePath, tableName, "id.inib");
|
|
730
|
+
if (!(await File.isExists(idFilePath))) return null;
|
|
635
731
|
const filterSchemaByColumns = (schema: Schema, columns: string[]): Schema =>
|
|
636
732
|
schema
|
|
637
733
|
.map((field) => {
|
|
638
|
-
if (columns.
|
|
734
|
+
if (columns.some((column) => column.startsWith("!")))
|
|
735
|
+
return columns.includes("!" + field.key) ? null : field;
|
|
736
|
+
if (columns.includes(field.key) || columns.includes("*"))
|
|
737
|
+
return field;
|
|
738
|
+
|
|
639
739
|
if (
|
|
640
740
|
(field.type === "array" || field.type === "object") &&
|
|
641
741
|
Utils.isArrayOfObjects(field.children) &&
|
|
642
|
-
columns.filter(
|
|
643
|
-
column
|
|
644
|
-
field.key +
|
|
645
|
-
|
|
742
|
+
columns.filter(
|
|
743
|
+
(column) =>
|
|
744
|
+
column.startsWith(field.key + ".") ||
|
|
745
|
+
column.startsWith("!" + field.key + ".")
|
|
646
746
|
).length
|
|
647
747
|
) {
|
|
648
748
|
field.children = filterSchemaByColumns(
|
|
649
749
|
field.children as Schema,
|
|
650
750
|
columns
|
|
651
|
-
.filter(
|
|
652
|
-
column
|
|
653
|
-
field.key +
|
|
654
|
-
|
|
655
|
-
)
|
|
656
|
-
.map((column) =>
|
|
657
|
-
column.replace(
|
|
658
|
-
field.key + (field.type === "array" ? ".*." : "."),
|
|
659
|
-
""
|
|
660
|
-
)
|
|
751
|
+
.filter(
|
|
752
|
+
(column) =>
|
|
753
|
+
column.startsWith(field.key + ".") ||
|
|
754
|
+
column.startsWith("!" + field.key + ".")
|
|
661
755
|
)
|
|
756
|
+
.map((column) => column.replace(field.key + ".", ""))
|
|
662
757
|
);
|
|
663
758
|
return field;
|
|
664
759
|
}
|
|
@@ -677,10 +772,144 @@ export default class Inibase {
|
|
|
677
772
|
let RETURN: Record<number, Data> = {};
|
|
678
773
|
for (const field of schema) {
|
|
679
774
|
if (
|
|
680
|
-
(field.type === "array" ||
|
|
681
|
-
|
|
775
|
+
(field.type === "array" ||
|
|
776
|
+
(Array.isArray(field.type) &&
|
|
777
|
+
(field.type as any).includes("array"))) &&
|
|
778
|
+
(field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
|
|
779
|
+
.children
|
|
682
780
|
) {
|
|
683
|
-
if (
|
|
781
|
+
if (
|
|
782
|
+
Utils.isArrayOfObjects(
|
|
783
|
+
(field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
|
|
784
|
+
.children
|
|
785
|
+
)
|
|
786
|
+
) {
|
|
787
|
+
if (
|
|
788
|
+
(
|
|
789
|
+
(field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
|
|
790
|
+
.children as Schema
|
|
791
|
+
).filter(
|
|
792
|
+
(children) =>
|
|
793
|
+
children.type === "array" &&
|
|
794
|
+
Utils.isArrayOfObjects(children.children)
|
|
795
|
+
).length
|
|
796
|
+
) {
|
|
797
|
+
// one of children has array field type and has children array of object = Schema
|
|
798
|
+
Object.entries(
|
|
799
|
+
(await getItemsFromSchema(
|
|
800
|
+
path,
|
|
801
|
+
(
|
|
802
|
+
(
|
|
803
|
+
field as FieldDefault &
|
|
804
|
+
(FieldArrayType | FieldArrayArrayType)
|
|
805
|
+
).children as Schema
|
|
806
|
+
).filter(
|
|
807
|
+
(children) =>
|
|
808
|
+
children.type === "array" &&
|
|
809
|
+
Utils.isArrayOfObjects(children.children)
|
|
810
|
+
),
|
|
811
|
+
linesNumber,
|
|
812
|
+
(prefix ?? "") + field.key + ".*."
|
|
813
|
+
)) ?? {}
|
|
814
|
+
).forEach(([index, item]) => {
|
|
815
|
+
if (Utils.isObject(item)) {
|
|
816
|
+
if (!RETURN[index]) RETURN[index] = {};
|
|
817
|
+
if (!RETURN[index][field.key]) RETURN[index][field.key] = [];
|
|
818
|
+
for (const child_field of (
|
|
819
|
+
(
|
|
820
|
+
field as FieldDefault &
|
|
821
|
+
(FieldArrayType | FieldArrayArrayType)
|
|
822
|
+
).children as Schema
|
|
823
|
+
).filter(
|
|
824
|
+
(children) =>
|
|
825
|
+
children.type === "array" &&
|
|
826
|
+
Utils.isArrayOfObjects(children.children)
|
|
827
|
+
)) {
|
|
828
|
+
if (Utils.isObject(item[child_field.key])) {
|
|
829
|
+
Object.entries(item[child_field.key]).forEach(
|
|
830
|
+
([key, value]) => {
|
|
831
|
+
for (let _i = 0; _i < value.length; _i++) {
|
|
832
|
+
if (!RETURN[index][field.key][_i])
|
|
833
|
+
RETURN[index][field.key][_i] = {};
|
|
834
|
+
if (!RETURN[index][field.key][_i][child_field.key])
|
|
835
|
+
RETURN[index][field.key][_i][child_field.key] =
|
|
836
|
+
[];
|
|
837
|
+
value[_i].forEach((_element, _index) => {
|
|
838
|
+
if (
|
|
839
|
+
!RETURN[index][field.key][_i][child_field.key][
|
|
840
|
+
_index
|
|
841
|
+
]
|
|
842
|
+
)
|
|
843
|
+
RETURN[index][field.key][_i][child_field.key][
|
|
844
|
+
_index
|
|
845
|
+
] = {};
|
|
846
|
+
RETURN[index][field.key][_i][child_field.key][
|
|
847
|
+
_index
|
|
848
|
+
][key] = _element;
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
(
|
|
858
|
+
field as FieldDefault & (FieldArrayType | FieldArrayArrayType)
|
|
859
|
+
).children = (
|
|
860
|
+
(field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
|
|
861
|
+
.children as Schema
|
|
862
|
+
).filter(
|
|
863
|
+
(children) =>
|
|
864
|
+
children.type !== "array" ||
|
|
865
|
+
!Utils.isArrayOfObjects(children.children)
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
Object.entries(
|
|
869
|
+
(await getItemsFromSchema(
|
|
870
|
+
path,
|
|
871
|
+
(field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
|
|
872
|
+
.children as Schema,
|
|
873
|
+
linesNumber,
|
|
874
|
+
(prefix ?? "") + field.key + ".*."
|
|
875
|
+
)) ?? {}
|
|
876
|
+
).forEach(([index, item]) => {
|
|
877
|
+
if (!RETURN[index]) RETURN[index] = {};
|
|
878
|
+
if (Utils.isObject(item)) {
|
|
879
|
+
if (!Object.values(item).every((i) => i === null)) {
|
|
880
|
+
if (RETURN[index][field.key])
|
|
881
|
+
Object.entries(item).forEach(([key, value], _index) => {
|
|
882
|
+
RETURN[index][field.key] = RETURN[index][field.key].map(
|
|
883
|
+
(_obj, _i) => ({ ..._obj, [key]: value[_i] })
|
|
884
|
+
);
|
|
885
|
+
});
|
|
886
|
+
else if (Object.values(item).every(Utils.isArrayOfArrays))
|
|
887
|
+
RETURN[index][field.key] = item;
|
|
888
|
+
else {
|
|
889
|
+
RETURN[index][field.key] = [];
|
|
890
|
+
Object.entries(item).forEach(([key, value]) => {
|
|
891
|
+
for (let _i = 0; _i < value.length; _i++) {
|
|
892
|
+
if (!RETURN[index][field.key][_i])
|
|
893
|
+
RETURN[index][field.key][_i] = {};
|
|
894
|
+
RETURN[index][field.key][_i][key] = value[_i];
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
} else RETURN[index][field.key] = null;
|
|
899
|
+
} else RETURN[index][field.key] = item;
|
|
900
|
+
});
|
|
901
|
+
} else if (
|
|
902
|
+
(field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
|
|
903
|
+
.children === "table" ||
|
|
904
|
+
(Array.isArray(
|
|
905
|
+
(field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
|
|
906
|
+
.children
|
|
907
|
+
) &&
|
|
908
|
+
(
|
|
909
|
+
(field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
|
|
910
|
+
.children as FieldType[]
|
|
911
|
+
).includes("table"))
|
|
912
|
+
) {
|
|
684
913
|
if (options.columns)
|
|
685
914
|
options.columns = (options.columns as string[])
|
|
686
915
|
.filter((column) => column.includes(`${field.key}.*.`))
|
|
@@ -691,8 +920,10 @@ export default class Inibase {
|
|
|
691
920
|
path,
|
|
692
921
|
File.encodeFileName((prefix ?? "") + field.key, "inib")
|
|
693
922
|
),
|
|
923
|
+
linesNumber,
|
|
694
924
|
field.type,
|
|
695
|
-
|
|
925
|
+
(field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
|
|
926
|
+
.children as FieldType | FieldType[]
|
|
696
927
|
)) ?? {}
|
|
697
928
|
)) {
|
|
698
929
|
if (!RETURN[index]) RETURN[index] = {};
|
|
@@ -700,35 +931,53 @@ export default class Inibase {
|
|
|
700
931
|
? await this.get(field.key, value as number, options)
|
|
701
932
|
: this.getDefaultValue(field);
|
|
702
933
|
}
|
|
703
|
-
} else if (
|
|
704
|
-
|
|
705
|
-
(
|
|
934
|
+
} else if (
|
|
935
|
+
await File.isExists(
|
|
936
|
+
join(
|
|
706
937
|
path,
|
|
707
|
-
|
|
938
|
+
File.encodeFileName((prefix ?? "") + field.key, "inib")
|
|
939
|
+
)
|
|
940
|
+
)
|
|
941
|
+
)
|
|
942
|
+
Object.entries(
|
|
943
|
+
(await File.get(
|
|
944
|
+
join(
|
|
945
|
+
path,
|
|
946
|
+
File.encodeFileName((prefix ?? "") + field.key, "inib")
|
|
947
|
+
),
|
|
708
948
|
linesNumber,
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
(field.type === "array" ? ".*." : ".")
|
|
949
|
+
field.type,
|
|
950
|
+
(field as any)?.children
|
|
712
951
|
)) ?? {}
|
|
713
952
|
).forEach(([index, item]) => {
|
|
714
953
|
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;
|
|
954
|
+
RETURN[index][field.key] = item ?? this.getDefaultValue(field);
|
|
721
955
|
});
|
|
722
|
-
|
|
956
|
+
} else if (field.type === "object") {
|
|
957
|
+
Object.entries(
|
|
958
|
+
(await getItemsFromSchema(
|
|
959
|
+
path,
|
|
960
|
+
field.children as Schema,
|
|
961
|
+
linesNumber,
|
|
962
|
+
(prefix ?? "") + field.key + "."
|
|
963
|
+
)) ?? {}
|
|
964
|
+
).forEach(([index, item]) => {
|
|
965
|
+
if (!RETURN[index]) RETURN[index] = {};
|
|
966
|
+
if (Utils.isObject(item)) {
|
|
967
|
+
if (!Object.values(item).every((i) => i === null))
|
|
968
|
+
RETURN[index][field.key] = item;
|
|
969
|
+
else RETURN[index][field.key] = null;
|
|
970
|
+
} else RETURN[index][field.key] = null;
|
|
971
|
+
});
|
|
723
972
|
} else if (field.type === "table") {
|
|
724
973
|
if (
|
|
725
|
-
|
|
726
|
-
|
|
974
|
+
(await File.isExists(join(this.databasePath, field.key))) &&
|
|
975
|
+
(await File.isExists(
|
|
727
976
|
join(
|
|
728
977
|
path,
|
|
729
978
|
File.encodeFileName((prefix ?? "") + field.key, "inib")
|
|
730
979
|
)
|
|
731
|
-
)
|
|
980
|
+
))
|
|
732
981
|
) {
|
|
733
982
|
if (options.columns)
|
|
734
983
|
options.columns = (options.columns as string[])
|
|
@@ -744,8 +993,8 @@ export default class Inibase {
|
|
|
744
993
|
path,
|
|
745
994
|
File.encodeFileName((prefix ?? "") + field.key, "inib")
|
|
746
995
|
),
|
|
747
|
-
|
|
748
|
-
|
|
996
|
+
linesNumber,
|
|
997
|
+
"number"
|
|
749
998
|
)) ?? {}
|
|
750
999
|
)) {
|
|
751
1000
|
if (!RETURN[index]) RETURN[index] = {};
|
|
@@ -755,24 +1004,24 @@ export default class Inibase {
|
|
|
755
1004
|
}
|
|
756
1005
|
}
|
|
757
1006
|
} else if (
|
|
758
|
-
|
|
1007
|
+
await File.isExists(
|
|
759
1008
|
join(path, File.encodeFileName((prefix ?? "") + field.key, "inib"))
|
|
760
1009
|
)
|
|
761
|
-
)
|
|
1010
|
+
)
|
|
762
1011
|
Object.entries(
|
|
763
1012
|
(await File.get(
|
|
764
1013
|
join(
|
|
765
1014
|
path,
|
|
766
1015
|
File.encodeFileName((prefix ?? "") + field.key, "inib")
|
|
767
1016
|
),
|
|
1017
|
+
linesNumber,
|
|
768
1018
|
field.type,
|
|
769
|
-
|
|
1019
|
+
(field as any)?.children
|
|
770
1020
|
)) ?? {}
|
|
771
1021
|
).forEach(([index, item]) => {
|
|
772
1022
|
if (!RETURN[index]) RETURN[index] = {};
|
|
773
1023
|
RETURN[index][field.key] = item ?? this.getDefaultValue(field);
|
|
774
1024
|
});
|
|
775
|
-
}
|
|
776
1025
|
}
|
|
777
1026
|
return RETURN;
|
|
778
1027
|
};
|
|
@@ -791,20 +1040,22 @@ export default class Inibase {
|
|
|
791
1040
|
)
|
|
792
1041
|
)
|
|
793
1042
|
);
|
|
794
|
-
} else if (
|
|
1043
|
+
} else if (Utils.isValidID(where) || Utils.isNumber(where)) {
|
|
795
1044
|
let Ids = where as string | number | (string | number)[];
|
|
796
1045
|
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
1046
|
const [lineNumbers, countItems] = await File.search(
|
|
800
1047
|
idFilePath,
|
|
801
|
-
"number",
|
|
802
1048
|
"[]",
|
|
803
1049
|
Utils.isNumber(Ids)
|
|
804
|
-
? Ids.map((id) =>
|
|
805
|
-
: Ids.map((id) =>
|
|
1050
|
+
? Ids.map((id) => Number(id as string))
|
|
1051
|
+
: Ids.map((id) => Utils.decodeID(id as string, this.salt)),
|
|
806
1052
|
undefined,
|
|
807
|
-
|
|
1053
|
+
"number",
|
|
1054
|
+
undefined,
|
|
1055
|
+
Ids.length,
|
|
1056
|
+
0,
|
|
1057
|
+
false,
|
|
1058
|
+
this.databasePath
|
|
808
1059
|
);
|
|
809
1060
|
if (!lineNumbers || !Object.keys(lineNumbers).length)
|
|
810
1061
|
throw this.throwError(
|
|
@@ -822,7 +1073,8 @@ export default class Inibase {
|
|
|
822
1073
|
} else if (Utils.isObject(where)) {
|
|
823
1074
|
// Criteria
|
|
824
1075
|
const FormatObjectCriteriaValue = (
|
|
825
|
-
value: string
|
|
1076
|
+
value: string,
|
|
1077
|
+
isParentArray: boolean = false
|
|
826
1078
|
): [ComparisonOperator, string | number | boolean | null] => {
|
|
827
1079
|
switch (value[0]) {
|
|
828
1080
|
case ">":
|
|
@@ -849,10 +1101,19 @@ export default class Inibase {
|
|
|
849
1101
|
value.slice(3) as string | number,
|
|
850
1102
|
]
|
|
851
1103
|
: [
|
|
852
|
-
value.slice(0, 1) as ComparisonOperator,
|
|
1104
|
+
(value.slice(0, 1) + "=") as ComparisonOperator,
|
|
853
1105
|
value.slice(1) as string | number,
|
|
854
1106
|
];
|
|
855
1107
|
case "=":
|
|
1108
|
+
return isParentArray
|
|
1109
|
+
? [
|
|
1110
|
+
value.slice(0, 1) as ComparisonOperator,
|
|
1111
|
+
value.slice(1) as string | number,
|
|
1112
|
+
]
|
|
1113
|
+
: [
|
|
1114
|
+
value.slice(0, 1) as ComparisonOperator,
|
|
1115
|
+
(value.slice(1) + ",") as string,
|
|
1116
|
+
];
|
|
856
1117
|
case "*":
|
|
857
1118
|
return [
|
|
858
1119
|
value.slice(0, 1) as ComparisonOperator,
|
|
@@ -899,6 +1160,7 @@ export default class Inibase {
|
|
|
899
1160
|
allTrue = true;
|
|
900
1161
|
let index = -1;
|
|
901
1162
|
for (const [key, value] of Object.entries(criteria)) {
|
|
1163
|
+
const field = this.getField(key, schema as Schema) as Field;
|
|
902
1164
|
index++;
|
|
903
1165
|
let searchOperator:
|
|
904
1166
|
| ComparisonOperator
|
|
@@ -1001,13 +1263,15 @@ export default class Inibase {
|
|
|
1001
1263
|
tableName,
|
|
1002
1264
|
File.encodeFileName(key, "inib")
|
|
1003
1265
|
),
|
|
1004
|
-
this.getField(key, schema as Schema)?.type ?? "string",
|
|
1005
1266
|
searchOperator,
|
|
1006
1267
|
searchComparedAtValue,
|
|
1007
1268
|
searchLogicalOperator,
|
|
1269
|
+
field?.type,
|
|
1270
|
+
(field as any)?.children,
|
|
1008
1271
|
options.per_page,
|
|
1009
1272
|
(options.page as number) - 1 * (options.per_page as number) + 1,
|
|
1010
|
-
true
|
|
1273
|
+
true,
|
|
1274
|
+
this.databasePath
|
|
1011
1275
|
);
|
|
1012
1276
|
if (searchResult) {
|
|
1013
1277
|
RETURN = Utils.deepMerge(RETURN, searchResult);
|
|
@@ -1044,7 +1308,7 @@ export default class Inibase {
|
|
|
1044
1308
|
);
|
|
1045
1309
|
if (greatestColumnTotalItems)
|
|
1046
1310
|
this.pageInfo = {
|
|
1047
|
-
...(({ columns, ...
|
|
1311
|
+
...(({ columns, ...restOfOptions }) => restOfOptions)(options),
|
|
1048
1312
|
...this.pageInfoArray[greatestColumnTotalItems],
|
|
1049
1313
|
total_pages: Math.ceil(
|
|
1050
1314
|
this.pageInfoArray[greatestColumnTotalItems].total_items /
|
|
@@ -1073,12 +1337,12 @@ export default class Inibase {
|
|
|
1073
1337
|
return null;
|
|
1074
1338
|
return Utils.isArrayOfObjects(RETURN)
|
|
1075
1339
|
? (RETURN as Data[]).map((data: Data) => {
|
|
1076
|
-
data.id =
|
|
1340
|
+
data.id = Utils.encodeID(data.id as number, this.salt);
|
|
1077
1341
|
return data;
|
|
1078
1342
|
})
|
|
1079
1343
|
: {
|
|
1080
1344
|
...(RETURN as Data),
|
|
1081
|
-
id:
|
|
1345
|
+
id: Utils.encodeID((RETURN as Data).id as number, this.salt),
|
|
1082
1346
|
};
|
|
1083
1347
|
}
|
|
1084
1348
|
|
|
@@ -1090,41 +1354,38 @@ export default class Inibase {
|
|
|
1090
1354
|
per_page: 15,
|
|
1091
1355
|
}
|
|
1092
1356
|
): Promise<Data | Data[] | null> {
|
|
1093
|
-
const schema = this.getTableSchema(tableName);
|
|
1357
|
+
const schema = await this.getTableSchema(tableName);
|
|
1094
1358
|
let RETURN: Data | Data[] | null | undefined;
|
|
1095
1359
|
if (!schema) throw this.throwError("NO_SCHEMA", tableName);
|
|
1096
1360
|
const idFilePath = join(this.databasePath, tableName, "id.inib");
|
|
1097
|
-
let last_id =
|
|
1098
|
-
? Number(Object.values(await File.get(idFilePath, "number"
|
|
1361
|
+
let last_id = (await File.isExists(idFilePath))
|
|
1362
|
+
? Number(Object.values(await File.get(idFilePath, -1, "number"))[0])
|
|
1099
1363
|
: 0;
|
|
1100
1364
|
if (Utils.isArrayOfObjects(data))
|
|
1101
1365
|
(data as Data[]).forEach((single_data, index) => {
|
|
1102
1366
|
if (!RETURN) RETURN = [];
|
|
1103
|
-
RETURN[index] = (({ id, updated_at, created_at, ...rest }) =>
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1367
|
+
RETURN[index] = (({ id, updated_at, created_at, ...rest }) => ({
|
|
1368
|
+
id: ++last_id,
|
|
1369
|
+
...rest,
|
|
1370
|
+
created_at: new Date(),
|
|
1371
|
+
}))(single_data);
|
|
1108
1372
|
});
|
|
1109
|
-
else
|
|
1110
|
-
RETURN = (({ id, updated_at, created_at, ...rest }) =>
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
}
|
|
1373
|
+
else
|
|
1374
|
+
RETURN = (({ id, updated_at, created_at, ...rest }) => ({
|
|
1375
|
+
id: ++last_id,
|
|
1376
|
+
...rest,
|
|
1377
|
+
created_at: new Date(),
|
|
1378
|
+
}))(data as Data);
|
|
1116
1379
|
if (!RETURN) throw this.throwError("NO_DATA");
|
|
1117
|
-
this.validateData(RETURN, schema);
|
|
1118
1380
|
RETURN = this.formatData(RETURN, schema);
|
|
1119
1381
|
const pathesContents = this.joinPathesContents(
|
|
1120
1382
|
join(this.databasePath, tableName),
|
|
1121
1383
|
RETURN
|
|
1122
1384
|
);
|
|
1123
|
-
for (const [path, content] of Object.entries(pathesContents))
|
|
1124
|
-
|
|
1385
|
+
for await (const [path, content] of Object.entries(pathesContents))
|
|
1386
|
+
await appendFile(
|
|
1125
1387
|
path,
|
|
1126
|
-
(Array.isArray(content) ? content.join("\n") : content ?? "") + "\n"
|
|
1127
|
-
"utf8"
|
|
1388
|
+
(Array.isArray(content) ? content.join("\n") : content ?? "") + "\n"
|
|
1128
1389
|
);
|
|
1129
1390
|
|
|
1130
1391
|
return this.get(
|
|
@@ -1139,64 +1400,76 @@ export default class Inibase {
|
|
|
1139
1400
|
public async put(
|
|
1140
1401
|
tableName: string,
|
|
1141
1402
|
data: Data | Data[],
|
|
1142
|
-
where?: number | string | (number | string)[] | Criteria
|
|
1143
|
-
|
|
1144
|
-
|
|
1403
|
+
where?: number | string | (number | string)[] | Criteria,
|
|
1404
|
+
options: Options = {
|
|
1405
|
+
page: 1,
|
|
1406
|
+
per_page: 15,
|
|
1407
|
+
}
|
|
1408
|
+
): Promise<Data | Data[] | null> {
|
|
1409
|
+
const schema = await this.getTableSchema(tableName);
|
|
1145
1410
|
if (!schema) throw this.throwError("NO_SCHEMA", tableName);
|
|
1146
|
-
this.
|
|
1411
|
+
const idFilePath = join(this.databasePath, tableName, "id.inib");
|
|
1412
|
+
if (!(await File.isExists(idFilePath)))
|
|
1413
|
+
throw this.throwError("NO_ITEMS", tableName);
|
|
1147
1414
|
data = this.formatData(data, schema, true);
|
|
1148
1415
|
if (!where) {
|
|
1149
1416
|
if (Utils.isArrayOfObjects(data)) {
|
|
1150
1417
|
if (
|
|
1151
1418
|
!(data as Data[]).every(
|
|
1152
|
-
(item) => item.hasOwnProperty("id") &&
|
|
1419
|
+
(item) => item.hasOwnProperty("id") && Utils.isValidID(item.id)
|
|
1153
1420
|
)
|
|
1154
1421
|
)
|
|
1155
1422
|
throw this.throwError("INVALID_ID");
|
|
1156
|
-
|
|
1423
|
+
return this.put(
|
|
1157
1424
|
tableName,
|
|
1158
1425
|
data,
|
|
1159
1426
|
(data as Data[]).map((item) => item.id)
|
|
1160
1427
|
);
|
|
1161
1428
|
} else if (data.hasOwnProperty("id")) {
|
|
1162
|
-
if (!
|
|
1429
|
+
if (!Utils.isValidID((data as Data).id))
|
|
1163
1430
|
throw this.throwError("INVALID_ID", (data as Data).id);
|
|
1164
|
-
|
|
1431
|
+
return this.put(
|
|
1165
1432
|
tableName,
|
|
1166
1433
|
data,
|
|
1167
|
-
|
|
1434
|
+
Utils.decodeID((data as Data).id as string, this.salt)
|
|
1168
1435
|
);
|
|
1169
1436
|
} else {
|
|
1170
1437
|
const pathesContents = this.joinPathesContents(
|
|
1171
1438
|
join(this.databasePath, tableName),
|
|
1172
1439
|
Utils.isArrayOfObjects(data)
|
|
1173
1440
|
? (data as Data[]).map((item) => ({
|
|
1174
|
-
...item,
|
|
1441
|
+
...(({ id, ...restOfData }) => restOfData)(item),
|
|
1175
1442
|
updated_at: new Date(),
|
|
1176
1443
|
}))
|
|
1177
|
-
: {
|
|
1444
|
+
: {
|
|
1445
|
+
...(({ id, ...restOfData }) => restOfData)(data as Data),
|
|
1446
|
+
updated_at: new Date(),
|
|
1447
|
+
}
|
|
1178
1448
|
);
|
|
1179
1449
|
for (const [path, content] of Object.entries(pathesContents))
|
|
1180
1450
|
await File.replace(path, content);
|
|
1451
|
+
return this.get(tableName, where, options);
|
|
1181
1452
|
}
|
|
1182
|
-
} else if (
|
|
1453
|
+
} else if (Utils.isValidID(where)) {
|
|
1183
1454
|
let Ids = where as string | string[];
|
|
1184
1455
|
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
1456
|
const [lineNumbers, countItems] = await File.search(
|
|
1188
1457
|
idFilePath,
|
|
1189
|
-
"number",
|
|
1190
1458
|
"[]",
|
|
1191
|
-
Ids.map((id) =>
|
|
1459
|
+
Ids.map((id) => Utils.decodeID(id, this.salt)),
|
|
1460
|
+
undefined,
|
|
1461
|
+
"number",
|
|
1192
1462
|
undefined,
|
|
1193
|
-
Ids.length
|
|
1463
|
+
Ids.length,
|
|
1464
|
+
0,
|
|
1465
|
+
false,
|
|
1466
|
+
this.databasePath
|
|
1194
1467
|
);
|
|
1195
1468
|
if (!lineNumbers || !Object.keys(lineNumbers).length)
|
|
1196
1469
|
throw this.throwError("INVALID_ID");
|
|
1197
|
-
|
|
1470
|
+
return this.put(tableName, data, Object.keys(lineNumbers).map(Number));
|
|
1198
1471
|
} else if (Utils.isNumber(where)) {
|
|
1199
|
-
// where in this case, is the line(s) number(s) and not id(s)
|
|
1472
|
+
// "where" in this case, is the line(s) number(s) and not id(s)
|
|
1200
1473
|
const pathesContents = Object.fromEntries(
|
|
1201
1474
|
Object.entries(
|
|
1202
1475
|
this.joinPathesContents(
|
|
@@ -1221,60 +1494,85 @@ export default class Inibase {
|
|
|
1221
1494
|
);
|
|
1222
1495
|
for (const [path, content] of Object.entries(pathesContents))
|
|
1223
1496
|
await File.replace(path, content);
|
|
1497
|
+
return this.get(tableName, where, options);
|
|
1224
1498
|
} else if (typeof where === "object" && !Array.isArray(where)) {
|
|
1225
1499
|
const lineNumbers = this.get(tableName, where, undefined, true);
|
|
1226
1500
|
if (!lineNumbers || !Array.isArray(lineNumbers) || !lineNumbers.length)
|
|
1227
1501
|
throw this.throwError("NO_ITEMS", tableName);
|
|
1228
|
-
|
|
1229
|
-
} else throw this.throwError("
|
|
1502
|
+
return this.put(tableName, data, lineNumbers);
|
|
1503
|
+
} else throw this.throwError("INVALID_PARAMETERS", tableName);
|
|
1230
1504
|
}
|
|
1231
1505
|
|
|
1232
1506
|
public async delete(
|
|
1233
1507
|
tableName: string,
|
|
1234
|
-
where?: number | string | (number | string)[] | Criteria
|
|
1235
|
-
|
|
1236
|
-
|
|
1508
|
+
where?: number | string | (number | string)[] | Criteria,
|
|
1509
|
+
_id?: string | string[]
|
|
1510
|
+
): Promise<string | string[] | null> {
|
|
1511
|
+
const schema = await this.getTableSchema(tableName);
|
|
1237
1512
|
if (!schema) throw this.throwError("NO_SCHEMA", tableName);
|
|
1513
|
+
const idFilePath = join(this.databasePath, tableName, "id.inib");
|
|
1514
|
+
if (!(await File.isExists(idFilePath)))
|
|
1515
|
+
throw this.throwError("NO_ITEMS", tableName);
|
|
1238
1516
|
if (!where) {
|
|
1239
|
-
const files =
|
|
1517
|
+
const files = await readdir(join(this.databasePath, tableName));
|
|
1240
1518
|
if (files.length) {
|
|
1241
1519
|
for (const file in files.filter(
|
|
1242
1520
|
(fileName: string) => fileName !== "schema.inib"
|
|
1243
1521
|
))
|
|
1244
|
-
|
|
1522
|
+
await unlink(join(this.databasePath, tableName, file));
|
|
1245
1523
|
}
|
|
1246
|
-
|
|
1524
|
+
return "*";
|
|
1525
|
+
} else if (Utils.isValidID(where)) {
|
|
1247
1526
|
let Ids = where as string | string[];
|
|
1248
1527
|
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
1528
|
const [lineNumbers, countItems] = await File.search(
|
|
1252
1529
|
idFilePath,
|
|
1253
|
-
"number",
|
|
1254
1530
|
"[]",
|
|
1255
|
-
Ids.map((id) =>
|
|
1531
|
+
Ids.map((id) => Utils.decodeID(id, this.salt)),
|
|
1256
1532
|
undefined,
|
|
1257
|
-
|
|
1533
|
+
"number",
|
|
1534
|
+
undefined,
|
|
1535
|
+
Ids.length,
|
|
1536
|
+
0,
|
|
1537
|
+
false,
|
|
1538
|
+
this.databasePath
|
|
1258
1539
|
);
|
|
1259
1540
|
if (!lineNumbers || !Object.keys(lineNumbers).length)
|
|
1260
1541
|
throw this.throwError("INVALID_ID");
|
|
1261
|
-
|
|
1542
|
+
return this.delete(
|
|
1543
|
+
tableName,
|
|
1544
|
+
Object.keys(lineNumbers).map(Number),
|
|
1545
|
+
where as string | string[]
|
|
1546
|
+
);
|
|
1262
1547
|
} else if (Utils.isNumber(where)) {
|
|
1263
|
-
const files =
|
|
1548
|
+
const files = await readdir(join(this.databasePath, tableName));
|
|
1264
1549
|
if (files.length) {
|
|
1265
|
-
|
|
1266
|
-
|
|
1550
|
+
if (!_id)
|
|
1551
|
+
_id = Object.values(
|
|
1552
|
+
await File.get(
|
|
1553
|
+
join(this.databasePath, tableName, "id.inib"),
|
|
1554
|
+
where as number | number[],
|
|
1555
|
+
"number"
|
|
1556
|
+
)
|
|
1557
|
+
)
|
|
1558
|
+
.map(Number)
|
|
1559
|
+
.map((id) => Utils.encodeID(id, this.salt));
|
|
1560
|
+
for (const file of files.filter(
|
|
1561
|
+
(fileName: string) =>
|
|
1562
|
+
fileName.endsWith(".inib") && fileName !== "schema.inib"
|
|
1267
1563
|
))
|
|
1268
1564
|
await File.remove(
|
|
1269
1565
|
join(this.databasePath, tableName, file),
|
|
1270
1566
|
where as number | number[]
|
|
1271
1567
|
);
|
|
1568
|
+
return Array.isArray(_id) && _id.length === 1 ? _id[0] : _id;
|
|
1272
1569
|
}
|
|
1273
1570
|
} else if (typeof where === "object" && !Array.isArray(where)) {
|
|
1274
1571
|
const lineNumbers = this.get(tableName, where, undefined, true);
|
|
1275
1572
|
if (!lineNumbers || !Array.isArray(lineNumbers) || !lineNumbers.length)
|
|
1276
1573
|
throw this.throwError("NO_ITEMS", tableName);
|
|
1277
|
-
|
|
1278
|
-
} else throw this.throwError("
|
|
1574
|
+
return this.delete(tableName, lineNumbers);
|
|
1575
|
+
} else throw this.throwError("INVALID_PARAMETERS", tableName);
|
|
1576
|
+
return null;
|
|
1279
1577
|
}
|
|
1280
1578
|
}
|