inibase 1.0.0-rc.37 → 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,7 +5,8 @@ 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 { detectFieldType, isArrayOfArrays, isNumber, isObject, } from "./utils.js";
8
+ import { Worker } from "node:worker_threads";
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";
11
12
  const gzip = promisify(gzipAsync);
@@ -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":
@@ -510,8 +512,7 @@ const isEqual = (originalValue, comparedAtValue, fieldType) => {
510
512
  switch (fieldType) {
511
513
  // Password comparison.
512
514
  case "password":
513
- return typeof originalValue === "string" &&
514
- typeof comparedAtValue === "string"
515
+ return isPassword(originalValue) && typeof comparedAtValue === "string"
515
516
  ? comparePassword(originalValue, comparedAtValue)
516
517
  : false;
517
518
  // Boolean comparison.
@@ -762,6 +763,19 @@ export const min = async (filePath, lineNumbers) => {
762
763
  await fileHandle.close();
763
764
  return min;
764
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
+ }
765
779
  /**
766
780
  * Asynchronously sorts the lines in a file in the specified direction.
767
781
  *
@@ -791,4 +805,5 @@ export default class File {
791
805
  static read = read;
792
806
  static lock = lock;
793
807
  static unlock = unlock;
808
+ static createWorker = createWorker;
794
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,14 @@ 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>;
71
+ private _decodeIdFromSchema;
72
+ private _schemaToIdsPath;
69
73
  setTableSchema(tableName: string, schema: Schema): Promise<void>;
70
74
  getTableSchema(tableName: string): Promise<Schema | undefined>;
71
75
  getField(keyPath: string, schema: Schema): (FieldDefault & FieldStringType) | (FieldDefault & FieldObjectType) | (FieldDefault & FieldArrayType) | null;
@@ -73,7 +77,12 @@ export default class Inibase {
73
77
  private formatField;
74
78
  private formatData;
75
79
  private getDefaultValue;
76
- private joinPathesContents;
80
+ private _combineObjectsToArray;
81
+ private _CombineData;
82
+ private _addPathToKeys;
83
+ joinPathesContents(mainPath: string, data: Data | Data[]): {
84
+ [key: string]: string[];
85
+ };
77
86
  private getItemsFromSchema;
78
87
  private applyCriteria;
79
88
  private _filterSchemaByColumns;
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,46 +54,67 @@ export default class Inibase {
53
54
  }
54
55
  return new Error(errorMessage);
55
56
  }
56
- async setTableSchema(tableName, schema) {
57
- const decodeIdFromSchema = (schema) => schema.map((field) => {
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
+ }
77
+ _decodeIdFromSchema = (schema) => schema.map((field) => {
78
+ if ((field.type === "array" || field.type === "object") &&
79
+ field.children &&
80
+ Utils.isArrayOfObjects(field.children))
81
+ field.children = this._decodeIdFromSchema(field.children);
82
+ if (field.id && !Utils.isNumber(field.id))
83
+ field.id = UtilsServer.decodeID(field.id, this.salt);
84
+ return field;
85
+ });
86
+ _schemaToIdsPath = (schema, prefix = "") => {
87
+ let RETURN = {};
88
+ for (const field of schema)
58
89
  if ((field.type === "array" || field.type === "object") &&
59
90
  field.children &&
60
- Utils.isArrayOfObjects(field.children))
61
- field.children = decodeIdFromSchema(field.children);
62
- if (field.id && !Utils.isNumber(field.id))
63
- field.id = UtilsServer.decodeID(field.id, this.salt);
64
- return field;
65
- });
91
+ Utils.isArrayOfObjects(field.children)) {
92
+ Utils.deepMerge(RETURN, this._schemaToIdsPath(field.children, (prefix ?? "") + field.key + "."));
93
+ }
94
+ else if (Utils.isValidID(field.id))
95
+ RETURN[UtilsServer.decodeID(field.id, this.salt)] =
96
+ (prefix ?? "") + field.key + ".inib";
97
+ return RETURN;
98
+ };
99
+ async setTableSchema(tableName, schema) {
100
+ const tablePath = join(this.folder, this.database, tableName), tableSchemaPath = join(tablePath, "schema.json"), isTablePathExists = await File.isExists(tablePath);
66
101
  // remove id from schema
67
102
  schema = schema.filter(({ key }) => !["id", "createdAt", "updatedAt"].includes(key));
68
- schema = UtilsServer.addIdToSchema(schema, UtilsServer.findLastIdNumber(schema, this.salt), this.salt);
69
- const tablePath = join(this.folder, this.database, tableName), tableSchemaPath = join(tablePath, "schema.json");
70
- if (!(await File.isExists(tablePath)))
103
+ schema = UtilsServer.addIdToSchema(schema, UtilsServer.findLastIdNumber(schema, this.salt), this.salt, isTablePathExists);
104
+ if (!isTablePathExists)
71
105
  await mkdir(tablePath, { recursive: true });
72
106
  if (!(await File.isExists(join(tablePath, ".tmp"))))
73
107
  await mkdir(join(tablePath, ".tmp"));
74
108
  if (await File.isExists(tableSchemaPath)) {
75
109
  // update columns files names based on field id
76
- const schemaToIdsPath = (schema, prefix = "") => {
77
- let RETURN = {};
78
- for (const field of schema)
79
- if ((field.type === "array" || field.type === "object") &&
80
- field.children &&
81
- Utils.isArrayOfObjects(field.children)) {
82
- Utils.deepMerge(RETURN, schemaToIdsPath(field.children, (prefix ?? "") + field.key + "."));
83
- }
84
- else if (Utils.isValidID(field.id))
85
- RETURN[UtilsServer.decodeID(field.id, this.salt)] =
86
- (prefix ?? "") + field.key + ".inib";
87
- return RETURN;
88
- }, replaceOldPathes = Utils.findChangedProperties(schemaToIdsPath((await this.getTableSchema(tableName)) ?? []), schemaToIdsPath(schema));
110
+ const replaceOldPathes = Utils.findChangedProperties(this._schemaToIdsPath((await this.getTableSchema(tableName)) ?? []), this._schemaToIdsPath(schema));
89
111
  if (replaceOldPathes)
90
112
  await Promise.all(Object.entries(replaceOldPathes).map(async ([oldPath, newPath]) => {
91
113
  if (await File.isExists(join(tablePath, oldPath)))
92
114
  await rename(join(tablePath, oldPath), join(tablePath, newPath));
93
115
  }));
94
116
  }
95
- await File.write(join(tablePath, "schema.json"), JSON.stringify(decodeIdFromSchema(schema), null, 2), true);
117
+ await File.write(join(tablePath, "schema.json"), JSON.stringify(isTablePathExists ? this._decodeIdFromSchema(schema) : schema, null, 2), true);
96
118
  }
97
119
  async getTableSchema(tableName) {
98
120
  const tableSchemaPath = join(this.folder, this.database, tableName, "schema.json");
@@ -158,7 +180,7 @@ export default class Inibase {
158
180
  !Utils.isArrayOfObjects(field.children)
159
181
  ? field.children
160
182
  : undefined))
161
- throw this.throwError("INVALID_TYPE", field.key + " " + field.type + " " + data[field.key]);
183
+ throw this.throwError("INVALID_TYPE", [field.key, field.type]);
162
184
  if ((field.type === "array" || field.type === "object") &&
163
185
  field.children &&
164
186
  Utils.isArrayOfObjects(field.children))
@@ -225,7 +247,7 @@ export default class Inibase {
225
247
  case "password":
226
248
  if (Array.isArray(value))
227
249
  value = value[0];
228
- return typeof value === "string" && value.length === 161
250
+ return Utils.isPassword(value)
229
251
  ? value
230
252
  : UtilsServer.hashPassword(String(value));
231
253
  case "number":
@@ -238,6 +260,8 @@ export default class Inibase {
238
260
  return Utils.isNumber(value)
239
261
  ? value
240
262
  : UtilsServer.decodeID(value, this.salt);
263
+ case "json":
264
+ return JSON.stringify(value);
241
265
  default:
242
266
  return value;
243
267
  }
@@ -292,13 +316,41 @@ export default class Inibase {
292
316
  return null;
293
317
  }
294
318
  }
319
+ _combineObjectsToArray = (input) => input.reduce((result, current) => {
320
+ for (const [key, value] of Object.entries(current))
321
+ if (!result[key])
322
+ result[key] = [value];
323
+ else
324
+ result[key].push(value);
325
+ return result;
326
+ }, {});
327
+ _CombineData = (_data, prefix) => {
328
+ let RETURN = {};
329
+ if (Utils.isArrayOfObjects(_data))
330
+ RETURN = this._combineObjectsToArray(_data.map((single_data) => this._CombineData(single_data)));
331
+ else
332
+ for (const [key, value] of Object.entries(_data)) {
333
+ if (Utils.isObject(value))
334
+ Object.assign(RETURN, this._CombineData(value, `${key}.`));
335
+ else if (Utils.isArrayOfObjects(value)) {
336
+ Object.assign(RETURN, this._CombineData(this._combineObjectsToArray(value), (prefix ?? "") + key + "."));
337
+ }
338
+ else if (Utils.isArrayOfArrays(value) &&
339
+ value.every(Utils.isArrayOfObjects))
340
+ Object.assign(RETURN, this._CombineData(this._combineObjectsToArray(value.map(this._combineObjectsToArray)), (prefix ?? "") + key + "."));
341
+ else
342
+ RETURN[(prefix ?? "") + key] = File.encode(value);
343
+ }
344
+ return RETURN;
345
+ };
346
+ _addPathToKeys = (obj, path) => {
347
+ const newObject = {};
348
+ for (const key in obj)
349
+ newObject[join(path, key + ".inib")] = obj[key];
350
+ return newObject;
351
+ };
295
352
  joinPathesContents(mainPath, data) {
296
- return Utils.isArrayOfObjects(data)
297
- ? Utils.combineObjects(data.map((single_data) => this.joinPathesContents(mainPath, single_data)))
298
- : Object.fromEntries(Object.entries(Utils.objectToDotNotation(data)).map(([key, value]) => [
299
- join(mainPath, key + ".inib"),
300
- File.encode(value, this.salt),
301
- ]));
353
+ return this._addPathToKeys(this._CombineData(data), mainPath);
302
354
  }
303
355
  async getItemsFromSchema(tableName, schema, linesNumber, options, prefix) {
304
356
  const tablePath = join(this.folder, this.database, tableName);
@@ -755,7 +807,9 @@ export default class Inibase {
755
807
  throw this.throwError("NO_DATA");
756
808
  RETURN = this.formatData(RETURN, schema);
757
809
  const pathesContents = this.joinPathesContents(join(tablePath), Array.isArray(RETURN) ? RETURN.toReversed() : RETURN);
758
- 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))));
759
813
  await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
760
814
  renameList = [];
761
815
  if (Config.isCacheEnabled) {
@@ -809,7 +863,9 @@ export default class Inibase {
809
863
  });
810
864
  try {
811
865
  await File.lock(join(tablePath, ".tmp"));
812
- 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))));
813
869
  await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
814
870
  if (Config.isCacheEnabled)
815
871
  await this.clearCache(join(tablePath, ".tmp"));
@@ -849,7 +905,9 @@ export default class Inibase {
849
905
  ]));
850
906
  try {
851
907
  await File.lock(join(tablePath, ".tmp"));
852
- 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))));
853
911
  await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
854
912
  renameList = [];
855
913
  if (Config.isCacheEnabled)
@@ -917,7 +975,12 @@ export default class Inibase {
917
975
  throw this.throwError("NO_ITEMS", tableName);
918
976
  try {
919
977
  await File.lock(join(tablePath, ".tmp"));
920
- 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))));
921
984
  await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
922
985
  if (Config.isCacheEnabled) {
923
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.d.ts CHANGED
@@ -167,13 +167,6 @@ export declare const detectFieldType: (input: any, availableTypes: FieldType[])
167
167
  * @returns A boolean indicating whether the value matches the specified field type(s).
168
168
  */
169
169
  export declare const validateFieldType: (value: any, fieldType: FieldType | FieldType[], fieldChildrenType?: FieldType | FieldType[]) => boolean;
170
- /**
171
- * Converts a nested object to dot notation, flattening the object's structure.
172
- *
173
- * @param input - The input object to be converted.
174
- * @returns A flattened object using dot notation for keys.
175
- */
176
- export declare const objectToDotNotation: (input: Record<string, any>) => Record<string, string | number | (string | number)[]>;
177
170
  export declare function FormatObjectCriteriaValue(value: string, isParentArray?: boolean): [
178
171
  ComparisonOperator,
179
172
  string | number | boolean | null | (string | number | null)[]
@@ -197,7 +190,6 @@ export default class Utils {
197
190
  static isHTML: (input: any) => boolean;
198
191
  static isIP: (input: any) => boolean;
199
192
  static validateFieldType: (value: any, fieldType: FieldType | FieldType[], fieldChildrenType?: FieldType | FieldType[] | undefined) => boolean;
200
- static objectToDotNotation: (input: Record<string, any>) => Record<string, string | number | (string | number)[]>;
201
193
  static isArrayOfNulls: (input: any) => input is null[] | null[][];
202
194
  static FormatObjectCriteriaValue: typeof FormatObjectCriteriaValue;
203
195
  }
package/dist/utils.js CHANGED
@@ -135,6 +135,7 @@ export const isURL = (input) => {
135
135
  else {
136
136
  var pattern = new RegExp("^(https?:\\/\\/)?" + // protocol
137
137
  "((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name
138
+ "localhost|" + // OR localhost
138
139
  "((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address
139
140
  "(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path
140
141
  "(\\?[;&a-z\\d%_.~+=-]*)?" + // query string
@@ -192,7 +193,7 @@ export const isBoolean = (input) => typeof input === "boolean" ||
192
193
  *
193
194
  * Note: Specifically checks for string length to determine if it matches the defined password length criterion.
194
195
  */
195
- export const isPassword = (input) => input.length === 161;
196
+ export const isPassword = (input) => typeof input === "string" && input.length === 97;
196
197
  /**
197
198
  * Checks if the input can be converted to a valid date.
198
199
  *
@@ -209,6 +210,24 @@ export const isDate = (input) => !isNaN(new Date(input).getTime()) || !isNaN(Dat
209
210
  export const isValidID = (input) => {
210
211
  return typeof input === "string" && input.length === 32;
211
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
+ }
212
231
  /**
213
232
  * Identifies and returns properties that have changed between two objects.
214
233
  *
@@ -256,8 +275,10 @@ export const detectFieldType = (input, availableTypes) => {
256
275
  return "url";
257
276
  else if (availableTypes.includes("password") && isPassword(input))
258
277
  return "password";
259
- else if (availableTypes.includes("date") && isDate(input))
260
- return "date";
278
+ else if (availableTypes.includes("json") && isJSON(input))
279
+ return "json";
280
+ else if (availableTypes.includes("json") && isDate(input))
281
+ return "json";
261
282
  else if (availableTypes.includes("string") && isString(input))
262
283
  return "string";
263
284
  else if (availableTypes.includes("ip") && isIP(input))
@@ -288,7 +309,7 @@ export const validateFieldType = (value, fieldType, fieldChildrenType) => {
288
309
  case "string":
289
310
  return isString(value);
290
311
  case "password":
291
- return isNumber(value) || isString(value) || isPassword(value);
312
+ return isNumber(value) || isPassword(value) || isString(value);
292
313
  case "number":
293
314
  return isNumber(value);
294
315
  case "html":
@@ -322,47 +343,12 @@ export const validateFieldType = (value, fieldType, fieldChildrenType) => {
322
343
  return isNumber(value) || isValidID(value);
323
344
  case "id":
324
345
  return isNumber(value) || isValidID(value);
346
+ case "json":
347
+ return isJSON(value) || Array.isArray(value) || isObject(value);
325
348
  default:
326
349
  return false;
327
350
  }
328
351
  };
329
- /**
330
- * Converts a nested object to dot notation, flattening the object's structure.
331
- *
332
- * @param input - The input object to be converted.
333
- * @returns A flattened object using dot notation for keys.
334
- */
335
- export const objectToDotNotation = (input) => {
336
- const result = {};
337
- const stack = [
338
- { obj: input },
339
- ];
340
- while (stack.length > 0) {
341
- const { obj, parentKey } = stack.pop();
342
- for (const key in obj) {
343
- if (obj.hasOwnProperty(key)) {
344
- const newKey = parentKey ? `${parentKey}.${key}` : key;
345
- const value = obj[key];
346
- const isArray = Array.isArray(value);
347
- const isStringOrNumberArray = isArray &&
348
- value.every((item) => typeof item === "string" || typeof item === "number");
349
- if (isStringOrNumberArray) {
350
- // If the property is an array of strings or numbers, keep the array as is
351
- result[newKey] = value;
352
- }
353
- else if (isObject(value)) {
354
- // If the property is an object, push it onto the stack for further processing
355
- stack.push({ obj: value, parentKey: newKey });
356
- }
357
- else {
358
- // Otherwise, assign the value to the dot notation key
359
- result[newKey] = value;
360
- }
361
- }
362
- }
363
- }
364
- return result;
365
- };
366
352
  export function FormatObjectCriteriaValue(value, isParentArray = false) {
367
353
  switch (value[0]) {
368
354
  case ">":
@@ -436,7 +422,6 @@ export default class Utils {
436
422
  static isHTML = isHTML;
437
423
  static isIP = isIP;
438
424
  static validateFieldType = validateFieldType;
439
- static objectToDotNotation = objectToDotNotation;
440
425
  static isArrayOfNulls = isArrayOfNulls;
441
426
  static FormatObjectCriteriaValue = FormatObjectCriteriaValue;
442
427
  }
@@ -45,14 +45,15 @@ export declare const findLastIdNumber: (schema: Schema, secretKeyOrSalt: string
45
45
  * @param schema - The schema to update, defined as an array of schema objects.
46
46
  * @param oldIndex - The starting index for generating new IDs, defaults to 0.
47
47
  * @param secretKeyOrSalt - The secret key or salt for encoding IDs, can be a string, number, or Buffer.
48
+ * @param encodeIDs - If true, IDs will be encoded, else they will remain as numbers.
48
49
  * @returns The updated schema with encoded IDs.
49
50
  */
50
- export declare const addIdToSchema: (schema: Schema, oldIndex: number | undefined, secretKeyOrSalt: string | number | Buffer) => (({
51
+ export declare const addIdToSchema: (schema: Schema, oldIndex: number | undefined, secretKeyOrSalt: string | number | Buffer, encodeIDs?: boolean) => (({
51
52
  id?: string | number | undefined;
52
53
  key: string;
53
54
  required?: boolean | undefined;
54
55
  } & {
55
- 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";
56
57
  children?: undefined;
57
58
  }) | ({
58
59
  id?: string | number | undefined;
@@ -67,7 +68,7 @@ export declare const addIdToSchema: (schema: Schema, oldIndex: number | undefine
67
68
  required?: boolean | undefined;
68
69
  } & {
69
70
  type: "array";
70
- 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")[];
71
72
  }))[];
72
73
  export declare const hashObject: (obj: any) => string;
73
74
  export default class UtilsServer {
@@ -76,12 +77,12 @@ export default class UtilsServer {
76
77
  static hashPassword: (password: string) => string;
77
78
  static comparePassword: (hashedPassword: string, inputPassword: string) => boolean;
78
79
  static findLastIdNumber: (schema: Schema, secretKeyOrSalt: string | number | Buffer) => number;
79
- static addIdToSchema: (schema: Schema, oldIndex: number | undefined, secretKeyOrSalt: string | number | Buffer) => (({
80
+ static addIdToSchema: (schema: Schema, oldIndex: number | undefined, secretKeyOrSalt: string | number | Buffer, encodeIDs?: boolean | undefined) => (({
80
81
  id?: string | number | undefined;
81
82
  key: string;
82
83
  required?: boolean | undefined;
83
84
  } & {
84
- 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";
85
86
  children?: undefined;
86
87
  }) | ({
87
88
  id?: string | number | undefined;
@@ -96,7 +97,7 @@ export default class UtilsServer {
96
97
  required?: boolean | undefined;
97
98
  } & {
98
99
  type: "array";
99
- 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")[];
100
101
  }))[];
101
102
  static hashObject: (obj: any) => string;
102
103
  }
@@ -87,24 +87,25 @@ export const findLastIdNumber = (schema, secretKeyOrSalt) => {
87
87
  * @param schema - The schema to update, defined as an array of schema objects.
88
88
  * @param oldIndex - The starting index for generating new IDs, defaults to 0.
89
89
  * @param secretKeyOrSalt - The secret key or salt for encoding IDs, can be a string, number, or Buffer.
90
+ * @param encodeIDs - If true, IDs will be encoded, else they will remain as numbers.
90
91
  * @returns The updated schema with encoded IDs.
91
92
  */
92
- export const addIdToSchema = (schema, oldIndex = 0, secretKeyOrSalt) => schema.map((field) => {
93
+ export const addIdToSchema = (schema, oldIndex = 0, secretKeyOrSalt, encodeIDs) => schema.map((field) => {
93
94
  if (!field.id) {
94
95
  oldIndex++;
95
- field.id = encodeID(oldIndex, secretKeyOrSalt);
96
+ field.id = encodeIDs ? encodeID(oldIndex, secretKeyOrSalt) : oldIndex;
96
97
  }
97
98
  else {
98
99
  if (!isNumber(field.id))
99
100
  oldIndex = decodeID(field.id, secretKeyOrSalt);
100
101
  else {
101
102
  oldIndex = field.id;
102
- field.id = encodeID(field.id, secretKeyOrSalt);
103
+ field.id = encodeIDs ? encodeID(field.id, secretKeyOrSalt) : field.id;
103
104
  }
104
105
  }
105
106
  if ((field.type === "array" || field.type === "object") &&
106
107
  isArrayOfObjects(field.children)) {
107
- field.children = addIdToSchema(field.children, oldIndex, secretKeyOrSalt);
108
+ field.children = addIdToSchema(field.children, oldIndex, secretKeyOrSalt, encodeIDs);
108
109
  oldIndex += field.children.length;
109
110
  }
110
111
  return field;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inibase",
3
- "version": "1.0.0-rc.37",
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