inibase 1.5.11 → 1.6.0
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 +1 -1
- package/dist/index.js +48 -7
- package/dist/utils.d.ts +30 -0
- package/dist/utils.js +44 -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: {
|
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,6 +40,7 @@ export default class Inibase {
|
|
|
39
40
|
uniqueMap;
|
|
40
41
|
schemaFileExtension = process.env.INIBASE_SCHEMA_EXTENSION ?? "json";
|
|
41
42
|
constructor(database, mainFolder = ".", language = "en") {
|
|
43
|
+
Utils.validateName(database);
|
|
42
44
|
this.databasePath = join(mainFolder, database);
|
|
43
45
|
this.language = language;
|
|
44
46
|
this.pageInfo = {};
|
|
@@ -69,6 +71,7 @@ export default class Inibase {
|
|
|
69
71
|
INVALID_TYPE: "Expect {variable} to be {variable}, got {variable} instead",
|
|
70
72
|
INVALID_PARAMETERS: "The given parameters are not valid",
|
|
71
73
|
INVALID_REGEX_MATCH: "Field {variable} does not match the expected pattern",
|
|
74
|
+
INVALID_NAME: "Name {variable} is not valid",
|
|
72
75
|
NO_ENV: Number(process.versions.node.split(".").reduce((a, b) => a + b)) >= 26
|
|
73
76
|
? "please run with '--env-file=.env'"
|
|
74
77
|
: "please use dotenv",
|
|
@@ -85,6 +88,7 @@ export default class Inibase {
|
|
|
85
88
|
INVALID_TYPE: "من المتوقع أن يكون {variable} من النوع {variable}، لكن تم العثور على {variable} بدلاً من ذلك",
|
|
86
89
|
INVALID_PARAMETERS: "المعلمات المقدمة غير صالحة",
|
|
87
90
|
INVALID_REGEX_MATCH: "الحقل {variable} لا يتطابق مع النمط المتوقع",
|
|
91
|
+
INVALID_NAME: "الاسم {variable} غير صالح",
|
|
88
92
|
NO_ENV: Number(process.versions.node.split(".").reduce((a, b) => a + b)) >= 26
|
|
89
93
|
? "يرجى التشغيل باستخدام '--env-file=.env'"
|
|
90
94
|
: "يرجى استخدام dotenv",
|
|
@@ -101,6 +105,7 @@ export default class Inibase {
|
|
|
101
105
|
INVALID_TYPE: "Attendu que {variable} soit de type {variable}, mais trouvé {variable} à la place",
|
|
102
106
|
INVALID_PARAMETERS: "Les paramètres donnés ne sont pas valides",
|
|
103
107
|
INVALID_REGEX_MATCH: "Le champ {variable} ne correspond pas au modèle attendu",
|
|
108
|
+
INVALID_NAME: "Le nom {variable} n'est pas valide",
|
|
104
109
|
NO_ENV: Number(process.versions.node.split(".").reduce((a, b) => a + b)) >= 26
|
|
105
110
|
? "veuillez exécuter avec '--env-file=.env'"
|
|
106
111
|
: "veuillez utiliser dotenv",
|
|
@@ -117,6 +122,7 @@ export default class Inibase {
|
|
|
117
122
|
INVALID_TYPE: "Se espera que {variable} sea {variable}, pero se encontró {variable} en su lugar",
|
|
118
123
|
INVALID_PARAMETERS: "Los parámetros proporcionados no son válidos",
|
|
119
124
|
INVALID_REGEX_MATCH: "El campo {variable} no coincide con el patrón esperado",
|
|
125
|
+
INVALID_NAME: "El nombre {variable} no es válido",
|
|
120
126
|
NO_ENV: Number(process.versions.node.split(".").reduce((a, b) => a + b)) >= 26
|
|
121
127
|
? "por favor ejecute con '--env-file=.env'"
|
|
122
128
|
: "por favor use dotenv",
|
|
@@ -163,6 +169,9 @@ export default class Inibase {
|
|
|
163
169
|
* @param {TableConfig} [config]
|
|
164
170
|
*/
|
|
165
171
|
async createTable(tableName, schema, config) {
|
|
172
|
+
Utils.validateName(tableName);
|
|
173
|
+
if (schema)
|
|
174
|
+
Utils.validateSchema(schema);
|
|
166
175
|
const tablePath = join(this.databasePath, tableName);
|
|
167
176
|
if (await File.isExists(tablePath))
|
|
168
177
|
throw this.createError("TABLE_EXISTS", tableName);
|
|
@@ -211,11 +220,15 @@ export default class Inibase {
|
|
|
211
220
|
* @param {(TableConfig&{name?: string})} [config]
|
|
212
221
|
*/
|
|
213
222
|
async updateTable(tableName, schema, config) {
|
|
223
|
+
Utils.validateName(tableName);
|
|
224
|
+
if (config?.name)
|
|
225
|
+
Utils.validateName(config.name);
|
|
214
226
|
const table = await this.getTable(tableName);
|
|
215
227
|
if (!table)
|
|
216
228
|
return;
|
|
217
229
|
const tablePath = join(this.databasePath, tableName);
|
|
218
230
|
if (schema) {
|
|
231
|
+
Utils.validateSchema(schema);
|
|
219
232
|
// remove id from schema
|
|
220
233
|
schema = schema.filter(({ key }) => !["id", "createdAt", "updatedAt"].includes(key));
|
|
221
234
|
let schemaIdFilePath = "";
|
|
@@ -330,6 +343,7 @@ export default class Inibase {
|
|
|
330
343
|
* @return {*} {Promise<TableObject | undefined>}
|
|
331
344
|
*/
|
|
332
345
|
async getTable(tableName) {
|
|
346
|
+
Utils.validateName(tableName);
|
|
333
347
|
const tablePath = join(this.databasePath, tableName);
|
|
334
348
|
if (!(await File.isExists(tablePath)))
|
|
335
349
|
throw this.createError("TABLE_NOT_EXISTS", tableName);
|
|
@@ -349,6 +363,7 @@ export default class Inibase {
|
|
|
349
363
|
return globalConfig[this.databasePath].tables?.get(tableName);
|
|
350
364
|
}
|
|
351
365
|
async getTableSchema(tableName) {
|
|
366
|
+
Utils.validateName(tableName);
|
|
352
367
|
const tablePath = join(this.databasePath, tableName);
|
|
353
368
|
let schemaFile;
|
|
354
369
|
let schema;
|
|
@@ -1151,6 +1166,7 @@ export default class Inibase {
|
|
|
1151
1166
|
* @param {string} tableName
|
|
1152
1167
|
*/
|
|
1153
1168
|
async clearCache(tableName) {
|
|
1169
|
+
Utils.validateName(tableName);
|
|
1154
1170
|
const cacheFolderPath = join(this.databasePath, tableName, ".cache");
|
|
1155
1171
|
await rm(cacheFolderPath, { recursive: true, force: true });
|
|
1156
1172
|
await mkdir(cacheFolderPath);
|
|
@@ -1159,12 +1175,15 @@ export default class Inibase {
|
|
|
1159
1175
|
page: 1,
|
|
1160
1176
|
perPage: 15,
|
|
1161
1177
|
}, onlyOne, onlyLinesNumbers, _whereIsLinesNumbers) {
|
|
1178
|
+
Utils.validateName(tableName);
|
|
1162
1179
|
const tablePath = join(this.databasePath, tableName);
|
|
1163
1180
|
// Ensure options.columns is an array
|
|
1164
1181
|
if (options.columns) {
|
|
1165
1182
|
options.columns = Array.isArray(options.columns)
|
|
1166
1183
|
? options.columns
|
|
1167
1184
|
: [options.columns];
|
|
1185
|
+
for (const column of options.columns)
|
|
1186
|
+
Utils.validateName(column);
|
|
1168
1187
|
if (options.columns.length && !options.columns.includes("id"))
|
|
1169
1188
|
options.columns.push("id");
|
|
1170
1189
|
}
|
|
@@ -1407,6 +1426,12 @@ export default class Inibase {
|
|
|
1407
1426
|
page: 1,
|
|
1408
1427
|
perPage: 15,
|
|
1409
1428
|
};
|
|
1429
|
+
Utils.validateName(tableName);
|
|
1430
|
+
if (options.columns)
|
|
1431
|
+
for (const column of (Array.isArray(options.columns)
|
|
1432
|
+
? options.columns
|
|
1433
|
+
: [options.columns]))
|
|
1434
|
+
Utils.validateName(column);
|
|
1410
1435
|
const tablePath = join(this.databasePath, tableName);
|
|
1411
1436
|
await this.getTable(tableName);
|
|
1412
1437
|
if (!globalConfig[this.databasePath].tables?.get(tableName)?.schema)
|
|
@@ -1487,6 +1512,12 @@ export default class Inibase {
|
|
|
1487
1512
|
perPage: 15,
|
|
1488
1513
|
}, returnUpdatedData, _whereIsLinesNumbers) {
|
|
1489
1514
|
const renameList = [];
|
|
1515
|
+
Utils.validateName(tableName);
|
|
1516
|
+
if (options.columns)
|
|
1517
|
+
for (const column of (Array.isArray(options.columns)
|
|
1518
|
+
? options.columns
|
|
1519
|
+
: [options.columns]))
|
|
1520
|
+
Utils.validateName(column);
|
|
1490
1521
|
const tablePath = join(this.databasePath, tableName);
|
|
1491
1522
|
await this.throwErrorIfTableEmpty(tableName);
|
|
1492
1523
|
let clonedData = structuredClone(data);
|
|
@@ -1601,6 +1632,7 @@ export default class Inibase {
|
|
|
1601
1632
|
* @return {boolean | null} {(Promise<boolean | null>)}
|
|
1602
1633
|
*/
|
|
1603
1634
|
async delete(tableName, where, _whereIsLinesNumbers) {
|
|
1635
|
+
Utils.validateName(tableName);
|
|
1604
1636
|
const tablePath = join(this.databasePath, tableName);
|
|
1605
1637
|
await this.throwErrorIfTableEmpty(tableName);
|
|
1606
1638
|
if (!where) {
|
|
@@ -1694,11 +1726,14 @@ export default class Inibase {
|
|
|
1694
1726
|
return false;
|
|
1695
1727
|
}
|
|
1696
1728
|
async sum(tableName, columns, where) {
|
|
1697
|
-
|
|
1698
|
-
const tablePath = join(this.databasePath, tableName);
|
|
1699
|
-
await this.throwErrorIfTableEmpty(tableName);
|
|
1729
|
+
Utils.validateName(tableName);
|
|
1700
1730
|
if (!Array.isArray(columns))
|
|
1701
1731
|
columns = [columns];
|
|
1732
|
+
for (const column of columns)
|
|
1733
|
+
Utils.validateName(column);
|
|
1734
|
+
await this.throwErrorIfTableEmpty(tableName);
|
|
1735
|
+
const RETURN = {};
|
|
1736
|
+
const tablePath = join(this.databasePath, tableName);
|
|
1702
1737
|
for await (const column of columns) {
|
|
1703
1738
|
const columnPath = join(tablePath, `${column}${this.getFileExtension(tableName)}`);
|
|
1704
1739
|
if (await File.isExists(columnPath)) {
|
|
@@ -1715,11 +1750,14 @@ export default class Inibase {
|
|
|
1715
1750
|
return columns.length > 1 ? RETURN : Object.values(RETURN)[0];
|
|
1716
1751
|
}
|
|
1717
1752
|
async max(tableName, columns, where) {
|
|
1753
|
+
Utils.validateName(tableName);
|
|
1754
|
+
if (!Array.isArray(columns))
|
|
1755
|
+
columns = [columns];
|
|
1756
|
+
for (const column of columns)
|
|
1757
|
+
Utils.validateName(column);
|
|
1718
1758
|
const RETURN = {};
|
|
1719
1759
|
const tablePath = join(this.databasePath, tableName);
|
|
1720
1760
|
await this.throwErrorIfTableEmpty(tableName);
|
|
1721
|
-
if (!Array.isArray(columns))
|
|
1722
|
-
columns = [columns];
|
|
1723
1761
|
for await (const column of columns) {
|
|
1724
1762
|
const columnPath = join(tablePath, `${column}${this.getFileExtension(tableName)}`);
|
|
1725
1763
|
if (await File.isExists(columnPath)) {
|
|
@@ -1736,11 +1774,14 @@ export default class Inibase {
|
|
|
1736
1774
|
return RETURN;
|
|
1737
1775
|
}
|
|
1738
1776
|
async min(tableName, columns, where) {
|
|
1777
|
+
Utils.validateName(tableName);
|
|
1778
|
+
if (!Array.isArray(columns))
|
|
1779
|
+
columns = [columns];
|
|
1780
|
+
for (const column of columns)
|
|
1781
|
+
Utils.validateName(column);
|
|
1739
1782
|
const RETURN = {};
|
|
1740
1783
|
const tablePath = join(this.databasePath, tableName);
|
|
1741
1784
|
await this.throwErrorIfTableEmpty(tableName);
|
|
1742
|
-
if (!Array.isArray(columns))
|
|
1743
|
-
columns = [columns];
|
|
1744
1785
|
for await (const column of columns) {
|
|
1745
1786
|
const columnPath = join(tablePath, `${column}${this.getFileExtension(tableName)}`);
|
|
1746
1787
|
if (await File.isExists(columnPath)) {
|
package/dist/utils.d.ts
CHANGED
|
@@ -218,3 +218,33 @@ 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
|
+
* Validates that a string is a safe name for a table, database or column.
|
|
223
|
+
*
|
|
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.
|
|
227
|
+
*
|
|
228
|
+
* This prevents path traversal (`../`, `..\\`), null-byte injection (`\0`),
|
|
229
|
+
* shell injection (`;`, `|`, backticks, `$()`, spaces, slashes) and other
|
|
230
|
+
* reserved-name issues.
|
|
231
|
+
*
|
|
232
|
+
* @param input - The value to be checked.
|
|
233
|
+
* @returns boolean - True if the name is safe to use, false otherwise.
|
|
234
|
+
*/
|
|
235
|
+
export declare const isValidName: (input: unknown) => input is string;
|
|
236
|
+
/**
|
|
237
|
+
* Validates that a string is a safe name for a table, database or column and
|
|
238
|
+
* throws an error if it is not.
|
|
239
|
+
*
|
|
240
|
+
* @param name - The name to validate.
|
|
241
|
+
* @throws {Error} If the name is not a safe name.
|
|
242
|
+
*/
|
|
243
|
+
export declare const validateName: (name: string) => void;
|
|
244
|
+
/**
|
|
245
|
+
* Recursively validates every field key of a schema.
|
|
246
|
+
*
|
|
247
|
+
* @param schema - The schema to validate.
|
|
248
|
+
* @throws {Error} If any field key is not a safe name.
|
|
249
|
+
*/
|
|
250
|
+
export declare const validateSchema: (schema: Schema) => void;
|
package/dist/utils.js
CHANGED
|
@@ -628,3 +628,47 @@ export function addIdToSchema(schema, startWithID) {
|
|
|
628
628
|
const addIdToSchemaHelper = (schema) => schema.map(addIdToField);
|
|
629
629
|
return addIdToSchemaHelper(clonedSchema);
|
|
630
630
|
}
|
|
631
|
+
/**
|
|
632
|
+
* Validates that a string is a safe name for a table, database or column.
|
|
633
|
+
*
|
|
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.
|
|
637
|
+
*
|
|
638
|
+
* This prevents path traversal (`../`, `..\\`), null-byte injection (`\0`),
|
|
639
|
+
* shell injection (`;`, `|`, backticks, `$()`, spaces, slashes) and other
|
|
640
|
+
* reserved-name issues.
|
|
641
|
+
*
|
|
642
|
+
* @param input - The value to be checked.
|
|
643
|
+
* @returns boolean - True if the name is safe to use, false otherwise.
|
|
644
|
+
*/
|
|
645
|
+
export const isValidName = (input) => typeof input === "string" &&
|
|
646
|
+
input.length > 0 &&
|
|
647
|
+
input.length <= 255 &&
|
|
648
|
+
/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(input);
|
|
649
|
+
/**
|
|
650
|
+
* Validates that a string is a safe name for a table, database or column and
|
|
651
|
+
* throws an error if it is not.
|
|
652
|
+
*
|
|
653
|
+
* @param name - The name to validate.
|
|
654
|
+
* @throws {Error} If the name is not a safe name.
|
|
655
|
+
*/
|
|
656
|
+
export const validateName = (name) => {
|
|
657
|
+
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.`);
|
|
659
|
+
};
|
|
660
|
+
/**
|
|
661
|
+
* Recursively validates every field key of a schema.
|
|
662
|
+
*
|
|
663
|
+
* @param schema - The schema to validate.
|
|
664
|
+
* @throws {Error} If any field key is not a safe name.
|
|
665
|
+
*/
|
|
666
|
+
export const validateSchema = (schema) => {
|
|
667
|
+
for (const field of schema) {
|
|
668
|
+
validateName(field.key);
|
|
669
|
+
if (field.table)
|
|
670
|
+
validateName(field.table);
|
|
671
|
+
if (field.children && isArrayOfObjects(field.children))
|
|
672
|
+
validateSchema(field.children);
|
|
673
|
+
}
|
|
674
|
+
};
|