inibase 1.0.0-rc.0 → 1.0.0-rc.4

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.
Files changed (4) hide show
  1. package/README.md +65 -34
  2. package/file.ts +19 -14
  3. package/index.ts +165 -129
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- ![Inibase banner](./.github/assets/banner.jpg)
1
+ [![Inibase banner](./.github/assets/banner.jpg)](https://github.com/inicontent/inibase)
2
2
 
3
- # Inibase
3
+ # Inibase :pencil:
4
4
 
5
5
  [![npmjs](https://img.shields.io/npm/dm/inibase.svg?style=flat)](https://www.npmjs.org/package/inibase) [![Node.js Version](https://img.shields.io/badge/node-18.11.0-blue)](https://nodejs.org/) [![License](https://img.shields.io/github/license/inicontent/inibase.svg?style=flat&colorA=18181B&colorB=28CF8D)](./LICENSE) [![Activity](https://img.shields.io/github/commit-activity/m/inicontent/inibase)](https://github.com/inicontent/inibase/pulse) [![GitHub stars](https://img.shields.io/github/stars/inicontent/inibase?style=social)](https://github.com/inicontent/inibase)
6
6
 
@@ -8,12 +8,13 @@
8
8
 
9
9
  ## Features
10
10
 
11
- - **Lightweight** 🪶 (~50kb)
11
+ - **Lightweight** 🪶 (~60kb)
12
12
  - **Minimalist** :white_circle:
13
13
  - **TypeScript** :large_blue_diamond:
14
14
  - **Super-Fast** :turtle:
15
15
  - **Suitable for large data** :page_with_curl:
16
16
  - **Safe** :lock:
17
+ - **Easy to use** :hourglass:
17
18
  - **...** and much more :rocket:
18
19
 
19
20
  ## Usage
@@ -54,14 +55,16 @@ npm install inibase
54
55
 
55
56
  // pnpm
56
57
  pnpm add inibase
57
-
58
- // yarn
59
- yarn add inibase
60
58
  ```
61
59
 
60
+ ## How it works?
61
+
62
+ To semplify the idea, each database has tables, each table has columns, each column will be stored in a seperated file. When POSTing new data, it will be appended to each columns file as new line. When GETing data, the file will be readed line-by-line so it can handle large data (without consuming a lot of resources)
63
+
62
64
  ## Examples
63
65
 
64
- #### POST
66
+ <details>
67
+ <summary>POST</summary>
65
68
 
66
69
  ```js
67
70
  import Inibase from "inibase";
@@ -268,7 +271,10 @@ const product = await db.post("product", product_data);
268
271
  // ]
269
272
  ```
270
273
 
271
- #### GET
274
+ </details>
275
+
276
+ <details>
277
+ <summary>GET</summary>
272
278
 
273
279
  ```js
274
280
  import Inibase from "inibase";
@@ -338,7 +344,10 @@ const users = await db.get("user", { favoriteFoods: "[]Pizza" });
338
344
  // ]
339
345
  ```
340
346
 
341
- #### PUT
347
+ </details>
348
+
349
+ <details>
350
+ <summary>PUT</summary>
342
351
 
343
352
  ```js
344
353
  import Inibase from "inibase";
@@ -354,7 +363,10 @@ await db.put("user", { isActive: false }, "1d88385d4b1581f8fb059334dec30f4c");
354
363
  await db.put("user", { isActive: false }, { isActive: true });
355
364
  ```
356
365
 
357
- #### DELETE
366
+ </details>
367
+
368
+ <details>
369
+ <summary>DELETE</summary>
358
370
 
359
371
  ```js
360
372
  import Inibase from "inibase";
@@ -370,46 +382,62 @@ await db.put("user", "1d88385d4b1581f8fb059334dec30f4c");
370
382
  await db.put("user", { isActive: false });
371
383
  ```
372
384
 
385
+ </details>
386
+
373
387
  ## Typescript
374
388
 
375
- #### Schema
389
+ <details>
390
+ <summary>Schema</summary>
376
391
 
377
392
  ```js
378
- type FieldType =
379
- | "string"
380
- | "number"
381
- | "boolean"
382
- | "date"
383
- | "email"
384
- | "url"
385
- | "table"
386
- | "object"
387
- | "array";
388
- type Field =
393
+ type Schema = Field[];
394
+ type Field = {
395
+ id?: string | number | null | undefined,
396
+ key: string,
397
+ required?: boolean,
398
+ } & (
389
399
  | {
390
- id?: string | number | null | undefined,
391
- key: string,
392
400
  type: Exclude<FieldType, "array" | "object">,
393
401
  required?: boolean,
394
402
  }
395
403
  | {
396
- id?: string | number | null | undefined,
397
- key: string,
398
404
  type: "array",
399
- required?: boolean,
400
405
  children: FieldType | FieldType[] | Schema,
401
406
  }
402
407
  | {
403
- id?: string | number | null | undefined,
404
- key: string,
405
408
  type: "object",
406
- required?: boolean,
407
409
  children: Schema,
408
- };
410
+ }
411
+ );
412
+ type FieldType =
413
+ | "string"
414
+ | "number"
415
+ | "boolean"
416
+ | "date"
417
+ | "email"
418
+ | "url"
419
+ | "table"
420
+ | "object"
421
+ | "array"
422
+ | "password";
423
+ ```
409
424
 
410
- type Schema = Field[];
425
+ </details>
426
+
427
+ <details>
428
+ <summary>Data</summary>
429
+
430
+ ```js
431
+ type Data = {
432
+ id?: number | string,
433
+ [key: string]: any,
434
+ created_at?: Date,
435
+ updated_at?: Date,
436
+ };
411
437
  ```
412
438
 
439
+ </details>
440
+
413
441
  ## Roadmap
414
442
 
415
443
  - [x] Actions:
@@ -431,9 +459,12 @@ type Schema = Field[];
431
459
  - [x] Table
432
460
  - [x] Object
433
461
  - [x] Array
434
- - [ ] Password
462
+ - [x] Password
435
463
  - [ ] IP
436
- - [ ] Suggest [new feature +](https://github.com/inicontent/inibase/discussions/new?category=ideas)
464
+ - [ ] Features:
465
+ - [ ] Encryption
466
+ - [ ] Compress data
467
+ - [ ] Suggest [new feature +](https://github.com/inicontent/inibase/discussions/new?category=ideas)
437
468
 
438
469
  ## License
439
470
 
package/file.ts CHANGED
@@ -183,11 +183,17 @@ export const search = async (
183
183
  // check if not array or object
184
184
  switch (operator) {
185
185
  case "=":
186
- return fieldType === "password" &&
187
- typeof value === "string" &&
188
- typeof comparedAtValue === "string"
189
- ? Utils.comparePassword(value, comparedAtValue)
190
- : value === comparedAtValue;
186
+ switch (fieldType) {
187
+ case "password":
188
+ return typeof value === "string" &&
189
+ typeof comparedAtValue === "string"
190
+ ? Utils.comparePassword(value, comparedAtValue)
191
+ : false;
192
+ case "boolean":
193
+ return Number(value) - Number(comparedAtValue) === 0;
194
+ default:
195
+ return value === comparedAtValue;
196
+ }
191
197
  case "!=":
192
198
  return !handleComparisonOperator(
193
199
  "=",
@@ -268,8 +274,7 @@ export const search = async (
268
274
  lineCount++;
269
275
  const decodedLine = Utils.decode(line, fieldType);
270
276
  if (
271
- decodedLine &&
272
- ((Array.isArray(operator) &&
277
+ (Array.isArray(operator) &&
273
278
  Array.isArray(comparedAtValue) &&
274
279
  ((logicalOperator &&
275
280
  logicalOperator === "or" &&
@@ -289,13 +294,13 @@ export const search = async (
289
294
  fieldType
290
295
  )
291
296
  ))) ||
292
- (!Array.isArray(operator) &&
293
- handleComparisonOperator(
294
- operator,
295
- decodedLine,
296
- comparedAtValue,
297
- fieldType
298
- )))
297
+ (!Array.isArray(operator) &&
298
+ handleComparisonOperator(
299
+ operator,
300
+ decodedLine,
301
+ comparedAtValue,
302
+ fieldType
303
+ ))
299
304
  ) {
300
305
  foundItems++;
301
306
  if (offset && foundItems < offset) continue;
package/index.ts CHANGED
@@ -18,7 +18,6 @@ export { File, Utils };
18
18
  export type Data = {
19
19
  id?: number | string;
20
20
  [key: string]: any;
21
- [id: number]: any;
22
21
  created_at?: Date;
23
22
  updated_at?: Date;
24
23
  };
@@ -32,29 +31,26 @@ export type FieldType =
32
31
  | "url"
33
32
  | "table"
34
33
  | "object"
35
- | "password"
36
- | "array";
37
- type Field =
34
+ | "array"
35
+ | "password";
36
+ type Field = {
37
+ id?: string | number | null | undefined;
38
+ key: string;
39
+ required?: boolean;
40
+ } & (
38
41
  | {
39
- id?: string | number | null | undefined;
40
- key: string;
41
42
  type: Exclude<FieldType, "array" | "object">;
42
43
  required?: boolean;
43
44
  }
44
45
  | {
45
- id?: string | number | null | undefined;
46
- key: string;
47
46
  type: "array";
48
- required?: boolean;
49
47
  children: FieldType | FieldType[] | Schema;
50
48
  }
51
49
  | {
52
- id?: string | number | null | undefined;
53
- key: string;
54
50
  type: "object";
55
- required?: boolean;
56
51
  children: Schema;
57
- };
52
+ }
53
+ );
58
54
 
59
55
  export type Schema = Field[];
60
56
 
@@ -83,13 +79,23 @@ type pageInfo = {
83
79
 
84
80
  export type Criteria =
85
81
  | {
86
- [logic in "and" | "or"]?: Criteria;
82
+ [logic in "and" | "or"]?: Criteria | (string | number | boolean | null)[];
87
83
  }
88
84
  | {
89
85
  [key: string]: string | number | boolean | Criteria;
90
86
  }
91
87
  | null;
92
88
 
89
+ declare global {
90
+ type Entries<T> = {
91
+ [K in keyof T]: [K, T[K]];
92
+ }[keyof T][];
93
+
94
+ interface ObjectConstructor {
95
+ entries<T extends object>(o: T): Entries<T>;
96
+ }
97
+ }
98
+
93
99
  export default class Inibase {
94
100
  public database: string;
95
101
  public databasePath: string;
@@ -567,7 +573,7 @@ export default class Inibase {
567
573
  (data as Data[]).map((single_data) => CombineData(single_data))
568
574
  );
569
575
  else {
570
- for (const [key, value] of Object.entries(data)) {
576
+ for (const [key, value] of Object.entries(data as Data)) {
571
577
  if (Utils.isObject(value))
572
578
  Object.assign(RETURN, CombineData(value, `${key}.`));
573
579
  else if (Array.isArray(value)) {
@@ -667,8 +673,8 @@ export default class Inibase {
667
673
  schema: Schema,
668
674
  linesNumber: number[],
669
675
  prefix?: string
670
- ): Promise<Data> => {
671
- let RETURN: Data = {};
676
+ ) => {
677
+ let RETURN: Record<number, Data> = {};
672
678
  for (const field of schema) {
673
679
  if (
674
680
  (field.type === "array" || field.type === "object") &&
@@ -706,7 +712,12 @@ export default class Inibase {
706
712
  )) ?? {}
707
713
  ).forEach(([index, item]) => {
708
714
  if (!RETURN[index]) RETURN[index] = {};
709
- RETURN[index][field.key] = item;
715
+ RETURN[index][field.key] =
716
+ field.type === "array" &&
717
+ Utils.isObject(item) &&
718
+ Object.values(item).every((i) => i === null)
719
+ ? []
720
+ : item;
710
721
  });
711
722
  }
712
723
  } else if (field.type === "table") {
@@ -808,7 +819,7 @@ export default class Inibase {
808
819
  )) ?? {}
809
820
  );
810
821
  if (RETURN.length && !Array.isArray(where)) RETURN = RETURN[0];
811
- } else if (typeof where === "object" && !Array.isArray(where)) {
822
+ } else if (Utils.isObject(where)) {
812
823
  // Criteria
813
824
  const FormatObjectCriteriaValue = (
814
825
  value: string
@@ -855,11 +866,14 @@ export default class Inibase {
855
866
  const applyCriteria = async (
856
867
  criteria?: Criteria,
857
868
  allTrue?: boolean
858
- ): Promise<Data | null> => {
859
- let RETURN: Data = {};
869
+ ): Promise<Record<number, Data> | null> => {
870
+ let RETURN: Record<number, Data> = {};
860
871
  if (!criteria) return null;
861
- if (criteria.and && typeof criteria.and === "object") {
862
- const searchResult = await applyCriteria(criteria.and, true);
872
+ if (criteria.and && Utils.isObject(criteria.and)) {
873
+ const searchResult = await applyCriteria(
874
+ criteria.and as Criteria,
875
+ true
876
+ );
863
877
  if (searchResult) {
864
878
  RETURN = Utils.deepMerge(
865
879
  RETURN,
@@ -875,108 +889,112 @@ export default class Inibase {
875
889
  } else return null;
876
890
  }
877
891
 
878
- if (criteria.or && typeof criteria.or === "object") {
879
- const searchResult = await applyCriteria(criteria.or);
892
+ if (criteria.or && Utils.isObject(criteria.or)) {
893
+ const searchResult = await applyCriteria(criteria.or as Criteria);
880
894
  delete criteria.or;
881
895
  if (searchResult) RETURN = Utils.deepMerge(RETURN, searchResult);
882
896
  }
883
897
 
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]
898
+ if (Object.keys(criteria).length > 0) {
899
+ allTrue = true;
900
+ let index = -1;
901
+ for (const [key, value] of Object.entries(criteria)) {
902
+ index++;
903
+ let searchOperator:
904
+ | ComparisonOperator
905
+ | ComparisonOperator[]
906
+ | undefined = undefined,
907
+ searchComparedAtValue:
908
+ | string
909
+ | number
910
+ | boolean
911
+ | null
912
+ | (string | number | boolean | null)[]
913
+ | undefined = undefined,
914
+ searchLogicalOperator: "and" | "or" | undefined = undefined;
915
+ if (Utils.isObject(value)) {
916
+ if (
917
+ (value as Criteria)?.or &&
918
+ Array.isArray((value as Criteria).or)
919
+ ) {
920
+ const searchCriteria = (
921
+ (value as Criteria).or as (string | number | boolean)[]
918
922
  )
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";
923
+ .map(
924
+ (
925
+ single_or
926
+ ): [ComparisonOperator, string | number | boolean | null] =>
927
+ typeof single_or === "string"
928
+ ? FormatObjectCriteriaValue(single_or)
929
+ : ["=", single_or]
930
+ )
931
+ .filter((a) => a) as [ComparisonOperator, string | number][];
932
+ if (searchCriteria.length > 0) {
933
+ searchOperator = searchCriteria.map(
934
+ (single_or) => single_or[0]
935
+ );
936
+ searchComparedAtValue = searchCriteria.map(
937
+ (single_or) => single_or[1]
938
+ );
939
+ searchLogicalOperator = "or";
940
+ }
941
+ delete (value as Criteria).or;
928
942
  }
929
- delete value.or;
930
- }
931
- if (value?.and && Array.isArray(value.and)) {
932
- const searchCriteria = value.and
943
+ if (
944
+ (value as Criteria)?.and &&
945
+ Array.isArray((value as Criteria).and)
946
+ ) {
947
+ const searchCriteria = (
948
+ (value as Criteria).and as (string | number | boolean)[]
949
+ )
950
+ .map(
951
+ (
952
+ single_and
953
+ ): [ComparisonOperator, string | number | boolean | null] =>
954
+ typeof single_and === "string"
955
+ ? FormatObjectCriteriaValue(single_and)
956
+ : ["=", single_and]
957
+ )
958
+ .filter((a) => a) as [ComparisonOperator, string | number][];
959
+ if (searchCriteria.length > 0) {
960
+ searchOperator = searchCriteria.map(
961
+ (single_and) => single_and[0]
962
+ );
963
+ searchComparedAtValue = searchCriteria.map(
964
+ (single_and) => single_and[1]
965
+ );
966
+ searchLogicalOperator = "and";
967
+ }
968
+ delete (value as Criteria).and;
969
+ }
970
+ } else if (Array.isArray(value)) {
971
+ const searchCriteria = value
933
972
  .map(
934
973
  (
935
- single_and
974
+ single
936
975
  ): [ComparisonOperator, string | number | boolean | null] =>
937
- typeof single_and === "string"
938
- ? FormatObjectCriteriaValue(single_and)
939
- : ["=", single_and]
976
+ typeof single === "string"
977
+ ? FormatObjectCriteriaValue(single)
978
+ : ["=", single]
940
979
  )
941
980
  .filter((a) => a) as [ComparisonOperator, string | number][];
942
981
  if (searchCriteria.length > 0) {
943
- searchOperator = searchCriteria.map(
944
- (single_and) => single_and[0]
945
- );
982
+ searchOperator = searchCriteria.map((single) => single[0]);
946
983
  searchComparedAtValue = searchCriteria.map(
947
- (single_and) => single_and[1]
984
+ (single) => single[1]
948
985
  );
949
986
  searchLogicalOperator = "and";
950
987
  }
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];
988
+ } else if (typeof value === "string") {
989
+ const ComparisonOperatorValue = FormatObjectCriteriaValue(value);
990
+ if (ComparisonOperatorValue) {
991
+ searchOperator = ComparisonOperatorValue[0];
992
+ searchComparedAtValue = ComparisonOperatorValue[1];
993
+ }
994
+ } else {
995
+ searchOperator = "=";
996
+ searchComparedAtValue = value as number | boolean;
974
997
  }
975
- } else {
976
- searchOperator = "=";
977
- searchComparedAtValue = value;
978
- }
979
- if (searchOperator && searchComparedAtValue) {
980
998
  const [searchResult, totlaItems] = await File.search(
981
999
  join(
982
1000
  this.databasePath,
@@ -996,12 +1014,20 @@ export default class Inibase {
996
1014
  if (!this.pageInfoArray[key]) this.pageInfoArray[key] = {};
997
1015
  this.pageInfoArray[key].total_items = totlaItems;
998
1016
  }
1017
+ if (allTrue && index > 0) {
1018
+ if (!Object.keys(RETURN).length) RETURN = {};
1019
+ RETURN = Object.fromEntries(
1020
+ Object.entries(RETURN).filter(
1021
+ ([_index, item]) => Object.keys(item).length > index
1022
+ )
1023
+ );
1024
+ if (!Object.keys(RETURN).length) RETURN = {};
1025
+ }
999
1026
  }
1000
1027
  }
1001
- return Object.keys(RETURN).length > 0 ? RETURN : null;
1028
+ return Object.keys(RETURN).length ? RETURN : null;
1002
1029
  };
1003
-
1004
- RETURN = await applyCriteria(where);
1030
+ RETURN = await applyCriteria(where as Criteria);
1005
1031
  if (RETURN) {
1006
1032
  if (onlyLinesNumbers) return Object.keys(RETURN).map(Number);
1007
1033
  const alreadyExistsColumns = Object.keys(Object.values(RETURN)[0]).map(
@@ -1039,22 +1065,30 @@ export default class Inibase {
1039
1065
  );
1040
1066
  }
1041
1067
  }
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;
1068
+ if (
1069
+ !RETURN ||
1070
+ (Utils.isObject(RETURN) && !Object.keys(RETURN).length) ||
1071
+ (Array.isArray(RETURN) && !RETURN.length)
1072
+ )
1073
+ return null;
1074
+ return Utils.isArrayOfObjects(RETURN)
1075
+ ? (RETURN as Data[]).map((data: Data) => {
1076
+ data.id = this.encodeID(data.id as number);
1077
+ return data;
1078
+ })
1079
+ : {
1080
+ ...(RETURN as Data),
1081
+ id: this.encodeID((RETURN as Data).id as number),
1082
+ };
1053
1083
  }
1054
1084
 
1055
1085
  public async post(
1056
1086
  tableName: string,
1057
- data: Data | Data[]
1087
+ data: Data | Data[],
1088
+ options: Options = {
1089
+ page: 1,
1090
+ per_page: 15,
1091
+ }
1058
1092
  ): Promise<Data | Data[] | null> {
1059
1093
  const schema = this.getTableSchema(tableName);
1060
1094
  let RETURN: Data | Data[] | null | undefined;
@@ -1092,12 +1126,14 @@ export default class Inibase {
1092
1126
  (Array.isArray(content) ? content.join("\n") : content ?? "") + "\n",
1093
1127
  "utf8"
1094
1128
  );
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) };
1129
+
1130
+ return this.get(
1131
+ tableName,
1132
+ Utils.isArrayOfObjects(RETURN)
1133
+ ? RETURN.map((data: Data) => data.id)
1134
+ : ((RETURN as Data).id as number),
1135
+ options
1136
+ );
1101
1137
  }
1102
1138
 
1103
1139
  public async put(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inibase",
3
- "version": "1.0.0-rc.0",
3
+ "version": "1.0.0-rc.4",
4
4
  "description": "File-based Relational Database for large data",
5
5
  "main": "index.ts",
6
6
  "repository": {