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/index.ts CHANGED
@@ -1,19 +1,16 @@
1
1
  import {
2
- readFileSync,
3
- writeFileSync,
4
- mkdirSync,
5
- existsSync,
6
- appendFileSync,
7
- readdirSync,
8
- unlinkSync,
9
- renameSync,
10
- } from "fs";
11
- import { join, parse } from "path";
12
- import { createDecipheriv, createCipheriv, scryptSync } from "crypto";
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
- type Field = {
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
- 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
- );
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
- PARAMETERS: "PARAMETERS: {variable}",
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
- public encodeID(id: number, secretKey?: string | number): string {
161
- if (!secretKey) secretKey = this.databasePath;
162
-
163
- const salt = scryptSync(secretKey.toString(), "salt", 32),
164
- cipher = createCipheriv("aes-256-cbc", salt, salt.subarray(0, 16));
165
-
166
- return cipher.update(id.toString(), "utf8", "hex") + cipher.final("hex");
167
- }
168
-
169
- public decodeID(input: string, secretKey?: string | number): number {
170
- if (!secretKey) secretKey = this.databasePath;
171
- const salt = scryptSync(secretKey.toString(), "salt", 32),
172
- decipher = createDecipheriv("aes-256-cbc", salt, salt.subarray(0, 16));
173
- return Number(
174
- decipher.update(input as string, "hex", "utf8") + decipher.final("utf8")
175
- );
176
- }
177
-
178
- public isValidID(input: any): boolean {
179
- return Array.isArray(input)
180
- ? input.every(this.isValidID)
181
- : typeof input === "string" && input.length === 32;
182
- }
183
-
184
- public validateData(
185
- data: Data | Data[],
186
- schema: Schema,
187
- skipRequiredField: boolean = false
188
- ): void {
189
- if (Utils.isArrayOfObjects(data))
190
- for (const single_data of data as Data[])
191
- this.validateData(single_data, schema, skipRequiredField);
192
- else if (!Array.isArray(data)) {
193
- const validateFieldType = (
194
- value: any,
195
- field: Field | FieldType | FieldType[]
196
- ): boolean => {
197
- if (Array.isArray(field))
198
- return field.some((item) => validateFieldType(value, item));
199
- switch (typeof field === "string" ? field : field.type) {
200
- case "string":
201
- return value === null || typeof value === "string";
202
- case "number":
203
- return value === null || typeof value === "number";
204
- case "boolean":
205
- return (
206
- value === null ||
207
- typeof value === "boolean" ||
208
- value === "true" ||
209
- value === "false"
210
- );
211
- case "date":
212
- return value === null || value instanceof Date;
213
- case "object":
214
- return (
215
- value === null ||
216
- (typeof value === "object" &&
217
- !Array.isArray(value) &&
218
- value !== null)
219
- );
220
- case "array":
221
- return (
222
- value === null ||
223
- (Array.isArray(value) &&
224
- value.every((item) => validateFieldType(item, field)))
225
- );
226
- case "email":
227
- return (
228
- value === null ||
229
- (typeof value === "string" &&
230
- /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
231
- );
232
- case "url":
233
- return (
234
- value === null ||
235
- (typeof value === "string" &&
236
- (value[0] === "#" ||
237
- /^((https?|www):\/\/)?[a-z0-9-]+(\.[a-z0-9-]+)*\.[a-z]+(\/[^\s]*)?$/.test(
238
- value
239
- )))
240
- );
241
- case "table":
242
- // feat: check if id exists
243
- if (Array.isArray(value))
244
- return (
245
- typeof field !== "string" &&
246
- field.type === "table" &&
247
- ((Utils.isArrayOfObjects(value) &&
248
- value.every(
249
- (element) =>
250
- element.hasOwnProperty("id") &&
251
- this.isValidID((element as Data).id)
252
- )) ||
253
- value.every(Utils.isNumber) ||
254
- this.isValidID(value))
255
- );
256
- else if (Utils.isObject(value))
257
- return (
258
- value.hasOwnProperty("id") && this.isValidID((value as Data).id)
259
- );
260
- else return Utils.isNumber(value) || this.isValidID(value);
261
- default:
262
- return true;
263
- }
264
- };
265
- for (const field of schema) {
266
- if (data.hasOwnProperty(field.key)) {
267
- if (!validateFieldType(data[field.key], field))
268
- throw this.throwError("INVALID_TYPE", field.key);
269
- if (
270
- (field.type === "array" || field.type === "object") &&
271
- field.children &&
272
- Utils.isArrayOfObjects(field.children)
273
- )
274
- this.validateData(
275
- data[field.key],
276
- field.children as Schema,
277
- skipRequiredField
278
- );
279
- } else if (field.required && !skipRequiredField)
280
- throw this.throwError("REQUIRED", field.key);
281
- }
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(tableName: string, schema: Schema): void {
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 ? this.decodeID(field.id as string) : null
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.type === "array" || field.type === "object") &&
299
- field.children &&
300
- Utils.isArrayOfObjects(field.children)
301
- ? encodeSchema(field.children as Schema) ?? null
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 = { ...field, id: this.encodeID(oldIndex) };
317
- } else oldIndex = this.decodeID(field.id as string);
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) oldIndex = this.decodeID(field.id as string);
232
+ } else if (field.id)
233
+ oldIndex = Utils.decodeID(field.id as string, this.salt);
321
234
  else {
322
235
  oldIndex++;
323
- field = { ...field, id: this.encodeID(oldIndex) };
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((field) => field.key !== "id");
341
- schema = addIdToSchema(schema, findLastIdNumber(schema));
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 (!existsSync(TablePath)) mkdirSync(TablePath, { recursive: true });
345
- if (existsSync(TableSchemaPath)) {
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 (this.isValidID(field.id))
361
- RETURN[this.decodeID(field.id)] = File.encodeFileName(
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
- for (const key1 in obj1) {
375
- if (obj2.hasOwnProperty(key1) && obj1[key1] !== obj2[key1]) {
376
- result[obj1[key1]] = obj2[key1];
377
- }
378
- }
379
-
380
- return Object.keys(result).length ? result : null;
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 (existsSync(join(TablePath, oldPath)))
389
- renameSync(join(TablePath, oldPath), join(TablePath, newPath));
390
- }
282
+ if (await File.isExists(join(TablePath, oldPath)))
283
+ await rename(join(TablePath, oldPath), join(TablePath, newPath));
391
284
  }
392
285
 
393
- writeFileSync(
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: this.encodeID(field[0]),
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 (!existsSync(TableSchemaPath)) return undefined;
313
+ if (!(await File.isExists(TableSchemaPath))) return undefined;
421
314
  if (!this.cache.has(TableSchemaPath)) {
422
- const TableSchemaPathContent = readFileSync(TableSchemaPath);
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
- { id: this.encodeID(0), key: "id", type: "number", required: true },
432
- ...(this.cache.get(TableSchemaPath) as unknown as Schema),
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(keyPath: string, schema: Schema | Field): Field | null {
437
- for (const key of keyPath.split(".")) {
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
- ? (foundItem.children as Schema)
446
- : foundItem;
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
- if (Utils.isArrayOfObjects(data)) {
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
- } else if (!Array.isArray(data)) {
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
- if (formatOnlyAvailiableKeys && !data.hasOwnProperty(field.key))
468
- continue;
469
-
470
- if (field.type === "array" || field.type === "object") {
471
- if (field.children)
472
- if (typeof field.children === "string") {
473
- if (field.type === "array" && field.children === "table") {
474
- if (Array.isArray(data[field.key])) {
475
- if (Utils.isArrayOfObjects(data[field.key])) {
476
- if (
477
- data[field.key].every(
478
- (item: any) =>
479
- item.hasOwnProperty("id") &&
480
- (this.isValidID(item.id) || Utils.isNumber(item.id))
481
- )
482
- )
483
- data[field.key].map((item: any) =>
484
- Utils.isNumber(item.id)
485
- ? parseFloat(item.id)
486
- : this.decodeID(item.id)
487
- );
488
- } else if (
489
- this.isValidID(data[field.key]) ||
490
- Utils.isNumber(data[field.key])
491
- )
492
- RETURN[field.key] = data[field.key].map(
493
- (item: number | string) =>
494
- Utils.isNumber(item)
495
- ? parseFloat(item as string)
496
- : this.decodeID(item as string)
497
- );
498
- } else if (this.isValidID(data[field.key]))
499
- RETURN[field.key] = [this.decodeID(data[field.key])];
500
- else if (Utils.isNumber(data[field.key]))
501
- RETURN[field.key] = [parseFloat(data[field.key])];
502
- } else if (data.hasOwnProperty(field.key))
503
- RETURN[field.key] = data[field.key];
504
- } else if (Utils.isArrayOfObjects(field.children))
505
- RETURN[field.key] = this.formatData(
506
- data[field.key],
507
- field.children as Schema,
508
- formatOnlyAvailiableKeys
509
- );
510
- } else if (field.type === "table") {
511
- if (Utils.isObject(data[field.key])) {
512
- if (
513
- data[field.key].hasOwnProperty("id") &&
514
- (this.isValidID(data[field.key].id) ||
515
- Utils.isNumber(data[field.key]))
516
- )
517
- RETURN[field.key] = Utils.isNumber(data[field.key].id)
518
- ? parseFloat(data[field.key].id)
519
- : this.decodeID(data[field.key].id);
520
- } else if (
521
- this.isValidID(data[field.key]) ||
522
- Utils.isNumber(data[field.key])
523
- )
524
- RETURN[field.key] = Utils.isNumber(data[field.key])
525
- ? parseFloat(data[field.key])
526
- : this.decodeID(data[field.key]);
527
- } else if (field.type === "password")
528
- RETURN[field.key] =
529
- data[field.key].length === 161
530
- ? data[field.key]
531
- : Utils.hashPassword(data[field.key]);
532
- else RETURN[field.key] = data[field.key];
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 = (data: Data | Data[], prefix?: string) => {
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
- if (Utils.isArrayOfObjects(data))
572
- RETURN = Utils.combineObjects(
573
- (data as Data[]).map((single_data) => CombineData(single_data))
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(data as Data)) {
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
- Utils.combineObjects(value),
679
+ combineObjectsToArray(value.map(combineObjectsToArray)),
585
680
  (prefix ?? "") + key + ".*."
586
681
  )
587
682
  );
588
683
  else
589
- RETURN[(prefix ?? "") + key] = Utils.encode(value) as
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] = Utils.encode(value) as
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.length &&
627
- !(options.columns as string[]).includes("id")
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.includes(field.key)) return field;
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((column) =>
643
- column.startsWith(
644
- field.key + (field.type === "array" ? ".*." : ".")
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((column) =>
652
- column.startsWith(
653
- field.key + (field.type === "array" ? ".*." : ".")
654
- )
655
- )
656
- .map((column) =>
657
- column.replace(
658
- field.key + (field.type === "array" ? ".*." : "."),
659
- ""
660
- )
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" || field.type === "object") &&
681
- field.children
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 (field.children === "table") {
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
- linesNumber
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 (Utils.isArrayOfObjects(field.children)) {
704
- Object.entries(
705
- (await getItemsFromSchema(
934
+ } else if (
935
+ await File.isExists(
936
+ join(
706
937
  path,
707
- field.children as Schema,
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
- (prefix ?? "") +
710
- field.key +
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
- existsSync(join(this.databasePath, field.key)) &&
726
- existsSync(
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
- "number",
748
- linesNumber
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
- existsSync(
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
- linesNumber
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 (this.isValidID(where) || Utils.isNumber(where)) {
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) => parseFloat(id as string))
805
- : Ids.map((id) => this.decodeID(id as string)),
1050
+ ? Ids.map((id) => Number(id as string))
1051
+ : Ids.map((id) => Utils.decodeID(id as string, this.salt)),
806
1052
  undefined,
807
- Ids.length
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, ...restOFOptions }) => restOFOptions)(options),
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 = this.encodeID(data.id as number);
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: this.encodeID((RETURN as Data).id as number),
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 = existsSync(idFilePath)
1098
- ? Number(Object.values(await File.get(idFilePath, "number", -1))[0])
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 }) => rest)(
1104
- single_data
1105
- );
1106
- RETURN[index].id = ++last_id;
1107
- RETURN[index].created_at = new Date();
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 }) => rest)(
1111
- data as Data
1112
- );
1113
- RETURN.id = ++last_id;
1114
- RETURN.created_at = new Date();
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
- appendFileSync(
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
- const schema = this.getTableSchema(tableName);
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.validateData(data, schema, true);
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") && this.isValidID(item.id)
1419
+ (item) => item.hasOwnProperty("id") && Utils.isValidID(item.id)
1153
1420
  )
1154
1421
  )
1155
1422
  throw this.throwError("INVALID_ID");
1156
- await this.put(
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 (!this.isValidID((data as Data).id))
1429
+ if (!Utils.isValidID((data as Data).id))
1163
1430
  throw this.throwError("INVALID_ID", (data as Data).id);
1164
- await this.put(
1431
+ return this.put(
1165
1432
  tableName,
1166
1433
  data,
1167
- this.decodeID((data as Data).id as string)
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
- : { ...data, updated_at: new Date() }
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 (this.isValidID(where)) {
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) => this.decodeID(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
- await this.put(tableName, data, Object.keys(lineNumbers).map(Number));
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
- await this.put(tableName, data, lineNumbers);
1229
- } else throw this.throwError("PARAMETERS", tableName);
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
- const schema = this.getTableSchema(tableName);
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 = readdirSync(join(this.databasePath, tableName));
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
- unlinkSync(join(this.databasePath, tableName, file));
1522
+ await unlink(join(this.databasePath, tableName, file));
1245
1523
  }
1246
- } else if (this.isValidID(where)) {
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) => this.decodeID(id)),
1531
+ Ids.map((id) => Utils.decodeID(id, this.salt)),
1256
1532
  undefined,
1257
- Ids.length
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
- await this.delete(tableName, Object.keys(lineNumbers).map(Number));
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 = readdirSync(join(this.databasePath, tableName));
1548
+ const files = await readdir(join(this.databasePath, tableName));
1264
1549
  if (files.length) {
1265
- for (const file in files.filter(
1266
- (fileName: string) => fileName !== "schema.inib"
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
- await this.delete(tableName, lineNumbers);
1278
- } else throw this.throwError("PARAMETERS", tableName);
1574
+ return this.delete(tableName, lineNumbers);
1575
+ } else throw this.throwError("INVALID_PARAMETERS", tableName);
1576
+ return null;
1279
1577
  }
1280
1578
  }