inibase 1.0.0-rc.49 → 1.0.0-rc.50

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
@@ -284,8 +284,9 @@ const product_schema = [
284
284
  type: "number",
285
285
  },
286
286
  {
287
- key: "user",
287
+ key: "createdBy",
288
288
  type: "table",
289
+ table: "user",
289
290
  required: true,
290
291
  },
291
292
  ];
@@ -294,12 +295,12 @@ const product_data = [
294
295
  {
295
296
  title: "Product 1",
296
297
  price: 16,
297
- user: "1d88385d4b1581f8fb059334dec30f4c",
298
+ createdBy: "1d88385d4b1581f8fb059334dec30f4c",
298
299
  },
299
300
  {
300
301
  title: "Product 2",
301
302
  price: 10,
302
- user: "5011c230aa44481bf7e8dcfe0710474f",
303
+ createdBy: "5011c230aa44481bf7e8dcfe0710474f",
303
304
  },
304
305
  ];
305
306
 
@@ -309,7 +310,7 @@ const product = await db.post("product", product_data);
309
310
  // "id": "1d88385d4b1581f8fb059334dec30f4c",
310
311
  // "title": "Product 1",
311
312
  // "price": 16,
312
- // "user": {
313
+ // "createdBy": {
313
314
  // "id": "1d88385d4b1581f8fb059334dec30f4c",
314
315
  // "username": "user1",
315
316
  // "email": "user1@example.com",
@@ -320,7 +321,7 @@ const product = await db.post("product", product_data);
320
321
  // "id": "5011c230aa44481bf7e8dcfe0710474f",
321
322
  // "title": "Product 2",
322
323
  // "price": 10,
323
- // "user": {
324
+ // "createdBy": {
324
325
  // "id": "5011c230aa44481bf7e8dcfe0710474f",
325
326
  // "username": "user2",
326
327
  // ...
package/dist/file.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
- import { ComparisonOperator, FieldType } from "./index.js";
2
+ import { ComparisonOperator, FieldType, Schema } from "./index.js";
3
3
  export declare const lock: (folderPath: string, prefix?: string) => Promise<void>;
4
4
  export declare const unlock: (folderPath: string, prefix?: string) => Promise<void>;
5
5
  export declare const write: (filePath: string, data: any, disableCompression?: boolean) => Promise<void>;
@@ -30,7 +30,7 @@ export declare const encode: (input: string | number | boolean | null | (string
30
30
  * @param secretKey - Optional secret key for decoding, can be a string or Buffer.
31
31
  * @returns Decoded value as a string, number, boolean, or array of these, or null if no fieldType or input is null/empty.
32
32
  */
33
- export declare const decode: (input: string | null | number, fieldType?: FieldType | FieldType[], fieldChildrenType?: FieldType | FieldType[], secretKey?: string | Buffer) => string | number | boolean | null | (string | number | null | boolean)[];
33
+ export declare const decode: (input: string | null | number, fieldType?: FieldType | FieldType[], fieldChildrenType?: FieldType | FieldType[] | Schema, secretKey?: string | Buffer) => string | number | boolean | null | (string | number | null | boolean)[];
34
34
  /**
35
35
  * Asynchronously reads and decodes data from a file at specified line numbers.
36
36
  * Decodes each line based on specified field types and an optional secret key.
@@ -44,7 +44,7 @@ export declare const decode: (input: string | null | number, fieldType?: FieldTy
44
44
  * 1. Record of line numbers and their decoded content or null if no lines are read.
45
45
  * 2. Total count of lines processed.
46
46
  */
47
- export declare function get(filePath: string, lineNumbers?: number | number[], fieldType?: FieldType | FieldType[], fieldChildrenType?: FieldType | FieldType[], secretKey?: string | Buffer, readWholeFile?: false): Promise<Record<number, string | number | boolean | null | (string | number | boolean | (string | number | boolean)[] | null)[]> | null>;
47
+ export declare function get(filePath: string, lineNumbers?: number | number[], fieldType?: FieldType | FieldType[], fieldChildrenType?: FieldType | FieldType[] | Schema, secretKey?: string | Buffer, readWholeFile?: false): Promise<Record<number, string | number | boolean | null | (string | number | boolean | (string | number | boolean)[] | null)[]> | null>;
48
48
  export declare function get(filePath: string, lineNumbers: undefined | number | number[], fieldType: undefined | FieldType | FieldType[], fieldChildrenType: undefined | FieldType | FieldType[], secretKey: undefined | string | Buffer, readWholeFile: true): Promise<[
49
49
  Record<number, string | number | boolean | null | (string | number | boolean | (string | number | boolean)[] | null)[]> | null,
50
50
  number
@@ -98,7 +98,7 @@ export declare const remove: (filePath: string, linesToDelete: number | number[]
98
98
  *
99
99
  * Note: Decodes each line for comparison and can handle complex queries with multiple conditions.
100
100
  */
101
- export declare const search: (filePath: string, operator: ComparisonOperator | ComparisonOperator[], comparedAtValue: string | number | boolean | null | (string | number | boolean | null)[], logicalOperator?: "and" | "or", fieldType?: FieldType | FieldType[], fieldChildrenType?: FieldType | FieldType[], limit?: number, offset?: number, readWholeFile?: boolean, secretKey?: string | Buffer) => Promise<[
101
+ export declare const search: (filePath: string, operator: ComparisonOperator | ComparisonOperator[], comparedAtValue: string | number | boolean | null | (string | number | boolean | null)[], logicalOperator?: "and" | "or", fieldType?: FieldType | FieldType[], fieldChildrenType?: FieldType | FieldType[] | Schema, limit?: number, offset?: number, readWholeFile?: boolean, secretKey?: string | Buffer) => Promise<[
102
102
  Record<number, string | number | boolean | null | (string | number | boolean | null)[]> | null,
103
103
  number,
104
104
  Set<number> | null
@@ -158,10 +158,10 @@ export declare const sort: (filePath: string, sortDirection: 1 | -1 | "asc" | "d
158
158
  export default class File {
159
159
  static get: typeof get;
160
160
  static remove: (filePath: string, linesToDelete: number | number[]) => Promise<string[]>;
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]>;
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[] | Schema | 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
163
  static encode: (input: string | number | boolean | (string | number | boolean | null)[] | null) => string | number | boolean | null;
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;
164
+ static decode: (input: string | number | null, fieldType?: FieldType | FieldType[] | undefined, fieldChildrenType?: FieldType | FieldType[] | Schema | 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>;
167
167
  static min: (filePath: string, lineNumbers?: number | number[] | undefined) => Promise<number>;
package/dist/file.js CHANGED
@@ -5,7 +5,7 @@ import { pipeline } from "node:stream/promises";
5
5
  import { createGzip, createGunzip, gunzipSync, gzipSync } from "node:zlib";
6
6
  import { join } from "node:path";
7
7
  import { Worker } from "node:worker_threads";
8
- import { detectFieldType, isJSON, isNumber, isObject } from "./utils.js";
8
+ import { detectFieldType, isArrayOfObjects, isJSON, isNumber, isObject, } from "./utils.js";
9
9
  import { encodeID, compare } from "./utils.server.js";
10
10
  import Config from "./config.js";
11
11
  import Inison from "inison";
@@ -122,7 +122,7 @@ const unSecureString = (input) => {
122
122
  if (isNumber(input))
123
123
  return Number(input);
124
124
  if (typeof input === "string")
125
- return input.replace(/\n/g, "\\n") || null;
125
+ return input.replace(/\\n/g, "\n") || null;
126
126
  return null;
127
127
  };
128
128
  /**
@@ -146,7 +146,7 @@ const decodeHelper = (value, fieldType, fieldChildrenType, secretKey) => {
146
146
  case "array":
147
147
  if (!Array.isArray(value))
148
148
  return [value];
149
- if (fieldChildrenType)
149
+ if (fieldChildrenType && !isArrayOfObjects(fieldChildrenType))
150
150
  return fieldChildrenType
151
151
  ? value.map((v) => decode(v, Array.isArray(fieldChildrenType)
152
152
  ? detectFieldType(v, fieldChildrenType)
@@ -423,11 +423,7 @@ export const search = async (filePath, operator, comparedAtValue, logicalOperato
423
423
  }
424
424
  // Convert the Map to an object using Object.fromEntries and return the result.
425
425
  return foundItems
426
- ? [
427
- matchingLines,
428
- readWholeFile ? foundItems : foundItems - 1,
429
- linesNumbers.size ? linesNumbers : null,
430
- ]
426
+ ? [matchingLines, foundItems, linesNumbers.size ? linesNumbers : null]
431
427
  : [null, 0, null];
432
428
  }
433
429
  finally {
package/dist/index.d.ts CHANGED
@@ -6,32 +6,15 @@ export interface Data {
6
6
  updatedAt?: number;
7
7
  }
8
8
  export type FieldType = "string" | "number" | "boolean" | "date" | "email" | "url" | "table" | "object" | "array" | "password" | "html" | "ip" | "json" | "id";
9
- type FieldDefault = {
9
+ export type Field = {
10
10
  id?: string | number;
11
11
  key: string;
12
+ type: FieldType | FieldType[];
12
13
  required?: boolean;
14
+ table?: string;
15
+ unique?: boolean;
16
+ children?: FieldType | FieldType[] | Schema;
13
17
  };
14
- type FieldStringType = {
15
- type: Exclude<FieldType, "array" | "object">;
16
- children?: never;
17
- };
18
- type FieldStringArrayType = {
19
- type: Array<Exclude<FieldType, "object">>;
20
- children?: never;
21
- };
22
- type FieldArrayType = {
23
- type: "array";
24
- children: Exclude<FieldType, "array"> | Array<Exclude<FieldType, "array">> | Schema;
25
- };
26
- type FieldArrayArrayType = {
27
- type: Array<"array" | Exclude<FieldType, "array" | "object">>;
28
- children: Exclude<FieldType, "array" | "object"> | Array<Exclude<FieldType, "array" | "object">>;
29
- };
30
- type FieldObjectType = {
31
- type: "object";
32
- children: Schema;
33
- };
34
- export type Field = FieldDefault & (FieldStringType | FieldStringArrayType | FieldArrayArrayType | FieldObjectType | FieldArrayType);
35
18
  export type Schema = Field[];
36
19
  export interface Options {
37
20
  page?: number;
@@ -57,26 +40,27 @@ declare global {
57
40
  entries<T extends object>(o: T): Entries<T>;
58
41
  }
59
42
  }
60
- export type ErrorCodes = "FIELD_REQUIRED" | "NO_SCHEMA" | "NO_ITEMS" | "NO_RESULTS" | "INVALID_ID" | "INVALID_TYPE" | "INVALID_PARAMETERS";
43
+ export type ErrorCodes = "FIELD_UNIQUE" | "FIELD_REQUIRED" | "NO_SCHEMA" | "NO_ITEMS" | "NO_RESULTS" | "INVALID_ID" | "INVALID_TYPE" | "INVALID_PARAMETERS";
61
44
  export type ErrorLang = "en";
62
45
  export default class Inibase {
63
46
  folder: string;
64
47
  database: string;
65
48
  table: string | null;
66
49
  pageInfo: Record<string, pageInfo>;
50
+ private checkIFunique;
67
51
  private isThreadEnabled;
68
52
  private totalItems;
69
53
  salt: Buffer;
70
54
  constructor(database: string, mainFolder?: string, _table?: string | null, _totalItems?: Record<string, number>, _pageInfo?: Record<string, pageInfo>, _isThreadEnabled?: boolean);
71
55
  private throwError;
72
56
  createWorker(functionName: "get" | "post" | "put" | "delete" | "sum" | "min" | "max" | "sort", arg: any[]): Promise<any>;
73
- private _decodeIdFromSchema;
74
57
  private _schemaToIdsPath;
75
58
  setTableSchema(tableName: string, schema: Schema): Promise<void>;
76
- getTableSchema(tableName: string): Promise<Schema | undefined>;
59
+ getTableSchema(tableName: string, encodeIDs?: boolean): Promise<Schema | undefined>;
77
60
  getSchemaWhenTableNotEmpty(tableName: string, schema?: Schema): Promise<never | Schema>;
78
61
  private validateData;
79
62
  private formatField;
63
+ private checkUnique;
80
64
  private formatData;
81
65
  private getDefaultValue;
82
66
  private _combineObjectsToArray;
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ export default class Inibase {
14
14
  database;
15
15
  table;
16
16
  pageInfo;
17
+ checkIFunique;
17
18
  isThreadEnabled = false;
18
19
  totalItems;
19
20
  salt;
@@ -24,6 +25,7 @@ export default class Inibase {
24
25
  this.totalItems = _totalItems;
25
26
  this.pageInfo = _pageInfo;
26
27
  this.isThreadEnabled = _isThreadEnabled;
28
+ this.checkIFunique = {};
27
29
  if (!existsSync(".env") || !process.env.INIBASE_SECRET) {
28
30
  this.salt = scryptSync(randomBytes(16), randomBytes(16), 32);
29
31
  appendFileSync(".env", `\nINIBASE_SECRET=${this.salt.toString("hex")}\n`);
@@ -34,6 +36,7 @@ export default class Inibase {
34
36
  throwError(code, variable, language = "en") {
35
37
  const errorMessages = {
36
38
  en: {
39
+ FIELD_UNIQUE: "Field {variable} should be unique, got {variable} instead",
37
40
  FIELD_REQUIRED: "Field {variable} is required",
38
41
  NO_SCHEMA: "Table {variable} does't have a schema",
39
42
  NO_ITEMS: "Table {variable} is empty",
@@ -73,15 +76,6 @@ export default class Inibase {
73
76
  worker.on("error", reject);
74
77
  });
75
78
  }
76
- _decodeIdFromSchema = (schema) => schema.map((field) => {
77
- if ((field.type === "array" || field.type === "object") &&
78
- field.children &&
79
- Utils.isArrayOfObjects(field.children))
80
- field.children = this._decodeIdFromSchema(field.children);
81
- if (field.id && !Utils.isNumber(field.id))
82
- field.id = UtilsServer.decodeID(field.id, this.salt);
83
- return field;
84
- });
85
79
  _schemaToIdsPath = (schema, prefix = "") => {
86
80
  let RETURN = {};
87
81
  for (const field of schema)
@@ -90,16 +84,14 @@ export default class Inibase {
90
84
  Utils.isArrayOfObjects(field.children)) {
91
85
  Utils.deepMerge(RETURN, this._schemaToIdsPath(field.children, (prefix ?? "") + field.key + "."));
92
86
  }
93
- else if (Utils.isValidID(field.id))
94
- RETURN[UtilsServer.decodeID(field.id, this.salt)] =
95
- (prefix ?? "") + field.key + ".inib";
87
+ else if (field.id)
88
+ RETURN[field.id] = (prefix ?? "") + field.key + ".inib";
96
89
  return RETURN;
97
90
  };
98
91
  async setTableSchema(tableName, schema) {
99
92
  const tablePath = join(this.folder, this.database, tableName), tableSchemaPath = join(tablePath, "schema.json"), isTablePathExists = await File.isExists(tablePath);
100
93
  // remove id from schema
101
94
  schema = schema.filter(({ key }) => !["id", "createdAt", "updatedAt"].includes(key));
102
- schema = UtilsServer.addIdToSchema(schema, UtilsServer.findLastIdNumber(schema, this.salt), this.salt, isTablePathExists);
103
95
  if (!isTablePathExists)
104
96
  await mkdir(tablePath, { recursive: true });
105
97
  if (!(await File.isExists(join(tablePath, ".tmp"))))
@@ -108,16 +100,24 @@ export default class Inibase {
108
100
  await mkdir(join(tablePath, ".cache"));
109
101
  if (await File.isExists(tableSchemaPath)) {
110
102
  // update columns files names based on field id
111
- const replaceOldPathes = Utils.findChangedProperties(this._schemaToIdsPath((await this.getTableSchema(tableName)) ?? []), this._schemaToIdsPath(schema));
112
- if (replaceOldPathes)
113
- await Promise.all(Object.entries(replaceOldPathes).map(async ([oldPath, newPath]) => {
114
- if (await File.isExists(join(tablePath, oldPath)))
115
- await rename(join(tablePath, oldPath), join(tablePath, newPath));
116
- }));
103
+ const currentSchema = await this.getTableSchema(tableName, false);
104
+ schema = UtilsServer.addIdToSchema(schema, currentSchema && currentSchema.length
105
+ ? UtilsServer.findLastIdNumber(currentSchema, this.salt)
106
+ : undefined, this.salt, false);
107
+ if (currentSchema && currentSchema.length) {
108
+ const replaceOldPathes = Utils.findChangedProperties(this._schemaToIdsPath(currentSchema), this._schemaToIdsPath(schema));
109
+ if (replaceOldPathes)
110
+ await Promise.all(Object.entries(replaceOldPathes).map(async ([oldPath, newPath]) => {
111
+ if (await File.isExists(join(tablePath, oldPath)))
112
+ await rename(join(tablePath, oldPath), join(tablePath, newPath));
113
+ }));
114
+ }
117
115
  }
118
- await File.write(join(tablePath, "schema.json"), JSON.stringify(isTablePathExists ? this._decodeIdFromSchema(schema) : schema, null, 2), true);
116
+ else
117
+ schema = UtilsServer.addIdToSchema(schema, undefined, this.salt, false);
118
+ await File.write(join(tablePath, "schema.json"), JSON.stringify(schema, null, 2), true);
119
119
  }
120
- async getTableSchema(tableName) {
120
+ async getTableSchema(tableName, encodeIDs = true) {
121
121
  const tableSchemaPath = join(this.folder, this.database, tableName, "schema.json");
122
122
  if (!(await File.isExists(tableSchemaPath)))
123
123
  return undefined;
@@ -125,27 +125,30 @@ export default class Inibase {
125
125
  if (!schemaFile)
126
126
  return undefined;
127
127
  const schema = JSON.parse(schemaFile), lastIdNumber = UtilsServer.findLastIdNumber(schema, this.salt);
128
- return [
129
- {
130
- id: UtilsServer.encodeID(0, this.salt),
131
- key: "id",
132
- type: "id",
133
- required: true,
134
- },
135
- ...UtilsServer.addIdToSchema(schema, lastIdNumber, this.salt, true),
136
- {
137
- id: UtilsServer.encodeID(lastIdNumber + 1, this.salt),
138
- key: "createdAt",
139
- type: "date",
140
- required: true,
141
- },
142
- {
143
- id: UtilsServer.encodeID(lastIdNumber + 2, this.salt),
144
- key: "updatedAt",
145
- type: "date",
146
- required: false,
147
- },
148
- ];
128
+ if (!encodeIDs)
129
+ return schema;
130
+ else
131
+ return [
132
+ {
133
+ id: UtilsServer.encodeID(0, this.salt),
134
+ key: "id",
135
+ type: "id",
136
+ required: true,
137
+ },
138
+ ...UtilsServer.encodeSchemaID(schema, this.salt),
139
+ {
140
+ id: UtilsServer.encodeID(lastIdNumber + 1, this.salt),
141
+ key: "createdAt",
142
+ type: "date",
143
+ required: true,
144
+ },
145
+ {
146
+ id: UtilsServer.encodeID(lastIdNumber + 2, this.salt),
147
+ key: "updatedAt",
148
+ type: "date",
149
+ required: false,
150
+ },
151
+ ];
149
152
  }
150
153
  async getSchemaWhenTableNotEmpty(tableName, schema) {
151
154
  const tablePath = join(this.folder, this.database, tableName);
@@ -186,6 +189,11 @@ export default class Inibase {
186
189
  field.children &&
187
190
  Utils.isArrayOfObjects(field.children))
188
191
  this.validateData(data[field.key], field.children, skipRequiredField);
192
+ else if (field.unique) {
193
+ if (!this.checkIFunique[field.key])
194
+ this.checkIFunique[field.key] = [];
195
+ this.checkIFunique[`${field.key}`].push(data[field.key]);
196
+ }
189
197
  }
190
198
  }
191
199
  }
@@ -271,8 +279,22 @@ export default class Inibase {
271
279
  }
272
280
  return null;
273
281
  }
282
+ async checkUnique(tableName, schema) {
283
+ const tablePath = join(this.folder, this.database, tableName);
284
+ for await (const [key, values] of Object.entries(this.checkIFunique)) {
285
+ const field = Utils.getField(key, schema);
286
+ if (!field)
287
+ continue;
288
+ const [searchResult, totalLines] = await File.search(join(tablePath, key + ".inib"), Array.isArray(values) ? "=" : "[]", values, undefined, field.type, field.children, 1, undefined, false, this.salt);
289
+ if (searchResult && totalLines > 0)
290
+ throw this.throwError("FIELD_UNIQUE", [
291
+ field.key,
292
+ Array.isArray(values) ? values.join(", ") : values,
293
+ ]);
294
+ }
295
+ this.checkIFunique = {};
296
+ }
274
297
  formatData(data, schema, formatOnlyAvailiableKeys) {
275
- this.validateData(data, schema, formatOnlyAvailiableKeys);
276
298
  if (Utils.isArrayOfObjects(data))
277
299
  return data.map((single_data) => this.formatData(single_data, schema, formatOnlyAvailiableKeys));
278
300
  else if (Utils.isObject(data)) {
@@ -311,6 +333,8 @@ export default class Inibase {
311
333
  ]
312
334
  : null;
313
335
  case "object":
336
+ if (!field.children || !Utils.isArrayOfObjects(field.children))
337
+ return null;
314
338
  return Utils.combineObjects(field.children.map((f) => ({
315
339
  [f.key]: this.getDefaultValue(f),
316
340
  })));
@@ -464,7 +488,8 @@ export default class Inibase {
464
488
  else if (field.children === "table" ||
465
489
  (Array.isArray(field.type) && field.type.includes("table")) ||
466
490
  (Array.isArray(field.children) && field.children.includes("table"))) {
467
- if ((await File.isExists(join(this.folder, this.database, field.key))) &&
491
+ if (field.table &&
492
+ (await File.isExists(join(this.folder, this.database, field.table))) &&
468
493
  (await File.isExists(join(tablePath, (prefix ?? "") + field.key + ".inib")))) {
469
494
  if (options.columns)
470
495
  options.columns = options.columns
@@ -476,13 +501,13 @@ export default class Inibase {
476
501
  if (!RETURN[index])
477
502
  RETURN[index] = {};
478
503
  RETURN[index][field.key] = item
479
- ? await this.get(field.key, item, options)
504
+ ? await this.get(field.table, item, options)
480
505
  : this.getDefaultValue(field);
481
506
  }));
482
507
  }
483
508
  }
484
509
  else if (await File.isExists(join(tablePath, (prefix ?? "") + field.key + ".inib"))) {
485
- const items = await File.get(join(tablePath, (prefix ?? "") + field.key + ".inib"), linesNumber, field.type, field?.children, this.salt);
510
+ const items = await File.get(join(tablePath, (prefix ?? "") + field.key + ".inib"), linesNumber, field.type, field.children, this.salt);
486
511
  if (items)
487
512
  for (const [index, item] of Object.entries(items)) {
488
513
  if (!RETURN[index])
@@ -510,8 +535,7 @@ export default class Inibase {
510
535
  (await File.isExists(join(tablePath, (prefix ?? "") + field.key + ".inib")))) {
511
536
  if (options.columns)
512
537
  options.columns = options.columns
513
- .filter((column) => column.includes(`${field.key}.`) &&
514
- !column.includes(`${field.key}.`))
538
+ .filter((column) => column.includes(`${field.key}.`))
515
539
  .map((column) => column.replace(`${field.key}.`, ""));
516
540
  const items = await File.get(join(tablePath, (prefix ?? "") + field.key + ".inib"), linesNumber, "number", undefined, this.salt);
517
541
  if (items)
@@ -525,7 +549,7 @@ export default class Inibase {
525
549
  }
526
550
  }
527
551
  else if (await File.isExists(join(tablePath, (prefix ?? "") + field.key + ".inib"))) {
528
- const items = await File.get(join(tablePath, (prefix ?? "") + field.key + ".inib"), linesNumber, field.type, field?.children, this.salt);
552
+ const items = await File.get(join(tablePath, (prefix ?? "") + field.key + ".inib"), linesNumber, field.type, field.children, this.salt);
529
553
  if (items)
530
554
  for (const [index, item] of Object.entries(items)) {
531
555
  if (!RETURN[index])
@@ -833,6 +857,8 @@ export default class Inibase {
833
857
  ...rest,
834
858
  createdAt: Date.now(),
835
859
  }))(data);
860
+ this.validateData(RETURN, schema);
861
+ await this.checkUnique(tableName, schema);
836
862
  RETURN = this.formatData(RETURN, schema);
837
863
  const pathesContents = this.joinPathesContents(tablePath, Config.isReverseEnabled
838
864
  ? Array.isArray(RETURN)
@@ -871,6 +897,8 @@ export default class Inibase {
871
897
  }, returnUpdatedData) {
872
898
  let renameList = [];
873
899
  const tablePath = join(this.folder, this.database, tableName), schema = await this.getSchemaWhenTableNotEmpty(tableName);
900
+ this.validateData(data, schema, true);
901
+ await this.checkUnique(tableName, schema);
874
902
  data = this.formatData(data, schema, true);
875
903
  if (!where) {
876
904
  if (Utils.isArrayOfObjects(data)) {
@@ -52,6 +52,7 @@ export declare const findLastIdNumber: (schema: Schema, secretKeyOrSalt: string
52
52
  * @returns The updated schema with encoded IDs.
53
53
  */
54
54
  export declare const addIdToSchema: (schema: Schema, oldIndex: number | undefined, secretKeyOrSalt: string | number | Buffer, encodeIDs?: boolean) => import("./index.js").Field[];
55
+ export declare const encodeSchemaID: (schema: Schema, secretKeyOrSalt: string | number | Buffer) => Schema;
55
56
  export declare const hashString: (str: string) => string;
56
57
  /**
57
58
  * Evaluates a comparison between two values based on a specified operator and field types.
@@ -104,4 +105,5 @@ export default class UtilsServer {
104
105
  static isEqual: (originalValue: string | number | boolean | (string | number | boolean | null)[] | null, comparedAtValue: string | number | boolean | (string | number | boolean | null)[] | null, fieldType?: FieldType | FieldType[] | undefined) => boolean;
105
106
  static isArrayEqual: (originalValue: string | number | boolean | (string | number | boolean | null)[] | null, comparedAtValue: string | number | boolean | (string | number | boolean | null)[] | null) => boolean;
106
107
  static isWildcardMatch: (originalValue: string | number | boolean | (string | number | boolean | null)[] | null, comparedAtValue: string | number | boolean | (string | number | boolean | null)[] | null) => boolean;
108
+ static encodeSchemaID: (schema: Schema, secretKeyOrSalt: string | number | Buffer) => Schema;
107
109
  }
@@ -64,6 +64,19 @@ export const decodeID = (input, secretKeyOrSalt) => {
64
64
  }
65
65
  return Number(decipher.update(input, "hex", "utf8") + decipher.final("utf8"));
66
66
  };
67
+ // Function to recursively flatten an array of objects and their nested children
68
+ const _flattenSchema = (schema, secretKeyOrSalt) => {
69
+ const result = [];
70
+ for (const field of schema) {
71
+ if (field.id)
72
+ result.push(typeof field.id === "number"
73
+ ? field.id
74
+ : decodeID(field.id, secretKeyOrSalt));
75
+ if (field.children && isArrayOfObjects(field.children))
76
+ result.push(..._flattenSchema(field.children, secretKeyOrSalt));
77
+ }
78
+ return result;
79
+ };
67
80
  /**
68
81
  * Finds the last ID number in a schema, potentially decoding it if encrypted.
69
82
  *
@@ -71,19 +84,7 @@ export const decodeID = (input, secretKeyOrSalt) => {
71
84
  * @param secretKeyOrSalt - The secret key or salt for decoding an encrypted ID, can be a string, number, or Buffer.
72
85
  * @returns The last ID number in the schema, decoded if necessary.
73
86
  */
74
- export const findLastIdNumber = (schema, secretKeyOrSalt) => {
75
- const lastField = schema[schema.length - 1];
76
- if (lastField) {
77
- if ((lastField.type === "array" || lastField.type === "object") &&
78
- isArrayOfObjects(lastField.children))
79
- return findLastIdNumber(lastField.children, secretKeyOrSalt);
80
- else if (lastField.id)
81
- return isValidID(lastField.id)
82
- ? decodeID(lastField.id, secretKeyOrSalt)
83
- : lastField.id;
84
- }
85
- return 0;
86
- };
87
+ export const findLastIdNumber = (schema, secretKeyOrSalt) => Math.max(..._flattenSchema(schema, secretKeyOrSalt));
87
88
  /**
88
89
  * Adds or updates IDs in a schema, encoding them using a provided secret key or salt.
89
90
  *
@@ -99,11 +100,15 @@ export const addIdToSchema = (schema, oldIndex = 0, secretKeyOrSalt, encodeIDs)
99
100
  field.id = encodeIDs ? encodeID(oldIndex, secretKeyOrSalt) : oldIndex;
100
101
  }
101
102
  else {
102
- if (!isNumber(field.id))
103
+ if (isValidID(field.id)) {
103
104
  oldIndex = decodeID(field.id, secretKeyOrSalt);
105
+ if (!encodeIDs)
106
+ field.id = oldIndex;
107
+ }
104
108
  else {
105
109
  oldIndex = field.id;
106
- field.id = encodeIDs ? encodeID(field.id, secretKeyOrSalt) : field.id;
110
+ if (encodeIDs)
111
+ field.id = encodeID(field.id, secretKeyOrSalt);
107
112
  }
108
113
  }
109
114
  if ((field.type === "array" || field.type === "object") &&
@@ -113,6 +118,17 @@ export const addIdToSchema = (schema, oldIndex = 0, secretKeyOrSalt, encodeIDs)
113
118
  }
114
119
  return field;
115
120
  });
121
+ export const encodeSchemaID = (schema, secretKeyOrSalt) => schema.map((field) => ({
122
+ ...field,
123
+ id: isNumber(field.id) ? encodeID(field.id, secretKeyOrSalt) : field.id,
124
+ ...(field.children
125
+ ? isArrayOfObjects(field.children)
126
+ ? {
127
+ children: encodeSchemaID(field.children, secretKeyOrSalt),
128
+ }
129
+ : { children: field.children }
130
+ : {}),
131
+ }));
116
132
  export const hashString = (str) => createHash("sha256").update(str).digest("hex");
117
133
  /**
118
134
  * Evaluates a comparison between two values based on a specified operator and field types.
@@ -251,4 +267,5 @@ export default class UtilsServer {
251
267
  static isEqual = isEqual;
252
268
  static isArrayEqual = isArrayEqual;
253
269
  static isWildcardMatch = isWildcardMatch;
270
+ static encodeSchemaID = encodeSchemaID;
254
271
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inibase",
3
- "version": "1.0.0-rc.49",
3
+ "version": "1.0.0-rc.50",
4
4
  "author": {
5
5
  "name": "Karim Amahtil",
6
6
  "email": "karim.amahtil@gmail.com"