inibase 1.6.0 → 1.6.1

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/dist/index.d.ts CHANGED
@@ -77,8 +77,9 @@ export default class Inibase {
77
77
  private uniqueMap;
78
78
  private schemaFileExtension;
79
79
  constructor(database: string, mainFolder?: string, language?: ErrorLang);
80
- private static errorMessages;
81
80
  createError(name: ErrorCode, variable?: string | number | (string | number)[]): Error;
81
+ private validateName;
82
+ private validateSchema;
82
83
  private getFileExtension;
83
84
  private schemaToIdsPath;
84
85
  /**
package/dist/index.js CHANGED
@@ -40,9 +40,9 @@ export default class Inibase {
40
40
  uniqueMap;
41
41
  schemaFileExtension = process.env.INIBASE_SCHEMA_EXTENSION ?? "json";
42
42
  constructor(database, mainFolder = ".", language = "en") {
43
- Utils.validateName(database);
44
- this.databasePath = join(mainFolder, database);
45
43
  this.language = language;
44
+ this.validateName(database);
45
+ this.databasePath = join(mainFolder, database);
46
46
  this.pageInfo = {};
47
47
  this.totalItems = new Map();
48
48
  this.uniqueMap = new Map();
@@ -58,87 +58,14 @@ export default class Inibase {
58
58
  else
59
59
  globalConfig.salt = Buffer.from(process.env.INIBASE_SECRET, "hex");
60
60
  }
61
- static errorMessages = {
62
- en: {
63
- TABLE_EMPTY: "Table {variable} is empty",
64
- TABLE_EXISTS: "Table {variable} already exists",
65
- TABLE_NOT_EXISTS: "Table {variable} doesn't exist",
66
- NO_SCHEMA: "Table {variable} does't have a schema",
67
- GROUP_UNIQUE: "Group {variable} should be unique, got duplicated content in {variable}",
68
- FIELD_UNIQUE: "Field {variable} should be unique, got {variable} instead",
69
- FIELD_REQUIRED: "Field {variable} is required",
70
- INVALID_ID: "The given ID(s) is/are not valid(s)",
71
- INVALID_TYPE: "Expect {variable} to be {variable}, got {variable} instead",
72
- INVALID_PARAMETERS: "The given parameters are not valid",
73
- INVALID_REGEX_MATCH: "Field {variable} does not match the expected pattern",
74
- INVALID_NAME: "Name {variable} is not valid",
75
- NO_ENV: Number(process.versions.node.split(".").reduce((a, b) => a + b)) >= 26
76
- ? "please run with '--env-file=.env'"
77
- : "please use dotenv",
78
- },
79
- ar: {
80
- TABLE_EMPTY: "الجدول {variable} فارغ",
81
- TABLE_EXISTS: "الجدول {variable} موجود بالفعل",
82
- TABLE_NOT_EXISTS: "الجدول {variable} غير موجود",
83
- NO_SCHEMA: "الجدول {variable} ليس لديه مخطط",
84
- GROUP_UNIQUE: "المجموعة {variable} يجب أن تكون فريدة، تم العثور على محتوى مكرر في {variable}",
85
- FIELD_UNIQUE: "الحقل {variable} يجب أن يكون فريدًا، تم العثور على {variable} بدلاً من ذلك",
86
- FIELD_REQUIRED: "الحقل {variable} مطلوب",
87
- INVALID_ID: "المعرف أو المعرفات المقدمة غير صالحة",
88
- INVALID_TYPE: "من المتوقع أن يكون {variable} من النوع {variable}، لكن تم العثور على {variable} بدلاً من ذلك",
89
- INVALID_PARAMETERS: "المعلمات المقدمة غير صالحة",
90
- INVALID_REGEX_MATCH: "الحقل {variable} لا يتطابق مع النمط المتوقع",
91
- INVALID_NAME: "الاسم {variable} غير صالح",
92
- NO_ENV: Number(process.versions.node.split(".").reduce((a, b) => a + b)) >= 26
93
- ? "يرجى التشغيل باستخدام '--env-file=.env'"
94
- : "يرجى استخدام dotenv",
95
- },
96
- fr: {
97
- TABLE_EMPTY: "La table {variable} est vide",
98
- TABLE_EXISTS: "La table {variable} existe déjà",
99
- TABLE_NOT_EXISTS: "La table {variable} n'existe pas",
100
- NO_SCHEMA: "La table {variable} n'a pas de schéma",
101
- GROUP_UNIQUE: "Le groupe {variable} doit être unique, contenu dupliqué trouvé dans {variable}",
102
- FIELD_UNIQUE: "Le champ {variable} doit être unique, trouvé {variable} à la place",
103
- FIELD_REQUIRED: "Le champ {variable} est obligatoire",
104
- INVALID_ID: "Le(s) ID donné(s) n'est/ne sont pas valide(s)",
105
- INVALID_TYPE: "Attendu que {variable} soit de type {variable}, mais trouvé {variable} à la place",
106
- INVALID_PARAMETERS: "Les paramètres donnés ne sont pas valides",
107
- INVALID_REGEX_MATCH: "Le champ {variable} ne correspond pas au modèle attendu",
108
- INVALID_NAME: "Le nom {variable} n'est pas valide",
109
- NO_ENV: Number(process.versions.node.split(".").reduce((a, b) => a + b)) >= 26
110
- ? "veuillez exécuter avec '--env-file=.env'"
111
- : "veuillez utiliser dotenv",
112
- },
113
- es: {
114
- TABLE_EMPTY: "La tabla {variable} está vacía",
115
- TABLE_EXISTS: "La tabla {variable} ya existe",
116
- TABLE_NOT_EXISTS: "La tabla {variable} no existe",
117
- NO_SCHEMA: "La tabla {variable} no tiene un esquema",
118
- GROUP_UNIQUE: "El grupo {variable} debe ser único, se encontró contenido duplicado en {variable}",
119
- FIELD_UNIQUE: "El campo {variable} debe ser único, se encontró {variable} en su lugar",
120
- FIELD_REQUIRED: "El campo {variable} es obligatorio",
121
- INVALID_ID: "El/los ID proporcionado(s) no es/son válido(s)",
122
- INVALID_TYPE: "Se espera que {variable} sea {variable}, pero se encontró {variable} en su lugar",
123
- INVALID_PARAMETERS: "Los parámetros proporcionados no son válidos",
124
- INVALID_REGEX_MATCH: "El campo {variable} no coincide con el patrón esperado",
125
- INVALID_NAME: "El nombre {variable} no es válido",
126
- NO_ENV: Number(process.versions.node.split(".").reduce((a, b) => a + b)) >= 26
127
- ? "por favor ejecute con '--env-file=.env'"
128
- : "por favor use dotenv",
129
- },
130
- };
131
61
  createError(name, variable) {
132
- const errorMessage = Inibase.errorMessages[this.language]?.[name];
133
- if (!errorMessage)
134
- return new Error("ERR");
135
- const error = new Error(variable
136
- ? Array.isArray(variable)
137
- ? errorMessage.replace(/\{variable\}/g, () => variable.shift()?.toString() ?? "")
138
- : errorMessage.replaceAll("{variable}", `'${variable.toString()}'`)
139
- : errorMessage.replaceAll("{variable}", ""));
140
- error.name = name;
141
- return error;
62
+ return Utils.createError(this.language, name, variable);
63
+ }
64
+ validateName(name) {
65
+ Utils.validateName(name, this.language);
66
+ }
67
+ validateSchema(schema) {
68
+ Utils.validateSchema(schema, this.language);
142
69
  }
143
70
  getFileExtension(tableName) {
144
71
  let mainExtension = this.fileExtension;
@@ -169,9 +96,9 @@ export default class Inibase {
169
96
  * @param {TableConfig} [config]
170
97
  */
171
98
  async createTable(tableName, schema, config) {
172
- Utils.validateName(tableName);
99
+ this.validateName(tableName);
173
100
  if (schema)
174
- Utils.validateSchema(schema);
101
+ this.validateSchema(schema);
175
102
  const tablePath = join(this.databasePath, tableName);
176
103
  if (await File.isExists(tablePath))
177
104
  throw this.createError("TABLE_EXISTS", tableName);
@@ -220,15 +147,15 @@ export default class Inibase {
220
147
  * @param {(TableConfig&{name?: string})} [config]
221
148
  */
222
149
  async updateTable(tableName, schema, config) {
223
- Utils.validateName(tableName);
150
+ this.validateName(tableName);
224
151
  if (config?.name)
225
- Utils.validateName(config.name);
152
+ this.validateName(config.name);
226
153
  const table = await this.getTable(tableName);
227
154
  if (!table)
228
155
  return;
229
156
  const tablePath = join(this.databasePath, tableName);
230
157
  if (schema) {
231
- Utils.validateSchema(schema);
158
+ this.validateSchema(schema);
232
159
  // remove id from schema
233
160
  schema = schema.filter(({ key }) => !["id", "createdAt", "updatedAt"].includes(key));
234
161
  let schemaIdFilePath = "";
@@ -343,7 +270,7 @@ export default class Inibase {
343
270
  * @return {*} {Promise<TableObject | undefined>}
344
271
  */
345
272
  async getTable(tableName) {
346
- Utils.validateName(tableName);
273
+ this.validateName(tableName);
347
274
  const tablePath = join(this.databasePath, tableName);
348
275
  if (!(await File.isExists(tablePath)))
349
276
  throw this.createError("TABLE_NOT_EXISTS", tableName);
@@ -363,7 +290,7 @@ export default class Inibase {
363
290
  return globalConfig[this.databasePath].tables?.get(tableName);
364
291
  }
365
292
  async getTableSchema(tableName) {
366
- Utils.validateName(tableName);
293
+ this.validateName(tableName);
367
294
  const tablePath = join(this.databasePath, tableName);
368
295
  let schemaFile;
369
296
  let schema;
@@ -1166,7 +1093,7 @@ export default class Inibase {
1166
1093
  * @param {string} tableName
1167
1094
  */
1168
1095
  async clearCache(tableName) {
1169
- Utils.validateName(tableName);
1096
+ this.validateName(tableName);
1170
1097
  const cacheFolderPath = join(this.databasePath, tableName, ".cache");
1171
1098
  await rm(cacheFolderPath, { recursive: true, force: true });
1172
1099
  await mkdir(cacheFolderPath);
@@ -1175,7 +1102,7 @@ export default class Inibase {
1175
1102
  page: 1,
1176
1103
  perPage: 15,
1177
1104
  }, onlyOne, onlyLinesNumbers, _whereIsLinesNumbers) {
1178
- Utils.validateName(tableName);
1105
+ this.validateName(tableName);
1179
1106
  const tablePath = join(this.databasePath, tableName);
1180
1107
  // Ensure options.columns is an array
1181
1108
  if (options.columns) {
@@ -1183,7 +1110,7 @@ export default class Inibase {
1183
1110
  ? options.columns
1184
1111
  : [options.columns];
1185
1112
  for (const column of options.columns)
1186
- Utils.validateName(column);
1113
+ this.validateName(column);
1187
1114
  if (options.columns.length && !options.columns.includes("id"))
1188
1115
  options.columns.push("id");
1189
1116
  }
@@ -1426,12 +1353,12 @@ export default class Inibase {
1426
1353
  page: 1,
1427
1354
  perPage: 15,
1428
1355
  };
1429
- Utils.validateName(tableName);
1356
+ this.validateName(tableName);
1430
1357
  if (options.columns)
1431
1358
  for (const column of (Array.isArray(options.columns)
1432
1359
  ? options.columns
1433
1360
  : [options.columns]))
1434
- Utils.validateName(column);
1361
+ this.validateName(column);
1435
1362
  const tablePath = join(this.databasePath, tableName);
1436
1363
  await this.getTable(tableName);
1437
1364
  if (!globalConfig[this.databasePath].tables?.get(tableName)?.schema)
@@ -1512,12 +1439,12 @@ export default class Inibase {
1512
1439
  perPage: 15,
1513
1440
  }, returnUpdatedData, _whereIsLinesNumbers) {
1514
1441
  const renameList = [];
1515
- Utils.validateName(tableName);
1442
+ this.validateName(tableName);
1516
1443
  if (options.columns)
1517
1444
  for (const column of (Array.isArray(options.columns)
1518
1445
  ? options.columns
1519
1446
  : [options.columns]))
1520
- Utils.validateName(column);
1447
+ this.validateName(column);
1521
1448
  const tablePath = join(this.databasePath, tableName);
1522
1449
  await this.throwErrorIfTableEmpty(tableName);
1523
1450
  let clonedData = structuredClone(data);
@@ -1632,7 +1559,7 @@ export default class Inibase {
1632
1559
  * @return {boolean | null} {(Promise<boolean | null>)}
1633
1560
  */
1634
1561
  async delete(tableName, where, _whereIsLinesNumbers) {
1635
- Utils.validateName(tableName);
1562
+ this.validateName(tableName);
1636
1563
  const tablePath = join(this.databasePath, tableName);
1637
1564
  await this.throwErrorIfTableEmpty(tableName);
1638
1565
  if (!where) {
@@ -1726,11 +1653,11 @@ export default class Inibase {
1726
1653
  return false;
1727
1654
  }
1728
1655
  async sum(tableName, columns, where) {
1729
- Utils.validateName(tableName);
1656
+ this.validateName(tableName);
1730
1657
  if (!Array.isArray(columns))
1731
1658
  columns = [columns];
1732
1659
  for (const column of columns)
1733
- Utils.validateName(column);
1660
+ this.validateName(column);
1734
1661
  await this.throwErrorIfTableEmpty(tableName);
1735
1662
  const RETURN = {};
1736
1663
  const tablePath = join(this.databasePath, tableName);
@@ -1750,11 +1677,11 @@ export default class Inibase {
1750
1677
  return columns.length > 1 ? RETURN : Object.values(RETURN)[0];
1751
1678
  }
1752
1679
  async max(tableName, columns, where) {
1753
- Utils.validateName(tableName);
1680
+ this.validateName(tableName);
1754
1681
  if (!Array.isArray(columns))
1755
1682
  columns = [columns];
1756
1683
  for (const column of columns)
1757
- Utils.validateName(column);
1684
+ this.validateName(column);
1758
1685
  const RETURN = {};
1759
1686
  const tablePath = join(this.databasePath, tableName);
1760
1687
  await this.throwErrorIfTableEmpty(tableName);
@@ -1774,11 +1701,11 @@ export default class Inibase {
1774
1701
  return RETURN;
1775
1702
  }
1776
1703
  async min(tableName, columns, where) {
1777
- Utils.validateName(tableName);
1704
+ this.validateName(tableName);
1778
1705
  if (!Array.isArray(columns))
1779
1706
  columns = [columns];
1780
1707
  for (const column of columns)
1781
- Utils.validateName(column);
1708
+ this.validateName(column);
1782
1709
  const RETURN = {};
1783
1710
  const tablePath = join(this.databasePath, tableName);
1784
1711
  await this.throwErrorIfTableEmpty(tableName);
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ComparisonOperator, Field, FieldType, Schema } from "./index.js";
1
+ import type { ComparisonOperator, ErrorCode, ErrorLang, Field, FieldType, Schema } from "./index.js";
2
2
  /**
3
3
  * Type guard function to check if the input is an array of objects.
4
4
  *
@@ -218,16 +218,31 @@ export declare const findLastIdNumber: (schema: Schema) => number;
218
218
  export declare function addIdToSchema(schema: Schema, startWithID: {
219
219
  value: number;
220
220
  }): Field[];
221
+ /**
222
+ * Translated error messages for every supported language and error code.
223
+ * The `{variable}` placeholder is replaced with the relevant value by
224
+ * {@link createError}.
225
+ */
226
+ export declare const ERROR_MESSAGES: Record<ErrorLang, Record<ErrorCode, string>>;
227
+ /**
228
+ * Creates a translated error exactly like the ones thrown by Inibase methods.
229
+ *
230
+ * @param language - The language to render the error message in.
231
+ * @param name - The error code, also used as the error `name`.
232
+ * @param variable - Optional value(s) used to fill the `{variable}` placeholders.
233
+ * @returns An `Error` whose `name` is the error code and `message` is translated.
234
+ */
235
+ export declare const createError: (language: ErrorLang, name: ErrorCode, variable?: string | number | (string | number)[]) => Error;
221
236
  /**
222
237
  * Validates that a string is a safe name for a table, database or column.
223
238
  *
224
- * Names must match `^[a-zA-Z0-9][a-zA-Z0-9_-]*$` (starts with an alphanumeric
225
- * character, followed by alphanumerics, underscores or hyphens) and be at most
226
- * 255 characters long.
239
+ * Names must start with an alphanumeric character (Latin or Arabic), followed
240
+ * by alphanumerics (Latin or Arabic), underscores, hyphens or spaces, and be
241
+ * at most 255 characters long. Leading or trailing spaces are not allowed.
227
242
  *
228
243
  * This prevents path traversal (`../`, `..\\`), null-byte injection (`\0`),
229
- * shell injection (`;`, `|`, backticks, `$()`, spaces, slashes) and other
230
- * reserved-name issues.
244
+ * shell injection (`;`, `|`, backticks, `$()`, tabs, newlines, slashes) and
245
+ * other reserved-name issues.
231
246
  *
232
247
  * @param input - The value to be checked.
233
248
  * @returns boolean - True if the name is safe to use, false otherwise.
@@ -235,16 +250,18 @@ export declare function addIdToSchema(schema: Schema, startWithID: {
235
250
  export declare const isValidName: (input: unknown) => input is string;
236
251
  /**
237
252
  * Validates that a string is a safe name for a table, database or column and
238
- * throws an error if it is not.
253
+ * throws a translated `INVALID_NAME` error if it is not.
239
254
  *
240
255
  * @param name - The name to validate.
256
+ * @param language - The language to render the error message in.
241
257
  * @throws {Error} If the name is not a safe name.
242
258
  */
243
- export declare const validateName: (name: string) => void;
259
+ export declare const validateName: (name: string, language?: ErrorLang) => void;
244
260
  /**
245
261
  * Recursively validates every field key of a schema.
246
262
  *
247
263
  * @param schema - The schema to validate.
264
+ * @param language - The language to render the error message in.
248
265
  * @throws {Error} If any field key is not a safe name.
249
266
  */
250
- export declare const validateSchema: (schema: Schema) => void;
267
+ export declare const validateSchema: (schema: Schema, language?: ErrorLang) => void;
package/dist/utils.js CHANGED
@@ -628,16 +628,111 @@ export function addIdToSchema(schema, startWithID) {
628
628
  const addIdToSchemaHelper = (schema) => schema.map(addIdToField);
629
629
  return addIdToSchemaHelper(clonedSchema);
630
630
  }
631
+ /**
632
+ * Translated error messages for every supported language and error code.
633
+ * The `{variable}` placeholder is replaced with the relevant value by
634
+ * {@link createError}.
635
+ */
636
+ export const ERROR_MESSAGES = {
637
+ en: {
638
+ TABLE_EMPTY: "Table {variable} is empty",
639
+ TABLE_EXISTS: "Table {variable} already exists",
640
+ TABLE_NOT_EXISTS: "Table {variable} doesn't exist",
641
+ NO_SCHEMA: "Table {variable} does't have a schema",
642
+ GROUP_UNIQUE: "Group {variable} should be unique, got duplicated content in {variable}",
643
+ FIELD_UNIQUE: "Field {variable} should be unique, got {variable} instead",
644
+ FIELD_REQUIRED: "Field {variable} is required",
645
+ INVALID_ID: "The given ID(s) is/are not valid(s)",
646
+ INVALID_TYPE: "Expect {variable} to be {variable}, got {variable} instead",
647
+ INVALID_PARAMETERS: "The given parameters are not valid",
648
+ INVALID_REGEX_MATCH: "Field {variable} does not match the expected pattern",
649
+ INVALID_NAME: "Name {variable} is not valid",
650
+ NO_ENV: Number(process.versions.node.split(".").reduce((a, b) => a + b)) >= 26
651
+ ? "please run with '--env-file=.env'"
652
+ : "please use dotenv",
653
+ },
654
+ ar: {
655
+ TABLE_EMPTY: "الجدول {variable} فارغ",
656
+ TABLE_EXISTS: "الجدول {variable} موجود بالفعل",
657
+ TABLE_NOT_EXISTS: "الجدول {variable} غير موجود",
658
+ NO_SCHEMA: "الجدول {variable} ليس لديه مخطط",
659
+ GROUP_UNIQUE: "المجموعة {variable} يجب أن تكون فريدة، تم العثور على محتوى مكرر في {variable}",
660
+ FIELD_UNIQUE: "الحقل {variable} يجب أن يكون فريدًا، تم العثور على {variable} بدلاً من ذلك",
661
+ FIELD_REQUIRED: "الحقل {variable} مطلوب",
662
+ INVALID_ID: "المعرف أو المعرفات المقدمة غير صالحة",
663
+ INVALID_TYPE: "من المتوقع أن يكون {variable} من النوع {variable}، لكن تم العثور على {variable} بدلاً من ذلك",
664
+ INVALID_PARAMETERS: "المعلمات المقدمة غير صالحة",
665
+ INVALID_REGEX_MATCH: "الحقل {variable} لا يتطابق مع النمط المتوقع",
666
+ INVALID_NAME: "الاسم {variable} غير صالح",
667
+ NO_ENV: Number(process.versions.node.split(".").reduce((a, b) => a + b)) >= 26
668
+ ? "يرجى التشغيل باستخدام '--env-file=.env'"
669
+ : "يرجى استخدام dotenv",
670
+ },
671
+ fr: {
672
+ TABLE_EMPTY: "La table {variable} est vide",
673
+ TABLE_EXISTS: "La table {variable} existe déjà",
674
+ TABLE_NOT_EXISTS: "La table {variable} n'existe pas",
675
+ NO_SCHEMA: "La table {variable} n'a pas de schéma",
676
+ GROUP_UNIQUE: "Le groupe {variable} doit être unique, contenu dupliqué trouvé dans {variable}",
677
+ FIELD_UNIQUE: "Le champ {variable} doit être unique, trouvé {variable} à la place",
678
+ FIELD_REQUIRED: "Le champ {variable} est obligatoire",
679
+ INVALID_ID: "Le(s) ID donné(s) n'est/ne sont pas valide(s)",
680
+ INVALID_TYPE: "Attendu que {variable} soit de type {variable}, mais trouvé {variable} à la place",
681
+ INVALID_PARAMETERS: "Les paramètres donnés ne sont pas valides",
682
+ INVALID_REGEX_MATCH: "Le champ {variable} ne correspond pas au modèle attendu",
683
+ INVALID_NAME: "Le nom {variable} n'est pas valide",
684
+ NO_ENV: Number(process.versions.node.split(".").reduce((a, b) => a + b)) >= 26
685
+ ? "veuillez exécuter avec '--env-file=.env'"
686
+ : "veuillez utiliser dotenv",
687
+ },
688
+ es: {
689
+ TABLE_EMPTY: "La tabla {variable} está vacía",
690
+ TABLE_EXISTS: "La tabla {variable} ya existe",
691
+ TABLE_NOT_EXISTS: "La tabla {variable} no existe",
692
+ NO_SCHEMA: "La tabla {variable} no tiene un esquema",
693
+ GROUP_UNIQUE: "El grupo {variable} debe ser único, se encontró contenido duplicado en {variable}",
694
+ FIELD_UNIQUE: "El campo {variable} debe ser único, se encontró {variable} en su lugar",
695
+ FIELD_REQUIRED: "El campo {variable} es obligatorio",
696
+ INVALID_ID: "El/los ID proporcionado(s) no es/son válido(s)",
697
+ INVALID_TYPE: "Se espera que {variable} sea {variable}, pero se encontró {variable} en su lugar",
698
+ INVALID_PARAMETERS: "Los parámetros proporcionados no son válidos",
699
+ INVALID_REGEX_MATCH: "El campo {variable} no coincide con el patrón esperado",
700
+ INVALID_NAME: "El nombre {variable} no es válido",
701
+ NO_ENV: Number(process.versions.node.split(".").reduce((a, b) => a + b)) >= 26
702
+ ? "por favor ejecute con '--env-file=.env'"
703
+ : "por favor use dotenv",
704
+ },
705
+ };
706
+ /**
707
+ * Creates a translated error exactly like the ones thrown by Inibase methods.
708
+ *
709
+ * @param language - The language to render the error message in.
710
+ * @param name - The error code, also used as the error `name`.
711
+ * @param variable - Optional value(s) used to fill the `{variable}` placeholders.
712
+ * @returns An `Error` whose `name` is the error code and `message` is translated.
713
+ */
714
+ export const createError = (language, name, variable) => {
715
+ const errorMessage = ERROR_MESSAGES[language]?.[name];
716
+ if (!errorMessage)
717
+ return new Error("ERR");
718
+ const error = new Error(variable
719
+ ? Array.isArray(variable)
720
+ ? errorMessage.replace(/\{variable\}/g, () => variable.shift()?.toString() ?? "")
721
+ : errorMessage.replaceAll("{variable}", `'${variable.toString()}'`)
722
+ : errorMessage.replaceAll("{variable}", ""));
723
+ error.name = name;
724
+ return error;
725
+ };
631
726
  /**
632
727
  * Validates that a string is a safe name for a table, database or column.
633
728
  *
634
- * Names must match `^[a-zA-Z0-9][a-zA-Z0-9_-]*$` (starts with an alphanumeric
635
- * character, followed by alphanumerics, underscores or hyphens) and be at most
636
- * 255 characters long.
729
+ * Names must start with an alphanumeric character (Latin or Arabic), followed
730
+ * by alphanumerics (Latin or Arabic), underscores, hyphens or spaces, and be
731
+ * at most 255 characters long. Leading or trailing spaces are not allowed.
637
732
  *
638
733
  * This prevents path traversal (`../`, `..\\`), null-byte injection (`\0`),
639
- * shell injection (`;`, `|`, backticks, `$()`, spaces, slashes) and other
640
- * reserved-name issues.
734
+ * shell injection (`;`, `|`, backticks, `$()`, tabs, newlines, slashes) and
735
+ * other reserved-name issues.
641
736
  *
642
737
  * @param input - The value to be checked.
643
738
  * @returns boolean - True if the name is safe to use, false otherwise.
@@ -645,30 +740,38 @@ export function addIdToSchema(schema, startWithID) {
645
740
  export const isValidName = (input) => typeof input === "string" &&
646
741
  input.length > 0 &&
647
742
  input.length <= 255 &&
648
- /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(input);
743
+ validNamePattern.test(input);
744
+ // Word characters: Latin alphanumerics, underscores, hyphens and Arabic blocks.
745
+ const nameWordCharClass = "a-zA-Z0-9_\\u0600-\\u06FF\\u0750-\\u077F\\u08A0-\\u08FF-";
746
+ // Leading characters: same as word chars minus underscore and hyphen.
747
+ const nameFirstCharClass = "a-zA-Z0-9\\u0600-\\u06FF\\u0750-\\u077F\\u08A0-\\u08FF";
748
+ // eslint-disable-next-line no-misleading-character-class
749
+ const validNamePattern = new RegExp(`^[${nameFirstCharClass}](?:[${nameWordCharClass} ]*[${nameWordCharClass}])?$`);
649
750
  /**
650
751
  * Validates that a string is a safe name for a table, database or column and
651
- * throws an error if it is not.
752
+ * throws a translated `INVALID_NAME` error if it is not.
652
753
  *
653
754
  * @param name - The name to validate.
755
+ * @param language - The language to render the error message in.
654
756
  * @throws {Error} If the name is not a safe name.
655
757
  */
656
- export const validateName = (name) => {
758
+ export const validateName = (name, language = "en") => {
657
759
  if (!isValidName(name))
658
- throw new Error(`Invalid name: '${name}'. Names must contain only alphanumeric characters, underscores or hyphens, must start with an alphanumeric character and must be at most 255 characters long.`);
760
+ throw createError(language, "INVALID_NAME", name);
659
761
  };
660
762
  /**
661
763
  * Recursively validates every field key of a schema.
662
764
  *
663
765
  * @param schema - The schema to validate.
766
+ * @param language - The language to render the error message in.
664
767
  * @throws {Error} If any field key is not a safe name.
665
768
  */
666
- export const validateSchema = (schema) => {
769
+ export const validateSchema = (schema, language = "en") => {
667
770
  for (const field of schema) {
668
- validateName(field.key);
771
+ validateName(field.key, language);
669
772
  if (field.table)
670
- validateName(field.table);
773
+ validateName(field.table, language);
671
774
  if (field.children && isArrayOfObjects(field.children))
672
- validateSchema(field.children);
775
+ validateSchema(field.children, language);
673
776
  }
674
777
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inibase",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "type": "module",
5
5
  "author": {
6
6
  "name": "Karim Amahtil",