inibase 1.0.0-rc.11 → 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,34 +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
- }
619
- } 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
620
543
  RETURN[(prefix ?? "") + key] = File.encode(value) as
621
544
  | boolean
622
545
  | number
@@ -636,29 +559,18 @@ export default class Inibase {
636
559
  return addPathToKeys(CombineData(data), mainPath);
637
560
  }
638
561
 
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
-
653
- public async get(
562
+ public async get<O extends boolean = false, N extends boolean = false>(
654
563
  tableName: string,
655
564
  where?: string | number | (string | number)[] | Criteria,
656
565
  options: Options = {
657
566
  page: 1,
658
567
  per_page: 15,
659
568
  },
660
- onlyLinesNumbers?: boolean
661
- ): 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
+ > {
662
574
  if (!options.columns) options.columns = [];
663
575
  else if (!Array.isArray(options.columns))
664
576
  options.columns = [options.columns];
@@ -777,19 +689,21 @@ export default class Inibase {
777
689
  if (!RETURN[index][field.key][_i][child_field.key])
778
690
  RETURN[index][field.key][_i][child_field.key] =
779
691
  [];
780
- value[_i].forEach((_element, _index) => {
781
- if (
782
- !RETURN[index][field.key][_i][child_field.key][
783
- _index
784
- ]
785
- )
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
+ ] = {};
786
702
  RETURN[index][field.key][_i][child_field.key][
787
703
  _index
788
- ] = {};
789
- RETURN[index][field.key][_i][child_field.key][
790
- _index
791
- ][key] = _element;
792
- });
704
+ ][key] = _element;
705
+ }
706
+ );
793
707
  }
794
708
  }
795
709
  );
@@ -808,6 +722,7 @@ export default class Inibase {
808
722
  !Utils.isArrayOfObjects(children.children)
809
723
  );
810
724
  }
725
+
811
726
  Object.entries(
812
727
  (await getItemsFromSchema(
813
728
  path,
@@ -823,7 +738,10 @@ export default class Inibase {
823
738
  if (RETURN[index][field.key])
824
739
  Object.entries(item).forEach(([key, value], _index) => {
825
740
  RETURN[index][field.key] = RETURN[index][field.key].map(
826
- (_obj, _i) => ({ ..._obj, [key]: value[_i] })
741
+ (_obj: any, _i: string | number) => ({
742
+ ..._obj,
743
+ [key]: value[_i],
744
+ })
827
745
  );
828
746
  });
829
747
  else if (Object.values(item).every(Utils.isArrayOfArrays))
@@ -975,15 +893,20 @@ export default class Inibase {
975
893
  )
976
894
  )
977
895
  );
978
- } 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
+ ) {
979
902
  let Ids = where as string | number | (string | number)[];
980
903
  if (!Array.isArray(Ids)) Ids = [Ids];
981
904
  const [lineNumbers, countItems] = await File.search(
982
905
  idFilePath,
983
906
  "[]",
984
- Utils.isNumber(Ids)
985
- ? Ids.map((id) => Number(id as string))
986
- : 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
+ ),
987
910
  undefined,
988
911
  "number",
989
912
  undefined,
@@ -997,6 +920,9 @@ export default class Inibase {
997
920
  "INVALID_ID",
998
921
  where as number | string | (number | string)[]
999
922
  );
923
+
924
+ if (onlyLinesNumbers) return Object.keys(lineNumbers).map(Number) as any;
925
+
1000
926
  RETURN = Object.values(
1001
927
  (await getItemsFromSchema(
1002
928
  join(this.folder, this.database, tableName),
@@ -1230,20 +1156,20 @@ export default class Inibase {
1230
1156
 
1231
1157
  RETURN = await applyCriteria(where as Criteria);
1232
1158
  if (RETURN) {
1233
- if (onlyLinesNumbers) return Object.keys(RETURN).map(Number);
1159
+ if (onlyLinesNumbers) return Object.keys(RETURN).map(Number) as any;
1234
1160
  const alreadyExistsColumns = Object.keys(Object.values(RETURN)[0]).map(
1235
1161
  (key) => parse(key).name
1236
1162
  );
1237
1163
  RETURN = Object.values(
1238
1164
  Utils.deepMerge(
1165
+ RETURN,
1239
1166
  await getItemsFromSchema(
1240
1167
  join(this.folder, this.database, tableName),
1241
1168
  schema.filter(
1242
1169
  (field) => !alreadyExistsColumns.includes(field.key)
1243
1170
  ),
1244
1171
  Object.keys(RETURN).map(Number)
1245
- ),
1246
- RETURN
1172
+ )
1247
1173
  )
1248
1174
  );
1249
1175
  }
@@ -1265,43 +1191,44 @@ export default class Inibase {
1265
1191
  total_pages: Math.ceil(greatestTotalItems / options.per_page),
1266
1192
  total: greatestTotalItems,
1267
1193
  };
1268
- return RETURN;
1194
+ return onlyOne ? RETURN[0] : RETURN;
1269
1195
  }
1270
1196
 
1271
- public async post(
1197
+ public async post<DataType extends Data | Data[]>(
1272
1198
  tableName: string,
1273
- data: Data | Data[],
1199
+ data: DataType,
1274
1200
  options: Options = {
1275
1201
  page: 1,
1276
1202
  per_page: 15,
1277
1203
  },
1278
1204
  returnPostedData: boolean = true
1279
- ): Promise<Data | Data[] | null | void> {
1205
+ ): Promise<
1206
+ DataType extends Data ? Data | null | void : Data[] | null | void
1207
+ > {
1280
1208
  const schema = await this.getTableSchema(tableName);
1281
1209
  let RETURN: Data | Data[] | null | undefined;
1282
1210
  if (!schema) throw this.throwError("NO_SCHEMA", tableName);
1283
1211
  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
- )
1290
- : 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];
1291
1218
  if (Utils.isArrayOfObjects(data))
1292
- (data as Data[]).forEach((single_data, index) => {
1219
+ data.forEach((single_data: any, index: string | number) => {
1293
1220
  if (!RETURN) RETURN = [];
1294
- RETURN[index] = (({ id, updated_at, created_at, ...rest }) => ({
1221
+ RETURN[index] = (({ id, updatedAt, createdAt, ...rest }) => ({
1295
1222
  id: ++last_id,
1296
1223
  ...rest,
1297
- created_at: new Date(),
1224
+ createdAt: new Date(),
1298
1225
  }))(single_data);
1299
1226
  });
1300
1227
  else
1301
- RETURN = (({ id, updated_at, created_at, ...rest }) => ({
1228
+ RETURN = (({ id, updatedAt, createdAt, ...rest }) => ({
1302
1229
  id: ++last_id,
1303
1230
  ...rest,
1304
- created_at: new Date(),
1231
+ createdAt: new Date(),
1305
1232
  }))(data as Data);
1306
1233
  if (!RETURN) throw this.throwError("NO_DATA");
1307
1234
 
@@ -1311,10 +1238,7 @@ export default class Inibase {
1311
1238
  RETURN
1312
1239
  );
1313
1240
  for await (const [path, content] of Object.entries(pathesContents))
1314
- await appendFile(
1315
- path,
1316
- (Array.isArray(content) ? content.join("\n") : content ?? "") + "\n"
1317
- );
1241
+ await File.replace(path, { [last_line_number]: content });
1318
1242
 
1319
1243
  if (returnPostedData)
1320
1244
  return this.get(
@@ -1322,11 +1246,12 @@ export default class Inibase {
1322
1246
  Utils.isArrayOfObjects(RETURN)
1323
1247
  ? RETURN.map((data: Data) => data.id)
1324
1248
  : ((RETURN as Data).id as number),
1325
- options
1249
+ options,
1250
+ !Utils.isArrayOfObjects(data) // return only one item if data is not array of objects
1326
1251
  );
1327
1252
  }
1328
1253
 
1329
- public async put(
1254
+ public async put<returnPostedDataType extends boolean = true>(
1330
1255
  tableName: string,
1331
1256
  data: Data | Data[],
1332
1257
  where?: number | string | (number | string)[] | Criteria,
@@ -1334,8 +1259,10 @@ export default class Inibase {
1334
1259
  page: 1,
1335
1260
  per_page: 15,
1336
1261
  },
1337
- returnPostedData: boolean = true
1338
- ): Promise<Data | Data[] | null | void> {
1262
+ returnPostedData?: returnPostedDataType
1263
+ ): Promise<
1264
+ (returnPostedDataType extends true ? Data | Data[] : void) | null
1265
+ > {
1339
1266
  const schema = await this.getTableSchema(tableName);
1340
1267
  if (!schema) throw this.throwError("NO_SCHEMA", tableName);
1341
1268
  const idFilePath = join(this.folder, this.database, tableName, "id.inib");
@@ -1345,15 +1272,15 @@ export default class Inibase {
1345
1272
  if (!where) {
1346
1273
  if (Utils.isArrayOfObjects(data)) {
1347
1274
  if (
1348
- !(data as Data[]).every(
1349
- (item) => item.hasOwnProperty("id") && Utils.isValidID(item.id)
1275
+ !data.every(
1276
+ (item: any) => item.hasOwnProperty("id") && Utils.isValidID(item.id)
1350
1277
  )
1351
1278
  )
1352
1279
  throw this.throwError("INVALID_ID");
1353
1280
  return this.put(
1354
1281
  tableName,
1355
1282
  data,
1356
- (data as Data[]).map((item) => item.id)
1283
+ data.map((item: { id: any }) => item.id)
1357
1284
  );
1358
1285
  } else if (data.hasOwnProperty("id")) {
1359
1286
  if (!Utils.isValidID((data as Data).id))
@@ -1361,72 +1288,90 @@ export default class Inibase {
1361
1288
  return this.put(
1362
1289
  tableName,
1363
1290
  data,
1364
- UtilsServer.decodeID((data as Data).id as string, this.salt)
1291
+ Utils.decodeID((data as Data).id as string, this.salt)
1365
1292
  );
1366
1293
  } else {
1367
1294
  const pathesContents = this.joinPathesContents(
1368
1295
  join(this.folder, this.database, tableName),
1369
1296
  Utils.isArrayOfObjects(data)
1370
- ? (data as Data[]).map((item) => ({
1297
+ ? data.map((item: any) => ({
1371
1298
  ...(({ id, ...restOfData }) => restOfData)(item),
1372
- updated_at: new Date(),
1299
+ updatedAt: new Date(),
1373
1300
  }))
1374
1301
  : {
1375
1302
  ...(({ id, ...restOfData }) => restOfData)(data as Data),
1376
- updated_at: new Date(),
1303
+ updatedAt: new Date(),
1377
1304
  }
1378
1305
  );
1379
- for (const [path, content] of Object.entries(pathesContents))
1306
+ for await (const [path, content] of Object.entries(pathesContents))
1380
1307
  await File.replace(path, content);
1381
- if (returnPostedData) return this.get(tableName, where, options);
1308
+
1309
+ if (returnPostedData) return this.get(tableName, where, options) as any;
1382
1310
  }
1383
- } else if (Utils.isValidID(where)) {
1384
- let Ids = where as string | string[];
1385
- if (!Array.isArray(Ids)) Ids = [Ids];
1386
- const [lineNumbers, countItems] = await File.search(
1387
- idFilePath,
1388
- "[]",
1389
- 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,
1390
1371
  undefined,
1391
- "number",
1392
1372
  undefined,
1393
- Ids.length,
1394
- 0,
1395
- false,
1396
- this.salt
1397
- );
1398
- if (!lineNumbers || !Object.keys(lineNumbers).length)
1399
- throw this.throwError("INVALID_ID");
1400
- return this.put(tableName, data, Object.keys(lineNumbers).map(Number));
1401
- } else if (Utils.isNumber(where)) {
1402
- // "where" in this case, is the line(s) number(s) and not id(s)
1403
- const pathesContents = Object.fromEntries(
1404
- Object.entries(
1405
- this.joinPathesContents(
1406
- join(this.folder, this.database, tableName),
1407
- Utils.isArrayOfObjects(data)
1408
- ? (data as Data[]).map((item) => ({
1409
- ...item,
1410
- updated_at: new Date(),
1411
- }))
1412
- : { ...data, updated_at: new Date() }
1413
- )
1414
- ).map(([key, value]) => [
1415
- key,
1416
- ([...(Array.isArray(where) ? where : [where])] as number[]).reduce(
1417
- (obj, key, index) => ({
1418
- ...obj,
1419
- [key]: Array.isArray(value) ? value[index] : value,
1420
- }),
1421
- {}
1422
- ),
1423
- ])
1373
+ true
1424
1374
  );
1425
- for (const [path, content] of Object.entries(pathesContents))
1426
- await File.replace(path, content);
1427
- if (returnPostedData) return this.get(tableName, where, options);
1428
- } else if (typeof where === "object" && !Array.isArray(where)) {
1429
- const lineNumbers = this.get(tableName, where, undefined, true);
1430
1375
  if (!lineNumbers || !Array.isArray(lineNumbers) || !lineNumbers.length)
1431
1376
  throw this.throwError("NO_ITEMS", tableName);
1432
1377
  return this.put(tableName, data, lineNumbers);
@@ -1447,64 +1392,196 @@ export default class Inibase {
1447
1392
  const files = await readdir(join(this.folder, this.database, tableName));
1448
1393
  if (files.length) {
1449
1394
  for (const file in files.filter(
1450
- (fileName: string) => fileName !== "schema"
1395
+ (fileName: string) => fileName !== "schema.json"
1451
1396
  ))
1452
1397
  await unlink(join(this.folder, this.database, tableName, file));
1453
1398
  }
1454
1399
  return "*";
1455
- } else if (Utils.isValidID(where)) {
1456
- let Ids = where as string | string[];
1457
- if (!Array.isArray(Ids)) Ids = [Ids];
1458
- const [lineNumbers, countItems] = await File.search(
1459
- idFilePath,
1460
- "[]",
1461
- 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,
1462
1453
  undefined,
1463
- "number",
1464
1454
  undefined,
1465
- Ids.length,
1466
- 0,
1467
- false,
1468
- this.salt
1455
+ true
1469
1456
  );
1470
- if (!lineNumbers || !Object.keys(lineNumbers).length)
1471
- throw this.throwError("INVALID_ID");
1472
- return this.delete(
1473
- tableName,
1474
- Object.keys(lineNumbers).map(Number),
1475
- where as string | string[]
1476
- );
1477
- } else if (Utils.isNumber(where)) {
1478
- const files = await readdir(join(this.folder, this.database, tableName));
1479
- if (files.length) {
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"
1495
- ))
1496
- await File.remove(
1497
- join(this.folder, this.database, tableName, file),
1498
- where as number | number[]
1499
- );
1500
- return Array.isArray(_id) && _id.length === 1 ? _id[0] : _id;
1501
- }
1502
- } else if (typeof where === "object" && !Array.isArray(where)) {
1503
- const lineNumbers = this.get(tableName, where, undefined, true);
1504
1457
  if (!lineNumbers || !Array.isArray(lineNumbers) || !lineNumbers.length)
1505
1458
  throw this.throwError("NO_ITEMS", tableName);
1506
1459
  return this.delete(tableName, lineNumbers);
1507
1460
  } else throw this.throwError("INVALID_PARAMETERS", tableName);
1508
1461
  return null;
1509
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
+ }
1510
1587
  }