inibase 1.0.0-rc.10 → 1.0.0-rc.12

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
@@ -3,7 +3,6 @@ import {
3
3
  rename,
4
4
  readFile,
5
5
  writeFile,
6
- appendFile,
7
6
  mkdir,
8
7
  readdir,
9
8
  } from "node:fs/promises";
@@ -11,14 +10,13 @@ import { join, parse } from "node:path";
11
10
  import { scryptSync } from "node:crypto";
12
11
  import File from "./file";
13
12
  import Utils from "./utils";
14
- import UtilsServer from "./utils.server";
15
13
 
16
- export type Data = {
14
+ export interface Data {
17
15
  id?: number | string;
18
16
  [key: string]: any;
19
- created_at?: Date;
20
- updated_at?: Date;
21
- };
17
+ createdAt?: Date;
18
+ updatedAt?: Date;
19
+ }
22
20
 
23
21
  export type FieldType =
24
22
  | "string"
@@ -74,6 +72,7 @@ export interface Options {
74
72
  page?: number;
75
73
  per_page?: number;
76
74
  columns?: string[] | string;
75
+ order?: Record<string, "asc" | "desc">;
77
76
  }
78
77
 
79
78
  export type ComparisonOperator =
@@ -94,12 +93,11 @@ type pageInfo = {
94
93
  } & Options;
95
94
 
96
95
  export type Criteria =
97
- | {
96
+ | ({
98
97
  [logic in "and" | "or"]?: Criteria | (string | number | boolean | null)[];
99
- }
100
- | {
98
+ } & {
101
99
  [key: string]: string | number | boolean | Criteria;
102
- }
100
+ })
103
101
  | null;
104
102
 
105
103
  declare global {
@@ -119,7 +117,7 @@ export default class Inibase {
119
117
  public pageInfo: pageInfo;
120
118
  private cache: Map<string, string>;
121
119
  private totalItems: Record<string, number>;
122
- private salt: Buffer;
120
+ public salt: Buffer;
123
121
 
124
122
  constructor(database: string, mainFolder: string = ".") {
125
123
  this.database = database;
@@ -128,7 +126,11 @@ export default class Inibase {
128
126
  this.cache = new Map<string, any>();
129
127
  this.totalItems = {};
130
128
  this.pageInfo = { page: 1, per_page: 15 };
131
- this.salt = scryptSync(database, "salt", 32);
129
+ this.salt = scryptSync(
130
+ process.env.INIBASE_SECRET ?? "inibase",
131
+ (process.env.INIBASE_SECRET ?? "inibase") + "_salt",
132
+ 32
133
+ );
132
134
  }
133
135
 
134
136
  private throwError(
@@ -177,100 +179,46 @@ export default class Inibase {
177
179
  return new Error(errorMessage);
178
180
  }
179
181
 
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);
190
- }
191
- return 0;
192
- }
193
-
194
182
  public async setTableSchema(
195
183
  tableName: string,
196
184
  schema: Schema
197
185
  ): Promise<void> {
198
- const encodeSchema = (schema: Schema) => {
199
- let RETURN: any[][] = [],
200
- index = 0;
201
- for (const field of schema) {
202
- if (!RETURN[index]) RETURN[index] = [];
203
- RETURN[index].push(
204
- field.id
205
- ? UtilsServer.decodeID(field.id as string, this.salt)
206
- : null
207
- );
208
- RETURN[index].push(field.key ?? null);
209
- RETURN[index].push(field.required ?? null);
210
- RETURN[index].push(field.type ?? null);
211
- RETURN[index].push(
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
216
- : null
217
- );
218
- index++;
219
- }
220
- return RETURN;
221
- },
222
- addIdToSchema = (schema: Schema, oldIndex: number = 0) =>
223
- schema.map((field) => {
224
- if (
225
- (field.type === "array" || field.type === "object") &&
226
- Utils.isArrayOfObjects(field.children)
227
- ) {
228
- if (!field.id) {
229
- oldIndex++;
230
- field = {
231
- ...field,
232
- id: UtilsServer.encodeID(oldIndex, this.salt),
233
- };
234
- } else
235
- oldIndex = UtilsServer.decodeID(field.id as string, this.salt);
236
- field.children = addIdToSchema(field.children as Schema, oldIndex);
237
- oldIndex += field.children.length;
238
- } else if (field.id)
239
- oldIndex = UtilsServer.decodeID(field.id as string, this.salt);
240
- else {
241
- oldIndex++;
242
- field = {
243
- ...field,
244
- id: UtilsServer.encodeID(oldIndex, this.salt),
245
- };
246
- }
247
- return field;
248
- });
249
-
186
+ const decodeIdFromSchema = (schema: Schema) =>
187
+ schema.map((field) => {
188
+ if (field.children && Utils.isArrayOfObjects(field.children))
189
+ field.children = decodeIdFromSchema(field.children as Schema);
190
+ if (!Utils.isNumber(field.id))
191
+ field.id = Utils.decodeID(field.id, this.salt);
192
+ return field;
193
+ });
250
194
  // remove id from schema
251
195
  schema = schema.filter(
252
- (field) => !["id", "created_at", "updated_at"].includes(field.key)
196
+ ({ key }) => !["id", "createdAt", "updatedAt"].includes(key)
197
+ );
198
+ schema = Utils.addIdToSchema(
199
+ schema,
200
+ Utils.findLastIdNumber(schema, this.salt),
201
+ this.salt
253
202
  );
254
- schema = addIdToSchema(schema, this.findLastIdNumber(schema));
255
203
  const TablePath = join(this.folder, this.database, tableName),
256
- TableSchemaPath = join(TablePath, "schema");
204
+ TableSchemaPath = join(TablePath, "schema.json");
257
205
  if (!(await File.isExists(TablePath)))
258
206
  await mkdir(TablePath, { recursive: true });
259
207
  if (await File.isExists(TableSchemaPath)) {
260
208
  // update columns files names based on field id
261
- const schemaToIdsPath = (schema: any, prefix = "") => {
209
+ const schemaToIdsPath = (schema: Schema, prefix = "") => {
262
210
  let RETURN: any = {};
263
211
  for (const field of schema)
264
212
  if (field.children && Utils.isArrayOfObjects(field.children)) {
265
213
  Utils.deepMerge(
266
214
  RETURN,
267
215
  schemaToIdsPath(
268
- field.children,
216
+ field.children as Schema,
269
217
  (prefix ?? "") + field.key + "."
270
218
  )
271
219
  );
272
220
  } else if (Utils.isValidID(field.id))
273
- RETURN[UtilsServer.decodeID(field.id, this.salt)] =
221
+ RETURN[Utils.decodeID(field.id, this.salt)] =
274
222
  (prefix ?? "") + field.key + ".inib";
275
223
 
276
224
  return RETURN;
@@ -286,63 +234,44 @@ export default class Inibase {
286
234
  }
287
235
 
288
236
  await writeFile(
289
- join(TablePath, "schema"),
290
- JSON.stringify(encodeSchema(schema))
237
+ join(TablePath, "schema.json"),
238
+ JSON.stringify(decodeIdFromSchema(schema))
291
239
  );
292
240
  }
293
241
 
294
242
  public async getTableSchema(tableName: string): Promise<Schema | undefined> {
295
- const decodeSchema = (encodedSchema: any) => {
296
- return encodedSchema.map((field: any) =>
297
- Array.isArray(field[0])
298
- ? decodeSchema(field)
299
- : Object.fromEntries(
300
- Object.entries({
301
- id: UtilsServer.encodeID(field[0], this.salt),
302
- key: field[1],
303
- required: field[2],
304
- type: field[3],
305
- children: field[4]
306
- ? Array.isArray(field[4])
307
- ? decodeSchema(field[4])
308
- : field[4]
309
- : null,
310
- }).filter(([_, v]) => v != null)
311
- )
312
- );
313
- },
314
- TableSchemaPath = join(this.folder, this.database, tableName, "schema");
243
+ const TableSchemaPath = join(
244
+ this.folder,
245
+ this.database,
246
+ tableName,
247
+ "schema.json"
248
+ );
315
249
  if (!(await File.isExists(TableSchemaPath))) return undefined;
316
- if (!this.cache.has(TableSchemaPath)) {
317
- const TableSchemaPathContent = await readFile(TableSchemaPath, {
318
- encoding: "utf8",
319
- });
320
- this.cache.set(
321
- TableSchemaPath,
322
- TableSchemaPathContent
323
- ? decodeSchema(JSON.parse(TableSchemaPathContent.toString()))
324
- : ""
325
- );
326
- }
327
- const schema = this.cache.get(TableSchemaPath) as unknown as Schema,
328
- lastIdNumber = this.findLastIdNumber(schema);
250
+
251
+ if (!this.cache.has(TableSchemaPath))
252
+ this.cache.set(TableSchemaPath, await readFile(TableSchemaPath, "utf8"));
253
+
254
+ if (!this.cache.get(TableSchemaPath)) return undefined;
255
+ const schema = JSON.parse(this.cache.get(TableSchemaPath)),
256
+ lastIdNumber = Utils.findLastIdNumber(schema, this.salt);
257
+
329
258
  return [
330
259
  {
331
- id: UtilsServer.encodeID(0, this.salt),
260
+ id: Utils.encodeID(0, this.salt),
332
261
  key: "id",
333
262
  type: "id",
334
263
  required: true,
335
264
  },
336
- ...schema,
265
+ ...Utils.addIdToSchema(schema, lastIdNumber, this.salt),
337
266
  {
338
- id: UtilsServer.encodeID(lastIdNumber + 1, this.salt),
339
- key: "created_at",
267
+ id: Utils.encodeID(lastIdNumber + 1, this.salt),
268
+ key: "createdAt",
340
269
  type: "date",
341
270
  required: true,
342
271
  },
343
272
  {
344
- id: UtilsServer.encodeID(lastIdNumber + 2, this.salt),
345
- key: "updated_at",
273
+ id: Utils.encodeID(lastIdNumber + 2, this.salt),
274
+ key: "updatedAt",
346
275
  type: "date",
347
276
  required: false,
348
277
  },
@@ -394,81 +323,76 @@ export default class Inibase {
394
323
  for (const single_data of data as Data[])
395
324
  this.validateData(single_data, schema, skipRequiredField);
396
325
  else if (Utils.isObject(data)) {
397
- for (const field of schema) {
326
+ for (const { key, type, required, children } of schema) {
327
+ if (!data.hasOwnProperty(key) && required && !skipRequiredField)
328
+ throw this.throwError("FIELD_REQUIRED", key);
398
329
  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) &&
330
+ data.hasOwnProperty(key) &&
406
331
  !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
332
+ data[key],
333
+ type,
334
+ children && !Utils.isArrayOfObjects(children) ? children : undefined
413
335
  )
414
336
  )
415
- throw this.throwError("INVALID_TYPE", field.key);
337
+ throw this.throwError("INVALID_TYPE", key);
416
338
  if (
417
- (field.type === "array" || field.type === "object") &&
418
- field.children &&
419
- Utils.isArrayOfObjects(field.children)
339
+ (type === "array" || type === "object") &&
340
+ children &&
341
+ Utils.isArrayOfObjects(children)
420
342
  )
421
- this.validateData(
422
- data[field.key],
423
- field.children as Schema,
424
- skipRequiredField
425
- );
343
+ this.validateData(data[key], children as Schema, skipRequiredField);
426
344
  }
427
345
  }
428
346
  }
429
347
 
430
- public formatData(
431
- data: Data | Data[],
348
+ public formatData<dataType extends Data | Data[]>(
349
+ data: dataType,
432
350
  schema: Schema,
433
351
  formatOnlyAvailiableKeys?: boolean
434
- ): Data | Data[] {
352
+ ): dataType extends Data ? Data : Data[] {
353
+ this.validateData(data, schema, formatOnlyAvailiableKeys);
354
+
435
355
  const formatField = (
436
- value: any,
356
+ value: Data | number | string | (number | string | Data)[],
437
357
  field: Field
438
358
  ): Data | Data[] | number | string => {
439
359
  if (Array.isArray(field.type))
440
360
  field.type = Utils.detectFieldType(value, field.type);
441
361
  switch (field.type) {
442
362
  case "array":
363
+ if (!Array.isArray(value)) value = [value];
443
364
  if (typeof field.children === "string") {
444
365
  if (field.type === "array" && field.children === "table") {
445
- if (Array.isArray(data[field.key])) {
446
- if (Utils.isArrayOfObjects(data[field.key])) {
366
+ if (Array.isArray(value)) {
367
+ if (Utils.isArrayOfObjects(value)) {
447
368
  if (
448
369
  value.every(
449
- (item: any) =>
370
+ (item: any): item is Data =>
450
371
  item.hasOwnProperty("id") &&
451
372
  (Utils.isValidID(item.id) || Utils.isNumber(item.id))
452
373
  )
453
374
  )
454
- value.map((item: any) =>
375
+ value.map((item) =>
455
376
  Utils.isNumber(item.id)
456
377
  ? Number(item.id)
457
- : UtilsServer.decodeID(item.id, this.salt)
378
+ : Utils.decodeID(item.id, this.salt)
458
379
  );
459
- } else if (Utils.isValidID(value) || Utils.isNumber(value))
460
- return value.map((item: number | string) =>
380
+ } else if (
381
+ (value as (number | string)[]).every(Utils.isValidID) ||
382
+ (value as (number | string)[]).every(Utils.isNumber)
383
+ )
384
+ return (value as (number | string)[]).map((item) =>
461
385
  Utils.isNumber(item)
462
- ? Number(item as string)
463
- : UtilsServer.decodeID(item as string, this.salt)
386
+ ? Number(item)
387
+ : Utils.decodeID(item, this.salt)
464
388
  );
465
389
  } else if (Utils.isValidID(value))
466
- return [UtilsServer.decodeID(value, this.salt)];
390
+ return [Utils.decodeID(value, this.salt)];
467
391
  else if (Utils.isNumber(value)) return [Number(value)];
468
392
  } else if (data.hasOwnProperty(field.key)) return value;
469
393
  } else if (Utils.isArrayOfObjects(field.children))
470
394
  return this.formatData(
471
- value,
395
+ value as Data[],
472
396
  field.children as Schema,
473
397
  formatOnlyAvailiableKeys
474
398
  );
@@ -478,41 +402,46 @@ export default class Inibase {
478
402
  case "object":
479
403
  if (Utils.isArrayOfObjects(field.children))
480
404
  return this.formatData(
481
- value,
482
- field.children,
405
+ value as Data,
406
+ field.children as Schema,
483
407
  formatOnlyAvailiableKeys
484
408
  );
485
409
  break;
486
410
  case "table":
411
+ if (Array.isArray(value)) value = value[0];
487
412
  if (Utils.isObject(value)) {
488
413
  if (
489
- value.hasOwnProperty("id") &&
490
- (Utils.isValidID(value.id) || Utils.isNumber(value))
414
+ (value as Data).hasOwnProperty("id") &&
415
+ (Utils.isValidID((value as Data).id) ||
416
+ Utils.isNumber((value as Data).id))
491
417
  )
492
- return Utils.isNumber(value.id)
493
- ? Number(value.id)
494
- : UtilsServer.decodeID(value.id, this.salt);
418
+ return Utils.isNumber((value as Data).id)
419
+ ? Number((value as Data).id)
420
+ : Utils.decodeID((value as Data).id as string, this.salt);
495
421
  } else if (Utils.isValidID(value) || Utils.isNumber(value))
496
422
  return Utils.isNumber(value)
497
423
  ? Number(value)
498
- : UtilsServer.decodeID(value, this.salt);
424
+ : Utils.decodeID(value, this.salt);
499
425
  break;
500
426
  case "password":
501
- return value.length === 161 ? value : UtilsServer.hashPassword(value);
427
+ if (Array.isArray(value)) value = value[0];
428
+ return typeof value === "string" && value.length === 161
429
+ ? value
430
+ : Utils.hashPassword(String(value));
502
431
  case "number":
432
+ if (Array.isArray(value)) value = value[0];
503
433
  return Utils.isNumber(value) ? Number(value) : null;
504
434
  case "id":
435
+ if (Array.isArray(value)) value = value[0];
505
436
  return Utils.isNumber(value)
506
437
  ? value
507
- : UtilsServer.decodeID(value, this.salt);
438
+ : Utils.decodeID(value as string, this.salt);
508
439
  default:
509
440
  return value;
510
441
  }
511
442
  return null;
512
443
  };
513
444
 
514
- this.validateData(data, schema, formatOnlyAvailiableKeys);
515
-
516
445
  if (Utils.isArrayOfObjects(data))
517
446
  return data.map((single_data: Data) =>
518
447
  this.formatData(single_data, schema, formatOnlyAvailiableKeys)
@@ -556,7 +485,9 @@ export default class Inibase {
556
485
  : [];
557
486
  case "object":
558
487
  return Utils.combineObjects(
559
- field.children.map((f) => ({ [f.key]: this.getDefaultValue(f) }))
488
+ field.children.map((f: Field) => ({
489
+ [f.key]: this.getDefaultValue(f),
490
+ }))
560
491
  );
561
492
  case "boolean":
562
493
  return false;
@@ -589,33 +520,26 @@ export default class Inibase {
589
520
  for (const [key, value] of Object.entries(_data as Data)) {
590
521
  if (Utils.isObject(value))
591
522
  Object.assign(RETURN, CombineData(value, `${key}.`));
592
- else if (Array.isArray(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
- )
605
- Object.assign(
606
- RETURN,
607
- CombineData(
608
- combineObjectsToArray(value.map(combineObjectsToArray)),
609
- (prefix ?? "") + key + "."
610
- )
611
- );
612
- else
613
- RETURN[(prefix ?? "") + key] = File.encode(value) as
614
- | boolean
615
- | number
616
- | string
617
- | null;
618
- } else
523
+ else if (Utils.isArrayOfObjects(value)) {
524
+ Object.assign(
525
+ RETURN,
526
+ CombineData(
527
+ combineObjectsToArray(value),
528
+ (prefix ?? "") + key + "."
529
+ )
530
+ );
531
+ } else if (
532
+ Utils.isArrayOfArrays(value) &&
533
+ value.every(Utils.isArrayOfObjects)
534
+ )
535
+ Object.assign(
536
+ RETURN,
537
+ CombineData(
538
+ combineObjectsToArray(value.map(combineObjectsToArray)),
539
+ (prefix ?? "") + key + "."
540
+ )
541
+ );
542
+ else
619
543
  RETURN[(prefix ?? "") + key] = File.encode(value) as
620
544
  | boolean
621
545
  | number
@@ -635,15 +559,18 @@ export default class Inibase {
635
559
  return addPathToKeys(CombineData(data), mainPath);
636
560
  }
637
561
 
638
- public async get(
562
+ public async get<O extends boolean = false, N extends boolean = false>(
639
563
  tableName: string,
640
564
  where?: string | number | (string | number)[] | Criteria,
641
565
  options: Options = {
642
566
  page: 1,
643
567
  per_page: 15,
644
568
  },
645
- onlyLinesNumbers?: boolean
646
- ): Promise<Data | Data[] | number[] | null> {
569
+ onlyOne?: O,
570
+ onlyLinesNumbers?: N
571
+ ): Promise<
572
+ (N extends true ? number[] : O extends true ? Data : Data[]) | null
573
+ > {
647
574
  if (!options.columns) options.columns = [];
648
575
  else if (!Array.isArray(options.columns))
649
576
  options.columns = [options.columns];
@@ -762,19 +689,21 @@ export default class Inibase {
762
689
  if (!RETURN[index][field.key][_i][child_field.key])
763
690
  RETURN[index][field.key][_i][child_field.key] =
764
691
  [];
765
- value[_i].forEach((_element, _index) => {
766
- if (
767
- !RETURN[index][field.key][_i][child_field.key][
768
- _index
769
- ]
770
- )
692
+ value[_i].forEach(
693
+ (_element: any, _index: string | number) => {
694
+ if (
695
+ !RETURN[index][field.key][_i][
696
+ child_field.key
697
+ ][_index]
698
+ )
699
+ RETURN[index][field.key][_i][child_field.key][
700
+ _index
701
+ ] = {};
771
702
  RETURN[index][field.key][_i][child_field.key][
772
703
  _index
773
- ] = {};
774
- RETURN[index][field.key][_i][child_field.key][
775
- _index
776
- ][key] = _element;
777
- });
704
+ ][key] = _element;
705
+ }
706
+ );
778
707
  }
779
708
  }
780
709
  );
@@ -793,6 +722,7 @@ export default class Inibase {
793
722
  !Utils.isArrayOfObjects(children.children)
794
723
  );
795
724
  }
725
+
796
726
  Object.entries(
797
727
  (await getItemsFromSchema(
798
728
  path,
@@ -808,7 +738,10 @@ export default class Inibase {
808
738
  if (RETURN[index][field.key])
809
739
  Object.entries(item).forEach(([key, value], _index) => {
810
740
  RETURN[index][field.key] = RETURN[index][field.key].map(
811
- (_obj, _i) => ({ ..._obj, [key]: value[_i] })
741
+ (_obj: any, _i: string | number) => ({
742
+ ..._obj,
743
+ [key]: value[_i],
744
+ })
812
745
  );
813
746
  });
814
747
  else if (Object.values(item).every(Utils.isArrayOfArrays))
@@ -960,15 +893,20 @@ export default class Inibase {
960
893
  )
961
894
  )
962
895
  );
963
- } else if (Utils.isValidID(where) || Utils.isNumber(where)) {
896
+ } else if (
897
+ (Array.isArray(where) &&
898
+ (where.every(Utils.isValidID) || where.every(Utils.isNumber))) ||
899
+ Utils.isValidID(where) ||
900
+ Utils.isNumber(where)
901
+ ) {
964
902
  let Ids = where as string | number | (string | number)[];
965
903
  if (!Array.isArray(Ids)) Ids = [Ids];
966
904
  const [lineNumbers, countItems] = await File.search(
967
905
  idFilePath,
968
906
  "[]",
969
- Utils.isNumber(Ids)
970
- ? Ids.map((id) => Number(id as string))
971
- : Ids.map((id) => UtilsServer.decodeID(id as string, this.salt)),
907
+ Ids.map((id) =>
908
+ Utils.isNumber(id) ? Number(id) : Utils.decodeID(id, this.salt)
909
+ ),
972
910
  undefined,
973
911
  "number",
974
912
  undefined,
@@ -982,6 +920,9 @@ export default class Inibase {
982
920
  "INVALID_ID",
983
921
  where as number | string | (number | string)[]
984
922
  );
923
+
924
+ if (onlyLinesNumbers) return Object.keys(lineNumbers).map(Number) as any;
925
+
985
926
  RETURN = Object.values(
986
927
  (await getItemsFromSchema(
987
928
  join(this.folder, this.database, tableName),
@@ -1215,20 +1156,20 @@ export default class Inibase {
1215
1156
 
1216
1157
  RETURN = await applyCriteria(where as Criteria);
1217
1158
  if (RETURN) {
1218
- if (onlyLinesNumbers) return Object.keys(RETURN).map(Number);
1159
+ if (onlyLinesNumbers) return Object.keys(RETURN).map(Number) as any;
1219
1160
  const alreadyExistsColumns = Object.keys(Object.values(RETURN)[0]).map(
1220
1161
  (key) => parse(key).name
1221
1162
  );
1222
1163
  RETURN = Object.values(
1223
1164
  Utils.deepMerge(
1165
+ RETURN,
1224
1166
  await getItemsFromSchema(
1225
1167
  join(this.folder, this.database, tableName),
1226
1168
  schema.filter(
1227
1169
  (field) => !alreadyExistsColumns.includes(field.key)
1228
1170
  ),
1229
1171
  Object.keys(RETURN).map(Number)
1230
- ),
1231
- RETURN
1172
+ )
1232
1173
  )
1233
1174
  );
1234
1175
  }
@@ -1250,55 +1191,54 @@ export default class Inibase {
1250
1191
  total_pages: Math.ceil(greatestTotalItems / options.per_page),
1251
1192
  total: greatestTotalItems,
1252
1193
  };
1253
- return RETURN;
1194
+ return onlyOne ? RETURN[0] : RETURN;
1254
1195
  }
1255
1196
 
1256
- public async post(
1197
+ public async post<DataType extends Data | Data[]>(
1257
1198
  tableName: string,
1258
- data: Data | Data[],
1199
+ data: DataType,
1259
1200
  options: Options = {
1260
1201
  page: 1,
1261
1202
  per_page: 15,
1262
1203
  },
1263
1204
  returnPostedData: boolean = true
1264
- ): Promise<Data | Data[] | null | void> {
1205
+ ): Promise<
1206
+ DataType extends Data ? Data | null | void : Data[] | null | void
1207
+ > {
1265
1208
  const schema = await this.getTableSchema(tableName);
1266
1209
  let RETURN: Data | Data[] | null | undefined;
1267
1210
  if (!schema) throw this.throwError("NO_SCHEMA", tableName);
1268
1211
  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
- )
1275
- : 0;
1212
+
1213
+ let [last_line_number, last_id] = (await File.isExists(idFilePath))
1214
+ ? (Object.entries(
1215
+ (await File.get(idFilePath, -1, "number", undefined, this.salt))[0]
1216
+ )[0] as [number, number] | undefined) ?? [1, 0]
1217
+ : [1, 0];
1276
1218
  if (Utils.isArrayOfObjects(data))
1277
- (data as Data[]).forEach((single_data, index) => {
1219
+ data.forEach((single_data: any, index: string | number) => {
1278
1220
  if (!RETURN) RETURN = [];
1279
- RETURN[index] = (({ id, updated_at, created_at, ...rest }) => ({
1221
+ RETURN[index] = (({ id, updatedAt, createdAt, ...rest }) => ({
1280
1222
  id: ++last_id,
1281
1223
  ...rest,
1282
- created_at: new Date(),
1224
+ createdAt: new Date(),
1283
1225
  }))(single_data);
1284
1226
  });
1285
1227
  else
1286
- RETURN = (({ id, updated_at, created_at, ...rest }) => ({
1228
+ RETURN = (({ id, updatedAt, createdAt, ...rest }) => ({
1287
1229
  id: ++last_id,
1288
1230
  ...rest,
1289
- created_at: new Date(),
1231
+ createdAt: new Date(),
1290
1232
  }))(data as Data);
1291
1233
  if (!RETURN) throw this.throwError("NO_DATA");
1234
+
1292
1235
  RETURN = this.formatData(RETURN, schema);
1293
1236
  const pathesContents = this.joinPathesContents(
1294
1237
  join(this.folder, this.database, tableName),
1295
1238
  RETURN
1296
1239
  );
1297
1240
  for await (const [path, content] of Object.entries(pathesContents))
1298
- await appendFile(
1299
- path,
1300
- (Array.isArray(content) ? content.join("\n") : content ?? "") + "\n"
1301
- );
1241
+ await File.replace(path, { [last_line_number]: content });
1302
1242
 
1303
1243
  if (returnPostedData)
1304
1244
  return this.get(
@@ -1306,11 +1246,12 @@ export default class Inibase {
1306
1246
  Utils.isArrayOfObjects(RETURN)
1307
1247
  ? RETURN.map((data: Data) => data.id)
1308
1248
  : ((RETURN as Data).id as number),
1309
- options
1249
+ options,
1250
+ !Utils.isArrayOfObjects(data) // return only one item if data is not array of objects
1310
1251
  );
1311
1252
  }
1312
1253
 
1313
- public async put(
1254
+ public async put<returnPostedDataType extends boolean = true>(
1314
1255
  tableName: string,
1315
1256
  data: Data | Data[],
1316
1257
  where?: number | string | (number | string)[] | Criteria,
@@ -1318,8 +1259,10 @@ export default class Inibase {
1318
1259
  page: 1,
1319
1260
  per_page: 15,
1320
1261
  },
1321
- returnPostedData: boolean = true
1322
- ): Promise<Data | Data[] | null | void> {
1262
+ returnPostedData?: returnPostedDataType
1263
+ ): Promise<
1264
+ (returnPostedDataType extends true ? Data | Data[] : void) | null
1265
+ > {
1323
1266
  const schema = await this.getTableSchema(tableName);
1324
1267
  if (!schema) throw this.throwError("NO_SCHEMA", tableName);
1325
1268
  const idFilePath = join(this.folder, this.database, tableName, "id.inib");
@@ -1329,15 +1272,15 @@ export default class Inibase {
1329
1272
  if (!where) {
1330
1273
  if (Utils.isArrayOfObjects(data)) {
1331
1274
  if (
1332
- !(data as Data[]).every(
1333
- (item) => item.hasOwnProperty("id") && Utils.isValidID(item.id)
1275
+ !data.every(
1276
+ (item: any) => item.hasOwnProperty("id") && Utils.isValidID(item.id)
1334
1277
  )
1335
1278
  )
1336
1279
  throw this.throwError("INVALID_ID");
1337
1280
  return this.put(
1338
1281
  tableName,
1339
1282
  data,
1340
- (data as Data[]).map((item) => item.id)
1283
+ data.map((item: { id: any }) => item.id)
1341
1284
  );
1342
1285
  } else if (data.hasOwnProperty("id")) {
1343
1286
  if (!Utils.isValidID((data as Data).id))
@@ -1345,72 +1288,90 @@ export default class Inibase {
1345
1288
  return this.put(
1346
1289
  tableName,
1347
1290
  data,
1348
- UtilsServer.decodeID((data as Data).id as string, this.salt)
1291
+ Utils.decodeID((data as Data).id as string, this.salt)
1349
1292
  );
1350
1293
  } else {
1351
1294
  const pathesContents = this.joinPathesContents(
1352
1295
  join(this.folder, this.database, tableName),
1353
1296
  Utils.isArrayOfObjects(data)
1354
- ? (data as Data[]).map((item) => ({
1297
+ ? data.map((item: any) => ({
1355
1298
  ...(({ id, ...restOfData }) => restOfData)(item),
1356
- updated_at: new Date(),
1299
+ updatedAt: new Date(),
1357
1300
  }))
1358
1301
  : {
1359
1302
  ...(({ id, ...restOfData }) => restOfData)(data as Data),
1360
- updated_at: new Date(),
1303
+ updatedAt: new Date(),
1361
1304
  }
1362
1305
  );
1363
- for (const [path, content] of Object.entries(pathesContents))
1306
+ for await (const [path, content] of Object.entries(pathesContents))
1364
1307
  await File.replace(path, content);
1365
- if (returnPostedData) return this.get(tableName, where, options);
1308
+
1309
+ if (returnPostedData) return this.get(tableName, where, options) as any;
1366
1310
  }
1367
- } else if (Utils.isValidID(where)) {
1368
- let Ids = where as string | string[];
1369
- if (!Array.isArray(Ids)) Ids = [Ids];
1370
- const [lineNumbers, countItems] = await File.search(
1371
- idFilePath,
1372
- "[]",
1373
- Ids.map((id) => UtilsServer.decodeID(id, this.salt)),
1311
+ } else if (
1312
+ (Array.isArray(where) &&
1313
+ (where.every(Utils.isValidID) || where.every(Utils.isNumber))) ||
1314
+ Utils.isValidID(where) ||
1315
+ Utils.isNumber(where)
1316
+ ) {
1317
+ if (
1318
+ (Array.isArray(where) && where.every(Utils.isValidID)) ||
1319
+ Utils.isValidID(where)
1320
+ ) {
1321
+ const lineNumbers = await this.get(
1322
+ tableName,
1323
+ where,
1324
+ undefined,
1325
+ undefined,
1326
+ true
1327
+ );
1328
+ return this.put(tableName, data, lineNumbers);
1329
+ } else if (
1330
+ (Array.isArray(where) && where.every(Utils.isNumber)) ||
1331
+ Utils.isNumber(where)
1332
+ ) {
1333
+ // "where" in this case, is the line(s) number(s) and not id(s)
1334
+ const pathesContents = Object.fromEntries(
1335
+ Object.entries(
1336
+ this.joinPathesContents(
1337
+ join(this.folder, this.database, tableName),
1338
+ Utils.isArrayOfObjects(data)
1339
+ ? data.map((item: any) => ({
1340
+ ...item,
1341
+ updatedAt: new Date(),
1342
+ }))
1343
+ : { ...data, updatedAt: new Date() }
1344
+ )
1345
+ ).map(([key, value]) => [
1346
+ key,
1347
+ ([...(Array.isArray(where) ? where : [where])] as number[]).reduce(
1348
+ (obj, key, index) => ({
1349
+ ...obj,
1350
+ [key]: Array.isArray(value) ? value[index] : value,
1351
+ }),
1352
+ {}
1353
+ ),
1354
+ ])
1355
+ );
1356
+ for await (const [path, content] of Object.entries(pathesContents))
1357
+ await File.replace(path, content);
1358
+
1359
+ if (returnPostedData)
1360
+ return this.get(
1361
+ tableName,
1362
+ where,
1363
+ options,
1364
+ !Array.isArray(where)
1365
+ ) as any;
1366
+ }
1367
+ } else if (typeof where === "object") {
1368
+ const lineNumbers = this.get(
1369
+ tableName,
1370
+ where,
1374
1371
  undefined,
1375
- "number",
1376
1372
  undefined,
1377
- Ids.length,
1378
- 0,
1379
- false,
1380
- this.salt
1381
- );
1382
- if (!lineNumbers || !Object.keys(lineNumbers).length)
1383
- throw this.throwError("INVALID_ID");
1384
- return this.put(tableName, data, Object.keys(lineNumbers).map(Number));
1385
- } else if (Utils.isNumber(where)) {
1386
- // "where" in this case, is the line(s) number(s) and not id(s)
1387
- const pathesContents = Object.fromEntries(
1388
- Object.entries(
1389
- this.joinPathesContents(
1390
- join(this.folder, this.database, tableName),
1391
- Utils.isArrayOfObjects(data)
1392
- ? (data as Data[]).map((item) => ({
1393
- ...item,
1394
- updated_at: new Date(),
1395
- }))
1396
- : { ...data, updated_at: new Date() }
1397
- )
1398
- ).map(([key, value]) => [
1399
- key,
1400
- ([...(Array.isArray(where) ? where : [where])] as number[]).reduce(
1401
- (obj, key, index) => ({
1402
- ...obj,
1403
- [key]: Array.isArray(value) ? value[index] : value,
1404
- }),
1405
- {}
1406
- ),
1407
- ])
1373
+ true
1408
1374
  );
1409
- for (const [path, content] of Object.entries(pathesContents))
1410
- await File.replace(path, content);
1411
- if (returnPostedData) return this.get(tableName, where, options);
1412
- } else if (typeof where === "object" && !Array.isArray(where)) {
1413
- const lineNumbers = this.get(tableName, where, undefined, true);
1414
1375
  if (!lineNumbers || !Array.isArray(lineNumbers) || !lineNumbers.length)
1415
1376
  throw this.throwError("NO_ITEMS", tableName);
1416
1377
  return this.put(tableName, data, lineNumbers);
@@ -1431,64 +1392,196 @@ export default class Inibase {
1431
1392
  const files = await readdir(join(this.folder, this.database, tableName));
1432
1393
  if (files.length) {
1433
1394
  for (const file in files.filter(
1434
- (fileName: string) => fileName !== "schema"
1395
+ (fileName: string) => fileName !== "schema.json"
1435
1396
  ))
1436
1397
  await unlink(join(this.folder, this.database, tableName, file));
1437
1398
  }
1438
1399
  return "*";
1439
- } else if (Utils.isValidID(where)) {
1440
- let Ids = where as string | string[];
1441
- if (!Array.isArray(Ids)) Ids = [Ids];
1442
- const [lineNumbers, countItems] = await File.search(
1443
- idFilePath,
1444
- "[]",
1445
- Ids.map((id) => UtilsServer.decodeID(id, this.salt)),
1400
+ } else if (
1401
+ (Array.isArray(where) &&
1402
+ (where.every(Utils.isValidID) || where.every(Utils.isNumber))) ||
1403
+ Utils.isValidID(where) ||
1404
+ Utils.isNumber(where)
1405
+ ) {
1406
+ if (
1407
+ (Array.isArray(where) && where.every(Utils.isValidID)) ||
1408
+ Utils.isValidID(where)
1409
+ ) {
1410
+ const lineNumbers = await this.get(
1411
+ tableName,
1412
+ where,
1413
+ undefined,
1414
+ undefined,
1415
+ true
1416
+ );
1417
+ return this.delete(tableName, lineNumbers, where as string | string[]);
1418
+ } else if (
1419
+ (Array.isArray(where) && where.every(Utils.isNumber)) ||
1420
+ Utils.isNumber(where)
1421
+ ) {
1422
+ // "where" in this case, is the line(s) number(s) and not id(s)
1423
+ const files = await readdir(
1424
+ join(this.folder, this.database, tableName)
1425
+ );
1426
+ if (files.length) {
1427
+ if (!_id)
1428
+ _id = Object.values(
1429
+ (
1430
+ await File.get(
1431
+ join(this.folder, this.database, tableName, "id.inib"),
1432
+ where,
1433
+ "number",
1434
+ undefined,
1435
+ this.salt
1436
+ )
1437
+ )[0]
1438
+ ).map((id) => Utils.encodeID(Number(id), this.salt));
1439
+ for (const file of files.filter((fileName: string) =>
1440
+ fileName.endsWith(".inib")
1441
+ ))
1442
+ await File.remove(
1443
+ join(this.folder, this.database, tableName, file),
1444
+ where as number | number[]
1445
+ );
1446
+ return Array.isArray(_id) && _id.length === 1 ? _id[0] : _id;
1447
+ }
1448
+ }
1449
+ } else if (typeof where === "object" && !Array.isArray(where)) {
1450
+ const lineNumbers = this.get(
1451
+ tableName,
1452
+ where,
1446
1453
  undefined,
1447
- "number",
1448
1454
  undefined,
1449
- Ids.length,
1450
- 0,
1451
- false,
1452
- this.salt
1453
- );
1454
- if (!lineNumbers || !Object.keys(lineNumbers).length)
1455
- throw this.throwError("INVALID_ID");
1456
- return this.delete(
1457
- tableName,
1458
- Object.keys(lineNumbers).map(Number),
1459
- where as string | string[]
1455
+ true
1460
1456
  );
1461
- } else if (Utils.isNumber(where)) {
1462
- const files = await readdir(join(this.folder, this.database, tableName));
1463
- if (files.length) {
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"
1479
- ))
1480
- await File.remove(
1481
- join(this.folder, this.database, tableName, file),
1482
- where as number | number[]
1483
- );
1484
- return Array.isArray(_id) && _id.length === 1 ? _id[0] : _id;
1485
- }
1486
- } else if (typeof where === "object" && !Array.isArray(where)) {
1487
- const lineNumbers = this.get(tableName, where, undefined, true);
1488
1457
  if (!lineNumbers || !Array.isArray(lineNumbers) || !lineNumbers.length)
1489
1458
  throw this.throwError("NO_ITEMS", tableName);
1490
1459
  return this.delete(tableName, lineNumbers);
1491
1460
  } else throw this.throwError("INVALID_PARAMETERS", tableName);
1492
1461
  return null;
1493
1462
  }
1463
+
1464
+ public async sum(
1465
+ tableName: string,
1466
+ columns: string,
1467
+ where?: number | string | (number | string)[] | Criteria
1468
+ ): Promise<number>;
1469
+ public async sum(
1470
+ tableName: string,
1471
+ columns: string[],
1472
+ where?: number | string | (number | string)[] | Criteria
1473
+ ): Promise<Record<string, number>>;
1474
+ public async sum(
1475
+ tableName: string,
1476
+ columns: string | string[],
1477
+ where?: number | string | (number | string)[] | Criteria
1478
+ ): Promise<number | Record<string, number>> {
1479
+ let RETURN: Record<string, number>;
1480
+ const schema = await this.getTableSchema(tableName);
1481
+ if (!schema) throw this.throwError("NO_SCHEMA", tableName);
1482
+ if (
1483
+ !(await File.isExists(
1484
+ join(this.folder, this.database, tableName, "id.inib")
1485
+ ))
1486
+ )
1487
+ throw this.throwError("NO_ITEMS", tableName);
1488
+ if (!Array.isArray(columns)) columns = [columns];
1489
+ for await (const column of columns) {
1490
+ const columnPath = join(
1491
+ this.folder,
1492
+ this.database,
1493
+ tableName,
1494
+ column + ".inib"
1495
+ );
1496
+ if (await File.isExists(columnPath)) {
1497
+ if (where) {
1498
+ const lineNumbers = await this.get(
1499
+ tableName,
1500
+ where,
1501
+ undefined,
1502
+ undefined,
1503
+ true
1504
+ );
1505
+ RETURN[column] = await File.sum(columnPath, lineNumbers);
1506
+ } else RETURN[column] = await File.sum(columnPath);
1507
+ }
1508
+ }
1509
+ return Array.isArray(columns) ? RETURN : Object.values(RETURN)[0];
1510
+ }
1511
+
1512
+ public async max(
1513
+ tableName: string,
1514
+ columns: string | string[],
1515
+ where?: number | string | (number | string)[] | Criteria
1516
+ ) {
1517
+ let RETURN: Record<string, number>;
1518
+ const schema = await this.getTableSchema(tableName);
1519
+ if (!schema) throw this.throwError("NO_SCHEMA", tableName);
1520
+ if (
1521
+ !(await File.isExists(
1522
+ join(this.folder, this.database, tableName, "id.inib")
1523
+ ))
1524
+ )
1525
+ throw this.throwError("NO_ITEMS", tableName);
1526
+ if (!Array.isArray(columns)) columns = [columns];
1527
+ for await (const column of columns) {
1528
+ const columnPath = join(
1529
+ this.folder,
1530
+ this.database,
1531
+ tableName,
1532
+ column + ".inib"
1533
+ );
1534
+ if (await File.isExists(columnPath)) {
1535
+ if (where) {
1536
+ const lineNumbers = await this.get(
1537
+ tableName,
1538
+ where,
1539
+ undefined,
1540
+ undefined,
1541
+ true
1542
+ );
1543
+ RETURN[column] = await File.max(columnPath, lineNumbers);
1544
+ } else RETURN[column] = await File.max(columnPath);
1545
+ }
1546
+ }
1547
+ return RETURN;
1548
+ }
1549
+
1550
+ public async min(
1551
+ tableName: string,
1552
+ columns: string | string[],
1553
+ where?: number | string | (number | string)[] | Criteria
1554
+ ) {
1555
+ let RETURN: Record<string, number>;
1556
+ const schema = await this.getTableSchema(tableName);
1557
+ if (!schema) throw this.throwError("NO_SCHEMA", tableName);
1558
+ if (
1559
+ !(await File.isExists(
1560
+ join(this.folder, this.database, tableName, "id.inib")
1561
+ ))
1562
+ )
1563
+ throw this.throwError("NO_ITEMS", tableName);
1564
+ if (!Array.isArray(columns)) columns = [columns];
1565
+ for await (const column of columns) {
1566
+ const columnPath = join(
1567
+ this.folder,
1568
+ this.database,
1569
+ tableName,
1570
+ column + ".inib"
1571
+ );
1572
+ if (await File.isExists(columnPath)) {
1573
+ if (where) {
1574
+ const lineNumbers = await this.get(
1575
+ tableName,
1576
+ where,
1577
+ undefined,
1578
+ undefined,
1579
+ true
1580
+ );
1581
+ RETURN[column] = await File.min(columnPath, lineNumbers);
1582
+ } else RETURN[column] = await File.min(columnPath);
1583
+ }
1584
+ }
1585
+ return RETURN;
1586
+ }
1494
1587
  }