inibase 1.5.11 → 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/cli.js +5 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +52 -84
- package/dist/utils.d.ts +48 -1
- package/dist/utils.js +147 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,8 @@ import { parseArgs } from "node:util";
|
|
|
7
7
|
import Inison from "inison";
|
|
8
8
|
import { isExists } from "./file.js";
|
|
9
9
|
import Inibase, {} from "./index.js";
|
|
10
|
-
import { isNumber, isStringified, setField, unsetField } from "./utils.js";
|
|
10
|
+
import { isNumber, isStringified, isValidName, setField, unsetField, } from "./utils.js";
|
|
11
|
+
const isSafeName = (input) => isValidName(input);
|
|
11
12
|
const textGreen = (input) => `\u001b[1;32m${input}\u001b[0m`;
|
|
12
13
|
const textRed = (input) => `\u001b[1;31m${input}\u001b[0m`;
|
|
13
14
|
const textBlue = (input) => `\u001b[1;34m${input}\u001b[0m`;
|
|
@@ -178,7 +179,9 @@ rl.on("line", async (input) => {
|
|
|
178
179
|
console.log(`${textRed(" Err:")} Please specify table name`);
|
|
179
180
|
break;
|
|
180
181
|
}
|
|
181
|
-
if (!(
|
|
182
|
+
if (!isSafeName(splitedInput[1]))
|
|
183
|
+
console.log(`${textRed(" Err:")} Invalid table name, only alphanumeric characters, underscores and hyphens are allowed`);
|
|
184
|
+
else if (!(await isExists(join(path, splitedInput[1]))))
|
|
182
185
|
console.log(`${textRed(" Err:")} Table doesn't exist`);
|
|
183
186
|
else {
|
|
184
187
|
table = splitedInput[1];
|
package/dist/index.d.ts
CHANGED
|
@@ -51,7 +51,7 @@ declare global {
|
|
|
51
51
|
entries<T extends object>(o: T): Entries<T>;
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
export declare const ERROR_CODES: readonly ["GROUP_UNIQUE", "FIELD_UNIQUE", "FIELD_REQUIRED", "NO_SCHEMA", "TABLE_EMPTY", "INVALID_ID", "INVALID_TYPE", "INVALID_PARAMETERS", "NO_ENV", "TABLE_EXISTS", "TABLE_NOT_EXISTS", "INVALID_REGEX_MATCH"];
|
|
54
|
+
export declare const ERROR_CODES: readonly ["GROUP_UNIQUE", "FIELD_UNIQUE", "FIELD_REQUIRED", "NO_SCHEMA", "TABLE_EMPTY", "INVALID_ID", "INVALID_TYPE", "INVALID_PARAMETERS", "NO_ENV", "TABLE_EXISTS", "TABLE_NOT_EXISTS", "INVALID_REGEX_MATCH", "INVALID_NAME"];
|
|
55
55
|
export type ErrorCode = (typeof ERROR_CODES)[number];
|
|
56
56
|
export type ErrorLang = "en" | "ar" | "fr" | "es";
|
|
57
57
|
export declare const globalConfig: {
|
|
@@ -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
|
@@ -21,6 +21,7 @@ export const ERROR_CODES = [
|
|
|
21
21
|
"TABLE_EXISTS",
|
|
22
22
|
"TABLE_NOT_EXISTS",
|
|
23
23
|
"INVALID_REGEX_MATCH",
|
|
24
|
+
"INVALID_NAME",
|
|
24
25
|
];
|
|
25
26
|
// hide ExperimentalWarning glob()
|
|
26
27
|
process.removeAllListeners("warning");
|
|
@@ -39,8 +40,9 @@ export default class Inibase {
|
|
|
39
40
|
uniqueMap;
|
|
40
41
|
schemaFileExtension = process.env.INIBASE_SCHEMA_EXTENSION ?? "json";
|
|
41
42
|
constructor(database, mainFolder = ".", language = "en") {
|
|
42
|
-
this.databasePath = join(mainFolder, database);
|
|
43
43
|
this.language = language;
|
|
44
|
+
this.validateName(database);
|
|
45
|
+
this.databasePath = join(mainFolder, database);
|
|
44
46
|
this.pageInfo = {};
|
|
45
47
|
this.totalItems = new Map();
|
|
46
48
|
this.uniqueMap = new Map();
|
|
@@ -56,83 +58,14 @@ export default class Inibase {
|
|
|
56
58
|
else
|
|
57
59
|
globalConfig.salt = Buffer.from(process.env.INIBASE_SECRET, "hex");
|
|
58
60
|
}
|
|
59
|
-
static errorMessages = {
|
|
60
|
-
en: {
|
|
61
|
-
TABLE_EMPTY: "Table {variable} is empty",
|
|
62
|
-
TABLE_EXISTS: "Table {variable} already exists",
|
|
63
|
-
TABLE_NOT_EXISTS: "Table {variable} doesn't exist",
|
|
64
|
-
NO_SCHEMA: "Table {variable} does't have a schema",
|
|
65
|
-
GROUP_UNIQUE: "Group {variable} should be unique, got duplicated content in {variable}",
|
|
66
|
-
FIELD_UNIQUE: "Field {variable} should be unique, got {variable} instead",
|
|
67
|
-
FIELD_REQUIRED: "Field {variable} is required",
|
|
68
|
-
INVALID_ID: "The given ID(s) is/are not valid(s)",
|
|
69
|
-
INVALID_TYPE: "Expect {variable} to be {variable}, got {variable} instead",
|
|
70
|
-
INVALID_PARAMETERS: "The given parameters are not valid",
|
|
71
|
-
INVALID_REGEX_MATCH: "Field {variable} does not match the expected pattern",
|
|
72
|
-
NO_ENV: Number(process.versions.node.split(".").reduce((a, b) => a + b)) >= 26
|
|
73
|
-
? "please run with '--env-file=.env'"
|
|
74
|
-
: "please use dotenv",
|
|
75
|
-
},
|
|
76
|
-
ar: {
|
|
77
|
-
TABLE_EMPTY: "الجدول {variable} فارغ",
|
|
78
|
-
TABLE_EXISTS: "الجدول {variable} موجود بالفعل",
|
|
79
|
-
TABLE_NOT_EXISTS: "الجدول {variable} غير موجود",
|
|
80
|
-
NO_SCHEMA: "الجدول {variable} ليس لديه مخطط",
|
|
81
|
-
GROUP_UNIQUE: "المجموعة {variable} يجب أن تكون فريدة، تم العثور على محتوى مكرر في {variable}",
|
|
82
|
-
FIELD_UNIQUE: "الحقل {variable} يجب أن يكون فريدًا، تم العثور على {variable} بدلاً من ذلك",
|
|
83
|
-
FIELD_REQUIRED: "الحقل {variable} مطلوب",
|
|
84
|
-
INVALID_ID: "المعرف أو المعرفات المقدمة غير صالحة",
|
|
85
|
-
INVALID_TYPE: "من المتوقع أن يكون {variable} من النوع {variable}، لكن تم العثور على {variable} بدلاً من ذلك",
|
|
86
|
-
INVALID_PARAMETERS: "المعلمات المقدمة غير صالحة",
|
|
87
|
-
INVALID_REGEX_MATCH: "الحقل {variable} لا يتطابق مع النمط المتوقع",
|
|
88
|
-
NO_ENV: Number(process.versions.node.split(".").reduce((a, b) => a + b)) >= 26
|
|
89
|
-
? "يرجى التشغيل باستخدام '--env-file=.env'"
|
|
90
|
-
: "يرجى استخدام dotenv",
|
|
91
|
-
},
|
|
92
|
-
fr: {
|
|
93
|
-
TABLE_EMPTY: "La table {variable} est vide",
|
|
94
|
-
TABLE_EXISTS: "La table {variable} existe déjà",
|
|
95
|
-
TABLE_NOT_EXISTS: "La table {variable} n'existe pas",
|
|
96
|
-
NO_SCHEMA: "La table {variable} n'a pas de schéma",
|
|
97
|
-
GROUP_UNIQUE: "Le groupe {variable} doit être unique, contenu dupliqué trouvé dans {variable}",
|
|
98
|
-
FIELD_UNIQUE: "Le champ {variable} doit être unique, trouvé {variable} à la place",
|
|
99
|
-
FIELD_REQUIRED: "Le champ {variable} est obligatoire",
|
|
100
|
-
INVALID_ID: "Le(s) ID donné(s) n'est/ne sont pas valide(s)",
|
|
101
|
-
INVALID_TYPE: "Attendu que {variable} soit de type {variable}, mais trouvé {variable} à la place",
|
|
102
|
-
INVALID_PARAMETERS: "Les paramètres donnés ne sont pas valides",
|
|
103
|
-
INVALID_REGEX_MATCH: "Le champ {variable} ne correspond pas au modèle attendu",
|
|
104
|
-
NO_ENV: Number(process.versions.node.split(".").reduce((a, b) => a + b)) >= 26
|
|
105
|
-
? "veuillez exécuter avec '--env-file=.env'"
|
|
106
|
-
: "veuillez utiliser dotenv",
|
|
107
|
-
},
|
|
108
|
-
es: {
|
|
109
|
-
TABLE_EMPTY: "La tabla {variable} está vacía",
|
|
110
|
-
TABLE_EXISTS: "La tabla {variable} ya existe",
|
|
111
|
-
TABLE_NOT_EXISTS: "La tabla {variable} no existe",
|
|
112
|
-
NO_SCHEMA: "La tabla {variable} no tiene un esquema",
|
|
113
|
-
GROUP_UNIQUE: "El grupo {variable} debe ser único, se encontró contenido duplicado en {variable}",
|
|
114
|
-
FIELD_UNIQUE: "El campo {variable} debe ser único, se encontró {variable} en su lugar",
|
|
115
|
-
FIELD_REQUIRED: "El campo {variable} es obligatorio",
|
|
116
|
-
INVALID_ID: "El/los ID proporcionado(s) no es/son válido(s)",
|
|
117
|
-
INVALID_TYPE: "Se espera que {variable} sea {variable}, pero se encontró {variable} en su lugar",
|
|
118
|
-
INVALID_PARAMETERS: "Los parámetros proporcionados no son válidos",
|
|
119
|
-
INVALID_REGEX_MATCH: "El campo {variable} no coincide con el patrón esperado",
|
|
120
|
-
NO_ENV: Number(process.versions.node.split(".").reduce((a, b) => a + b)) >= 26
|
|
121
|
-
? "por favor ejecute con '--env-file=.env'"
|
|
122
|
-
: "por favor use dotenv",
|
|
123
|
-
},
|
|
124
|
-
};
|
|
125
61
|
createError(name, variable) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
: errorMessage.replaceAll("{variable}", ""));
|
|
134
|
-
error.name = name;
|
|
135
|
-
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);
|
|
136
69
|
}
|
|
137
70
|
getFileExtension(tableName) {
|
|
138
71
|
let mainExtension = this.fileExtension;
|
|
@@ -163,6 +96,9 @@ export default class Inibase {
|
|
|
163
96
|
* @param {TableConfig} [config]
|
|
164
97
|
*/
|
|
165
98
|
async createTable(tableName, schema, config) {
|
|
99
|
+
this.validateName(tableName);
|
|
100
|
+
if (schema)
|
|
101
|
+
this.validateSchema(schema);
|
|
166
102
|
const tablePath = join(this.databasePath, tableName);
|
|
167
103
|
if (await File.isExists(tablePath))
|
|
168
104
|
throw this.createError("TABLE_EXISTS", tableName);
|
|
@@ -211,11 +147,15 @@ export default class Inibase {
|
|
|
211
147
|
* @param {(TableConfig&{name?: string})} [config]
|
|
212
148
|
*/
|
|
213
149
|
async updateTable(tableName, schema, config) {
|
|
150
|
+
this.validateName(tableName);
|
|
151
|
+
if (config?.name)
|
|
152
|
+
this.validateName(config.name);
|
|
214
153
|
const table = await this.getTable(tableName);
|
|
215
154
|
if (!table)
|
|
216
155
|
return;
|
|
217
156
|
const tablePath = join(this.databasePath, tableName);
|
|
218
157
|
if (schema) {
|
|
158
|
+
this.validateSchema(schema);
|
|
219
159
|
// remove id from schema
|
|
220
160
|
schema = schema.filter(({ key }) => !["id", "createdAt", "updatedAt"].includes(key));
|
|
221
161
|
let schemaIdFilePath = "";
|
|
@@ -330,6 +270,7 @@ export default class Inibase {
|
|
|
330
270
|
* @return {*} {Promise<TableObject | undefined>}
|
|
331
271
|
*/
|
|
332
272
|
async getTable(tableName) {
|
|
273
|
+
this.validateName(tableName);
|
|
333
274
|
const tablePath = join(this.databasePath, tableName);
|
|
334
275
|
if (!(await File.isExists(tablePath)))
|
|
335
276
|
throw this.createError("TABLE_NOT_EXISTS", tableName);
|
|
@@ -349,6 +290,7 @@ export default class Inibase {
|
|
|
349
290
|
return globalConfig[this.databasePath].tables?.get(tableName);
|
|
350
291
|
}
|
|
351
292
|
async getTableSchema(tableName) {
|
|
293
|
+
this.validateName(tableName);
|
|
352
294
|
const tablePath = join(this.databasePath, tableName);
|
|
353
295
|
let schemaFile;
|
|
354
296
|
let schema;
|
|
@@ -1151,6 +1093,7 @@ export default class Inibase {
|
|
|
1151
1093
|
* @param {string} tableName
|
|
1152
1094
|
*/
|
|
1153
1095
|
async clearCache(tableName) {
|
|
1096
|
+
this.validateName(tableName);
|
|
1154
1097
|
const cacheFolderPath = join(this.databasePath, tableName, ".cache");
|
|
1155
1098
|
await rm(cacheFolderPath, { recursive: true, force: true });
|
|
1156
1099
|
await mkdir(cacheFolderPath);
|
|
@@ -1159,12 +1102,15 @@ export default class Inibase {
|
|
|
1159
1102
|
page: 1,
|
|
1160
1103
|
perPage: 15,
|
|
1161
1104
|
}, onlyOne, onlyLinesNumbers, _whereIsLinesNumbers) {
|
|
1105
|
+
this.validateName(tableName);
|
|
1162
1106
|
const tablePath = join(this.databasePath, tableName);
|
|
1163
1107
|
// Ensure options.columns is an array
|
|
1164
1108
|
if (options.columns) {
|
|
1165
1109
|
options.columns = Array.isArray(options.columns)
|
|
1166
1110
|
? options.columns
|
|
1167
1111
|
: [options.columns];
|
|
1112
|
+
for (const column of options.columns)
|
|
1113
|
+
this.validateName(column);
|
|
1168
1114
|
if (options.columns.length && !options.columns.includes("id"))
|
|
1169
1115
|
options.columns.push("id");
|
|
1170
1116
|
}
|
|
@@ -1407,6 +1353,12 @@ export default class Inibase {
|
|
|
1407
1353
|
page: 1,
|
|
1408
1354
|
perPage: 15,
|
|
1409
1355
|
};
|
|
1356
|
+
this.validateName(tableName);
|
|
1357
|
+
if (options.columns)
|
|
1358
|
+
for (const column of (Array.isArray(options.columns)
|
|
1359
|
+
? options.columns
|
|
1360
|
+
: [options.columns]))
|
|
1361
|
+
this.validateName(column);
|
|
1410
1362
|
const tablePath = join(this.databasePath, tableName);
|
|
1411
1363
|
await this.getTable(tableName);
|
|
1412
1364
|
if (!globalConfig[this.databasePath].tables?.get(tableName)?.schema)
|
|
@@ -1487,6 +1439,12 @@ export default class Inibase {
|
|
|
1487
1439
|
perPage: 15,
|
|
1488
1440
|
}, returnUpdatedData, _whereIsLinesNumbers) {
|
|
1489
1441
|
const renameList = [];
|
|
1442
|
+
this.validateName(tableName);
|
|
1443
|
+
if (options.columns)
|
|
1444
|
+
for (const column of (Array.isArray(options.columns)
|
|
1445
|
+
? options.columns
|
|
1446
|
+
: [options.columns]))
|
|
1447
|
+
this.validateName(column);
|
|
1490
1448
|
const tablePath = join(this.databasePath, tableName);
|
|
1491
1449
|
await this.throwErrorIfTableEmpty(tableName);
|
|
1492
1450
|
let clonedData = structuredClone(data);
|
|
@@ -1601,6 +1559,7 @@ export default class Inibase {
|
|
|
1601
1559
|
* @return {boolean | null} {(Promise<boolean | null>)}
|
|
1602
1560
|
*/
|
|
1603
1561
|
async delete(tableName, where, _whereIsLinesNumbers) {
|
|
1562
|
+
this.validateName(tableName);
|
|
1604
1563
|
const tablePath = join(this.databasePath, tableName);
|
|
1605
1564
|
await this.throwErrorIfTableEmpty(tableName);
|
|
1606
1565
|
if (!where) {
|
|
@@ -1694,11 +1653,14 @@ export default class Inibase {
|
|
|
1694
1653
|
return false;
|
|
1695
1654
|
}
|
|
1696
1655
|
async sum(tableName, columns, where) {
|
|
1697
|
-
|
|
1698
|
-
const tablePath = join(this.databasePath, tableName);
|
|
1699
|
-
await this.throwErrorIfTableEmpty(tableName);
|
|
1656
|
+
this.validateName(tableName);
|
|
1700
1657
|
if (!Array.isArray(columns))
|
|
1701
1658
|
columns = [columns];
|
|
1659
|
+
for (const column of columns)
|
|
1660
|
+
this.validateName(column);
|
|
1661
|
+
await this.throwErrorIfTableEmpty(tableName);
|
|
1662
|
+
const RETURN = {};
|
|
1663
|
+
const tablePath = join(this.databasePath, tableName);
|
|
1702
1664
|
for await (const column of columns) {
|
|
1703
1665
|
const columnPath = join(tablePath, `${column}${this.getFileExtension(tableName)}`);
|
|
1704
1666
|
if (await File.isExists(columnPath)) {
|
|
@@ -1715,11 +1677,14 @@ export default class Inibase {
|
|
|
1715
1677
|
return columns.length > 1 ? RETURN : Object.values(RETURN)[0];
|
|
1716
1678
|
}
|
|
1717
1679
|
async max(tableName, columns, where) {
|
|
1680
|
+
this.validateName(tableName);
|
|
1681
|
+
if (!Array.isArray(columns))
|
|
1682
|
+
columns = [columns];
|
|
1683
|
+
for (const column of columns)
|
|
1684
|
+
this.validateName(column);
|
|
1718
1685
|
const RETURN = {};
|
|
1719
1686
|
const tablePath = join(this.databasePath, tableName);
|
|
1720
1687
|
await this.throwErrorIfTableEmpty(tableName);
|
|
1721
|
-
if (!Array.isArray(columns))
|
|
1722
|
-
columns = [columns];
|
|
1723
1688
|
for await (const column of columns) {
|
|
1724
1689
|
const columnPath = join(tablePath, `${column}${this.getFileExtension(tableName)}`);
|
|
1725
1690
|
if (await File.isExists(columnPath)) {
|
|
@@ -1736,11 +1701,14 @@ export default class Inibase {
|
|
|
1736
1701
|
return RETURN;
|
|
1737
1702
|
}
|
|
1738
1703
|
async min(tableName, columns, where) {
|
|
1704
|
+
this.validateName(tableName);
|
|
1705
|
+
if (!Array.isArray(columns))
|
|
1706
|
+
columns = [columns];
|
|
1707
|
+
for (const column of columns)
|
|
1708
|
+
this.validateName(column);
|
|
1739
1709
|
const RETURN = {};
|
|
1740
1710
|
const tablePath = join(this.databasePath, tableName);
|
|
1741
1711
|
await this.throwErrorIfTableEmpty(tableName);
|
|
1742
|
-
if (!Array.isArray(columns))
|
|
1743
|
-
columns = [columns];
|
|
1744
1712
|
for await (const column of columns) {
|
|
1745
1713
|
const columnPath = join(tablePath, `${column}${this.getFileExtension(tableName)}`);
|
|
1746
1714
|
if (await File.isExists(columnPath)) {
|
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,3 +218,50 @@ 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;
|
|
236
|
+
/**
|
|
237
|
+
* Validates that a string is a safe name for a table, database or column.
|
|
238
|
+
*
|
|
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.
|
|
242
|
+
*
|
|
243
|
+
* This prevents path traversal (`../`, `..\\`), null-byte injection (`\0`),
|
|
244
|
+
* shell injection (`;`, `|`, backticks, `$()`, tabs, newlines, slashes) and
|
|
245
|
+
* other reserved-name issues.
|
|
246
|
+
*
|
|
247
|
+
* @param input - The value to be checked.
|
|
248
|
+
* @returns boolean - True if the name is safe to use, false otherwise.
|
|
249
|
+
*/
|
|
250
|
+
export declare const isValidName: (input: unknown) => input is string;
|
|
251
|
+
/**
|
|
252
|
+
* Validates that a string is a safe name for a table, database or column and
|
|
253
|
+
* throws a translated `INVALID_NAME` error if it is not.
|
|
254
|
+
*
|
|
255
|
+
* @param name - The name to validate.
|
|
256
|
+
* @param language - The language to render the error message in.
|
|
257
|
+
* @throws {Error} If the name is not a safe name.
|
|
258
|
+
*/
|
|
259
|
+
export declare const validateName: (name: string, language?: ErrorLang) => void;
|
|
260
|
+
/**
|
|
261
|
+
* Recursively validates every field key of a schema.
|
|
262
|
+
*
|
|
263
|
+
* @param schema - The schema to validate.
|
|
264
|
+
* @param language - The language to render the error message in.
|
|
265
|
+
* @throws {Error} If any field key is not a safe name.
|
|
266
|
+
*/
|
|
267
|
+
export declare const validateSchema: (schema: Schema, language?: ErrorLang) => void;
|
package/dist/utils.js
CHANGED
|
@@ -628,3 +628,150 @@ 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
|
+
};
|
|
726
|
+
/**
|
|
727
|
+
* Validates that a string is a safe name for a table, database or column.
|
|
728
|
+
*
|
|
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.
|
|
732
|
+
*
|
|
733
|
+
* This prevents path traversal (`../`, `..\\`), null-byte injection (`\0`),
|
|
734
|
+
* shell injection (`;`, `|`, backticks, `$()`, tabs, newlines, slashes) and
|
|
735
|
+
* other reserved-name issues.
|
|
736
|
+
*
|
|
737
|
+
* @param input - The value to be checked.
|
|
738
|
+
* @returns boolean - True if the name is safe to use, false otherwise.
|
|
739
|
+
*/
|
|
740
|
+
export const isValidName = (input) => typeof input === "string" &&
|
|
741
|
+
input.length > 0 &&
|
|
742
|
+
input.length <= 255 &&
|
|
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}])?$`);
|
|
750
|
+
/**
|
|
751
|
+
* Validates that a string is a safe name for a table, database or column and
|
|
752
|
+
* throws a translated `INVALID_NAME` error if it is not.
|
|
753
|
+
*
|
|
754
|
+
* @param name - The name to validate.
|
|
755
|
+
* @param language - The language to render the error message in.
|
|
756
|
+
* @throws {Error} If the name is not a safe name.
|
|
757
|
+
*/
|
|
758
|
+
export const validateName = (name, language = "en") => {
|
|
759
|
+
if (!isValidName(name))
|
|
760
|
+
throw createError(language, "INVALID_NAME", name);
|
|
761
|
+
};
|
|
762
|
+
/**
|
|
763
|
+
* Recursively validates every field key of a schema.
|
|
764
|
+
*
|
|
765
|
+
* @param schema - The schema to validate.
|
|
766
|
+
* @param language - The language to render the error message in.
|
|
767
|
+
* @throws {Error} If any field key is not a safe name.
|
|
768
|
+
*/
|
|
769
|
+
export const validateSchema = (schema, language = "en") => {
|
|
770
|
+
for (const field of schema) {
|
|
771
|
+
validateName(field.key, language);
|
|
772
|
+
if (field.table)
|
|
773
|
+
validateName(field.table, language);
|
|
774
|
+
if (field.children && isArrayOfObjects(field.children))
|
|
775
|
+
validateSchema(field.children, language);
|
|
776
|
+
}
|
|
777
|
+
};
|