inibase 1.0.0-rc.0 → 1.0.0-rc.11

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> = {};
272
+ } else if (Utils.isValidID(field.id))
273
+ RETURN[UtilsServer.decodeID(field.id, this.salt)] =
274
+ (prefix ?? "") + field.key + ".inib";
367
275
 
368
- for (const key1 in obj1) {
369
- if (obj2.hasOwnProperty(key1) && obj1[key1] !== obj2[key1]) {
370
- result[obj1[key1]] = obj2[key1];
371
- }
372
- }
373
-
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,55 @@ 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
- else
583
- RETURN[(prefix ?? "") + key] = Utils.encode(value) as
612
+ else {
613
+ RETURN[(prefix ?? "") + key] = File.encode(value) as
584
614
  | boolean
585
615
  | number
586
616
  | string
587
617
  | null;
618
+ }
588
619
  } else
589
- RETURN[(prefix ?? "") + key] = Utils.encode(value) as
620
+ RETURN[(prefix ?? "") + key] = File.encode(value) as
590
621
  | boolean
591
622
  | number
592
623
  | string
@@ -598,14 +629,27 @@ export default class Inibase {
598
629
  const addPathToKeys = (obj: Record<string, any>, path: string) => {
599
630
  const newObject: Record<string, any> = {};
600
631
 
601
- for (const key in obj)
602
- newObject[join(path, File.encodeFileName(key, "inib"))] = obj[key];
632
+ for (const key in obj) newObject[join(path, key + ".inib")] = obj[key];
603
633
 
604
634
  return newObject;
605
635
  };
606
636
  return addPathToKeys(CombineData(data), mainPath);
607
637
  }
608
638
 
639
+ public async getOne(
640
+ tableName: string,
641
+ where?: string | number | (string | number)[] | Criteria,
642
+ options: Options = {
643
+ page: 1,
644
+ per_page: 15,
645
+ }
646
+ ): Promise<Data | null> {
647
+ const _get = await this.get(tableName, where, options);
648
+ if (!_get) return null;
649
+ else if (Array.isArray(_get)) return (_get as Data)[0];
650
+ else return _get;
651
+ }
652
+
609
653
  public async get(
610
654
  tableName: string,
611
655
  where?: string | number | (string | number)[] | Criteria,
@@ -616,43 +660,43 @@ export default class Inibase {
616
660
  onlyLinesNumbers?: boolean
617
661
  ): Promise<Data | Data[] | number[] | null> {
618
662
  if (!options.columns) options.columns = [];
619
- else if (
620
- options.columns.length &&
621
- !(options.columns as string[]).includes("id")
622
- )
663
+ else if (!Array.isArray(options.columns))
664
+ options.columns = [options.columns];
665
+ if (options.columns.length && !(options.columns as string[]).includes("id"))
623
666
  options.columns.push("id");
624
667
  if (!options.page) options.page = 1;
625
668
  if (!options.per_page) options.per_page = 15;
626
669
  let RETURN!: Data | Data[] | null;
627
- let schema = this.getTableSchema(tableName);
670
+ let schema = await this.getTableSchema(tableName);
628
671
  if (!schema) throw this.throwError("NO_SCHEMA", tableName);
672
+ const idFilePath = join(this.folder, this.database, tableName, "id.inib");
673
+ if (!(await File.isExists(idFilePath))) return null;
629
674
  const filterSchemaByColumns = (schema: Schema, columns: string[]): Schema =>
630
675
  schema
631
676
  .map((field) => {
632
- if (columns.includes(field.key)) return field;
677
+ if (columns.some((column) => column.startsWith("!")))
678
+ return columns.includes("!" + field.key) ? null : field;
679
+ if (columns.includes(field.key) || columns.includes("*"))
680
+ return field;
681
+
633
682
  if (
634
683
  (field.type === "array" || field.type === "object") &&
635
684
  Utils.isArrayOfObjects(field.children) &&
636
- columns.filter((column) =>
637
- column.startsWith(
638
- field.key + (field.type === "array" ? ".*." : ".")
639
- )
685
+ columns.filter(
686
+ (column) =>
687
+ column.startsWith(field.key + ".") ||
688
+ column.startsWith("!" + field.key + ".")
640
689
  ).length
641
690
  ) {
642
691
  field.children = filterSchemaByColumns(
643
692
  field.children as Schema,
644
693
  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
- )
694
+ .filter(
695
+ (column) =>
696
+ column.startsWith(field.key + ".") ||
697
+ column.startsWith("!" + field.key + ".")
655
698
  )
699
+ .map((column) => column.replace(field.key + ".", ""))
656
700
  );
657
701
  return field;
658
702
  }
@@ -667,100 +711,251 @@ export default class Inibase {
667
711
  schema: Schema,
668
712
  linesNumber: number[],
669
713
  prefix?: string
670
- ): Promise<Data> => {
671
- let RETURN: Data = {};
714
+ ) => {
715
+ let RETURN: Record<number, Data> = {};
672
716
  for (const field of schema) {
673
717
  if (
674
- (field.type === "array" || field.type === "object") &&
675
- field.children
718
+ (field.type === "array" ||
719
+ (Array.isArray(field.type) &&
720
+ (field.type as any).includes("array"))) &&
721
+ (field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
722
+ .children
676
723
  ) {
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(
724
+ if (
725
+ Utils.isArrayOfObjects(
726
+ (field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
727
+ .children
728
+ )
729
+ ) {
730
+ if (
731
+ (
732
+ (field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
733
+ .children as Schema
734
+ ).filter(
735
+ (children) =>
736
+ children.type === "array" &&
737
+ Utils.isArrayOfObjects(children.children)
738
+ ).length
739
+ ) {
740
+ // one of children has array field type and has children array of object = Schema
741
+ Object.entries(
742
+ (await getItemsFromSchema(
685
743
  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);
744
+ (
745
+ (
746
+ field as FieldDefault &
747
+ (FieldArrayType | FieldArrayArrayType)
748
+ ).children as Schema
749
+ ).filter(
750
+ (children) =>
751
+ children.type === "array" &&
752
+ Utils.isArrayOfObjects(children.children)
753
+ ),
754
+ linesNumber,
755
+ (prefix ?? "") + field.key + "."
756
+ )) ?? {}
757
+ ).forEach(([index, item]) => {
758
+ if (Utils.isObject(item)) {
759
+ if (!RETURN[index]) RETURN[index] = {};
760
+ if (!RETURN[index][field.key]) RETURN[index][field.key] = [];
761
+ for (const child_field of (
762
+ (
763
+ field as FieldDefault &
764
+ (FieldArrayType | FieldArrayArrayType)
765
+ ).children as Schema
766
+ ).filter(
767
+ (children) =>
768
+ children.type === "array" &&
769
+ Utils.isArrayOfObjects(children.children)
770
+ )) {
771
+ if (Utils.isObject(item[child_field.key])) {
772
+ Object.entries(item[child_field.key]).forEach(
773
+ ([key, value]) => {
774
+ for (let _i = 0; _i < value.length; _i++) {
775
+ if (!RETURN[index][field.key][_i])
776
+ RETURN[index][field.key][_i] = {};
777
+ if (!RETURN[index][field.key][_i][child_field.key])
778
+ RETURN[index][field.key][_i][child_field.key] =
779
+ [];
780
+ value[_i].forEach((_element, _index) => {
781
+ if (
782
+ !RETURN[index][field.key][_i][child_field.key][
783
+ _index
784
+ ]
785
+ )
786
+ RETURN[index][field.key][_i][child_field.key][
787
+ _index
788
+ ] = {};
789
+ RETURN[index][field.key][_i][child_field.key][
790
+ _index
791
+ ][key] = _element;
792
+ });
793
+ }
794
+ }
795
+ );
796
+ }
797
+ }
798
+ }
799
+ });
800
+ (
801
+ field as FieldDefault & (FieldArrayType | FieldArrayArrayType)
802
+ ).children = (
803
+ (field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
804
+ .children as Schema
805
+ ).filter(
806
+ (children) =>
807
+ children.type !== "array" ||
808
+ !Utils.isArrayOfObjects(children.children)
809
+ );
696
810
  }
697
- } else if (Utils.isArrayOfObjects(field.children)) {
698
811
  Object.entries(
699
812
  (await getItemsFromSchema(
700
813
  path,
701
- field.children as Schema,
814
+ (field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
815
+ .children as Schema,
702
816
  linesNumber,
703
- (prefix ?? "") +
704
- field.key +
705
- (field.type === "array" ? ".*." : ".")
817
+ (prefix ?? "") + field.key + "."
706
818
  )) ?? {}
707
819
  ).forEach(([index, item]) => {
708
820
  if (!RETURN[index]) RETURN[index] = {};
709
- RETURN[index][field.key] = item;
821
+ if (Utils.isObject(item)) {
822
+ if (!Object.values(item).every((i) => i === null)) {
823
+ if (RETURN[index][field.key])
824
+ Object.entries(item).forEach(([key, value], _index) => {
825
+ RETURN[index][field.key] = RETURN[index][field.key].map(
826
+ (_obj, _i) => ({ ..._obj, [key]: value[_i] })
827
+ );
828
+ });
829
+ else if (Object.values(item).every(Utils.isArrayOfArrays))
830
+ RETURN[index][field.key] = item;
831
+ else {
832
+ RETURN[index][field.key] = [];
833
+ Object.entries(item).forEach(([key, value]) => {
834
+ for (let _i = 0; _i < value.length; _i++) {
835
+ if (!RETURN[index][field.key][_i])
836
+ RETURN[index][field.key][_i] = {};
837
+ RETURN[index][field.key][_i][key] = value[_i];
838
+ }
839
+ });
840
+ }
841
+ } else RETURN[index][field.key] = null;
842
+ } else RETURN[index][field.key] = item;
710
843
  });
844
+ } else if (
845
+ (field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
846
+ .children === "table" ||
847
+ (Array.isArray(
848
+ (field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
849
+ .children
850
+ ) &&
851
+ (
852
+ (field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
853
+ .children as FieldType[]
854
+ ).includes("table"))
855
+ ) {
856
+ if (options.columns)
857
+ options.columns = (options.columns as string[])
858
+ .filter((column) => column.includes(`${field.key}.`))
859
+ .map((column) => column.replace(`${field.key}.`, ""));
860
+ const [items, total_lines] = await File.get(
861
+ join(path, (prefix ?? "") + field.key + ".inib"),
862
+ linesNumber,
863
+ field.type,
864
+ (field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
865
+ .children as FieldType | FieldType[],
866
+ this.salt
867
+ );
868
+
869
+ this.totalItems[tableName + "-" + field.key] = total_lines;
870
+ for (const [index, item] of Object.entries(items)) {
871
+ if (!RETURN[index]) RETURN[index] = {};
872
+ RETURN[index][field.key] = item
873
+ ? await this.get(field.key, item as number, options)
874
+ : this.getDefaultValue(field);
875
+ }
876
+ } else if (
877
+ await File.isExists(
878
+ join(path, (prefix ?? "") + field.key + ".inib")
879
+ )
880
+ ) {
881
+ const [items, total_lines] = await File.get(
882
+ join(path, (prefix ?? "") + field.key + ".inib"),
883
+ linesNumber,
884
+ field.type,
885
+ (field as any)?.children,
886
+ this.salt
887
+ );
888
+
889
+ this.totalItems[tableName + "-" + field.key] = total_lines;
890
+ for (const [index, item] of Object.entries(items)) {
891
+ if (!RETURN[index]) RETURN[index] = {};
892
+ RETURN[index][field.key] = item ?? this.getDefaultValue(field);
893
+ }
894
+ }
895
+ } else if (field.type === "object") {
896
+ for (const [index, item] of Object.entries(
897
+ (await getItemsFromSchema(
898
+ path,
899
+ field.children as Schema,
900
+ linesNumber,
901
+ (prefix ?? "") + field.key + "."
902
+ )) ?? {}
903
+ )) {
904
+ if (!RETURN[index]) RETURN[index] = {};
905
+ if (Utils.isObject(item)) {
906
+ if (!Object.values(item).every((i) => i === null))
907
+ RETURN[index][field.key] = item;
908
+ else RETURN[index][field.key] = null;
909
+ } else RETURN[index][field.key] = null;
711
910
  }
712
911
  } else if (field.type === "table") {
713
912
  if (
714
- existsSync(join(this.databasePath, field.key)) &&
715
- existsSync(
716
- join(
717
- path,
718
- File.encodeFileName((prefix ?? "") + field.key, "inib")
719
- )
720
- )
913
+ (await File.isExists(
914
+ join(this.folder, this.database, field.key)
915
+ )) &&
916
+ (await File.isExists(
917
+ join(path, (prefix ?? "") + field.key + ".inib")
918
+ ))
721
919
  ) {
722
920
  if (options.columns)
723
921
  options.columns = (options.columns as string[])
724
922
  .filter(
725
923
  (column) =>
726
924
  column.includes(`${field.key}.`) &&
727
- !column.includes(`${field.key}.*.`)
925
+ !column.includes(`${field.key}.`)
728
926
  )
729
927
  .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
- )) {
928
+ const [items, total_lines] = await File.get(
929
+ join(path, (prefix ?? "") + field.key + ".inib"),
930
+ linesNumber,
931
+ "number",
932
+ undefined,
933
+ this.salt
934
+ );
935
+ this.totalItems[tableName + "-" + field.key] = total_lines;
936
+ for (const [index, item] of Object.entries(items)) {
740
937
  if (!RETURN[index]) RETURN[index] = {};
741
- RETURN[index][field.key] = value
742
- ? await this.get(field.key, value as number, options)
938
+ RETURN[index][field.key] = item
939
+ ? await this.get(field.key, item as number, options)
743
940
  : this.getDefaultValue(field);
744
941
  }
745
942
  }
746
943
  } else if (
747
- existsSync(
748
- join(path, File.encodeFileName((prefix ?? "") + field.key, "inib"))
749
- )
944
+ await File.isExists(join(path, (prefix ?? "") + field.key + ".inib"))
750
945
  ) {
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]) => {
946
+ const [items, total_lines] = await File.get(
947
+ join(path, (prefix ?? "") + field.key + ".inib"),
948
+ linesNumber,
949
+ field.type,
950
+ (field as any)?.children,
951
+ this.salt
952
+ );
953
+
954
+ this.totalItems[tableName + "-" + field.key] = total_lines;
955
+ for (const [index, item] of Object.entries(items)) {
761
956
  if (!RETURN[index]) RETURN[index] = {};
762
957
  RETURN[index][field.key] = item ?? this.getDefaultValue(field);
763
- });
958
+ }
764
959
  }
765
960
  }
766
961
  return RETURN;
@@ -769,7 +964,7 @@ export default class Inibase {
769
964
  // Display all data
770
965
  RETURN = Object.values(
771
966
  await getItemsFromSchema(
772
- join(this.databasePath, tableName),
967
+ join(this.folder, this.database, tableName),
773
968
  schema,
774
969
  Array.from(
775
970
  { length: options.per_page },
@@ -780,20 +975,22 @@ export default class Inibase {
780
975
  )
781
976
  )
782
977
  );
783
- } else if (this.isValidID(where) || Utils.isNumber(where)) {
978
+ } else if (Utils.isValidID(where) || Utils.isNumber(where)) {
784
979
  let Ids = where as string | number | (string | number)[];
785
980
  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
981
  const [lineNumbers, countItems] = await File.search(
789
982
  idFilePath,
790
- "number",
791
983
  "[]",
792
984
  Utils.isNumber(Ids)
793
- ? Ids.map((id) => parseFloat(id as string))
794
- : Ids.map((id) => this.decodeID(id as string)),
985
+ ? Ids.map((id) => Number(id as string))
986
+ : Ids.map((id) => UtilsServer.decodeID(id as string, this.salt)),
795
987
  undefined,
796
- Ids.length
988
+ "number",
989
+ undefined,
990
+ Ids.length,
991
+ 0,
992
+ false,
993
+ this.salt
797
994
  );
798
995
  if (!lineNumbers || !Object.keys(lineNumbers).length)
799
996
  throw this.throwError(
@@ -802,16 +999,17 @@ export default class Inibase {
802
999
  );
803
1000
  RETURN = Object.values(
804
1001
  (await getItemsFromSchema(
805
- join(this.databasePath, tableName),
1002
+ join(this.folder, this.database, tableName),
806
1003
  schema,
807
1004
  Object.keys(lineNumbers).map(Number)
808
1005
  )) ?? {}
809
1006
  );
810
1007
  if (RETURN.length && !Array.isArray(where)) RETURN = RETURN[0];
811
- } else if (typeof where === "object" && !Array.isArray(where)) {
1008
+ } else if (Utils.isObject(where)) {
812
1009
  // Criteria
813
1010
  const FormatObjectCriteriaValue = (
814
- value: string
1011
+ value: string,
1012
+ isParentArray: boolean = false
815
1013
  ): [ComparisonOperator, string | number | boolean | null] => {
816
1014
  switch (value[0]) {
817
1015
  case ">":
@@ -838,10 +1036,19 @@ export default class Inibase {
838
1036
  value.slice(3) as string | number,
839
1037
  ]
840
1038
  : [
841
- value.slice(0, 1) as ComparisonOperator,
1039
+ (value.slice(0, 1) + "=") as ComparisonOperator,
842
1040
  value.slice(1) as string | number,
843
1041
  ];
844
1042
  case "=":
1043
+ return isParentArray
1044
+ ? [
1045
+ value.slice(0, 1) as ComparisonOperator,
1046
+ value.slice(1) as string | number,
1047
+ ]
1048
+ : [
1049
+ value.slice(0, 1) as ComparisonOperator,
1050
+ (value.slice(1) + ",") as string,
1051
+ ];
845
1052
  case "*":
846
1053
  return [
847
1054
  value.slice(0, 1) as ComparisonOperator,
@@ -855,11 +1062,14 @@ export default class Inibase {
855
1062
  const applyCriteria = async (
856
1063
  criteria?: Criteria,
857
1064
  allTrue?: boolean
858
- ): Promise<Data | null> => {
859
- let RETURN: Data = {};
1065
+ ): Promise<Record<number, Data> | null> => {
1066
+ let RETURN: Record<number, Data> = {};
860
1067
  if (!criteria) return null;
861
- if (criteria.and && typeof criteria.and === "object") {
862
- const searchResult = await applyCriteria(criteria.and, true);
1068
+ if (criteria.and && Utils.isObject(criteria.and)) {
1069
+ const searchResult = await applyCriteria(
1070
+ criteria.and as Criteria,
1071
+ true
1072
+ );
863
1073
  if (searchResult) {
864
1074
  RETURN = Utils.deepMerge(
865
1075
  RETURN,
@@ -875,160 +1085,159 @@ export default class Inibase {
875
1085
  } else return null;
876
1086
  }
877
1087
 
878
- if (criteria.or && typeof criteria.or === "object") {
879
- const searchResult = await applyCriteria(criteria.or);
1088
+ if (criteria.or && Utils.isObject(criteria.or)) {
1089
+ const searchResult = await applyCriteria(
1090
+ criteria.or as Criteria,
1091
+ false
1092
+ );
880
1093
  delete criteria.or;
881
1094
  if (searchResult) RETURN = Utils.deepMerge(RETURN, searchResult);
882
1095
  }
883
1096
 
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]
1097
+ if (Object.keys(criteria).length > 0) {
1098
+ if (allTrue === undefined) allTrue = true;
1099
+
1100
+ let index = -1;
1101
+ for (const [key, value] of Object.entries(criteria)) {
1102
+ const field = this.getField(key, schema as Schema) as Field;
1103
+ index++;
1104
+ let searchOperator:
1105
+ | ComparisonOperator
1106
+ | ComparisonOperator[]
1107
+ | undefined = undefined,
1108
+ searchComparedAtValue:
1109
+ | string
1110
+ | number
1111
+ | boolean
1112
+ | null
1113
+ | (string | number | boolean | null)[]
1114
+ | undefined = undefined,
1115
+ searchLogicalOperator: "and" | "or" | undefined = undefined;
1116
+ if (Utils.isObject(value)) {
1117
+ if (
1118
+ (value as Criteria)?.or &&
1119
+ Array.isArray((value as Criteria).or)
1120
+ ) {
1121
+ const searchCriteria = (
1122
+ (value as Criteria).or as (string | number | boolean)[]
918
1123
  )
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";
1124
+ .map(
1125
+ (
1126
+ single_or
1127
+ ): [ComparisonOperator, string | number | boolean | null] =>
1128
+ typeof single_or === "string"
1129
+ ? FormatObjectCriteriaValue(single_or)
1130
+ : ["=", single_or]
1131
+ )
1132
+ .filter((a) => a) as [ComparisonOperator, string | number][];
1133
+ if (searchCriteria.length > 0) {
1134
+ searchOperator = searchCriteria.map(
1135
+ (single_or) => single_or[0]
1136
+ );
1137
+ searchComparedAtValue = searchCriteria.map(
1138
+ (single_or) => single_or[1]
1139
+ );
1140
+ searchLogicalOperator = "or";
1141
+ }
1142
+ delete (value as Criteria).or;
928
1143
  }
929
- delete value.or;
930
- }
931
- if (value?.and && Array.isArray(value.and)) {
932
- const searchCriteria = value.and
1144
+ if (
1145
+ (value as Criteria)?.and &&
1146
+ Array.isArray((value as Criteria).and)
1147
+ ) {
1148
+ const searchCriteria = (
1149
+ (value as Criteria).and as (string | number | boolean)[]
1150
+ )
1151
+ .map(
1152
+ (
1153
+ single_and
1154
+ ): [ComparisonOperator, string | number | boolean | null] =>
1155
+ typeof single_and === "string"
1156
+ ? FormatObjectCriteriaValue(single_and)
1157
+ : ["=", single_and]
1158
+ )
1159
+ .filter((a) => a) as [ComparisonOperator, string | number][];
1160
+ if (searchCriteria.length > 0) {
1161
+ searchOperator = searchCriteria.map(
1162
+ (single_and) => single_and[0]
1163
+ );
1164
+ searchComparedAtValue = searchCriteria.map(
1165
+ (single_and) => single_and[1]
1166
+ );
1167
+ searchLogicalOperator = "and";
1168
+ }
1169
+ delete (value as Criteria).and;
1170
+ }
1171
+ } else if (Array.isArray(value)) {
1172
+ const searchCriteria = value
933
1173
  .map(
934
1174
  (
935
- single_and
1175
+ single
936
1176
  ): [ComparisonOperator, string | number | boolean | null] =>
937
- typeof single_and === "string"
938
- ? FormatObjectCriteriaValue(single_and)
939
- : ["=", single_and]
1177
+ typeof single === "string"
1178
+ ? FormatObjectCriteriaValue(single)
1179
+ : ["=", single]
940
1180
  )
941
1181
  .filter((a) => a) as [ComparisonOperator, string | number][];
942
1182
  if (searchCriteria.length > 0) {
943
- searchOperator = searchCriteria.map(
944
- (single_and) => single_and[0]
945
- );
1183
+ searchOperator = searchCriteria.map((single) => single[0]);
946
1184
  searchComparedAtValue = searchCriteria.map(
947
- (single_and) => single_and[1]
1185
+ (single) => single[1]
948
1186
  );
949
1187
  searchLogicalOperator = "and";
950
1188
  }
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];
1189
+ } else if (typeof value === "string") {
1190
+ const ComparisonOperatorValue = FormatObjectCriteriaValue(value);
1191
+ if (ComparisonOperatorValue) {
1192
+ searchOperator = ComparisonOperatorValue[0];
1193
+ searchComparedAtValue = ComparisonOperatorValue[1];
1194
+ }
1195
+ } else {
1196
+ searchOperator = "=";
1197
+ searchComparedAtValue = value as number | boolean;
974
1198
  }
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",
1199
+ const [searchResult, total_lines] = await File.search(
1200
+ join(this.folder, this.database, tableName, key + ".inib"),
987
1201
  searchOperator,
988
1202
  searchComparedAtValue,
989
1203
  searchLogicalOperator,
1204
+ field?.type,
1205
+ (field as any)?.children,
990
1206
  options.per_page,
991
1207
  (options.page as number) - 1 * (options.per_page as number) + 1,
992
- true
1208
+ true,
1209
+ this.salt
993
1210
  );
994
1211
  if (searchResult) {
995
1212
  RETURN = Utils.deepMerge(RETURN, searchResult);
996
- if (!this.pageInfoArray[key]) this.pageInfoArray[key] = {};
997
- this.pageInfoArray[key].total_items = totlaItems;
1213
+ this.totalItems[tableName + "-" + key] = total_lines;
1214
+ }
1215
+
1216
+ if (allTrue && index > 0) {
1217
+ if (!Object.keys(RETURN).length) RETURN = {};
1218
+ RETURN = Object.fromEntries(
1219
+ Object.entries(RETURN).filter(
1220
+ ([_index, item]) => Object.keys(item).length > index
1221
+ )
1222
+ );
1223
+ if (!Object.keys(RETURN).length) RETURN = {};
998
1224
  }
999
1225
  }
1000
1226
  }
1001
- return Object.keys(RETURN).length > 0 ? RETURN : null;
1227
+
1228
+ return Object.keys(RETURN).length ? RETURN : null;
1002
1229
  };
1003
1230
 
1004
- RETURN = await applyCriteria(where);
1231
+ RETURN = await applyCriteria(where as Criteria);
1005
1232
  if (RETURN) {
1006
1233
  if (onlyLinesNumbers) return Object.keys(RETURN).map(Number);
1007
1234
  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
- };
1235
+ (key) => parse(key).name
1236
+ );
1028
1237
  RETURN = Object.values(
1029
1238
  Utils.deepMerge(
1030
1239
  await getItemsFromSchema(
1031
- join(this.databasePath, tableName),
1240
+ join(this.folder, this.database, tableName),
1032
1241
  schema.filter(
1033
1242
  (field) => !alreadyExistsColumns.includes(field.key)
1034
1243
  ),
@@ -1039,132 +1248,162 @@ export default class Inibase {
1039
1248
  );
1040
1249
  }
1041
1250
  }
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;
1251
+ if (
1252
+ !RETURN ||
1253
+ (Utils.isObject(RETURN) && !Object.keys(RETURN).length) ||
1254
+ (Array.isArray(RETURN) && !RETURN.length)
1255
+ )
1256
+ return null;
1257
+
1258
+ const greatestTotalItems = Math.max(
1259
+ ...Object.entries(this.totalItems)
1260
+ .filter(([k]) => k.startsWith(tableName + "-"))
1261
+ .map(([, v]) => v)
1262
+ );
1263
+ this.pageInfo = {
1264
+ ...(({ columns, ...restOfOptions }) => restOfOptions)(options),
1265
+ total_pages: Math.ceil(greatestTotalItems / options.per_page),
1266
+ total: greatestTotalItems,
1267
+ };
1268
+ return RETURN;
1053
1269
  }
1054
1270
 
1055
1271
  public async post(
1056
1272
  tableName: string,
1057
- data: Data | Data[]
1058
- ): Promise<Data | Data[] | null> {
1059
- const schema = this.getTableSchema(tableName);
1273
+ data: Data | Data[],
1274
+ options: Options = {
1275
+ page: 1,
1276
+ per_page: 15,
1277
+ },
1278
+ returnPostedData: boolean = true
1279
+ ): Promise<Data | Data[] | null | void> {
1280
+ const schema = await this.getTableSchema(tableName);
1060
1281
  let RETURN: Data | Data[] | null | undefined;
1061
1282
  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])
1283
+ const idFilePath = join(this.folder, this.database, tableName, "id.inib");
1284
+ let last_id = (await File.isExists(idFilePath))
1285
+ ? Number(
1286
+ Object.values(
1287
+ (await File.get(idFilePath, -1, "number", undefined, this.salt))[0]
1288
+ )[0]
1289
+ )
1065
1290
  : 0;
1066
1291
  if (Utils.isArrayOfObjects(data))
1067
1292
  (data as Data[]).forEach((single_data, index) => {
1068
1293
  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();
1294
+ RETURN[index] = (({ id, updated_at, created_at, ...rest }) => ({
1295
+ id: ++last_id,
1296
+ ...rest,
1297
+ created_at: new Date(),
1298
+ }))(single_data);
1074
1299
  });
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
- }
1300
+ else
1301
+ RETURN = (({ id, updated_at, created_at, ...rest }) => ({
1302
+ id: ++last_id,
1303
+ ...rest,
1304
+ created_at: new Date(),
1305
+ }))(data as Data);
1082
1306
  if (!RETURN) throw this.throwError("NO_DATA");
1083
- this.validateData(RETURN, schema);
1307
+
1084
1308
  RETURN = this.formatData(RETURN, schema);
1085
1309
  const pathesContents = this.joinPathesContents(
1086
- join(this.databasePath, tableName),
1310
+ join(this.folder, this.database, tableName),
1087
1311
  RETURN
1088
1312
  );
1089
- for (const [path, content] of Object.entries(pathesContents))
1090
- appendFileSync(
1313
+ for await (const [path, content] of Object.entries(pathesContents))
1314
+ await appendFile(
1091
1315
  path,
1092
- (Array.isArray(content) ? content.join("\n") : content ?? "") + "\n",
1093
- "utf8"
1316
+ (Array.isArray(content) ? content.join("\n") : content ?? "") + "\n"
1317
+ );
1318
+
1319
+ if (returnPostedData)
1320
+ return this.get(
1321
+ tableName,
1322
+ Utils.isArrayOfObjects(RETURN)
1323
+ ? RETURN.map((data: Data) => data.id)
1324
+ : ((RETURN as Data).id as number),
1325
+ options
1094
1326
  );
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
1327
  }
1102
1328
 
1103
1329
  public async put(
1104
1330
  tableName: string,
1105
1331
  data: Data | Data[],
1106
- where?: number | string | (number | string)[] | Criteria
1107
- ) {
1108
- const schema = this.getTableSchema(tableName);
1332
+ where?: number | string | (number | string)[] | Criteria,
1333
+ options: Options = {
1334
+ page: 1,
1335
+ per_page: 15,
1336
+ },
1337
+ returnPostedData: boolean = true
1338
+ ): Promise<Data | Data[] | null | void> {
1339
+ const schema = await this.getTableSchema(tableName);
1109
1340
  if (!schema) throw this.throwError("NO_SCHEMA", tableName);
1110
- this.validateData(data, schema, true);
1341
+ const idFilePath = join(this.folder, this.database, tableName, "id.inib");
1342
+ if (!(await File.isExists(idFilePath)))
1343
+ throw this.throwError("NO_ITEMS", tableName);
1111
1344
  data = this.formatData(data, schema, true);
1112
1345
  if (!where) {
1113
1346
  if (Utils.isArrayOfObjects(data)) {
1114
1347
  if (
1115
1348
  !(data as Data[]).every(
1116
- (item) => item.hasOwnProperty("id") && this.isValidID(item.id)
1349
+ (item) => item.hasOwnProperty("id") && Utils.isValidID(item.id)
1117
1350
  )
1118
1351
  )
1119
1352
  throw this.throwError("INVALID_ID");
1120
- await this.put(
1353
+ return this.put(
1121
1354
  tableName,
1122
1355
  data,
1123
1356
  (data as Data[]).map((item) => item.id)
1124
1357
  );
1125
1358
  } else if (data.hasOwnProperty("id")) {
1126
- if (!this.isValidID((data as Data).id))
1359
+ if (!Utils.isValidID((data as Data).id))
1127
1360
  throw this.throwError("INVALID_ID", (data as Data).id);
1128
- await this.put(
1361
+ return this.put(
1129
1362
  tableName,
1130
1363
  data,
1131
- this.decodeID((data as Data).id as string)
1364
+ UtilsServer.decodeID((data as Data).id as string, this.salt)
1132
1365
  );
1133
1366
  } else {
1134
1367
  const pathesContents = this.joinPathesContents(
1135
- join(this.databasePath, tableName),
1368
+ join(this.folder, this.database, tableName),
1136
1369
  Utils.isArrayOfObjects(data)
1137
1370
  ? (data as Data[]).map((item) => ({
1138
- ...item,
1371
+ ...(({ id, ...restOfData }) => restOfData)(item),
1139
1372
  updated_at: new Date(),
1140
1373
  }))
1141
- : { ...data, updated_at: new Date() }
1374
+ : {
1375
+ ...(({ id, ...restOfData }) => restOfData)(data as Data),
1376
+ updated_at: new Date(),
1377
+ }
1142
1378
  );
1143
1379
  for (const [path, content] of Object.entries(pathesContents))
1144
1380
  await File.replace(path, content);
1381
+ if (returnPostedData) return this.get(tableName, where, options);
1145
1382
  }
1146
- } else if (this.isValidID(where)) {
1383
+ } else if (Utils.isValidID(where)) {
1147
1384
  let Ids = where as string | string[];
1148
1385
  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
1386
  const [lineNumbers, countItems] = await File.search(
1152
1387
  idFilePath,
1153
- "number",
1154
1388
  "[]",
1155
- Ids.map((id) => this.decodeID(id)),
1389
+ Ids.map((id) => UtilsServer.decodeID(id, this.salt)),
1390
+ undefined,
1391
+ "number",
1156
1392
  undefined,
1157
- Ids.length
1393
+ Ids.length,
1394
+ 0,
1395
+ false,
1396
+ this.salt
1158
1397
  );
1159
1398
  if (!lineNumbers || !Object.keys(lineNumbers).length)
1160
1399
  throw this.throwError("INVALID_ID");
1161
- await this.put(tableName, data, Object.keys(lineNumbers).map(Number));
1400
+ return this.put(tableName, data, Object.keys(lineNumbers).map(Number));
1162
1401
  } else if (Utils.isNumber(where)) {
1163
- // where in this case, is the line(s) number(s) and not id(s)
1402
+ // "where" in this case, is the line(s) number(s) and not id(s)
1164
1403
  const pathesContents = Object.fromEntries(
1165
1404
  Object.entries(
1166
1405
  this.joinPathesContents(
1167
- join(this.databasePath, tableName),
1406
+ join(this.folder, this.database, tableName),
1168
1407
  Utils.isArrayOfObjects(data)
1169
1408
  ? (data as Data[]).map((item) => ({
1170
1409
  ...item,
@@ -1185,60 +1424,87 @@ export default class Inibase {
1185
1424
  );
1186
1425
  for (const [path, content] of Object.entries(pathesContents))
1187
1426
  await File.replace(path, content);
1427
+ if (returnPostedData) return this.get(tableName, where, options);
1188
1428
  } else if (typeof where === "object" && !Array.isArray(where)) {
1189
1429
  const lineNumbers = this.get(tableName, where, undefined, true);
1190
1430
  if (!lineNumbers || !Array.isArray(lineNumbers) || !lineNumbers.length)
1191
1431
  throw this.throwError("NO_ITEMS", tableName);
1192
- await this.put(tableName, data, lineNumbers);
1193
- } else throw this.throwError("PARAMETERS", tableName);
1432
+ return this.put(tableName, data, lineNumbers);
1433
+ } else throw this.throwError("INVALID_PARAMETERS", tableName);
1194
1434
  }
1195
1435
 
1196
1436
  public async delete(
1197
1437
  tableName: string,
1198
- where?: number | string | (number | string)[] | Criteria
1199
- ) {
1200
- const schema = this.getTableSchema(tableName);
1438
+ where?: number | string | (number | string)[] | Criteria,
1439
+ _id?: string | string[]
1440
+ ): Promise<string | string[] | null> {
1441
+ const schema = await this.getTableSchema(tableName);
1201
1442
  if (!schema) throw this.throwError("NO_SCHEMA", tableName);
1443
+ const idFilePath = join(this.folder, this.database, tableName, "id.inib");
1444
+ if (!(await File.isExists(idFilePath)))
1445
+ throw this.throwError("NO_ITEMS", tableName);
1202
1446
  if (!where) {
1203
- const files = readdirSync(join(this.databasePath, tableName));
1447
+ const files = await readdir(join(this.folder, this.database, tableName));
1204
1448
  if (files.length) {
1205
1449
  for (const file in files.filter(
1206
- (fileName: string) => fileName !== "schema.inib"
1450
+ (fileName: string) => fileName !== "schema"
1207
1451
  ))
1208
- unlinkSync(join(this.databasePath, tableName, file));
1452
+ await unlink(join(this.folder, this.database, tableName, file));
1209
1453
  }
1210
- } else if (this.isValidID(where)) {
1454
+ return "*";
1455
+ } else if (Utils.isValidID(where)) {
1211
1456
  let Ids = where as string | string[];
1212
1457
  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
1458
  const [lineNumbers, countItems] = await File.search(
1216
1459
  idFilePath,
1217
- "number",
1218
1460
  "[]",
1219
- Ids.map((id) => this.decodeID(id)),
1461
+ Ids.map((id) => UtilsServer.decodeID(id, this.salt)),
1462
+ undefined,
1463
+ "number",
1220
1464
  undefined,
1221
- Ids.length
1465
+ Ids.length,
1466
+ 0,
1467
+ false,
1468
+ this.salt
1222
1469
  );
1223
1470
  if (!lineNumbers || !Object.keys(lineNumbers).length)
1224
1471
  throw this.throwError("INVALID_ID");
1225
- await this.delete(tableName, Object.keys(lineNumbers).map(Number));
1472
+ return this.delete(
1473
+ tableName,
1474
+ Object.keys(lineNumbers).map(Number),
1475
+ where as string | string[]
1476
+ );
1226
1477
  } else if (Utils.isNumber(where)) {
1227
- const files = readdirSync(join(this.databasePath, tableName));
1478
+ const files = await readdir(join(this.folder, this.database, tableName));
1228
1479
  if (files.length) {
1229
- for (const file in files.filter(
1230
- (fileName: string) => fileName !== "schema.inib"
1480
+ if (!_id)
1481
+ _id = Object.values(
1482
+ (
1483
+ await File.get(
1484
+ join(this.folder, this.database, tableName, "id.inib"),
1485
+ where as number | number[],
1486
+ "number",
1487
+ undefined,
1488
+ this.salt
1489
+ )
1490
+ )[0]
1491
+ ).map((id) => UtilsServer.encodeID(Number(id), this.salt));
1492
+ for (const file of files.filter(
1493
+ (fileName: string) =>
1494
+ fileName.endsWith(".inib") && fileName !== "schema"
1231
1495
  ))
1232
1496
  await File.remove(
1233
- join(this.databasePath, tableName, file),
1497
+ join(this.folder, this.database, tableName, file),
1234
1498
  where as number | number[]
1235
1499
  );
1500
+ return Array.isArray(_id) && _id.length === 1 ? _id[0] : _id;
1236
1501
  }
1237
1502
  } else if (typeof where === "object" && !Array.isArray(where)) {
1238
1503
  const lineNumbers = this.get(tableName, where, undefined, true);
1239
1504
  if (!lineNumbers || !Array.isArray(lineNumbers) || !lineNumbers.length)
1240
1505
  throw this.throwError("NO_ITEMS", tableName);
1241
- await this.delete(tableName, lineNumbers);
1242
- } else throw this.throwError("PARAMETERS", tableName);
1506
+ return this.delete(tableName, lineNumbers);
1507
+ } else throw this.throwError("INVALID_PARAMETERS", tableName);
1508
+ return null;
1243
1509
  }
1244
1510
  }