inibase 1.0.0-rc.124 → 1.0.0-rc.126

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
@@ -106,40 +106,124 @@ interface {
106
106
  <summary>Schema</summary>
107
107
  <blockquote>
108
108
 
109
+ <details>
110
+ <summary>Types</summary>
111
+ <blockquote>
112
+
109
113
  ```ts
110
- interface {
114
+ interface Field {
111
115
  id: number; // stored as a Number but displayed as a hashed ID
112
116
  key: string;
113
117
  required?: boolean;
114
- unique?: boolean;
115
- type: "string" | "number" | "boolean" | "date" | "email" | "url" | "password" | "html" | "ip" | "json" | "id";
118
+ unique?: boolean | string; // boolean for simple uniqueness, string for grouped uniqueness
119
+ regex?: RegExp; // Regular expression for custom validation
120
+ type:
121
+ | "string"
122
+ | "number"
123
+ | "boolean"
124
+ | "date"
125
+ | "email"
126
+ | "url"
127
+ | "password"
128
+ | "html"
129
+ | "ip"
130
+ | "json"
131
+ | "id";
116
132
  }
117
- interface Table {
133
+
134
+ interface TableField {
118
135
  id: number;
119
136
  key: string;
120
137
  required?: boolean;
138
+ unique?: boolean | string; // Supports uniqueness constraints
121
139
  type: "table";
122
140
  table: string;
123
141
  }
124
- interface Array {
142
+
143
+ interface ArrayField {
125
144
  id: number;
126
145
  key: string;
127
146
  required?: boolean;
147
+ unique?: boolean | string; // Supports uniqueness constraints
128
148
  type: "array";
129
- children: string|string[];
149
+ children: string | string[]; // Can be a single type or an array of types
130
150
  }
131
- interface ObjectOrArrayOfObjects {
151
+
152
+ interface ObjectOrArrayOfObjectsField {
132
153
  id: number;
133
154
  key: string;
134
155
  required?: boolean;
156
+ unique?: boolean | string; // Supports uniqueness constraints
157
+ regex?: RegExp; // For validation of object-level keys
135
158
  type: "object" | "array";
136
- children: Schema;
159
+ children: Schema; // Nested schema for objects or arrays
160
+ }
161
+ ```
162
+
163
+ </blockquote>
164
+ </details>
165
+
166
+ <details>
167
+ <summary>Unique</summary>
168
+ <blockquote>
169
+
170
+ The `unique` property ensures that the values of a specific column or a group of columns are unique within a table. This property can be either a boolean or a string.
171
+ - **Boolean**: Setting `unique: true` ensures that the values in the column are unique across all rows.
172
+ - **String**: By setting a string value, you can group columns to enforce a combined uniqueness constraint. This is useful when you need to ensure that a combination of values across multiple fields is unique.
173
+
174
+ <details>
175
+ <summary>Examples</summary>
176
+ <blockquote>
177
+
178
+ <details>
179
+ <summary>Unique Column</summary>
180
+ <blockquote>
181
+
182
+ ```js
183
+ {
184
+ key: "email",
185
+ type: "string",
186
+ required: true,
187
+ unique: true, // Ensures all email values are unique
137
188
  }
138
189
  ```
139
190
 
140
191
  </blockquote>
141
192
  </details>
142
193
 
194
+ <details>
195
+ <summary>Group of Unique Columns</summary>
196
+ <blockquote>
197
+
198
+ ```js
199
+ [
200
+ {
201
+ key: "firstName",
202
+ type: "string",
203
+ required: true,
204
+ unique: "nameGroup", // Part of "nameGroup" uniqueness
205
+ },
206
+ {
207
+ key: "lastName",
208
+ type: "string",
209
+ required: true,
210
+ unique: "nameGroup", // Part of "nameGroup" uniqueness
211
+ },
212
+ ]
213
+ ```
214
+
215
+ </blockquote>
216
+ </details>
217
+
218
+ </blockquote>
219
+ </details>
220
+
221
+ </blockquote>
222
+ </details>
223
+
224
+ </blockquote>
225
+ </details>
226
+
143
227
  <details>
144
228
  <summary>Create Table</summary>
145
229
  <blockquote>
@@ -357,7 +441,7 @@ const product = await db.post("product", productTableData);
357
441
  </blockquote>
358
442
  </details>
359
443
 
360
- <details>
444
+ <details open>
361
445
  <summary>Methods</summary>
362
446
  <blockquote>
363
447
 
package/dist/file.js CHANGED
@@ -1,11 +1,9 @@
1
- import { createWriteStream } from "node:fs";
2
1
  import { access, appendFile, copyFile, constants as fsConstants, open, readFile, unlink, writeFile, } from "node:fs/promises";
3
2
  import { join, resolve } from "node:path";
4
3
  import { createInterface } from "node:readline";
5
4
  import { Transform } from "node:stream";
6
5
  import { pipeline } from "node:stream/promises";
7
6
  import { createGunzip, createGzip } from "node:zlib";
8
- import { spawn as spawnSync } from "node:child_process";
9
7
  import Inison from "inison";
10
8
  import { detectFieldType, isArrayOfObjects, isStringified, isNumber, isObject, } from "./utils.js";
11
9
  import { compare, encodeID, exec, gunzip, gzip } from "./utils.server.js";
@@ -275,9 +273,9 @@ export async function get(filePath, lineNumbers, fieldType, fieldChildrenType, s
275
273
  export const replace = async (filePath, replacements, totalItems) => {
276
274
  const fileTempPath = filePath.replace(/([^/]+)\/?$/, ".tmp/$1");
277
275
  const isReplacementsObject = isObject(replacements);
278
- const isReplacementsObjectHasLineNumbers = isReplacementsObject && !Number.isNaN(Number(Object.keys(replacements)[0]));
276
+ const isReplacementsLineNumbered = isReplacementsObject && !Number.isNaN(Number(Object.keys(replacements)[0]));
279
277
  if (await isExists(filePath)) {
280
- if (isReplacementsObjectHasLineNumbers) {
278
+ if (isReplacementsLineNumbered) {
281
279
  let fileHandle = null;
282
280
  let fileTempHandle = null;
283
281
  try {
@@ -328,34 +326,24 @@ export const replace = async (filePath, replacements, totalItems) => {
328
326
  }
329
327
  }
330
328
  else {
331
- return new Promise((resolve) => {
332
- const sedProcess = spawnSync("sed", [
333
- "-e",
334
- `s/.*/${replacements}/`,
335
- "-e",
336
- `/^$/s/^/${replacements}/`,
337
- filePath,
338
- ]);
339
- const outputStream = createWriteStream(fileTempPath); // Temp file for output
340
- // Pipe sed output to the temporary file
341
- sedProcess.stdout.pipe(outputStream);
342
- // Handle the process close
343
- sedProcess.on("close", (code) => {
344
- if (code === 0)
345
- resolve([fileTempPath, filePath]);
346
- else
347
- resolve([fileTempPath, null]);
348
- });
349
- // Handle errors in spawning the sed process
350
- sedProcess.on("error", () => {
351
- resolve([fileTempPath, null]);
352
- });
353
- });
329
+ const escapedFilePath = escapeShellPath(filePath);
330
+ const escapedFileTempPath = escapeShellPath(fileTempPath);
331
+ const sedCommand = `sed -e s/.*/${replacements}/ -e /^$/s/^/${replacements}/ ${escapedFilePath}`;
332
+ const command = filePath.endsWith(".gz")
333
+ ? `zcat ${escapedFilePath} | ${sedCommand} | gzip > ${escapedFileTempPath}`
334
+ : `${sedCommand} > ${escapedFileTempPath}`;
335
+ try {
336
+ await exec(command);
337
+ return [fileTempPath, filePath];
338
+ }
339
+ catch {
340
+ return [fileTempPath, null];
341
+ }
354
342
  }
355
343
  }
356
344
  else if (isReplacementsObject) {
357
345
  try {
358
- if (isReplacementsObjectHasLineNumbers) {
346
+ if (isReplacementsLineNumbered) {
359
347
  const replacementsKeys = Object.keys(replacements)
360
348
  .map(Number)
361
349
  .toSorted((a, b) => a - b);
@@ -399,7 +387,7 @@ export const append = async (filePath, data) => {
399
387
  }
400
388
  else {
401
389
  const escapedFileTempPath = escapeShellPath(fileTempPath);
402
- await exec(`echo $'${(Array.isArray(data) ? data.join("\n") : data)
390
+ await exec(`echo '${(Array.isArray(data) ? data.join("\n") : data)
403
391
  .toString()
404
392
  .replace(/'/g, "\\'")}' | gzip - >> ${escapedFileTempPath}`);
405
393
  }
@@ -542,7 +530,8 @@ export const search = async (filePath, operator, comparedAtValue, logicalOperato
542
530
  // Increment the line count for each line.
543
531
  linesCount++;
544
532
  // Search only in provided linesNumbers
545
- if (searchIn && (!searchIn.has(linesCount) || searchIn.has(-linesCount)))
533
+ if (searchIn?.size &&
534
+ (!searchIn.has(linesCount) || searchIn.has(-linesCount)))
546
535
  continue;
547
536
  // Decode the line for comparison.
548
537
  const decodedLine = decode(line, fieldType, fieldChildrenType, secretKey);
package/dist/index.d.ts CHANGED
@@ -62,7 +62,7 @@ export default class Inibase {
62
62
  private totalItems;
63
63
  constructor(database: string, mainFolder?: string);
64
64
  private static errorMessages;
65
- createError(code: ErrorCodes, variable?: string | number | (string | number)[], language?: ErrorLang): Error;
65
+ createError(name: ErrorCodes, variable?: string | number | (string | number)[], language?: ErrorLang): Error;
66
66
  clear(): void;
67
67
  private getFileExtension;
68
68
  private _schemaToIdsPath;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import "dotenv/config";
2
- import { randomBytes, randomInt, scryptSync } from "node:crypto";
2
+ import { randomBytes, scryptSync } from "node:crypto";
3
3
  import { appendFileSync, existsSync, readFileSync } from "node:fs";
4
4
  import { glob, mkdir, readFile, readdir, rename, rm, unlink, writeFile, } from "node:fs/promises";
5
5
  import { join, parse } from "node:path";
@@ -48,15 +48,17 @@ export default class Inibase {
48
48
  : "please use dotenv",
49
49
  },
50
50
  };
51
- createError(code, variable, language = "en") {
52
- const errorMessage = Inibase.errorMessages[language]?.[code];
51
+ createError(name, variable, language = "en") {
52
+ const errorMessage = Inibase.errorMessages[language]?.[name];
53
53
  if (!errorMessage)
54
54
  return new Error("ERR");
55
- return new Error(variable
55
+ const error = new Error(variable
56
56
  ? Array.isArray(variable)
57
57
  ? errorMessage.replace(/\{variable\}/g, () => variable.shift()?.toString() ?? "")
58
58
  : errorMessage.replaceAll("{variable}", `'${variable.toString()}'`)
59
59
  : errorMessage.replaceAll("{variable}", ""));
60
+ error.name = name;
61
+ return error;
60
62
  }
61
63
  clear() {
62
64
  this.tablesMap = new Map();
@@ -293,10 +295,12 @@ export default class Inibase {
293
295
  throw this.createError("TABLE_EMPTY", tableName);
294
296
  }
295
297
  _validateData(data, schema, skipRequiredField = false) {
296
- if (Utils.isArrayOfObjects(data))
298
+ if (Utils.isArrayOfObjects(data)) {
297
299
  for (const single_data of data)
298
300
  this._validateData(single_data, schema, skipRequiredField);
299
- else if (Utils.isObject(data)) {
301
+ return;
302
+ }
303
+ if (Utils.isObject(data)) {
300
304
  for (const field of schema) {
301
305
  if (!Object.hasOwn(data, field.key) ||
302
306
  data[field.key] === null ||
@@ -304,7 +308,7 @@ export default class Inibase {
304
308
  data[field.key] === "") {
305
309
  if (field.required && !skipRequiredField)
306
310
  throw this.createError("FIELD_REQUIRED", field.key);
307
- return;
311
+ continue;
308
312
  }
309
313
  if (!Utils.validateFieldType(data[field.key], field.type, (field.type === "array" || field.type === "object") &&
310
314
  field.children &&
@@ -313,7 +317,12 @@ export default class Inibase {
313
317
  : undefined))
314
318
  throw this.createError("INVALID_TYPE", [
315
319
  field.key,
316
- Array.isArray(field.type) ? field.type.join(", ") : field.type,
320
+ (Array.isArray(field.type) ? field.type.join(", ") : field.type) +
321
+ (Array.isArray(field.children)
322
+ ? Utils.isArrayOfObjects(field.children)
323
+ ? "[object]"
324
+ : `[${field.children.join("|")}]`
325
+ : `[${field.children}]`),
317
326
  data[field.key],
318
327
  ]);
319
328
  if ((field.type === "array" || field.type === "object") &&
@@ -329,7 +338,7 @@ export default class Inibase {
329
338
  if (field.unique) {
330
339
  let uniqueKey;
331
340
  if (typeof field.unique === "boolean")
332
- uniqueKey = randomInt(55);
341
+ uniqueKey = field.id;
333
342
  else
334
343
  uniqueKey = field.unique;
335
344
  if (!this.uniqueMap.has(uniqueKey))
@@ -433,22 +442,38 @@ export default class Inibase {
433
442
  async checkUnique(tableName) {
434
443
  const tablePath = join(this.databasePath, tableName);
435
444
  const flattenSchema = Utils.flattenSchema(this.tablesMap.get(tableName).schema);
445
+ function hasDuplicates(setA, setB) {
446
+ for (const value of setA)
447
+ if (setB.has(value))
448
+ return true; // Stop and return true if a duplicate is found
449
+ return false; // No duplicates found
450
+ }
436
451
  for await (const [_uniqueID, valueObject] of this.uniqueMap) {
437
452
  let index = 0;
453
+ let shouldContinueParent = false; // Flag to manage parent loop continuation
454
+ const mergedLineNumbers = new Set();
438
455
  for await (const [columnID, values] of valueObject.columnsValues) {
439
456
  index++;
440
457
  const field = flattenSchema.find(({ id }) => id === columnID);
441
- const [searchResult, totalLines] = await File.search(join(tablePath, `${field.key}${this.getFileExtension(tableName)}`), "[]", values, undefined, valueObject.exclude, field.type, field.children, 1, undefined, false, this.salt);
458
+ const [searchResult, totalLines, lineNumbers] = await File.search(join(tablePath, `${field.key}${this.getFileExtension(tableName)}`), "[]", Array.from(values), undefined, valueObject.exclude, field.type, field.children, 1, undefined, false, this.salt);
442
459
  if (searchResult && totalLines > 0) {
443
- if (index === valueObject.columnsValues.size)
460
+ if (valueObject.columnsValues.size === 1 ||
461
+ hasDuplicates(lineNumbers, mergedLineNumbers)) {
462
+ this.uniqueMap = new Map();
444
463
  throw this.createError("FIELD_UNIQUE", [
445
464
  field.key,
446
- Array.isArray(values) ? values.join(", ") : values,
465
+ Array.from(values).join(", "),
447
466
  ]);
467
+ }
468
+ lineNumbers.forEach(mergedLineNumbers.add, mergedLineNumbers);
469
+ }
470
+ else {
471
+ shouldContinueParent = true; // Flag to skip the rest of this inner loop
472
+ break; // Exit the inner loop
448
473
  }
449
- else
450
- continue;
451
474
  }
475
+ if (shouldContinueParent)
476
+ continue;
452
477
  }
453
478
  this.uniqueMap = new Map();
454
479
  }
@@ -1165,8 +1190,9 @@ export default class Inibase {
1165
1190
  throw this.createError("NO_SCHEMA", tableName);
1166
1191
  if (!returnPostedData)
1167
1192
  returnPostedData = false;
1168
- const keys = UtilsServer.hashString(Object.keys(Array.isArray(data) ? data[0] : data).join("."));
1169
- await this.validateData(tableName, data);
1193
+ let clonedData = JSON.parse(JSON.stringify(data));
1194
+ const keys = UtilsServer.hashString(Object.keys(Array.isArray(clonedData) ? clonedData[0] : clonedData).join("."));
1195
+ await this.validateData(tableName, clonedData);
1170
1196
  const renameList = [];
1171
1197
  try {
1172
1198
  await File.lock(join(tablePath, ".tmp"), keys);
@@ -1177,24 +1203,24 @@ export default class Inibase {
1177
1203
  .name.split("-")
1178
1204
  .map(Number);
1179
1205
  this.totalItems.set(`${tableName}-*`, _totalItems);
1180
- if (Utils.isArrayOfObjects(data))
1181
- for (let index = 0; index < data.length; index++) {
1182
- const element = data[index];
1206
+ if (Utils.isArrayOfObjects(clonedData))
1207
+ for (let index = 0; index < clonedData.length; index++) {
1208
+ const element = clonedData[index];
1183
1209
  element.id = ++lastId;
1184
1210
  element.createdAt = Date.now();
1185
1211
  element.updatedAt = undefined;
1186
1212
  }
1187
1213
  else {
1188
- data.id = ++lastId;
1189
- data.createdAt = Date.now();
1190
- data.updatedAt = undefined;
1214
+ clonedData.id = ++lastId;
1215
+ clonedData.createdAt = Date.now();
1216
+ clonedData.updatedAt = undefined;
1191
1217
  }
1192
- data = this.formatData(data, this.tablesMap.get(tableName).schema, true);
1218
+ clonedData = this.formatData(clonedData, this.tablesMap.get(tableName).schema, true);
1193
1219
  const pathesContents = this.joinPathesContents(tableName, this.tablesMap.get(tableName).config.prepend
1194
- ? Array.isArray(data)
1195
- ? data.toReversed()
1196
- : data
1197
- : data);
1220
+ ? Array.isArray(clonedData)
1221
+ ? clonedData.toReversed()
1222
+ : clonedData
1223
+ : clonedData);
1198
1224
  await Promise.allSettled(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(this.tablesMap.get(tableName).config.prepend
1199
1225
  ? await File.prepend(path, content)
1200
1226
  : await File.append(path, content))));
@@ -1208,14 +1234,14 @@ export default class Inibase {
1208
1234
  await rename(paginationFilePath, join(tablePath, `${lastId}-${this.totalItems.get(`${tableName}-*`)}.pagination`));
1209
1235
  if (returnPostedData)
1210
1236
  return this.get(tableName, this.tablesMap.get(tableName).config.prepend
1211
- ? Array.isArray(data)
1212
- ? data.map((_, index) => index + 1).toReversed()
1237
+ ? Array.isArray(clonedData)
1238
+ ? clonedData.map((_, index) => index + 1).toReversed()
1213
1239
  : 1
1214
- : Array.isArray(data)
1215
- ? data
1240
+ : Array.isArray(clonedData)
1241
+ ? clonedData
1216
1242
  .map((_, index) => this.totalItems.get(`${tableName}-*`) - index)
1217
1243
  .toReversed()
1218
- : this.totalItems.get(`${tableName}-*`), options, !Utils.isArrayOfObjects(data));
1244
+ : this.totalItems.get(`${tableName}-*`), options, !Utils.isArrayOfObjects(clonedData));
1219
1245
  }
1220
1246
  finally {
1221
1247
  if (renameList.length)
@@ -1230,21 +1256,22 @@ export default class Inibase {
1230
1256
  const renameList = [];
1231
1257
  const tablePath = join(this.databasePath, tableName);
1232
1258
  await this.throwErrorIfTableEmpty(tableName);
1259
+ let clonedData = JSON.parse(JSON.stringify(data));
1233
1260
  if (!where) {
1234
- if (Utils.isArrayOfObjects(data)) {
1235
- if (!data.every((item) => Object.hasOwn(item, "id") && Utils.isValidID(item.id)))
1261
+ if (Utils.isArrayOfObjects(clonedData)) {
1262
+ if (!clonedData.every((item) => Object.hasOwn(item, "id") && Utils.isValidID(item.id)))
1236
1263
  throw this.createError("INVALID_ID");
1237
- return this.put(tableName, data, data.map(({ id }) => id), options, returnUpdatedData || undefined);
1264
+ return this.put(tableName, clonedData, clonedData.map(({ id }) => id), options, returnUpdatedData || undefined);
1238
1265
  }
1239
1266
  if (Object.hasOwn(data, "id")) {
1240
- if (!Utils.isValidID(data.id))
1241
- throw this.createError("INVALID_ID", data.id);
1242
- return this.put(tableName, data, data.id, options, returnUpdatedData || undefined);
1267
+ if (!Utils.isValidID(clonedData.id))
1268
+ throw this.createError("INVALID_ID", clonedData.id);
1269
+ return this.put(tableName, data, clonedData.id, options, returnUpdatedData || undefined);
1243
1270
  }
1244
- await this.validateData(tableName, data, true);
1245
- data = this.formatData(data, this.tablesMap.get(tableName).schema, true);
1271
+ await this.validateData(tableName, clonedData, true);
1272
+ clonedData = this.formatData(clonedData, this.tablesMap.get(tableName).schema, true);
1246
1273
  const pathesContents = this.joinPathesContents(tableName, {
1247
- ...(({ id, ...restOfData }) => restOfData)(data),
1274
+ ...(({ id, ...restOfData }) => restOfData)(clonedData),
1248
1275
  updatedAt: Date.now(),
1249
1276
  });
1250
1277
  try {
@@ -1271,19 +1298,19 @@ export default class Inibase {
1271
1298
  else if ((Array.isArray(where) && where.every(Utils.isValidID)) ||
1272
1299
  Utils.isValidID(where)) {
1273
1300
  const lineNumbers = await this.get(tableName, where, undefined, undefined, true);
1274
- return this.put(tableName, data, lineNumbers, options, returnUpdatedData || undefined);
1301
+ return this.put(tableName, clonedData, lineNumbers, options, returnUpdatedData || undefined);
1275
1302
  }
1276
1303
  else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
1277
1304
  Utils.isNumber(where)) {
1278
1305
  // "where" in this case, is the line(s) number(s) and not id(s)
1279
- await this.validateData(tableName, data, true);
1280
- data = this.formatData(data, this.tablesMap.get(tableName).schema, true);
1281
- const pathesContents = Object.fromEntries(Object.entries(this.joinPathesContents(tableName, Utils.isArrayOfObjects(data)
1282
- ? data.map((item) => ({
1306
+ await this.validateData(tableName, clonedData, true);
1307
+ clonedData = this.formatData(clonedData, this.tablesMap.get(tableName).schema, true);
1308
+ const pathesContents = Object.fromEntries(Object.entries(this.joinPathesContents(tableName, Utils.isArrayOfObjects(clonedData)
1309
+ ? clonedData.map((item) => ({
1283
1310
  ...item,
1284
1311
  updatedAt: Date.now(),
1285
1312
  }))
1286
- : { ...data, updatedAt: Date.now() })).map(([path, content]) => [
1313
+ : { ...clonedData, updatedAt: Date.now() })).map(([path, content]) => [
1287
1314
  path,
1288
1315
  [...(Array.isArray(where) ? where : [where])].reduce((obj, lineNum, index) => Object.assign(obj, {
1289
1316
  [lineNum]: Array.isArray(content) ? content[index] : content,
@@ -1312,7 +1339,7 @@ export default class Inibase {
1312
1339
  else if (Utils.isObject(where)) {
1313
1340
  const lineNumbers = await this.get(tableName, where, undefined, undefined, true);
1314
1341
  if (lineNumbers)
1315
- return this.put(tableName, data, lineNumbers, options, returnUpdatedData || undefined);
1342
+ return this.put(tableName, clonedData, lineNumbers, options, returnUpdatedData || undefined);
1316
1343
  }
1317
1344
  else
1318
1345
  throw this.createError("INVALID_PARAMETERS");
package/dist/utils.js CHANGED
@@ -318,16 +318,17 @@ export const validateFieldType = (value, fieldType, fieldChildrenType) => {
318
318
  fieldType = detectedFieldType;
319
319
  }
320
320
  if (fieldType === "array" && fieldChildrenType)
321
- return value.every((v) => {
322
- let _fieldChildrenType = fieldChildrenType;
323
- if (Array.isArray(_fieldChildrenType)) {
324
- const detectedFieldType = detectFieldType(v, _fieldChildrenType);
325
- if (!detectedFieldType)
326
- return false;
327
- _fieldChildrenType = detectedFieldType;
328
- }
329
- return validateFieldType(v, _fieldChildrenType);
330
- });
321
+ return (Array.isArray(value) &&
322
+ value.every((v) => {
323
+ let _fieldChildrenType = fieldChildrenType;
324
+ if (Array.isArray(_fieldChildrenType)) {
325
+ const detectedFieldType = detectFieldType(v, _fieldChildrenType);
326
+ if (!detectedFieldType)
327
+ return false;
328
+ _fieldChildrenType = detectedFieldType;
329
+ }
330
+ return validateFieldType(v, _fieldChildrenType);
331
+ }));
331
332
  switch (fieldType) {
332
333
  case "string":
333
334
  return isString(value);
@@ -210,9 +210,20 @@ export const isEqual = (originalValue, comparedValue, fieldType) => {
210
210
  : false;
211
211
  case "boolean":
212
212
  return Number(originalValue) === Number(comparedValue);
213
- default:
214
- return ((!String(comparedValue).length && originalValue === undefined) ||
215
- originalValue == comparedValue);
213
+ default: {
214
+ // Fast checks for null-like values
215
+ const isOriginalNullLike = originalValue === null ||
216
+ originalValue === undefined ||
217
+ originalValue === "";
218
+ const isComparedNullLike = comparedValue === null ||
219
+ comparedValue === undefined ||
220
+ comparedValue === "";
221
+ // If both are null-like, treat as equivalent
222
+ if (isOriginalNullLike && isComparedNullLike)
223
+ return true;
224
+ // Direct equality check for other cases
225
+ return originalValue === comparedValue;
226
+ }
216
227
  }
217
228
  };
218
229
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inibase",
3
- "version": "1.0.0-rc.124",
3
+ "version": "1.0.0-rc.126",
4
4
  "type": "module",
5
5
  "author": {
6
6
  "name": "Karim Amahtil",
@@ -68,6 +68,7 @@
68
68
  }
69
69
  },
70
70
  "devDependencies": {
71
+ "@biomejs/biome": "1.9.4",
71
72
  "@types/bun": "^1.1.10",
72
73
  "@types/node": "^22.7.4",
73
74
  "tinybench": "^3.0.7",
@@ -81,6 +82,7 @@
81
82
  "scripts": {
82
83
  "prepublish": "npx tsc",
83
84
  "build": "npx tsc",
84
- "benchmark": "./benchmark/run.js"
85
+ "benchmark": "./benchmark/run.js",
86
+ "test": "npx tsx ./tests/inibase.test.ts"
85
87
  }
86
88
  }