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.
Files changed (45) hide show
  1. package/README.md +1 -0
  2. package/dist/connection/Connection.d.ts +6 -0
  3. package/dist/connection/Connection.js +60 -11
  4. package/dist/connection/types.d.ts +2 -0
  5. package/dist/decorators/Column.d.ts +3 -2
  6. package/dist/decorators/PrimaryColumn.d.ts +1 -1
  7. package/dist/decorators/PrimaryColumn.js +3 -0
  8. package/dist/errors/AccioError.d.ts +44 -0
  9. package/dist/errors/AccioError.js +55 -0
  10. package/dist/errors/ConnectionError.d.ts +27 -0
  11. package/dist/errors/ConnectionError.js +33 -0
  12. package/dist/errors/DatabaseError.d.ts +35 -0
  13. package/dist/errors/DatabaseError.js +35 -0
  14. package/dist/errors/QueryError.d.ts +36 -0
  15. package/dist/errors/QueryError.js +36 -0
  16. package/dist/errors/ValidationError.d.ts +35 -0
  17. package/dist/errors/ValidationError.js +35 -0
  18. package/dist/errors/index.d.ts +11 -0
  19. package/dist/errors/index.js +19 -0
  20. package/dist/index.d.ts +6 -3
  21. package/dist/index.js +17 -1
  22. package/dist/logger/LogLevel.d.ts +47 -0
  23. package/dist/logger/LogLevel.js +57 -0
  24. package/dist/logger/Logger.d.ts +171 -0
  25. package/dist/logger/Logger.js +207 -0
  26. package/dist/logger/index.d.ts +8 -0
  27. package/dist/logger/index.js +14 -0
  28. package/dist/metadata/MetadataStorage.d.ts +1 -1
  29. package/dist/metadata/MetadataStorage.js +12 -11
  30. package/dist/query/QueryBuilder.d.ts +0 -5
  31. package/dist/query/QueryBuilder.js +10 -31
  32. package/dist/repository/Repository.js +12 -11
  33. package/dist/types/PostgresTypes.d.ts +74 -0
  34. package/dist/types/PostgresTypes.js +98 -0
  35. package/dist/types/index.d.ts +6 -0
  36. package/dist/types/index.js +12 -0
  37. package/dist/validation/ConnectionValidation.d.ts +27 -0
  38. package/dist/validation/ConnectionValidation.js +58 -0
  39. package/dist/validation/IdentifierValidator.d.ts +101 -0
  40. package/dist/validation/IdentifierValidator.js +127 -0
  41. package/dist/validation/QueryValidator.d.ts +61 -0
  42. package/dist/validation/QueryValidator.js +81 -0
  43. package/dist/validation/index.d.ts +9 -0
  44. package/dist/validation/index.js +15 -0
  45. 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 Error(`Entity ${this.entityClass.name} has no primary key`);
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 Error(`Entity ${this.entityClass.name} has no primary key`);
49
+ throw new errors_1.QueryError(`Entity ${this.entityClass.name} has no primary key defined`);
49
50
  }
50
- const primaryKey = this.metadata.primaryColumn.columnName;
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 Error(`Entity ${this.entityClass.name} has no columns to insert (only primary key)`);
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 Error(`Entity ${this.entityClass.name} has no primary key`);
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 Error(`Entity ${this.entityClass.name} has no columns to update (only primary key)`);
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 Error(`Update failed: Entity with ${primaryKey} = ${String(primaryValue)} not found`);
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 Error(`Entity ${this.entityClass.name} has no primary key`);
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 Error(`Cannot delete entity: primary key ${primaryKey} is ${primaryValue}`);
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 Error(`Entity ${this.entityClass.name} has no primary key`);
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 Error(`Entity ${this.entityClass.name} has no primary key`);
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,6 @@
1
+ /**
2
+ * TypeScript types for the Accio ORM
3
+ *
4
+ * Provides strict type mapping between TypeScript and PostgreSQL types,
5
+ */
6
+ export { getPostgresTypeForTS, isValidPostgresType, PostgresType } from './PostgresTypes';
@@ -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
+ }