inibase 1.0.0-rc.38 → 1.0.0-rc.39

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/README.md CHANGED
@@ -105,6 +105,7 @@ Ps: Testing by default with `user` table, with username, email, password fields
105
105
  - [x] IP
106
106
  - [x] HTML
107
107
  - [x] Id
108
+ - [x] JSON
108
109
  - [ ] TO-DO:
109
110
  - [x] Improve caching
110
111
  - [x] Commenting the code
@@ -491,6 +492,33 @@ await db.min("user", ["age", ...], { isActive: false });
491
492
 
492
493
  </details>
493
494
 
495
+ <details>
496
+ <summary>createWorker</summary>
497
+
498
+ ```js
499
+ import Inibase from "inibase";
500
+ const db = new Inibase("/database_name");
501
+
502
+ // POST 10,000 USER
503
+ await Promise.all(
504
+ [...Array(10)]
505
+ .map((x, i) => i)
506
+ .map(
507
+ (_index) =>
508
+ db.createWorker("post", [
509
+ "user",
510
+ [...Array(1000)].map((_, i) => ({
511
+ username: `username_${i + 1}`,
512
+ email: `email_${i + 1}@test.com`,
513
+ password: `password_${i + 1}`,
514
+ })),
515
+ ])
516
+ )
517
+ )
518
+ ```
519
+
520
+ </details>
521
+
494
522
  ## Config
495
523
 
496
524
  The `.env` file supports the following parameters (make sure to run command with flag --env-file=.env)
package/dist/file.d.ts CHANGED
@@ -17,10 +17,9 @@ export declare const isExists: (path: string) => Promise<boolean>;
17
17
  * If the input is a single value, it is directly secured.
18
18
  *
19
19
  * @param input - A value or array of values (string, number, boolean, null).
20
- * @param secretKey - Optional secret key for encoding, can be a string or Buffer.
21
20
  * @returns The secured and/or joined string.
22
21
  */
23
- export declare const encode: (input: string | number | boolean | null | (string | number | boolean | null)[], secretKey?: string | Buffer) => string | number | boolean | null;
22
+ export declare const encode: (input: string | number | boolean | null | (string | number | boolean | null)[]) => string | number | boolean | null;
24
23
  /**
25
24
  * Decodes the input based on the specified field type(s) and an optional secret key.
26
25
  * Handles different formats of input, including strings, numbers, and their array representations.
@@ -143,6 +142,7 @@ export declare const max: (filePath: string, lineNumbers?: number | number[]) =>
143
142
  * Note: Decodes each line as a number using the 'decode' function. Considers only numerical values for determining the minimum.
144
143
  */
145
144
  export declare const min: (filePath: string, lineNumbers?: number | number[]) => Promise<number>;
145
+ export declare function createWorker(functionName: "get" | "remove" | "search" | "replace" | "sum" | "min" | "max" | "append" | "count", arg: any[]): Promise<any>;
146
146
  /**
147
147
  * Asynchronously sorts the lines in a file in the specified direction.
148
148
  *
@@ -160,7 +160,7 @@ export default class File {
160
160
  static remove: (filePath: string, linesToDelete: number | number[]) => Promise<string[]>;
161
161
  static search: (filePath: string, operator: ComparisonOperator | ComparisonOperator[], comparedAtValue: string | number | boolean | (string | number | boolean | null)[] | null, logicalOperator?: "and" | "or" | undefined, fieldType?: FieldType | FieldType[] | undefined, fieldChildrenType?: FieldType | FieldType[] | undefined, limit?: number | undefined, offset?: number | undefined, readWholeFile?: boolean | undefined, secretKey?: string | Buffer | undefined) => Promise<[Record<number, string | number | boolean | (string | number | boolean | null)[] | null> | null, number, Set<number> | null]>;
162
162
  static replace: (filePath: string, replacements: string | number | boolean | (string | number | boolean | null)[] | Record<number, string | number | boolean | (string | number | boolean | null)[] | null> | null) => Promise<string[]>;
163
- static encode: (input: string | number | boolean | (string | number | boolean | null)[] | null, secretKey?: string | Buffer | undefined) => string | number | boolean | null;
163
+ static encode: (input: string | number | boolean | (string | number | boolean | null)[] | null) => string | number | boolean | null;
164
164
  static decode: (input: string | number | null, fieldType?: FieldType | FieldType[] | undefined, fieldChildrenType?: FieldType | FieldType[] | undefined, secretKey?: string | Buffer | undefined) => string | number | boolean | (string | number | boolean | null)[] | null;
165
165
  static isExists: (path: string) => Promise<boolean>;
166
166
  static sum: (filePath: string, lineNumbers?: number | number[] | undefined) => Promise<number>;
@@ -172,4 +172,5 @@ export default class File {
172
172
  static read: (filePath: string, disableCompression?: boolean) => Promise<string>;
173
173
  static lock: (folderPath: string) => Promise<void>;
174
174
  static unlock: (folderPath: string) => Promise<void>;
175
+ static createWorker: typeof createWorker;
175
176
  }
package/dist/file.js CHANGED
@@ -5,6 +5,7 @@ import { pipeline } from "node:stream/promises";
5
5
  import { createGzip, createGunzip, gzip as gzipAsync, gunzip as gunzipAsync, } from "node:zlib";
6
6
  import { promisify } from "node:util";
7
7
  import { join } from "node:path";
8
+ import { Worker } from "node:worker_threads";
8
9
  import { detectFieldType, isArrayOfArrays, isNumber, isObject, isPassword, } from "./utils.js";
9
10
  import { encodeID, comparePassword } from "./utils.server.js";
10
11
  import Config from "./config.js";
@@ -144,10 +145,9 @@ const joinMultidimensionalArray = (arr, delimiter_index = 0) => {
144
145
  * If the input is a single value, it is directly secured.
145
146
  *
146
147
  * @param input - A value or array of values (string, number, boolean, null).
147
- * @param secretKey - Optional secret key for encoding, can be a string or Buffer.
148
148
  * @returns The secured and/or joined string.
149
149
  */
150
- export const encode = (input, secretKey) => {
150
+ export const encode = (input) => {
151
151
  // Use the optimized secureArray and joinMultidimensionalArray functions.
152
152
  return Array.isArray(input)
153
153
  ? joinMultidimensionalArray(secureArray(input))
@@ -217,6 +217,8 @@ const decodeHelper = (value, fieldType, fieldChildrenType, secretKey) => {
217
217
  if (Array.isArray(value) && fieldType !== "array")
218
218
  return value.map((v) => decodeHelper(v, fieldType, fieldChildrenType, secretKey));
219
219
  switch (fieldType) {
220
+ case "json":
221
+ return JSON.parse(value);
220
222
  case "number":
221
223
  return isNumber(value) ? Number(value) : null;
222
224
  case "boolean":
@@ -761,6 +763,19 @@ export const min = async (filePath, lineNumbers) => {
761
763
  await fileHandle.close();
762
764
  return min;
763
765
  };
766
+ export function createWorker(functionName, arg) {
767
+ return new Promise(function (resolve, reject) {
768
+ const worker = new Worker("./dist/file.thread.js", {
769
+ workerData: { functionName, arg },
770
+ });
771
+ worker.on("message", (data) => {
772
+ resolve(data);
773
+ });
774
+ worker.on("error", (msg) => {
775
+ reject(`An error ocurred: ${msg}`);
776
+ });
777
+ });
778
+ }
764
779
  /**
765
780
  * Asynchronously sorts the lines in a file in the specified direction.
766
781
  *
@@ -790,4 +805,5 @@ export default class File {
790
805
  static read = read;
791
806
  static lock = lock;
792
807
  static unlock = unlock;
808
+ static createWorker = createWorker;
793
809
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import { parentPort, workerData } from "node:worker_threads";
2
+ import File from "./file.js";
3
+ const { functionName, arg } = workerData;
4
+ // @ts-ignore
5
+ File[functionName](...arg).then((res) => parentPort.postMessage(res));
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export interface Data {
5
5
  createdAt?: number;
6
6
  updatedAt?: number;
7
7
  }
8
- export type FieldType = "string" | "number" | "boolean" | "date" | "email" | "url" | "table" | "object" | "array" | "password" | "html" | "ip" | "id";
8
+ export type FieldType = "string" | "number" | "boolean" | "date" | "email" | "url" | "table" | "object" | "array" | "password" | "html" | "ip" | "json" | "id";
9
9
  type FieldDefault = {
10
10
  id?: string | number;
11
11
  key: string;
@@ -62,10 +62,12 @@ export default class Inibase {
62
62
  database: string;
63
63
  table: string | null;
64
64
  pageInfo: Record<string, pageInfo>;
65
+ private isThreadEnabled;
65
66
  private totalItems;
66
67
  salt: Buffer;
67
- constructor(database: string, mainFolder?: string);
68
+ constructor(database: string, mainFolder?: string, _table?: string | null, _totalItems?: Record<string, number>, _pageInfo?: Record<string, pageInfo>, _isThreadEnabled?: boolean);
68
69
  private throwError;
70
+ createWorker(functionName: "get" | "post" | "put" | "delete" | "sum" | "min" | "max", arg: any[]): Promise<any>;
69
71
  private _decodeIdFromSchema;
70
72
  private _schemaToIdsPath;
71
73
  setTableSchema(tableName: string, schema: Schema): Promise<void>;
package/dist/index.js CHANGED
@@ -1,26 +1,27 @@
1
1
  import { unlink, rename, mkdir, readdir } from "node:fs/promises";
2
2
  import { existsSync, appendFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
- import { cpus } from "node:os";
5
4
  import { scryptSync, randomBytes } from "node:crypto";
5
+ import { Worker } from "node:worker_threads";
6
6
  import File from "./file.js";
7
7
  import Utils from "./utils.js";
8
8
  import UtilsServer from "./utils.server.js";
9
9
  import Config from "./config.js";
10
- process.env.UV_THREADPOOL_SIZE = cpus().length.toString();
11
10
  export default class Inibase {
12
11
  folder;
13
12
  database;
14
13
  table;
15
14
  pageInfo;
15
+ isThreadEnabled = false;
16
16
  totalItems;
17
17
  salt;
18
- constructor(database, mainFolder = ".") {
18
+ constructor(database, mainFolder = ".", _table = null, _totalItems = {}, _pageInfo = {}, _isThreadEnabled = false) {
19
19
  this.database = database;
20
20
  this.folder = mainFolder;
21
- this.table = null;
22
- this.totalItems = {};
23
- this.pageInfo = {};
21
+ this.table = _table;
22
+ this.totalItems = _totalItems;
23
+ this.pageInfo = _pageInfo;
24
+ this.isThreadEnabled = _isThreadEnabled;
24
25
  if (!existsSync(".env") || !process.env.INIBASE_SECRET) {
25
26
  this.salt = scryptSync(randomBytes(16), randomBytes(16), 32);
26
27
  appendFileSync(".env", `\nINIBASE_SECRET=${this.salt.toString("hex")}\n`);
@@ -53,6 +54,26 @@ export default class Inibase {
53
54
  }
54
55
  return new Error(errorMessage);
55
56
  }
57
+ async createWorker(functionName, arg) {
58
+ return new Promise((resolve, reject) => {
59
+ const worker = new Worker("./dist/index.thread.js", {
60
+ workerData: {
61
+ _constructor: [
62
+ this.database,
63
+ this.folder,
64
+ this.table,
65
+ this.totalItems,
66
+ this.pageInfo,
67
+ true, // enable Thread
68
+ ],
69
+ functionName,
70
+ arg,
71
+ },
72
+ });
73
+ worker.on("message", resolve);
74
+ worker.on("error", reject);
75
+ });
76
+ }
56
77
  _decodeIdFromSchema = (schema) => schema.map((field) => {
57
78
  if ((field.type === "array" || field.type === "object") &&
58
79
  field.children &&
@@ -159,7 +180,7 @@ export default class Inibase {
159
180
  !Utils.isArrayOfObjects(field.children)
160
181
  ? field.children
161
182
  : undefined))
162
- throw this.throwError("INVALID_TYPE", field.key + " " + field.type + " " + data[field.key]);
183
+ throw this.throwError("INVALID_TYPE", [field.key, field.type]);
163
184
  if ((field.type === "array" || field.type === "object") &&
164
185
  field.children &&
165
186
  Utils.isArrayOfObjects(field.children))
@@ -239,6 +260,8 @@ export default class Inibase {
239
260
  return Utils.isNumber(value)
240
261
  ? value
241
262
  : UtilsServer.decodeID(value, this.salt);
263
+ case "json":
264
+ return JSON.stringify(value);
242
265
  default:
243
266
  return value;
244
267
  }
@@ -784,7 +807,9 @@ export default class Inibase {
784
807
  throw this.throwError("NO_DATA");
785
808
  RETURN = this.formatData(RETURN, schema);
786
809
  const pathesContents = this.joinPathesContents(join(tablePath), Array.isArray(RETURN) ? RETURN.toReversed() : RETURN);
787
- await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(await File.append(path, content))));
810
+ await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(this.isThreadEnabled
811
+ ? await File.createWorker("append", [path, content])
812
+ : await File.append(path, content))));
788
813
  await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
789
814
  renameList = [];
790
815
  if (Config.isCacheEnabled) {
@@ -838,7 +863,9 @@ export default class Inibase {
838
863
  });
839
864
  try {
840
865
  await File.lock(join(tablePath, ".tmp"));
841
- await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(await File.replace(path, content))));
866
+ await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(this.isThreadEnabled
867
+ ? await File.createWorker("replace", [path, content])
868
+ : await File.replace(path, content))));
842
869
  await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
843
870
  if (Config.isCacheEnabled)
844
871
  await this.clearCache(join(tablePath, ".tmp"));
@@ -878,7 +905,9 @@ export default class Inibase {
878
905
  ]));
879
906
  try {
880
907
  await File.lock(join(tablePath, ".tmp"));
881
- await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(await File.replace(path, content))));
908
+ await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(this.isThreadEnabled
909
+ ? await File.createWorker("replace", [path, content])
910
+ : await File.replace(path, content))));
882
911
  await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
883
912
  renameList = [];
884
913
  if (Config.isCacheEnabled)
@@ -946,7 +975,12 @@ export default class Inibase {
946
975
  throw this.throwError("NO_ITEMS", tableName);
947
976
  try {
948
977
  await File.lock(join(tablePath, ".tmp"));
949
- await Promise.all(files.map(async (file) => renameList.push(await File.remove(join(tablePath, file), where))));
978
+ await Promise.all(files.map(async (file) => renameList.push(this.isThreadEnabled
979
+ ? await File.createWorker("remove", [
980
+ join(tablePath, file),
981
+ where,
982
+ ])
983
+ : await File.remove(join(tablePath, file), where))));
950
984
  await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
951
985
  if (Config.isCacheEnabled) {
952
986
  await this.clearCache(tablePath);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import Inibase from "./index.js";
2
+ import { parentPort, workerData } from "node:worker_threads";
3
+ const { _constructor, functionName, arg } = workerData;
4
+ // @ts-ignore
5
+ new Inibase(..._constructor)[functionName](...arg)
6
+ .then((res) => parentPort?.postMessage(res));
package/dist/utils.js CHANGED
@@ -210,6 +210,24 @@ export const isDate = (input) => !isNaN(new Date(input).getTime()) || !isNaN(Dat
210
210
  export const isValidID = (input) => {
211
211
  return typeof input === "string" && input.length === 32;
212
212
  };
213
+ /**
214
+ * Checks if a given string is a valid JSON.
215
+ *
216
+ * @param {string} str - The string to be checked.
217
+ * @returns {boolean} Returns true if the string is valid JSON, otherwise false.
218
+ */
219
+ function isJSON(str) {
220
+ try {
221
+ // Attempt to parse the string as JSON
222
+ JSON.parse(str);
223
+ // If parsing succeeds, return true
224
+ return true;
225
+ }
226
+ catch (error) {
227
+ // If an error occurs during parsing, return false
228
+ return false;
229
+ }
230
+ }
213
231
  /**
214
232
  * Identifies and returns properties that have changed between two objects.
215
233
  *
@@ -257,8 +275,10 @@ export const detectFieldType = (input, availableTypes) => {
257
275
  return "url";
258
276
  else if (availableTypes.includes("password") && isPassword(input))
259
277
  return "password";
260
- else if (availableTypes.includes("date") && isDate(input))
261
- return "date";
278
+ else if (availableTypes.includes("json") && isJSON(input))
279
+ return "json";
280
+ else if (availableTypes.includes("json") && isDate(input))
281
+ return "json";
262
282
  else if (availableTypes.includes("string") && isString(input))
263
283
  return "string";
264
284
  else if (availableTypes.includes("ip") && isIP(input))
@@ -323,6 +343,8 @@ export const validateFieldType = (value, fieldType, fieldChildrenType) => {
323
343
  return isNumber(value) || isValidID(value);
324
344
  case "id":
325
345
  return isNumber(value) || isValidID(value);
346
+ case "json":
347
+ return isJSON(value) || Array.isArray(value) || isObject(value);
326
348
  default:
327
349
  return false;
328
350
  }
@@ -53,7 +53,7 @@ export declare const addIdToSchema: (schema: Schema, oldIndex: number | undefine
53
53
  key: string;
54
54
  required?: boolean | undefined;
55
55
  } & {
56
- type: "string" | "number" | "boolean" | "id" | "url" | "html" | "table" | "email" | "date" | "password" | "ip";
56
+ type: "string" | "number" | "boolean" | "id" | "url" | "html" | "table" | "email" | "json" | "date" | "password" | "ip";
57
57
  children?: undefined;
58
58
  }) | ({
59
59
  id?: string | number | undefined;
@@ -68,7 +68,7 @@ export declare const addIdToSchema: (schema: Schema, oldIndex: number | undefine
68
68
  required?: boolean | undefined;
69
69
  } & {
70
70
  type: "array";
71
- children: "string" | "number" | "boolean" | "object" | "id" | "url" | "html" | "table" | "email" | "date" | "password" | "ip" | Schema | ("string" | "number" | "boolean" | "object" | "id" | "url" | "html" | "table" | "email" | "date" | "password" | "ip")[];
71
+ children: "string" | "number" | "boolean" | "object" | "id" | "url" | "html" | "table" | "email" | "json" | "date" | "password" | "ip" | Schema | ("string" | "number" | "boolean" | "object" | "id" | "url" | "html" | "table" | "email" | "json" | "date" | "password" | "ip")[];
72
72
  }))[];
73
73
  export declare const hashObject: (obj: any) => string;
74
74
  export default class UtilsServer {
@@ -82,7 +82,7 @@ export default class UtilsServer {
82
82
  key: string;
83
83
  required?: boolean | undefined;
84
84
  } & {
85
- type: "string" | "number" | "boolean" | "id" | "url" | "html" | "table" | "email" | "date" | "password" | "ip";
85
+ type: "string" | "number" | "boolean" | "id" | "url" | "html" | "table" | "email" | "json" | "date" | "password" | "ip";
86
86
  children?: undefined;
87
87
  }) | ({
88
88
  id?: string | number | undefined;
@@ -97,7 +97,7 @@ export default class UtilsServer {
97
97
  required?: boolean | undefined;
98
98
  } & {
99
99
  type: "array";
100
- children: "string" | "number" | "boolean" | "object" | "id" | "url" | "html" | "table" | "email" | "date" | "password" | "ip" | Schema | ("string" | "number" | "boolean" | "object" | "id" | "url" | "html" | "table" | "email" | "date" | "password" | "ip")[];
100
+ children: "string" | "number" | "boolean" | "object" | "id" | "url" | "html" | "table" | "email" | "json" | "date" | "password" | "ip" | Schema | ("string" | "number" | "boolean" | "object" | "id" | "url" | "html" | "table" | "email" | "json" | "date" | "password" | "ip")[];
101
101
  }))[];
102
102
  static hashObject: (obj: any) => string;
103
103
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inibase",
3
- "version": "1.0.0-rc.38",
3
+ "version": "1.0.0-rc.39",
4
4
  "author": {
5
5
  "name": "Karim Amahtil",
6
6
  "email": "karim.amahtil@gmail.com"
@@ -9,7 +9,9 @@
9
9
  "main": "./dist/index.js",
10
10
  "exports": {
11
11
  ".": "./dist/index.js",
12
+ "./thread": "./dist/index.thread.js",
12
13
  "./file": "./dist/file.js",
14
+ "./file.thread": "./dist/file.thread.js",
13
15
  "./config": "./dist/config.js",
14
16
  "./utils": "./dist/utils.js",
15
17
  "./utils.server": "./dist/utils.server.js"
@@ -51,9 +53,15 @@
51
53
  "types": "./dist",
52
54
  "typesVersions": {
53
55
  "*": {
56
+ "thread": [
57
+ "./dist/index.thread.d.ts"
58
+ ],
54
59
  "file": [
55
60
  "./dist/file.d.ts"
56
61
  ],
62
+ "file.thread": [
63
+ "./dist/file.thread.d.ts"
64
+ ],
57
65
  "utils": [
58
66
  "./dist/utils.d.ts"
59
67
  ],
package/dist/Archive.zip DELETED
Binary file