inibase 1.0.0-rc.0 → 1.0.0-rc.10

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