inibase 1.0.0-rc.3 → 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/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 Field = {
33
+ type FieldDefault = {
37
34
  id?: string | number | null | undefined;
38
35
  key: string;
39
36
  required?: boolean;
40
- } & (
41
- | {
42
- type: Exclude<FieldType, "array" | "object">;
43
- required?: boolean;
44
- }
45
- | {
46
- type: "array";
47
- children: FieldType | FieldType[] | Schema;
48
- }
49
- | {
50
- type: "object";
51
- children: Schema;
52
- }
53
- );
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 =
@@ -79,13 +90,23 @@ type pageInfo = {
79
90
 
80
91
  export type Criteria =
81
92
  | {
82
- [logic in "and" | "or"]?: Criteria;
93
+ [logic in "and" | "or"]?: Criteria | (string | number | boolean | null)[];
83
94
  }
84
95
  | {
85
96
  [key: string]: string | number | boolean | Criteria;
86
97
  }
87
98
  | null;
88
99
 
100
+ declare global {
101
+ type Entries<T> = {
102
+ [K in keyof T]: [K, T[K]];
103
+ }[keyof T][];
104
+
105
+ interface ObjectConstructor {
106
+ entries<T extends object>(o: T): Entries<T>;
107
+ }
108
+ }
109
+
89
110
  export default class Inibase {
90
111
  public database: string;
91
112
  public databasePath: string;
@@ -93,7 +114,7 @@ export default class Inibase {
93
114
  public pageInfoArray: Record<string, Record<string, number>>;
94
115
  public pageInfo: pageInfo;
95
116
 
96
- constructor(databaseName: string, mainFolder: string = "/") {
117
+ constructor(databaseName: string, mainFolder: string = ".") {
97
118
  this.database = databaseName;
98
119
  this.databasePath = join(mainFolder, databaseName);
99
120
  this.cache = new Map<string, any>();
@@ -112,14 +133,14 @@ export default class Inibase {
112
133
  ): Error {
113
134
  const errorMessages: Record<string, Record<string, string>> = {
114
135
  en: {
136
+ FIELD_REQUIRED: "REQUIRED: {variable}",
115
137
  NO_SCHEMA: "NO_SCHEMA: {variable}",
116
138
  NO_ITEMS: "NO_ITEMS: {variable}",
139
+ NO_DATA: "NO_DATA: {variable}",
117
140
  INVALID_ID: "INVALID_ID: {variable}",
118
141
  INVALID_TYPE: "INVALID_TYPE: {variable}",
119
- REQUIRED: "REQUIRED: {variable}",
120
- NO_DATA: "NO_DATA: {variable}",
121
142
  INVALID_OPERATOR: "INVALID_OPERATOR: {variable}",
122
- PARAMETERS: "PARAMETERS: {variable}",
143
+ INVALID_PARAMETERS: "PARAMETERS: {variable}",
123
144
  },
124
145
  // Add more languages and error messages as needed
125
146
  };
@@ -147,131 +168,6 @@ export default class Inibase {
147
168
  return new Error(errorMessage);
148
169
  }
149
170
 
150
- public encodeID(id: number, secretKey?: string | number): string {
151
- if (!secretKey) secretKey = this.databasePath;
152
-
153
- const salt = scryptSync(secretKey.toString(), "salt", 32),
154
- cipher = createCipheriv("aes-256-cbc", salt, salt.subarray(0, 16));
155
-
156
- return cipher.update(id.toString(), "utf8", "hex") + cipher.final("hex");
157
- }
158
-
159
- public decodeID(input: string, secretKey?: string | number): number {
160
- if (!secretKey) secretKey = this.databasePath;
161
- const salt = scryptSync(secretKey.toString(), "salt", 32),
162
- decipher = createDecipheriv("aes-256-cbc", salt, salt.subarray(0, 16));
163
- return Number(
164
- decipher.update(input as string, "hex", "utf8") + decipher.final("utf8")
165
- );
166
- }
167
-
168
- public isValidID(input: any): boolean {
169
- return Array.isArray(input)
170
- ? input.every(this.isValidID)
171
- : typeof input === "string" && input.length === 32;
172
- }
173
-
174
- public validateData(
175
- data: Data | Data[],
176
- schema: Schema,
177
- skipRequiredField: boolean = false
178
- ): void {
179
- if (Utils.isArrayOfObjects(data))
180
- for (const single_data of data as Data[])
181
- this.validateData(single_data, schema, skipRequiredField);
182
- else if (!Array.isArray(data)) {
183
- const validateFieldType = (
184
- value: any,
185
- field: Field | FieldType | FieldType[]
186
- ): boolean => {
187
- if (Array.isArray(field))
188
- return field.some((item) => validateFieldType(value, item));
189
- switch (typeof field === "string" ? field : field.type) {
190
- case "string":
191
- return value === null || typeof value === "string";
192
- case "number":
193
- return value === null || typeof value === "number";
194
- case "boolean":
195
- return (
196
- value === null ||
197
- typeof value === "boolean" ||
198
- value === "true" ||
199
- value === "false"
200
- );
201
- case "date":
202
- return value === null || value instanceof Date;
203
- case "object":
204
- return (
205
- value === null ||
206
- (typeof value === "object" &&
207
- !Array.isArray(value) &&
208
- value !== null)
209
- );
210
- case "array":
211
- return (
212
- value === null ||
213
- (Array.isArray(value) &&
214
- value.every((item) => validateFieldType(item, field)))
215
- );
216
- case "email":
217
- return (
218
- value === null ||
219
- (typeof value === "string" &&
220
- /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
221
- );
222
- case "url":
223
- return (
224
- value === null ||
225
- (typeof value === "string" &&
226
- (value[0] === "#" ||
227
- /^((https?|www):\/\/)?[a-z0-9-]+(\.[a-z0-9-]+)*\.[a-z]+(\/[^\s]*)?$/.test(
228
- value
229
- )))
230
- );
231
- case "table":
232
- // feat: check if id exists
233
- if (Array.isArray(value))
234
- return (
235
- typeof field !== "string" &&
236
- field.type === "table" &&
237
- ((Utils.isArrayOfObjects(value) &&
238
- value.every(
239
- (element) =>
240
- element.hasOwnProperty("id") &&
241
- this.isValidID((element as Data).id)
242
- )) ||
243
- value.every(Utils.isNumber) ||
244
- this.isValidID(value))
245
- );
246
- else if (Utils.isObject(value))
247
- return (
248
- value.hasOwnProperty("id") && this.isValidID((value as Data).id)
249
- );
250
- else return Utils.isNumber(value) || this.isValidID(value);
251
- default:
252
- return true;
253
- }
254
- };
255
- for (const field of schema) {
256
- if (data.hasOwnProperty(field.key)) {
257
- if (!validateFieldType(data[field.key], field))
258
- throw this.throwError("INVALID_TYPE", field.key);
259
- if (
260
- (field.type === "array" || field.type === "object") &&
261
- field.children &&
262
- Utils.isArrayOfObjects(field.children)
263
- )
264
- this.validateData(
265
- data[field.key],
266
- field.children as Schema,
267
- skipRequiredField
268
- );
269
- } else if (field.required && !skipRequiredField)
270
- throw this.throwError("REQUIRED", field.key);
271
- }
272
- }
273
- }
274
-
275
171
  public setTableSchema(tableName: string, schema: Schema): void {
276
172
  const encodeSchema = (schema: Schema) => {
277
173
  let RETURN: any[][] = [],
@@ -279,7 +175,9 @@ export default class Inibase {
279
175
  for (const field of schema) {
280
176
  if (!RETURN[index]) RETURN[index] = [];
281
177
  RETURN[index].push(
282
- field.id ? this.decodeID(field.id as string) : null
178
+ field.id
179
+ ? Utils.decodeID(field.id as string, this.databasePath)
180
+ : null
283
181
  );
284
182
  RETURN[index].push(field.key ?? null);
285
183
  RETURN[index].push(field.required ?? null);
@@ -303,14 +201,22 @@ export default class Inibase {
303
201
  ) {
304
202
  if (!field.id) {
305
203
  oldIndex++;
306
- field = { ...field, id: this.encodeID(oldIndex) };
307
- } else oldIndex = this.decodeID(field.id as string);
204
+ field = {
205
+ ...field,
206
+ id: Utils.encodeID(oldIndex, this.databasePath),
207
+ };
208
+ } else
209
+ oldIndex = Utils.decodeID(field.id as string, this.databasePath);
308
210
  field.children = addIdToSchema(field.children as Schema, oldIndex);
309
211
  oldIndex += field.children.length;
310
- } else if (field.id) oldIndex = this.decodeID(field.id as string);
212
+ } else if (field.id)
213
+ oldIndex = Utils.decodeID(field.id as string, this.databasePath);
311
214
  else {
312
215
  oldIndex++;
313
- field = { ...field, id: this.encodeID(oldIndex) };
216
+ field = {
217
+ ...field,
218
+ id: Utils.encodeID(oldIndex, this.databasePath),
219
+ };
314
220
  }
315
221
  return field;
316
222
  }),
@@ -322,8 +228,10 @@ export default class Inibase {
322
228
  Utils.isArrayOfObjects(lastField.children)
323
229
  )
324
230
  return findLastIdNumber(lastField.children as Schema);
325
- else return this.decodeID(lastField.id as string);
326
- } else return 0;
231
+ else if (lastField.id && Utils.isValidID(lastField.id))
232
+ return Utils.decodeID(lastField.id as string, this.databasePath);
233
+ }
234
+ return 0;
327
235
  };
328
236
 
329
237
  // remove id from schema
@@ -336,7 +244,7 @@ export default class Inibase {
336
244
  // update columns files names based on field id
337
245
  const schemaToIdsPath = (schema: any, prefix = "") => {
338
246
  let RETURN: any = {};
339
- for (const field of schema) {
247
+ for (const field of schema)
340
248
  if (field.children && Utils.isArrayOfObjects(field.children)) {
341
249
  Utils.deepMerge(
342
250
  RETURN,
@@ -347,37 +255,20 @@ export default class Inibase {
347
255
  (field.type === "array" ? ".*." : ".")
348
256
  )
349
257
  );
350
- } else if (this.isValidID(field.id))
351
- RETURN[this.decodeID(field.id)] = File.encodeFileName(
352
- (prefix ?? "") + field.key,
353
- "inib"
354
- );
355
- }
356
- return RETURN;
357
- },
358
- findChangedProperties = (
359
- obj1: Record<string, string>,
360
- obj2: Record<string, string>
361
- ): Record<string, string> | null => {
362
- 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");
363
261
 
364
- for (const key1 in obj1) {
365
- if (obj2.hasOwnProperty(key1) && obj1[key1] !== obj2[key1]) {
366
- result[obj1[key1]] = obj2[key1];
367
- }
368
- }
369
-
370
- return Object.keys(result).length ? result : null;
262
+ return RETURN;
371
263
  },
372
- replaceOldPathes = findChangedProperties(
264
+ replaceOldPathes = Utils.findChangedProperties(
373
265
  schemaToIdsPath(this.getTableSchema(tableName)),
374
266
  schemaToIdsPath(schema)
375
267
  );
376
- if (replaceOldPathes) {
268
+ if (replaceOldPathes)
377
269
  for (const [oldPath, newPath] of Object.entries(replaceOldPathes))
378
270
  if (existsSync(join(TablePath, oldPath)))
379
271
  renameSync(join(TablePath, oldPath), join(TablePath, newPath));
380
- }
381
272
  }
382
273
 
383
274
  writeFileSync(
@@ -393,7 +284,7 @@ export default class Inibase {
393
284
  ? decodeSchema(field)
394
285
  : Object.fromEntries(
395
286
  Object.entries({
396
- id: this.encodeID(field[0]),
287
+ id: Utils.encodeID(field[0], this.databasePath),
397
288
  key: field[1],
398
289
  required: field[2],
399
290
  type: field[3],
@@ -418,24 +309,156 @@ export default class Inibase {
418
309
  );
419
310
  }
420
311
  return [
421
- { id: this.encodeID(0), key: "id", type: "number", required: true },
312
+ {
313
+ id: Utils.encodeID(0, this.databasePath),
314
+ key: "id",
315
+ type: "number",
316
+ required: true,
317
+ },
422
318
  ...(this.cache.get(TableSchemaPath) as unknown as Schema),
423
319
  ];
424
320
  }
425
321
 
426
- public getField(keyPath: string, schema: Schema | Field): Field | null {
427
- for (const key of keyPath.split(".")) {
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()) {
428
329
  if (key === "*") continue;
429
330
  const foundItem = (schema as Schema).find((item) => item.key === key);
430
331
  if (!foundItem) return null;
431
- schema =
332
+ if (index === keyPathSplited.length - 1) schema = foundItem;
333
+ if (
432
334
  (foundItem.type === "array" || foundItem.type === "object") &&
433
335
  foundItem.children &&
434
336
  Utils.isArrayOfObjects(foundItem.children)
435
- ? (foundItem.children as Schema)
436
- : foundItem;
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
+ }
437
461
  }
438
- return schema as Field;
439
462
  }
440
463
 
441
464
  public formatData(
@@ -443,89 +466,111 @@ export default class Inibase {
443
466
  schema: Schema,
444
467
  formatOnlyAvailiableKeys?: boolean
445
468
  ): Data | Data[] {
446
- if (Utils.isArrayOfObjects(data)) {
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))
447
545
  return data.map((single_data: Data) =>
448
- this.formatData(single_data, schema)
546
+ this.formatData(single_data, schema, formatOnlyAvailiableKeys)
449
547
  );
450
- } else if (!Array.isArray(data)) {
548
+ else if (Utils.isObject(data)) {
451
549
  let RETURN: Data = {};
452
550
  for (const field of schema) {
453
551
  if (!data.hasOwnProperty(field.key)) {
552
+ if (formatOnlyAvailiableKeys) continue;
454
553
  RETURN[field.key] = this.getDefaultValue(field);
455
554
  continue;
456
555
  }
457
- if (formatOnlyAvailiableKeys && !data.hasOwnProperty(field.key))
458
- continue;
459
-
460
- if (field.type === "array" || field.type === "object") {
461
- if (field.children)
462
- if (typeof field.children === "string") {
463
- if (field.type === "array" && field.children === "table") {
464
- if (Array.isArray(data[field.key])) {
465
- if (Utils.isArrayOfObjects(data[field.key])) {
466
- if (
467
- data[field.key].every(
468
- (item: any) =>
469
- item.hasOwnProperty("id") &&
470
- (this.isValidID(item.id) || Utils.isNumber(item.id))
471
- )
472
- )
473
- data[field.key].map((item: any) =>
474
- Utils.isNumber(item.id)
475
- ? parseFloat(item.id)
476
- : this.decodeID(item.id)
477
- );
478
- } else if (
479
- this.isValidID(data[field.key]) ||
480
- Utils.isNumber(data[field.key])
481
- )
482
- RETURN[field.key] = data[field.key].map(
483
- (item: number | string) =>
484
- Utils.isNumber(item)
485
- ? parseFloat(item as string)
486
- : this.decodeID(item as string)
487
- );
488
- } else if (this.isValidID(data[field.key]))
489
- RETURN[field.key] = [this.decodeID(data[field.key])];
490
- else if (Utils.isNumber(data[field.key]))
491
- RETURN[field.key] = [parseFloat(data[field.key])];
492
- } else if (data.hasOwnProperty(field.key))
493
- RETURN[field.key] = data[field.key];
494
- } else if (Utils.isArrayOfObjects(field.children))
495
- RETURN[field.key] = this.formatData(
496
- data[field.key],
497
- field.children as Schema,
498
- formatOnlyAvailiableKeys
499
- );
500
- } else if (field.type === "table") {
501
- if (Utils.isObject(data[field.key])) {
502
- if (
503
- data[field.key].hasOwnProperty("id") &&
504
- (this.isValidID(data[field.key].id) ||
505
- Utils.isNumber(data[field.key]))
506
- )
507
- RETURN[field.key] = Utils.isNumber(data[field.key].id)
508
- ? parseFloat(data[field.key].id)
509
- : this.decodeID(data[field.key].id);
510
- } else if (
511
- this.isValidID(data[field.key]) ||
512
- Utils.isNumber(data[field.key])
513
- )
514
- RETURN[field.key] = Utils.isNumber(data[field.key])
515
- ? parseFloat(data[field.key])
516
- : this.decodeID(data[field.key]);
517
- } else if (field.type === "password")
518
- RETURN[field.key] =
519
- data[field.key].length === 161
520
- ? data[field.key]
521
- : Utils.hashPassword(data[field.key]);
522
- else RETURN[field.key] = data[field.key];
556
+ RETURN[field.key] = formatField(data[field.key], field);
523
557
  }
524
558
  return RETURN;
525
559
  } else return [];
526
560
  }
527
561
 
528
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
+
529
574
  switch (field.type) {
530
575
  case "array":
531
576
  return Utils.isArrayOfObjects(field.children)
@@ -552,37 +597,54 @@ export default class Inibase {
552
597
  mainPath: string,
553
598
  data: Data | Data[]
554
599
  ): { [key: string]: string[] } {
555
- const CombineData = (data: Data | Data[], prefix?: string) => {
600
+ const CombineData = (_data: Data | Data[], prefix?: string) => {
556
601
  let RETURN: Record<
557
602
  string,
558
603
  string | boolean | number | null | (string | boolean | number | null)[]
559
604
  > = {};
560
-
561
- if (Utils.isArrayOfObjects(data))
562
- RETURN = Utils.combineObjects(
563
- (data as Data[]).map((single_data) => CombineData(single_data))
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))
564
615
  );
565
616
  else {
566
- for (const [key, value] of Object.entries(data)) {
617
+ for (const [key, value] of Object.entries(_data as Data)) {
567
618
  if (Utils.isObject(value))
568
619
  Object.assign(RETURN, CombineData(value, `${key}.`));
569
620
  else if (Array.isArray(value)) {
570
- if (Utils.isArrayOfObjects(value))
621
+ if (Utils.isArrayOfObjects(value)) {
571
622
  Object.assign(
572
623
  RETURN,
573
624
  CombineData(
574
- Utils.combineObjects(value),
625
+ combineObjectsToArray(value),
626
+ (prefix ?? "") + key + ".*."
627
+ )
628
+ );
629
+ } else if (
630
+ Utils.isArrayOfArrays(value) &&
631
+ value.every(Utils.isArrayOfObjects)
632
+ )
633
+ Object.assign(
634
+ RETURN,
635
+ CombineData(
636
+ combineObjectsToArray(value.map(combineObjectsToArray)),
575
637
  (prefix ?? "") + key + ".*."
576
638
  )
577
639
  );
578
640
  else
579
- RETURN[(prefix ?? "") + key] = Utils.encode(value) as
641
+ RETURN[(prefix ?? "") + key] = File.encode(value) as
580
642
  | boolean
581
643
  | number
582
644
  | string
583
645
  | null;
584
646
  } else
585
- RETURN[(prefix ?? "") + key] = Utils.encode(value) as
647
+ RETURN[(prefix ?? "") + key] = File.encode(value) as
586
648
  | boolean
587
649
  | number
588
650
  | string
@@ -612,6 +674,8 @@ export default class Inibase {
612
674
  onlyLinesNumbers?: boolean
613
675
  ): Promise<Data | Data[] | number[] | null> {
614
676
  if (!options.columns) options.columns = [];
677
+ else if (!Array.isArray(options.columns))
678
+ options.columns = [options.columns];
615
679
  else if (
616
680
  options.columns.length &&
617
681
  !(options.columns as string[]).includes("id")
@@ -625,23 +689,35 @@ export default class Inibase {
625
689
  const filterSchemaByColumns = (schema: Schema, columns: string[]): Schema =>
626
690
  schema
627
691
  .map((field) => {
628
- if (columns.includes(field.key)) return field;
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
+
629
697
  if (
630
698
  (field.type === "array" || field.type === "object") &&
631
699
  Utils.isArrayOfObjects(field.children) &&
632
- columns.filter((column) =>
633
- column.startsWith(
634
- field.key + (field.type === "array" ? ".*." : ".")
635
- )
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
+ )
636
708
  ).length
637
709
  ) {
638
710
  field.children = filterSchemaByColumns(
639
711
  field.children as Schema,
640
712
  columns
641
- .filter((column) =>
642
- column.startsWith(
643
- field.key + (field.type === "array" ? ".*." : ".")
644
- )
713
+ .filter(
714
+ (column) =>
715
+ column.startsWith(
716
+ field.key + (field.type === "array" ? ".*." : ".")
717
+ ) ||
718
+ column.startsWith(
719
+ "!" + field.key + (field.type === "array" ? ".*." : ".")
720
+ )
645
721
  )
646
722
  .map((column) =>
647
723
  column.replace(
@@ -667,10 +743,144 @@ export default class Inibase {
667
743
  let RETURN: Record<number, Data> = {};
668
744
  for (const field of schema) {
669
745
  if (
670
- (field.type === "array" || field.type === "object") &&
671
- field.children
746
+ (field.type === "array" ||
747
+ (Array.isArray(field.type) &&
748
+ (field.type as any).includes("array"))) &&
749
+ (field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
750
+ .children
672
751
  ) {
673
- if (field.children === "table") {
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
+ ) {
674
884
  if (options.columns)
675
885
  options.columns = (options.columns as string[])
676
886
  .filter((column) => column.includes(`${field.key}.*.`))
@@ -681,8 +891,10 @@ export default class Inibase {
681
891
  path,
682
892
  File.encodeFileName((prefix ?? "") + field.key, "inib")
683
893
  ),
894
+ linesNumber,
684
895
  field.type,
685
- linesNumber
896
+ (field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
897
+ .children as FieldType | FieldType[]
686
898
  )) ?? {}
687
899
  )) {
688
900
  if (!RETURN[index]) RETURN[index] = {};
@@ -690,26 +902,44 @@ export default class Inibase {
690
902
  ? await this.get(field.key, value as number, options)
691
903
  : this.getDefaultValue(field);
692
904
  }
693
- } else if (Utils.isArrayOfObjects(field.children)) {
694
- Object.entries(
695
- (await getItemsFromSchema(
905
+ } else if (
906
+ existsSync(
907
+ join(
696
908
  path,
697
- field.children as Schema,
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
+ ),
698
919
  linesNumber,
699
- (prefix ?? "") +
700
- field.key +
701
- (field.type === "array" ? ".*." : ".")
920
+ field.type,
921
+ (field as any)?.children
702
922
  )) ?? {}
703
923
  ).forEach(([index, item]) => {
704
924
  if (!RETURN[index]) RETURN[index] = {};
705
- RETURN[index][field.key] =
706
- field.type === "array" &&
707
- Utils.isObject(item) &&
708
- Object.values(item).every((i) => i === null)
709
- ? []
710
- : item;
925
+ RETURN[index][field.key] = item ?? this.getDefaultValue(field);
711
926
  });
712
- }
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
+ });
713
943
  } else if (field.type === "table") {
714
944
  if (
715
945
  existsSync(join(this.databasePath, field.key)) &&
@@ -734,8 +964,8 @@ export default class Inibase {
734
964
  path,
735
965
  File.encodeFileName((prefix ?? "") + field.key, "inib")
736
966
  ),
737
- "number",
738
- linesNumber
967
+ linesNumber,
968
+ "number"
739
969
  )) ?? {}
740
970
  )) {
741
971
  if (!RETURN[index]) RETURN[index] = {};
@@ -748,21 +978,21 @@ export default class Inibase {
748
978
  existsSync(
749
979
  join(path, File.encodeFileName((prefix ?? "") + field.key, "inib"))
750
980
  )
751
- ) {
981
+ )
752
982
  Object.entries(
753
983
  (await File.get(
754
984
  join(
755
985
  path,
756
986
  File.encodeFileName((prefix ?? "") + field.key, "inib")
757
987
  ),
988
+ linesNumber,
758
989
  field.type,
759
- linesNumber
990
+ (field as any)?.children
760
991
  )) ?? {}
761
992
  ).forEach(([index, item]) => {
762
993
  if (!RETURN[index]) RETURN[index] = {};
763
994
  RETURN[index][field.key] = item ?? this.getDefaultValue(field);
764
995
  });
765
- }
766
996
  }
767
997
  return RETURN;
768
998
  };
@@ -781,18 +1011,19 @@ export default class Inibase {
781
1011
  )
782
1012
  )
783
1013
  );
784
- } else if (this.isValidID(where) || Utils.isNumber(where)) {
1014
+ } else if (Utils.isValidID(where) || Utils.isNumber(where)) {
785
1015
  let Ids = where as string | number | (string | number)[];
786
1016
  if (!Array.isArray(Ids)) Ids = [Ids];
787
1017
  const idFilePath = join(this.databasePath, tableName, "id.inib");
788
1018
  if (!existsSync(idFilePath)) throw this.throwError("NO_ITEMS", tableName);
789
1019
  const [lineNumbers, countItems] = await File.search(
790
1020
  idFilePath,
791
- "number",
792
1021
  "[]",
793
1022
  Utils.isNumber(Ids)
794
- ? Ids.map((id) => parseFloat(id as string))
795
- : Ids.map((id) => this.decodeID(id as string)),
1023
+ ? Ids.map((id) => Number(id as string))
1024
+ : Ids.map((id) => Utils.decodeID(id as string, this.databasePath)),
1025
+ undefined,
1026
+ "number",
796
1027
  undefined,
797
1028
  Ids.length
798
1029
  );
@@ -809,10 +1040,11 @@ export default class Inibase {
809
1040
  )) ?? {}
810
1041
  );
811
1042
  if (RETURN.length && !Array.isArray(where)) RETURN = RETURN[0];
812
- } else if (typeof where === "object" && !Array.isArray(where)) {
1043
+ } else if (Utils.isObject(where)) {
813
1044
  // Criteria
814
1045
  const FormatObjectCriteriaValue = (
815
- value: string
1046
+ value: string,
1047
+ isParentArray: boolean = false
816
1048
  ): [ComparisonOperator, string | number | boolean | null] => {
817
1049
  switch (value[0]) {
818
1050
  case ">":
@@ -839,10 +1071,19 @@ export default class Inibase {
839
1071
  value.slice(3) as string | number,
840
1072
  ]
841
1073
  : [
842
- value.slice(0, 1) as ComparisonOperator,
1074
+ (value.slice(0, 1) + "=") as ComparisonOperator,
843
1075
  value.slice(1) as string | number,
844
1076
  ];
845
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
+ ];
846
1087
  case "*":
847
1088
  return [
848
1089
  value.slice(0, 1) as ComparisonOperator,
@@ -856,11 +1097,14 @@ export default class Inibase {
856
1097
  const applyCriteria = async (
857
1098
  criteria?: Criteria,
858
1099
  allTrue?: boolean
859
- ): Promise<Data | null> => {
860
- let RETURN: Data = {};
1100
+ ): Promise<Record<number, Data> | null> => {
1101
+ let RETURN: Record<number, Data> = {};
861
1102
  if (!criteria) return null;
862
- if (criteria.and && typeof criteria.and === "object") {
863
- const searchResult = await applyCriteria(criteria.and, true);
1103
+ if (criteria.and && Utils.isObject(criteria.and)) {
1104
+ const searchResult = await applyCriteria(
1105
+ criteria.and as Criteria,
1106
+ true
1107
+ );
864
1108
  if (searchResult) {
865
1109
  RETURN = Utils.deepMerge(
866
1110
  RETURN,
@@ -876,118 +1120,124 @@ export default class Inibase {
876
1120
  } else return null;
877
1121
  }
878
1122
 
879
- if (criteria.or && typeof criteria.or === "object") {
880
- const searchResult = await applyCriteria(criteria.or);
1123
+ if (criteria.or && Utils.isObject(criteria.or)) {
1124
+ const searchResult = await applyCriteria(criteria.or as Criteria);
881
1125
  delete criteria.or;
882
1126
  if (searchResult) RETURN = Utils.deepMerge(RETURN, searchResult);
883
1127
  }
884
1128
 
885
- let index = -1;
886
- for (const [key, value] of Object.entries(criteria)) {
887
- index++;
888
- if (
889
- allTrue &&
890
- index > 0 &&
891
- (!Object.keys(RETURN).length ||
892
- Object.values(RETURN).every(
893
- (item) => Object.keys(item).length >= index
894
- ))
895
- )
896
- break;
897
- let searchOperator:
898
- | ComparisonOperator
899
- | ComparisonOperator[]
900
- | undefined = undefined,
901
- searchComparedAtValue:
902
- | string
903
- | number
904
- | boolean
905
- | null
906
- | (string | number | boolean | null)[]
907
- | undefined = undefined,
908
- searchLogicalOperator: "and" | "or" | undefined = undefined;
909
- if (typeof value === "object") {
910
- if (value?.or && Array.isArray(value.or)) {
911
- const searchCriteria = value.or
912
- .map(
913
- (
914
- single_or
915
- ): [ComparisonOperator, string | number | boolean | null] =>
916
- typeof single_or === "string"
917
- ? FormatObjectCriteriaValue(single_or)
918
- : ["=", single_or]
1129
+ if (Object.keys(criteria).length > 0) {
1130
+ allTrue = true;
1131
+ let index = -1;
1132
+ for (const [key, value] of Object.entries(criteria)) {
1133
+ const field = this.getField(key, schema as Schema) as Field;
1134
+ index++;
1135
+ let searchOperator:
1136
+ | ComparisonOperator
1137
+ | ComparisonOperator[]
1138
+ | undefined = undefined,
1139
+ searchComparedAtValue:
1140
+ | string
1141
+ | number
1142
+ | boolean
1143
+ | null
1144
+ | (string | number | boolean | null)[]
1145
+ | undefined = undefined,
1146
+ searchLogicalOperator: "and" | "or" | undefined = undefined;
1147
+ if (Utils.isObject(value)) {
1148
+ if (
1149
+ (value as Criteria)?.or &&
1150
+ Array.isArray((value as Criteria).or)
1151
+ ) {
1152
+ const searchCriteria = (
1153
+ (value as Criteria).or as (string | number | boolean)[]
919
1154
  )
920
- .filter((a) => a) as [ComparisonOperator, string | number][];
921
- if (searchCriteria.length > 0) {
922
- searchOperator = searchCriteria.map(
923
- (single_or) => single_or[0]
924
- );
925
- searchComparedAtValue = searchCriteria.map(
926
- (single_or) => single_or[1]
927
- );
928
- searchLogicalOperator = "or";
1155
+ .map(
1156
+ (
1157
+ single_or
1158
+ ): [ComparisonOperator, string | number | boolean | null] =>
1159
+ typeof single_or === "string"
1160
+ ? FormatObjectCriteriaValue(single_or)
1161
+ : ["=", single_or]
1162
+ )
1163
+ .filter((a) => a) as [ComparisonOperator, string | number][];
1164
+ if (searchCriteria.length > 0) {
1165
+ searchOperator = searchCriteria.map(
1166
+ (single_or) => single_or[0]
1167
+ );
1168
+ searchComparedAtValue = searchCriteria.map(
1169
+ (single_or) => single_or[1]
1170
+ );
1171
+ searchLogicalOperator = "or";
1172
+ }
1173
+ delete (value as Criteria).or;
929
1174
  }
930
- delete value.or;
931
- }
932
- if (value?.and && Array.isArray(value.and)) {
933
- const searchCriteria = value.and
1175
+ if (
1176
+ (value as Criteria)?.and &&
1177
+ Array.isArray((value as Criteria).and)
1178
+ ) {
1179
+ const searchCriteria = (
1180
+ (value as Criteria).and as (string | number | boolean)[]
1181
+ )
1182
+ .map(
1183
+ (
1184
+ single_and
1185
+ ): [ComparisonOperator, string | number | boolean | null] =>
1186
+ typeof single_and === "string"
1187
+ ? FormatObjectCriteriaValue(single_and)
1188
+ : ["=", single_and]
1189
+ )
1190
+ .filter((a) => a) as [ComparisonOperator, string | number][];
1191
+ if (searchCriteria.length > 0) {
1192
+ searchOperator = searchCriteria.map(
1193
+ (single_and) => single_and[0]
1194
+ );
1195
+ searchComparedAtValue = searchCriteria.map(
1196
+ (single_and) => single_and[1]
1197
+ );
1198
+ searchLogicalOperator = "and";
1199
+ }
1200
+ delete (value as Criteria).and;
1201
+ }
1202
+ } else if (Array.isArray(value)) {
1203
+ const searchCriteria = value
934
1204
  .map(
935
1205
  (
936
- single_and
1206
+ single
937
1207
  ): [ComparisonOperator, string | number | boolean | null] =>
938
- typeof single_and === "string"
939
- ? FormatObjectCriteriaValue(single_and)
940
- : ["=", single_and]
1208
+ typeof single === "string"
1209
+ ? FormatObjectCriteriaValue(single)
1210
+ : ["=", single]
941
1211
  )
942
1212
  .filter((a) => a) as [ComparisonOperator, string | number][];
943
1213
  if (searchCriteria.length > 0) {
944
- searchOperator = searchCriteria.map(
945
- (single_and) => single_and[0]
946
- );
1214
+ searchOperator = searchCriteria.map((single) => single[0]);
947
1215
  searchComparedAtValue = searchCriteria.map(
948
- (single_and) => single_and[1]
1216
+ (single) => single[1]
949
1217
  );
950
1218
  searchLogicalOperator = "and";
951
1219
  }
952
- delete value.and;
953
- }
954
- } else if (Array.isArray(value)) {
955
- const searchCriteria = value
956
- .map(
957
- (
958
- single
959
- ): [ComparisonOperator, string | number | boolean | null] =>
960
- typeof single === "string"
961
- ? FormatObjectCriteriaValue(single)
962
- : ["=", single]
963
- )
964
- .filter((a) => a) as [ComparisonOperator, string | number][];
965
- if (searchCriteria.length > 0) {
966
- searchOperator = searchCriteria.map((single) => single[0]);
967
- searchComparedAtValue = searchCriteria.map((single) => single[1]);
968
- searchLogicalOperator = "and";
969
- }
970
- } else if (typeof value === "string") {
971
- const ComparisonOperatorValue = FormatObjectCriteriaValue(value);
972
- if (ComparisonOperatorValue) {
973
- searchOperator = ComparisonOperatorValue[0];
974
- searchComparedAtValue = ComparisonOperatorValue[1];
1220
+ } else if (typeof value === "string") {
1221
+ const ComparisonOperatorValue = FormatObjectCriteriaValue(value);
1222
+ if (ComparisonOperatorValue) {
1223
+ searchOperator = ComparisonOperatorValue[0];
1224
+ searchComparedAtValue = ComparisonOperatorValue[1];
1225
+ }
1226
+ } else {
1227
+ searchOperator = "=";
1228
+ searchComparedAtValue = value as number | boolean;
975
1229
  }
976
- } else {
977
- searchOperator = "=";
978
- searchComparedAtValue = value;
979
- }
980
- if (searchOperator && searchComparedAtValue) {
981
1230
  const [searchResult, totlaItems] = await File.search(
982
1231
  join(
983
1232
  this.databasePath,
984
1233
  tableName,
985
1234
  File.encodeFileName(key, "inib")
986
1235
  ),
987
- this.getField(key, schema as Schema)?.type ?? "string",
988
1236
  searchOperator,
989
1237
  searchComparedAtValue,
990
1238
  searchLogicalOperator,
1239
+ field?.type,
1240
+ (field as any)?.children,
991
1241
  options.per_page,
992
1242
  (options.page as number) - 1 * (options.per_page as number) + 1,
993
1243
  true
@@ -997,12 +1247,20 @@ export default class Inibase {
997
1247
  if (!this.pageInfoArray[key]) this.pageInfoArray[key] = {};
998
1248
  this.pageInfoArray[key].total_items = totlaItems;
999
1249
  }
1250
+ if (allTrue && index > 0) {
1251
+ if (!Object.keys(RETURN).length) RETURN = {};
1252
+ RETURN = Object.fromEntries(
1253
+ Object.entries(RETURN).filter(
1254
+ ([_index, item]) => Object.keys(item).length > index
1255
+ )
1256
+ );
1257
+ if (!Object.keys(RETURN).length) RETURN = {};
1258
+ }
1000
1259
  }
1001
1260
  }
1002
- return Object.keys(RETURN).length > 0 ? RETURN : null;
1261
+ return Object.keys(RETURN).length ? RETURN : null;
1003
1262
  };
1004
-
1005
- RETURN = await applyCriteria(where);
1263
+ RETURN = await applyCriteria(where as Criteria);
1006
1264
  if (RETURN) {
1007
1265
  if (onlyLinesNumbers) return Object.keys(RETURN).map(Number);
1008
1266
  const alreadyExistsColumns = Object.keys(Object.values(RETURN)[0]).map(
@@ -1019,7 +1277,7 @@ export default class Inibase {
1019
1277
  );
1020
1278
  if (greatestColumnTotalItems)
1021
1279
  this.pageInfo = {
1022
- ...(({ columns, ...restOFOptions }) => restOFOptions)(options),
1280
+ ...(({ columns, ...restOfOptions }) => restOfOptions)(options),
1023
1281
  ...this.pageInfoArray[greatestColumnTotalItems],
1024
1282
  total_pages: Math.ceil(
1025
1283
  this.pageInfoArray[greatestColumnTotalItems].total_items /
@@ -1040,17 +1298,21 @@ export default class Inibase {
1040
1298
  );
1041
1299
  }
1042
1300
  }
1043
- return RETURN
1044
- ? Utils.isArrayOfObjects(RETURN)
1045
- ? (RETURN as Data[]).map((data: Data) => {
1046
- data.id = this.encodeID(data.id as number);
1047
- return data;
1048
- })
1049
- : {
1050
- ...(RETURN as Data),
1051
- id: this.encodeID((RETURN as Data).id as number),
1052
- }
1053
- : null;
1301
+ if (
1302
+ !RETURN ||
1303
+ (Utils.isObject(RETURN) && !Object.keys(RETURN).length) ||
1304
+ (Array.isArray(RETURN) && !RETURN.length)
1305
+ )
1306
+ return null;
1307
+ return Utils.isArrayOfObjects(RETURN)
1308
+ ? (RETURN as Data[]).map((data: Data) => {
1309
+ data.id = Utils.encodeID(data.id as number, this.databasePath);
1310
+ return data;
1311
+ })
1312
+ : {
1313
+ ...(RETURN as Data),
1314
+ id: Utils.encodeID((RETURN as Data).id as number, this.databasePath),
1315
+ };
1054
1316
  }
1055
1317
 
1056
1318
  public async post(
@@ -1066,7 +1328,7 @@ export default class Inibase {
1066
1328
  if (!schema) throw this.throwError("NO_SCHEMA", tableName);
1067
1329
  const idFilePath = join(this.databasePath, tableName, "id.inib");
1068
1330
  let last_id = existsSync(idFilePath)
1069
- ? Number(Object.values(await File.get(idFilePath, "number", -1))[0])
1331
+ ? Number(Object.values(await File.get(idFilePath, -1, "number"))[0])
1070
1332
  : 0;
1071
1333
  if (Utils.isArrayOfObjects(data))
1072
1334
  (data as Data[]).forEach((single_data, index) => {
@@ -1110,8 +1372,12 @@ export default class Inibase {
1110
1372
  public async put(
1111
1373
  tableName: string,
1112
1374
  data: Data | Data[],
1113
- where?: number | string | (number | string)[] | Criteria
1114
- ) {
1375
+ where?: number | string | (number | string)[] | Criteria,
1376
+ options: Options = {
1377
+ page: 1,
1378
+ per_page: 15,
1379
+ }
1380
+ ): Promise<Data | Data[] | null> {
1115
1381
  const schema = this.getTableSchema(tableName);
1116
1382
  if (!schema) throw this.throwError("NO_SCHEMA", tableName);
1117
1383
  this.validateData(data, schema, true);
@@ -1120,54 +1386,59 @@ export default class Inibase {
1120
1386
  if (Utils.isArrayOfObjects(data)) {
1121
1387
  if (
1122
1388
  !(data as Data[]).every(
1123
- (item) => item.hasOwnProperty("id") && this.isValidID(item.id)
1389
+ (item) => item.hasOwnProperty("id") && Utils.isValidID(item.id)
1124
1390
  )
1125
1391
  )
1126
1392
  throw this.throwError("INVALID_ID");
1127
- await this.put(
1393
+ return this.put(
1128
1394
  tableName,
1129
1395
  data,
1130
1396
  (data as Data[]).map((item) => item.id)
1131
1397
  );
1132
1398
  } else if (data.hasOwnProperty("id")) {
1133
- if (!this.isValidID((data as Data).id))
1399
+ if (!Utils.isValidID((data as Data).id))
1134
1400
  throw this.throwError("INVALID_ID", (data as Data).id);
1135
- await this.put(
1401
+ return this.put(
1136
1402
  tableName,
1137
1403
  data,
1138
- this.decodeID((data as Data).id as string)
1404
+ Utils.decodeID((data as Data).id as string, this.databasePath)
1139
1405
  );
1140
1406
  } else {
1141
1407
  const pathesContents = this.joinPathesContents(
1142
1408
  join(this.databasePath, tableName),
1143
1409
  Utils.isArrayOfObjects(data)
1144
1410
  ? (data as Data[]).map((item) => ({
1145
- ...item,
1411
+ ...(({ id, ...restOfData }) => restOfData)(item),
1146
1412
  updated_at: new Date(),
1147
1413
  }))
1148
- : { ...data, updated_at: new Date() }
1414
+ : {
1415
+ ...(({ id, ...restOfData }) => restOfData)(data as Data),
1416
+ updated_at: new Date(),
1417
+ }
1149
1418
  );
1150
1419
  for (const [path, content] of Object.entries(pathesContents))
1151
1420
  await File.replace(path, content);
1421
+ return this.get(tableName, where, options);
1152
1422
  }
1153
- } else if (this.isValidID(where)) {
1423
+ } else if (Utils.isValidID(where)) {
1154
1424
  let Ids = where as string | string[];
1155
1425
  if (!Array.isArray(Ids)) Ids = [Ids];
1156
1426
  const idFilePath = join(this.databasePath, tableName, "id.inib");
1157
1427
  if (!existsSync(idFilePath)) throw this.throwError("NO_ITEMS", tableName);
1158
1428
  const [lineNumbers, countItems] = await File.search(
1159
1429
  idFilePath,
1160
- "number",
1161
1430
  "[]",
1162
- Ids.map((id) => this.decodeID(id)),
1431
+ Ids.map((id) => Utils.decodeID(id, this.databasePath)),
1432
+ undefined,
1433
+ "number",
1163
1434
  undefined,
1164
1435
  Ids.length
1165
1436
  );
1166
1437
  if (!lineNumbers || !Object.keys(lineNumbers).length)
1167
1438
  throw this.throwError("INVALID_ID");
1168
- await this.put(tableName, data, Object.keys(lineNumbers).map(Number));
1439
+ return this.put(tableName, data, Object.keys(lineNumbers).map(Number));
1169
1440
  } else if (Utils.isNumber(where)) {
1170
- // 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)
1171
1442
  const pathesContents = Object.fromEntries(
1172
1443
  Object.entries(
1173
1444
  this.joinPathesContents(
@@ -1192,18 +1463,20 @@ export default class Inibase {
1192
1463
  );
1193
1464
  for (const [path, content] of Object.entries(pathesContents))
1194
1465
  await File.replace(path, content);
1466
+ return this.get(tableName, where, options);
1195
1467
  } else if (typeof where === "object" && !Array.isArray(where)) {
1196
1468
  const lineNumbers = this.get(tableName, where, undefined, true);
1197
1469
  if (!lineNumbers || !Array.isArray(lineNumbers) || !lineNumbers.length)
1198
1470
  throw this.throwError("NO_ITEMS", tableName);
1199
- await this.put(tableName, data, lineNumbers);
1200
- } else throw this.throwError("PARAMETERS", tableName);
1471
+ return this.put(tableName, data, lineNumbers);
1472
+ } else throw this.throwError("INVALID_PARAMETERS", tableName);
1201
1473
  }
1202
1474
 
1203
1475
  public async delete(
1204
1476
  tableName: string,
1205
- where?: number | string | (number | string)[] | Criteria
1206
- ) {
1477
+ where?: number | string | (number | string)[] | Criteria,
1478
+ _id?: string | string[]
1479
+ ): Promise<string | string[]> {
1207
1480
  const schema = this.getTableSchema(tableName);
1208
1481
  if (!schema) throw this.throwError("NO_SCHEMA", tableName);
1209
1482
  if (!where) {
@@ -1214,25 +1487,41 @@ export default class Inibase {
1214
1487
  ))
1215
1488
  unlinkSync(join(this.databasePath, tableName, file));
1216
1489
  }
1217
- } else if (this.isValidID(where)) {
1490
+ return "*";
1491
+ } else if (Utils.isValidID(where)) {
1218
1492
  let Ids = where as string | string[];
1219
1493
  if (!Array.isArray(Ids)) Ids = [Ids];
1220
1494
  const idFilePath = join(this.databasePath, tableName, "id.inib");
1221
1495
  if (!existsSync(idFilePath)) throw this.throwError("NO_ITEMS", tableName);
1222
1496
  const [lineNumbers, countItems] = await File.search(
1223
1497
  idFilePath,
1224
- "number",
1225
1498
  "[]",
1226
- Ids.map((id) => this.decodeID(id)),
1499
+ Ids.map((id) => Utils.decodeID(id, this.databasePath)),
1500
+ undefined,
1501
+ "number",
1227
1502
  undefined,
1228
1503
  Ids.length
1229
1504
  );
1230
1505
  if (!lineNumbers || !Object.keys(lineNumbers).length)
1231
1506
  throw this.throwError("INVALID_ID");
1232
- await this.delete(tableName, Object.keys(lineNumbers).map(Number));
1507
+ return this.delete(
1508
+ tableName,
1509
+ Object.keys(lineNumbers).map(Number),
1510
+ where as string | string[]
1511
+ );
1233
1512
  } else if (Utils.isNumber(where)) {
1234
1513
  const files = readdirSync(join(this.databasePath, tableName));
1235
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));
1236
1525
  for (const file in files.filter(
1237
1526
  (fileName: string) => fileName !== "schema.inib"
1238
1527
  ))
@@ -1240,12 +1529,13 @@ export default class Inibase {
1240
1529
  join(this.databasePath, tableName, file),
1241
1530
  where as number | number[]
1242
1531
  );
1532
+ return Array.isArray(_id) && _id.length === 1 ? _id[0] : _id;
1243
1533
  }
1244
1534
  } else if (typeof where === "object" && !Array.isArray(where)) {
1245
1535
  const lineNumbers = this.get(tableName, where, undefined, true);
1246
1536
  if (!lineNumbers || !Array.isArray(lineNumbers) || !lineNumbers.length)
1247
1537
  throw this.throwError("NO_ITEMS", tableName);
1248
- await this.delete(tableName, lineNumbers);
1249
- } else throw this.throwError("PARAMETERS", tableName);
1538
+ return this.delete(tableName, lineNumbers);
1539
+ } else throw this.throwError("INVALID_PARAMETERS", tableName);
1250
1540
  }
1251
1541
  }