accio-orm 0.1.0 → 0.1.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/README.md +1 -0
- package/dist/connection/Connection.d.ts +6 -0
- package/dist/connection/Connection.js +60 -11
- package/dist/connection/types.d.ts +2 -0
- package/dist/decorators/Column.d.ts +3 -2
- package/dist/decorators/PrimaryColumn.d.ts +1 -1
- package/dist/decorators/PrimaryColumn.js +3 -0
- package/dist/errors/AccioError.d.ts +44 -0
- package/dist/errors/AccioError.js +55 -0
- package/dist/errors/ConnectionError.d.ts +27 -0
- package/dist/errors/ConnectionError.js +33 -0
- package/dist/errors/DatabaseError.d.ts +35 -0
- package/dist/errors/DatabaseError.js +35 -0
- package/dist/errors/QueryError.d.ts +36 -0
- package/dist/errors/QueryError.js +36 -0
- package/dist/errors/ValidationError.d.ts +35 -0
- package/dist/errors/ValidationError.js +35 -0
- package/dist/errors/index.d.ts +11 -0
- package/dist/errors/index.js +19 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.js +17 -1
- package/dist/logger/LogLevel.d.ts +47 -0
- package/dist/logger/LogLevel.js +57 -0
- package/dist/logger/Logger.d.ts +171 -0
- package/dist/logger/Logger.js +207 -0
- package/dist/logger/index.d.ts +8 -0
- package/dist/logger/index.js +14 -0
- package/dist/metadata/MetadataStorage.d.ts +1 -1
- package/dist/metadata/MetadataStorage.js +12 -11
- package/dist/query/QueryBuilder.d.ts +0 -5
- package/dist/query/QueryBuilder.js +10 -31
- package/dist/repository/Repository.js +12 -11
- package/dist/types/PostgresTypes.d.ts +74 -0
- package/dist/types/PostgresTypes.js +98 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.js +12 -0
- package/dist/validation/ConnectionValidation.d.ts +27 -0
- package/dist/validation/ConnectionValidation.js +58 -0
- package/dist/validation/IdentifierValidator.d.ts +101 -0
- package/dist/validation/IdentifierValidator.js +127 -0
- package/dist/validation/QueryValidator.d.ts +61 -0
- package/dist/validation/QueryValidator.js +81 -0
- package/dist/validation/index.d.ts +9 -0
- package/dist/validation/index.js +15 -0
- package/package.json +3 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Repository = void 0;
|
|
4
|
+
const errors_1 = require("@/errors");
|
|
4
5
|
const MetadataStorage_1 = require("@/metadata/MetadataStorage");
|
|
5
6
|
const QueryBuilder_1 = require("@/query/QueryBuilder");
|
|
6
7
|
const entityMapper_1 = require("../utils/entityMapper");
|
|
@@ -25,7 +26,7 @@ class Repository {
|
|
|
25
26
|
}
|
|
26
27
|
async findById(id) {
|
|
27
28
|
if (!this.metadata.primaryColumn) {
|
|
28
|
-
throw new
|
|
29
|
+
throw new errors_1.QueryError(`Entity ${this.entityClass.name} has no primary key defined`);
|
|
29
30
|
}
|
|
30
31
|
const primaryKey = this.metadata.primaryColumn.columnName;
|
|
31
32
|
const sql = `SELECT * FROM ${this.metadata.tableName} WHERE ${primaryKey} = $1`;
|
|
@@ -45,9 +46,9 @@ class Repository {
|
|
|
45
46
|
}
|
|
46
47
|
save(entity) {
|
|
47
48
|
if (!this.metadata.primaryColumn) {
|
|
48
|
-
throw new
|
|
49
|
+
throw new errors_1.QueryError(`Entity ${this.entityClass.name} has no primary key defined`);
|
|
49
50
|
}
|
|
50
|
-
const primaryKey = this.metadata.primaryColumn.
|
|
51
|
+
const primaryKey = this.metadata.primaryColumn.propertyKey;
|
|
51
52
|
const id = entity[primaryKey];
|
|
52
53
|
if (id !== undefined && id !== null) {
|
|
53
54
|
return this.update(entity);
|
|
@@ -62,7 +63,7 @@ class Repository {
|
|
|
62
63
|
async insert(entity) {
|
|
63
64
|
const columns = this.metadata.columns.filter((col) => !col.isPrimary);
|
|
64
65
|
if (columns.length === 0) {
|
|
65
|
-
throw new
|
|
66
|
+
throw new errors_1.QueryError(`Entity ${this.entityClass.name} has no columns to insert (only primary key)`);
|
|
66
67
|
}
|
|
67
68
|
const columnNames = columns.map((col) => col.columnName).join(', ');
|
|
68
69
|
const placeholders = columns.map((_, i) => `$${i + 1}`).join(', ');
|
|
@@ -80,11 +81,11 @@ class Repository {
|
|
|
80
81
|
*/
|
|
81
82
|
async update(entity) {
|
|
82
83
|
if (!this.metadata.primaryColumn) {
|
|
83
|
-
throw new
|
|
84
|
+
throw new errors_1.QueryError(`Entity ${this.entityClass.name} has no primary key defined`);
|
|
84
85
|
}
|
|
85
86
|
const columns = this.metadata.columns.filter((col) => !col.isPrimary);
|
|
86
87
|
if (columns.length === 0) {
|
|
87
|
-
throw new
|
|
88
|
+
throw new errors_1.QueryError(`Entity ${this.entityClass.name} has no columns to update (only primary key)`);
|
|
88
89
|
}
|
|
89
90
|
const primaryKey = this.metadata.primaryColumn.columnName;
|
|
90
91
|
const primaryValue = entity[this.metadata.primaryColumn.propertyKey];
|
|
@@ -101,7 +102,7 @@ class Repository {
|
|
|
101
102
|
`;
|
|
102
103
|
const result = await this.connection.query(sql, [...values, primaryValue]);
|
|
103
104
|
if (result.rows.length === 0) {
|
|
104
|
-
throw new
|
|
105
|
+
throw new errors_1.DatabaseError(`Update failed: Entity with ${primaryKey} = ${String(primaryValue)} not found`, undefined, `No rows affected by UPDATE operation`);
|
|
105
106
|
}
|
|
106
107
|
return (0, entityMapper_1.mapRowToEntity)(result.rows[0], this.entityClass, this.metadata);
|
|
107
108
|
}
|
|
@@ -110,12 +111,12 @@ class Repository {
|
|
|
110
111
|
*/
|
|
111
112
|
async delete(entity) {
|
|
112
113
|
if (!this.metadata.primaryColumn) {
|
|
113
|
-
throw new
|
|
114
|
+
throw new errors_1.QueryError(`Entity ${this.entityClass.name} has no primary key defined`);
|
|
114
115
|
}
|
|
115
116
|
const primaryKey = this.metadata.primaryColumn.columnName;
|
|
116
117
|
const primaryValue = entity[this.metadata.primaryColumn.propertyKey];
|
|
117
118
|
if (primaryValue === undefined || primaryValue === null) {
|
|
118
|
-
throw new
|
|
119
|
+
throw new errors_1.QueryError(`Cannot delete entity: primary key ${primaryKey} is ${primaryValue}`);
|
|
119
120
|
}
|
|
120
121
|
const sql = `DELETE FROM ${this.metadata.tableName} WHERE ${primaryKey} = $1`;
|
|
121
122
|
await this.connection.query(sql, [primaryValue]);
|
|
@@ -125,7 +126,7 @@ class Repository {
|
|
|
125
126
|
*/
|
|
126
127
|
async deleteById(id) {
|
|
127
128
|
if (!this.metadata.primaryColumn) {
|
|
128
|
-
throw new
|
|
129
|
+
throw new errors_1.QueryError(`Entity ${this.entityClass.name} has no primary key defined`);
|
|
129
130
|
}
|
|
130
131
|
const primaryKey = this.metadata.primaryColumn.columnName;
|
|
131
132
|
const sql = `DELETE FROM ${this.metadata.tableName} WHERE ${primaryKey} = $1`;
|
|
@@ -145,7 +146,7 @@ class Repository {
|
|
|
145
146
|
*/
|
|
146
147
|
async exists(id) {
|
|
147
148
|
if (!this.metadata.primaryColumn) {
|
|
148
|
-
throw new
|
|
149
|
+
throw new errors_1.QueryError(`Entity ${this.entityClass.name} has no primary key defined`);
|
|
149
150
|
}
|
|
150
151
|
const primaryKey = this.metadata.primaryColumn.columnName;
|
|
151
152
|
const sql = `SELECT 1 FROM ${this.metadata.tableName} WHERE ${primaryKey} = $1 LIMIT 1`;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostgresSQL column data definitions
|
|
3
|
+
*
|
|
4
|
+
* Provides strict type mapping between TypeScript and PostgreSQL types,
|
|
5
|
+
* ensuring type safety and consistency when defining database columns.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* @Column({ type: PostgresType.INTEGER })
|
|
10
|
+
* id!: number;
|
|
11
|
+
*
|
|
12
|
+
* @Column({ type: PostgresType.TEXT })
|
|
13
|
+
* name!: string;
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare enum PostgresType {
|
|
17
|
+
SMALLINT = "SMALLINT",
|
|
18
|
+
INTEGER = "INTEGER",
|
|
19
|
+
BIGINT = "BIGINT",
|
|
20
|
+
DECIMAL = "DECIMAL",
|
|
21
|
+
NUMERIC = "NUMERIC",
|
|
22
|
+
REAL = "REAL",
|
|
23
|
+
DOUBLE_PRECISION = "DOUBLE PRECISION",
|
|
24
|
+
SMALLSERIAL = "SMALLSERIAL",
|
|
25
|
+
SERIAL = "SERIAL",
|
|
26
|
+
BIGSERIAL = "BIGSERIAL",
|
|
27
|
+
CHAR = "CHAR",
|
|
28
|
+
VARCHAR = "VARCHAR",
|
|
29
|
+
TEXT = "TEXT",
|
|
30
|
+
BOOLEAN = "BOOLEAN",
|
|
31
|
+
DATE = "DATE",
|
|
32
|
+
TIME = "TIME",
|
|
33
|
+
TIME_WITH_TIMEZONE = "TIMETZ",
|
|
34
|
+
TIMESTAMP = "TIMESTAMP",
|
|
35
|
+
TIMESTAMP_WITH_TIMEZONE = "TIMESTAMPZ",
|
|
36
|
+
INTERVAL = "INTERVAL",
|
|
37
|
+
UUID = "UUID",
|
|
38
|
+
JSON = "JSON",
|
|
39
|
+
JSONB = "JSONB",
|
|
40
|
+
BYTEA = "BYTEA",
|
|
41
|
+
ARRAY = "ARRAY"
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Mapping from Typescript types to PostgresSQL types
|
|
45
|
+
*
|
|
46
|
+
* Used for automatic type inference when no explicit type is specified
|
|
47
|
+
*/
|
|
48
|
+
export declare const TypeScriptToPostgresMap: Record<string, PostgresType>;
|
|
49
|
+
/**
|
|
50
|
+
* Validates if a string is a valid PostreSQL type
|
|
51
|
+
*
|
|
52
|
+
* @param type - The type string to validate
|
|
53
|
+
* @returns true if the type is a valid PostgreSQL type, false otherwise
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```typeacript
|
|
57
|
+
* isValidPostgresType('INTEGER'); // true
|
|
58
|
+
* isValidPostgresType('INVALID'); // false
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare function isValidPostgresType(type: string): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Gets the corresponding PostgresSQL type for a TypeScript type
|
|
64
|
+
*
|
|
65
|
+
* @param tsType - The TypeScript type name (e.g. 'number', 'string', 'boolean', 'Date', 'object')
|
|
66
|
+
* @returns The corresponding PostgresSQL type (e.g. 'INTEGER', 'TEXT', 'BOOLEAN', 'TIMESTAMP', 'JSONB')
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* getPostgresTypeForTS('number'); // PostgresType.INTEGER
|
|
71
|
+
* getPostgresTypeForTS('string'); // PostgresType.TEXT
|
|
72
|
+
* getPostgresTypeForTS('unknown'); // PostgresType.TEXT
|
|
73
|
+
*/
|
|
74
|
+
export declare function getPostgresTypeForTS(tsType: string): PostgresType;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TypeScriptToPostgresMap = exports.PostgresType = void 0;
|
|
4
|
+
exports.isValidPostgresType = isValidPostgresType;
|
|
5
|
+
exports.getPostgresTypeForTS = getPostgresTypeForTS;
|
|
6
|
+
/**
|
|
7
|
+
* PostgresSQL column data definitions
|
|
8
|
+
*
|
|
9
|
+
* Provides strict type mapping between TypeScript and PostgreSQL types,
|
|
10
|
+
* ensuring type safety and consistency when defining database columns.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* @Column({ type: PostgresType.INTEGER })
|
|
15
|
+
* id!: number;
|
|
16
|
+
*
|
|
17
|
+
* @Column({ type: PostgresType.TEXT })
|
|
18
|
+
* name!: string;
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
var PostgresType;
|
|
22
|
+
(function (PostgresType) {
|
|
23
|
+
// Numeric
|
|
24
|
+
PostgresType["SMALLINT"] = "SMALLINT";
|
|
25
|
+
PostgresType["INTEGER"] = "INTEGER";
|
|
26
|
+
PostgresType["BIGINT"] = "BIGINT";
|
|
27
|
+
PostgresType["DECIMAL"] = "DECIMAL";
|
|
28
|
+
PostgresType["NUMERIC"] = "NUMERIC";
|
|
29
|
+
PostgresType["REAL"] = "REAL";
|
|
30
|
+
PostgresType["DOUBLE_PRECISION"] = "DOUBLE PRECISION";
|
|
31
|
+
PostgresType["SMALLSERIAL"] = "SMALLSERIAL";
|
|
32
|
+
PostgresType["SERIAL"] = "SERIAL";
|
|
33
|
+
PostgresType["BIGSERIAL"] = "BIGSERIAL";
|
|
34
|
+
// Character
|
|
35
|
+
PostgresType["CHAR"] = "CHAR";
|
|
36
|
+
PostgresType["VARCHAR"] = "VARCHAR";
|
|
37
|
+
PostgresType["TEXT"] = "TEXT";
|
|
38
|
+
// Boolean
|
|
39
|
+
PostgresType["BOOLEAN"] = "BOOLEAN";
|
|
40
|
+
// Date / Time
|
|
41
|
+
PostgresType["DATE"] = "DATE";
|
|
42
|
+
PostgresType["TIME"] = "TIME";
|
|
43
|
+
PostgresType["TIME_WITH_TIMEZONE"] = "TIMETZ";
|
|
44
|
+
PostgresType["TIMESTAMP"] = "TIMESTAMP";
|
|
45
|
+
PostgresType["TIMESTAMP_WITH_TIMEZONE"] = "TIMESTAMPZ";
|
|
46
|
+
PostgresType["INTERVAL"] = "INTERVAL";
|
|
47
|
+
// UUID
|
|
48
|
+
PostgresType["UUID"] = "UUID";
|
|
49
|
+
// JSON
|
|
50
|
+
PostgresType["JSON"] = "JSON";
|
|
51
|
+
PostgresType["JSONB"] = "JSONB";
|
|
52
|
+
// Binary
|
|
53
|
+
PostgresType["BYTEA"] = "BYTEA";
|
|
54
|
+
// Arrays (generic)
|
|
55
|
+
PostgresType["ARRAY"] = "ARRAY";
|
|
56
|
+
})(PostgresType || (exports.PostgresType = PostgresType = {}));
|
|
57
|
+
/**
|
|
58
|
+
* Mapping from Typescript types to PostgresSQL types
|
|
59
|
+
*
|
|
60
|
+
* Used for automatic type inference when no explicit type is specified
|
|
61
|
+
*/
|
|
62
|
+
exports.TypeScriptToPostgresMap = {
|
|
63
|
+
number: PostgresType.INTEGER,
|
|
64
|
+
string: PostgresType.TEXT,
|
|
65
|
+
boolean: PostgresType.BOOLEAN,
|
|
66
|
+
Date: PostgresType.TIMESTAMP,
|
|
67
|
+
object: PostgresType.JSONB
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Validates if a string is a valid PostreSQL type
|
|
71
|
+
*
|
|
72
|
+
* @param type - The type string to validate
|
|
73
|
+
* @returns true if the type is a valid PostgreSQL type, false otherwise
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typeacript
|
|
77
|
+
* isValidPostgresType('INTEGER'); // true
|
|
78
|
+
* isValidPostgresType('INVALID'); // false
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
function isValidPostgresType(type) {
|
|
82
|
+
return Object.values(PostgresType).includes(type);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Gets the corresponding PostgresSQL type for a TypeScript type
|
|
86
|
+
*
|
|
87
|
+
* @param tsType - The TypeScript type name (e.g. 'number', 'string', 'boolean', 'Date', 'object')
|
|
88
|
+
* @returns The corresponding PostgresSQL type (e.g. 'INTEGER', 'TEXT', 'BOOLEAN', 'TIMESTAMP', 'JSONB')
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* getPostgresTypeForTS('number'); // PostgresType.INTEGER
|
|
93
|
+
* getPostgresTypeForTS('string'); // PostgresType.TEXT
|
|
94
|
+
* getPostgresTypeForTS('unknown'); // PostgresType.TEXT
|
|
95
|
+
*/
|
|
96
|
+
function getPostgresTypeForTS(tsType) {
|
|
97
|
+
return exports.TypeScriptToPostgresMap[tsType] ?? PostgresType.TEXT;
|
|
98
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* TypeScript types for the Accio ORM
|
|
4
|
+
*
|
|
5
|
+
* Provides strict type mapping between TypeScript and PostgreSQL types,
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.PostgresType = exports.isValidPostgresType = exports.getPostgresTypeForTS = void 0;
|
|
9
|
+
var PostgresTypes_1 = require("./PostgresTypes");
|
|
10
|
+
Object.defineProperty(exports, "getPostgresTypeForTS", { enumerable: true, get: function () { return PostgresTypes_1.getPostgresTypeForTS; } });
|
|
11
|
+
Object.defineProperty(exports, "isValidPostgresType", { enumerable: true, get: function () { return PostgresTypes_1.isValidPostgresType; } });
|
|
12
|
+
Object.defineProperty(exports, "PostgresType", { enumerable: true, get: function () { return PostgresTypes_1.PostgresType; } });
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ConnectionConfig } from '@/connection/types';
|
|
2
|
+
/**
|
|
3
|
+
* Validates connection configuration
|
|
4
|
+
*
|
|
5
|
+
* Ensures all required connection parameters are provided and valid
|
|
6
|
+
* beforre attempting to establish a database connection.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* try {
|
|
11
|
+
* ConnectioValidator.validate(config);
|
|
12
|
+
* } catch (error) {
|
|
13
|
+
* if (error instanceof ValidationError) {
|
|
14
|
+
* console.error(`Invalid config: ${error.field}`);
|
|
15
|
+
* }
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare class ConnectionValidator {
|
|
20
|
+
/**
|
|
21
|
+
* Validate connection configuration
|
|
22
|
+
*
|
|
23
|
+
* @param config - The connection configuration to validate
|
|
24
|
+
* @throws {ValidationError} if any configuration parameter is invalid
|
|
25
|
+
*/
|
|
26
|
+
static validate(config: ConnectionConfig): void;
|
|
27
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConnectionValidator = void 0;
|
|
4
|
+
const errors_1 = require("@/errors");
|
|
5
|
+
/**
|
|
6
|
+
* Validates connection configuration
|
|
7
|
+
*
|
|
8
|
+
* Ensures all required connection parameters are provided and valid
|
|
9
|
+
* beforre attempting to establish a database connection.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* try {
|
|
14
|
+
* ConnectioValidator.validate(config);
|
|
15
|
+
* } catch (error) {
|
|
16
|
+
* if (error instanceof ValidationError) {
|
|
17
|
+
* console.error(`Invalid config: ${error.field}`);
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
class ConnectionValidator {
|
|
23
|
+
/**
|
|
24
|
+
* Validate connection configuration
|
|
25
|
+
*
|
|
26
|
+
* @param config - The connection configuration to validate
|
|
27
|
+
* @throws {ValidationError} if any configuration parameter is invalid
|
|
28
|
+
*/
|
|
29
|
+
static validate(config) {
|
|
30
|
+
if (!config.host ||
|
|
31
|
+
typeof config.host !== 'string' ||
|
|
32
|
+
config.host.trim() === '') {
|
|
33
|
+
throw new errors_1.ValidationError('Connection host must be a non-empty string', 'host', config.host);
|
|
34
|
+
}
|
|
35
|
+
if (!config.database ||
|
|
36
|
+
typeof config.database !== 'string' ||
|
|
37
|
+
config.database.trim() === '') {
|
|
38
|
+
throw new errors_1.ValidationError('Database name must be a non-empty string', 'database', config.database);
|
|
39
|
+
}
|
|
40
|
+
if (!config.user ||
|
|
41
|
+
typeof config.user !== 'string' ||
|
|
42
|
+
config.user.trim() === '') {
|
|
43
|
+
throw new errors_1.ValidationError('Database user must be a non-empty string', 'user', config.user);
|
|
44
|
+
}
|
|
45
|
+
if (!config.password || typeof config.password !== 'string') {
|
|
46
|
+
throw new errors_1.ValidationError('Database password must be a non-empty string', 'password', '[REDACTED]');
|
|
47
|
+
}
|
|
48
|
+
if (config.port !== undefined) {
|
|
49
|
+
if (typeof config.port !== 'number' || !Number.isInteger(config.port)) {
|
|
50
|
+
throw new errors_1.ValidationError('Port must be an integer', 'port', config.port);
|
|
51
|
+
}
|
|
52
|
+
if (config.port < 1 || config.port > 65535) {
|
|
53
|
+
throw new errors_1.ValidationError('Port must be between 1 and 65535', 'port', config.port);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.ConnectionValidator = ConnectionValidator;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { EntityMetadata } from '@/metadata/types';
|
|
2
|
+
/**
|
|
3
|
+
* Validate SQL identifiers (table/column names) against metadata
|
|
4
|
+
*
|
|
5
|
+
* **CRITICAL FOR SECURITY**: Prevents SQL injection through identifier manipulation.
|
|
6
|
+
* All table and column names must be validated against entity metadata before
|
|
7
|
+
* being used in SQL queries
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* // Validate that a column exists in metadata (prevents injection)
|
|
12
|
+
* IdentifierValidator.validateColumnName(
|
|
13
|
+
* userInput,
|
|
14
|
+
* metadata,
|
|
15
|
+
* 'ORDER BY'
|
|
16
|
+
* );
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare class identifierValidator {
|
|
20
|
+
/**
|
|
21
|
+
* PostgresSQL identifier rules
|
|
22
|
+
* - Max 63 characters
|
|
23
|
+
* - Must start with letter or underscore
|
|
24
|
+
* - Can contain letters, numbers, and underscores
|
|
25
|
+
*/
|
|
26
|
+
private static readonly IDENTIFIER_REGEX;
|
|
27
|
+
/**
|
|
28
|
+
* Validate that a string is a safe SQL identifier
|
|
29
|
+
*
|
|
30
|
+
* @param identifier = The identifier to validate
|
|
31
|
+
* @returns true if valid, false otherwise
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* IdentifierValidator.isValidIdentifier('user_name'); // true
|
|
36
|
+
* IdentifierValidator.isValidIdentifier('1user'); // false
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
static isValidIdentifier(identifier: string): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Validate identifier format or throw
|
|
42
|
+
*
|
|
43
|
+
* @param identifier - The identifier to validate
|
|
44
|
+
* @param context - Context description for error message
|
|
45
|
+
* @throws {ValidationError} If identifier format is invalid
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* IdentifierValidator.validateIdentifier('users', 'table name');
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
static validateIdentifier(identifier: string, context: string): void;
|
|
53
|
+
/**
|
|
54
|
+
* Validate identifier format or throw
|
|
55
|
+
*
|
|
56
|
+
* @param propertyKey - The property ket to validate
|
|
57
|
+
* @param metadata - Entity metadata to check against
|
|
58
|
+
* @param operation - Operation context for error message
|
|
59
|
+
* @throws {ValidationError} If property doesn't exist
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* IdentifierValidator.validatePropertyExists(
|
|
64
|
+
* 'age',
|
|
65
|
+
* metadata,
|
|
66
|
+
* 'WHERE clause'
|
|
67
|
+
* );
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
static validatePropertyExists(propertyKey: string, metadata: EntityMetadata, operation: string): void;
|
|
71
|
+
/**
|
|
72
|
+
*
|
|
73
|
+
* @param columnName - The column name to validate
|
|
74
|
+
* @param metadata - Entity metadata to check against
|
|
75
|
+
* @param operation - Operation context for error message
|
|
76
|
+
* @throws {ValidationError} if column doesn't exist in metadata
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```typescript
|
|
80
|
+
* // Prevent injection in ORDER BY clause
|
|
81
|
+
* IdentifierValidator.validateColumnName(
|
|
82
|
+
* orderByColumn,
|
|
83
|
+
* metadata,
|
|
84
|
+
* 'ORDER BY'
|
|
85
|
+
* );
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
static validateColumnName(columnName: string, metadata: EntityMetadata, operation: string): void;
|
|
89
|
+
/**
|
|
90
|
+
* Validate table name format
|
|
91
|
+
*
|
|
92
|
+
* @param tableName - The table name to validate
|
|
93
|
+
* @throws {ValidationError} If table name format is invalid
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```typescript
|
|
97
|
+
* IdentifierValidator.validateTableName('users');
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
static validateTableName(tableName: string): void;
|
|
101
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.identifierValidator = void 0;
|
|
4
|
+
const errors_1 = require("@/errors");
|
|
5
|
+
/**
|
|
6
|
+
* Validate SQL identifiers (table/column names) against metadata
|
|
7
|
+
*
|
|
8
|
+
* **CRITICAL FOR SECURITY**: Prevents SQL injection through identifier manipulation.
|
|
9
|
+
* All table and column names must be validated against entity metadata before
|
|
10
|
+
* being used in SQL queries
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // Validate that a column exists in metadata (prevents injection)
|
|
15
|
+
* IdentifierValidator.validateColumnName(
|
|
16
|
+
* userInput,
|
|
17
|
+
* metadata,
|
|
18
|
+
* 'ORDER BY'
|
|
19
|
+
* );
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
class identifierValidator {
|
|
23
|
+
/**
|
|
24
|
+
* Validate that a string is a safe SQL identifier
|
|
25
|
+
*
|
|
26
|
+
* @param identifier = The identifier to validate
|
|
27
|
+
* @returns true if valid, false otherwise
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* IdentifierValidator.isValidIdentifier('user_name'); // true
|
|
32
|
+
* IdentifierValidator.isValidIdentifier('1user'); // false
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
static isValidIdentifier(identifier) {
|
|
36
|
+
return this.IDENTIFIER_REGEX.test(identifier);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Validate identifier format or throw
|
|
40
|
+
*
|
|
41
|
+
* @param identifier - The identifier to validate
|
|
42
|
+
* @param context - Context description for error message
|
|
43
|
+
* @throws {ValidationError} If identifier format is invalid
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* IdentifierValidator.validateIdentifier('users', 'table name');
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
static validateIdentifier(identifier, context) {
|
|
51
|
+
if (!this.isValidIdentifier(identifier)) {
|
|
52
|
+
throw new errors_1.ValidationError(`Invalid ${context} identifier: "${identifier}". ` +
|
|
53
|
+
'Identifiers must start with a letter or underscore, contain only ' +
|
|
54
|
+
'alphanumeric characters and underscores, and be max 63 characters', context, identifier);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Validate identifier format or throw
|
|
59
|
+
*
|
|
60
|
+
* @param propertyKey - The property ket to validate
|
|
61
|
+
* @param metadata - Entity metadata to check against
|
|
62
|
+
* @param operation - Operation context for error message
|
|
63
|
+
* @throws {ValidationError} If property doesn't exist
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* IdentifierValidator.validatePropertyExists(
|
|
68
|
+
* 'age',
|
|
69
|
+
* metadata,
|
|
70
|
+
* 'WHERE clause'
|
|
71
|
+
* );
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
static validatePropertyExists(propertyKey, metadata, operation) {
|
|
75
|
+
const exists = metadata.columns.some((col) => col.propertyKey === propertyKey);
|
|
76
|
+
if (!exists) {
|
|
77
|
+
throw new errors_1.ValidationError(`Property '${propertyKey}' does not exist on entity ${metadata.tableName} for ${operation}. ` +
|
|
78
|
+
`Available properties: ${metadata.columns.map((c) => c.propertyKey).join(', ')}`, 'propertyKey', propertyKey, { entityName: metadata.tableName, operation });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
*
|
|
83
|
+
* @param columnName - The column name to validate
|
|
84
|
+
* @param metadata - Entity metadata to check against
|
|
85
|
+
* @param operation - Operation context for error message
|
|
86
|
+
* @throws {ValidationError} if column doesn't exist in metadata
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* // Prevent injection in ORDER BY clause
|
|
91
|
+
* IdentifierValidator.validateColumnName(
|
|
92
|
+
* orderByColumn,
|
|
93
|
+
* metadata,
|
|
94
|
+
* 'ORDER BY'
|
|
95
|
+
* );
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
static validateColumnName(columnName, metadata, operation) {
|
|
99
|
+
const exists = metadata.columns.some((col) => col.columnName === columnName);
|
|
100
|
+
if (!exists) {
|
|
101
|
+
throw new errors_1.ValidationError(`Column '${columnName}' does not exist in table ${metadata.tableName} for ${operation}. ` +
|
|
102
|
+
`Available columns: ${metadata.columns.map((c) => c.columnName).join(', ')}`, 'columnName', columnName, { entityName: metadata.tableName, operation });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Validate table name format
|
|
107
|
+
*
|
|
108
|
+
* @param tableName - The table name to validate
|
|
109
|
+
* @throws {ValidationError} If table name format is invalid
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```typescript
|
|
113
|
+
* IdentifierValidator.validateTableName('users');
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
static validateTableName(tableName) {
|
|
117
|
+
this.validateIdentifier(tableName, 'table name');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
exports.identifierValidator = identifierValidator;
|
|
121
|
+
/**
|
|
122
|
+
* PostgresSQL identifier rules
|
|
123
|
+
* - Max 63 characters
|
|
124
|
+
* - Must start with letter or underscore
|
|
125
|
+
* - Can contain letters, numbers, and underscores
|
|
126
|
+
*/
|
|
127
|
+
identifierValidator.IDENTIFIER_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate query paramters and options
|
|
3
|
+
*
|
|
4
|
+
* Ensures query parameters like LIMIT, OFFSET, and ORDER BY direction
|
|
5
|
+
* are valid before building SQL queries.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* QueryValidator.validateLimit(10); // OK
|
|
10
|
+
* QueryValidator.validateLimit(-5); // Throws ValidationError
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare class QueryValidator {
|
|
14
|
+
/**
|
|
15
|
+
* Maximum safe LIMIT value to prevent memory issues
|
|
16
|
+
*/
|
|
17
|
+
private static readonly MAX_LIMIT;
|
|
18
|
+
/**
|
|
19
|
+
* Validate LIMIT value
|
|
20
|
+
*
|
|
21
|
+
* @param value - The limit value to validate
|
|
22
|
+
* @throws {ValidationError} If limit is invalid
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* QueryValidator.validateLimit(100); // OK
|
|
27
|
+
* QueryValidator.validateLimit(-1); // Throws
|
|
28
|
+
* QueryValidator.validateLimit(20000); // Throws (exceeds max)
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
static validateLimit(value: number): void;
|
|
32
|
+
/**
|
|
33
|
+
* Validate OFFSET value
|
|
34
|
+
*
|
|
35
|
+
* @param value - The offset value to validate
|
|
36
|
+
* @throws {ValidationError} If offset is invalid
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* QueryValidator.validateOffset(0); // OK
|
|
41
|
+
* QueryValidator.validateOffset(100); // OK
|
|
42
|
+
* QueryValidator.validateOffset(-1); // Throws
|
|
43
|
+
* QueryValidator.validateOffset(3.14); // Throws (not integer)
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
static validateOffset(value: number): void;
|
|
47
|
+
/**
|
|
48
|
+
* Validate ORDER BY direction
|
|
49
|
+
*
|
|
50
|
+
* @param direction - The order direction to validate
|
|
51
|
+
* @throws {ValidationError} If direction is not 'ASC' or 'DESC'
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* QueryValidator.validateOrderDirection('ASC'); // OK
|
|
56
|
+
* QueryValidator.validateOrderDirection('DESC'); // OK
|
|
57
|
+
* QueryValidator.validateOrderDirection('RANDOM'); // Throws
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
static validateOrderDirection(direction: string): void;
|
|
61
|
+
}
|