@nitronjs/framework 0.2.3 → 0.2.5
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 +3 -1
- package/cli/create.js +88 -72
- package/cli/njs.js +14 -7
- package/lib/Auth/Auth.js +167 -0
- package/lib/Build/CssBuilder.js +9 -0
- package/lib/Build/FileAnalyzer.js +16 -0
- package/lib/Build/HydrationBuilder.js +17 -0
- package/lib/Build/Manager.js +15 -0
- package/lib/Build/colors.js +4 -0
- package/lib/Build/plugins.js +84 -20
- package/lib/Console/Commands/DevCommand.js +13 -9
- package/lib/Console/Commands/MakeCommand.js +24 -10
- package/lib/Console/Commands/MigrateCommand.js +0 -1
- package/lib/Console/Commands/MigrateFreshCommand.js +17 -25
- package/lib/Console/Commands/MigrateRollbackCommand.js +6 -3
- package/lib/Console/Commands/MigrateStatusCommand.js +6 -3
- package/lib/Console/Commands/SeedCommand.js +4 -2
- package/lib/Console/Commands/StorageLinkCommand.js +20 -5
- package/lib/Console/Output.js +142 -0
- package/lib/Core/Config.js +2 -1
- package/lib/Core/Paths.js +8 -0
- package/lib/Database/DB.js +141 -51
- package/lib/Database/Drivers/MySQLDriver.js +102 -157
- package/lib/Database/Migration/Checksum.js +3 -8
- package/lib/Database/Migration/MigrationRepository.js +25 -35
- package/lib/Database/Migration/MigrationRunner.js +56 -61
- package/lib/Database/Model.js +157 -83
- package/lib/Database/QueryBuilder.js +31 -0
- package/lib/Database/QueryValidation.js +36 -44
- package/lib/Database/Schema/Blueprint.js +25 -36
- package/lib/Database/Schema/Manager.js +31 -68
- package/lib/Database/Seeder/SeederRunner.js +12 -31
- package/lib/Date/DateTime.js +9 -0
- package/lib/Encryption/Encryption.js +52 -0
- package/lib/Faker/Faker.js +11 -0
- package/lib/Filesystem/Storage.js +120 -0
- package/lib/HMR/Server.js +81 -10
- package/lib/Hashing/Hash.js +41 -0
- package/lib/Http/Server.js +177 -152
- package/lib/Logging/{Manager.js → Log.js} +68 -80
- package/lib/Mail/Mail.js +187 -0
- package/lib/Route/Router.js +416 -0
- package/lib/Session/File.js +135 -233
- package/lib/Session/Manager.js +117 -171
- package/lib/Session/Memory.js +28 -38
- package/lib/Session/Session.js +71 -107
- package/lib/Support/Str.js +103 -0
- package/lib/Translation/Lang.js +54 -0
- package/lib/View/Client/hmr-client.js +94 -51
- package/lib/View/Client/nitronjs-icon.png +0 -0
- package/lib/View/{Manager.js → View.js} +44 -29
- package/lib/index.d.ts +42 -8
- package/lib/index.js +19 -12
- package/package.json +1 -1
- package/skeleton/app/Controllers/HomeController.js +7 -1
- package/skeleton/resources/css/global.css +1 -0
- package/skeleton/resources/views/Site/Home.tsx +456 -79
- package/skeleton/tsconfig.json +6 -1
- package/lib/Auth/Manager.js +0 -111
- package/lib/Database/Connection.js +0 -61
- package/lib/Database/Manager.js +0 -162
- package/lib/Encryption/Manager.js +0 -47
- package/lib/Filesystem/Manager.js +0 -74
- package/lib/Hashing/Manager.js +0 -25
- package/lib/Mail/Manager.js +0 -120
- package/lib/Route/Loader.js +0 -80
- package/lib/Route/Manager.js +0 -286
- package/lib/Translation/Manager.js +0 -49
package/lib/Database/DB.js
CHANGED
|
@@ -1,84 +1,174 @@
|
|
|
1
|
-
import
|
|
1
|
+
import MySQLDriver from "./Drivers/MySQLDriver.js";
|
|
2
2
|
import { query as createQueryBuilder, RawExpression } from "./QueryBuilder.js";
|
|
3
|
+
import Config from "../Core/Config.js";
|
|
3
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Database manager providing query builder access, raw queries, and transactions.
|
|
7
|
+
* Supports multiple database drivers (currently MySQL).
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const users = await DB.table("users").where("active", 1).get();
|
|
11
|
+
* await DB.transaction(async (conn) => { ... });
|
|
12
|
+
*/
|
|
4
13
|
class DB {
|
|
5
|
-
static
|
|
6
|
-
|
|
14
|
+
static #driver = null;
|
|
15
|
+
static #config = null;
|
|
16
|
+
static #credentials = null;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Initializes database connection with configured driver.
|
|
20
|
+
* @returns {Promise<void>}
|
|
21
|
+
* @throws {Error} If health check fails
|
|
22
|
+
*/
|
|
23
|
+
static async setup() {
|
|
24
|
+
this.#config = Config.all("database");
|
|
25
|
+
|
|
26
|
+
const envDriver = (process.env.DATABASE_DRIVER || "mysql").toLowerCase();
|
|
27
|
+
|
|
28
|
+
if (envDriver === "none") {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
this.#driver = this.#createDriver(envDriver);
|
|
33
|
+
|
|
34
|
+
const isHealthy = await this.#driver.healthCheck();
|
|
35
|
+
|
|
36
|
+
if (!isHealthy) {
|
|
37
|
+
throw new Error("Database connection health check failed. Check your .env credentials and ensure MySQL is running.");
|
|
38
|
+
}
|
|
7
39
|
}
|
|
8
40
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Closes database connection and releases resources.
|
|
43
|
+
* @returns {Promise<void>}
|
|
44
|
+
*/
|
|
45
|
+
static async close() {
|
|
46
|
+
if (this.#driver) {
|
|
47
|
+
await this.#driver.close();
|
|
48
|
+
this.#driver = null;
|
|
49
|
+
}
|
|
12
50
|
}
|
|
13
51
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Creates a query builder for the specified table.
|
|
54
|
+
* @param {string} table - Table name
|
|
55
|
+
* @param {Function|null} modelClass - Optional model class for result mapping
|
|
56
|
+
* @returns {Object} Query builder instance
|
|
57
|
+
* @throws {Error} If database is disabled
|
|
58
|
+
*/
|
|
59
|
+
static table(table, modelClass = null) {
|
|
60
|
+
if (!this.#driver) {
|
|
61
|
+
throw new Error("Database is disabled (DATABASE_DRIVER=none)");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return createQueryBuilder(table, this.#driver, modelClass);
|
|
17
65
|
}
|
|
18
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Creates a raw SQL expression for use in queries.
|
|
69
|
+
* @param {string} expression - Raw SQL expression
|
|
70
|
+
* @returns {RawExpression} Raw expression wrapper
|
|
71
|
+
*/
|
|
19
72
|
static rawExpr(expression) {
|
|
20
73
|
return new RawExpression(expression);
|
|
21
74
|
}
|
|
22
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Executes a raw SQL query.
|
|
78
|
+
* @param {string} sql - Raw SQL string
|
|
79
|
+
* @returns {Promise<Array>} Query result
|
|
80
|
+
* @throws {Error} If database is disabled
|
|
81
|
+
*/
|
|
23
82
|
static async rawQuery(sql) {
|
|
24
|
-
if (!this.#
|
|
25
|
-
|
|
26
|
-
|
|
83
|
+
if (!this.#driver) {
|
|
84
|
+
throw new Error("Database is disabled (DATABASE_DRIVER=none)");
|
|
85
|
+
}
|
|
27
86
|
|
|
28
|
-
|
|
29
|
-
if (!this.#isEnabled()) return null;
|
|
30
|
-
return await this.connection().withTransaction(callback);
|
|
87
|
+
return await this.#driver.raw(sql);
|
|
31
88
|
}
|
|
32
89
|
|
|
33
90
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* @param {
|
|
37
|
-
* @param {
|
|
38
|
-
* @
|
|
39
|
-
* @
|
|
91
|
+
* Executes a callback within a database transaction.
|
|
92
|
+
* @param {Function} callback - Async function receiving connection
|
|
93
|
+
* @param {Object} options - Transaction options
|
|
94
|
+
* @param {number} options.timeout - Timeout in milliseconds (default: 30000)
|
|
95
|
+
* @returns {Promise<*>} Callback result
|
|
96
|
+
* @throws {Error} If transaction times out or fails
|
|
40
97
|
*/
|
|
41
|
-
static
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
static async setup() {
|
|
48
|
-
if (!this.#isEnabled()) return;
|
|
98
|
+
static async transaction(callback, options = {}) {
|
|
99
|
+
if (!this.#driver) {
|
|
100
|
+
throw new Error("Database is disabled (DATABASE_DRIVER=none)");
|
|
101
|
+
}
|
|
49
102
|
|
|
50
|
-
const
|
|
51
|
-
const
|
|
103
|
+
const timeout = options.timeout || 30000;
|
|
104
|
+
const controller = new AbortController();
|
|
105
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
52
106
|
|
|
53
107
|
try {
|
|
54
|
-
const
|
|
55
|
-
|
|
108
|
+
const result = await this.#driver.withTransaction(callback, { signal: controller.signal });
|
|
109
|
+
clearTimeout(timeoutId);
|
|
56
110
|
|
|
57
|
-
|
|
58
|
-
throw new Error(`Database connection '${defaultConnection}' health check failed`);
|
|
59
|
-
}
|
|
111
|
+
return result;
|
|
60
112
|
}
|
|
61
|
-
catch (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
113
|
+
catch (err) {
|
|
114
|
+
clearTimeout(timeoutId);
|
|
115
|
+
|
|
116
|
+
if (controller.signal.aborted) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Transaction timeout after ${timeout}ms. ` +
|
|
119
|
+
"Consider: 1) Adding await to all async operations, 2) Increasing timeout, 3) Optimizing queries."
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
throw err;
|
|
65
124
|
}
|
|
66
125
|
}
|
|
67
126
|
|
|
68
|
-
|
|
69
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Reconfigures database with new credentials.
|
|
129
|
+
* Used by installer to test and save database settings.
|
|
130
|
+
* @param {Object} credentials - Database credentials
|
|
131
|
+
* @param {string} credentials.host - Database host
|
|
132
|
+
* @param {number} credentials.port - Database port
|
|
133
|
+
* @param {string} credentials.database - Database name
|
|
134
|
+
* @param {string} credentials.username - Database username
|
|
135
|
+
* @param {string} credentials.password - Database password
|
|
136
|
+
* @returns {Promise<void>}
|
|
137
|
+
*/
|
|
138
|
+
static async reconfigure(credentials) {
|
|
139
|
+
await this.close();
|
|
70
140
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
141
|
+
this.#credentials = {
|
|
142
|
+
host: credentials.host,
|
|
143
|
+
port: String(credentials.port),
|
|
144
|
+
database: credentials.database,
|
|
145
|
+
username: credentials.username,
|
|
146
|
+
password: credentials.password || ""
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
this.#driver = this.#createDriver("mysql");
|
|
77
150
|
}
|
|
78
151
|
|
|
79
|
-
|
|
80
|
-
|
|
152
|
+
/** @private */
|
|
153
|
+
static #createDriver(driverName) {
|
|
154
|
+
const connections = this.#config?.connections || {};
|
|
155
|
+
const config = connections[driverName];
|
|
156
|
+
|
|
157
|
+
if (!config) {
|
|
158
|
+
throw new Error(`Database connection '${driverName}' is not configured`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
switch (config.driver) {
|
|
162
|
+
case "mysql":
|
|
163
|
+
return new MySQLDriver(config, this.#credentials);
|
|
164
|
+
case "postgres":
|
|
165
|
+
throw new Error("Postgres driver not yet implemented");
|
|
166
|
+
case "mongodb":
|
|
167
|
+
throw new Error("MongoDB driver not yet implemented");
|
|
168
|
+
default:
|
|
169
|
+
throw new Error(`Unknown database driver: ${config.driver}`);
|
|
170
|
+
}
|
|
81
171
|
}
|
|
82
172
|
}
|
|
83
173
|
|
|
84
|
-
export default DB;
|
|
174
|
+
export default DB;
|
|
@@ -1,33 +1,37 @@
|
|
|
1
|
-
import mysql from
|
|
2
|
-
import Environment from
|
|
1
|
+
import mysql from "mysql2/promise";
|
|
2
|
+
import Environment from "../../Core/Environment.js";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* MySQL database driver using connection pooling.
|
|
6
|
+
* Handles query execution, transactions, and error management.
|
|
7
|
+
*/
|
|
4
8
|
class MySQLDriver {
|
|
5
9
|
#pool = null;
|
|
6
10
|
#config = null;
|
|
7
11
|
#credentials = null;
|
|
8
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Creates a new MySQL driver instance with connection pool.
|
|
15
|
+
* @param {Object} config - Database configuration from config/database.js
|
|
16
|
+
* @param {Object|null} credentials - Optional credential override for reconfiguration
|
|
17
|
+
*/
|
|
9
18
|
constructor(config, credentials = null) {
|
|
10
19
|
this.#config = config;
|
|
11
20
|
this.#credentials = credentials;
|
|
12
|
-
this.#createPool();
|
|
13
|
-
}
|
|
14
21
|
|
|
15
|
-
#createPool() {
|
|
16
22
|
const creds = this.#credentials;
|
|
17
23
|
|
|
18
24
|
this.#pool = mysql.createPool({
|
|
19
|
-
host: creds?.host ?? process.env.DATABASE_HOST ??
|
|
20
|
-
port: parseInt(creds?.port ?? process.env.DATABASE_PORT ??
|
|
21
|
-
user: creds?.username ?? process.env.DATABASE_USERNAME ??
|
|
22
|
-
password: creds?.password ?? process.env.DATABASE_PASSWORD ??
|
|
23
|
-
database: creds?.database ?? process.env.DATABASE_NAME ??
|
|
24
|
-
charset: this.#config.charset ||
|
|
25
|
-
|
|
25
|
+
host: creds?.host ?? process.env.DATABASE_HOST ?? "127.0.0.1",
|
|
26
|
+
port: parseInt(creds?.port ?? process.env.DATABASE_PORT ?? "3306"),
|
|
27
|
+
user: creds?.username ?? process.env.DATABASE_USERNAME ?? "root",
|
|
28
|
+
password: creds?.password ?? process.env.DATABASE_PASSWORD ?? "",
|
|
29
|
+
database: creds?.database ?? process.env.DATABASE_NAME ?? "test",
|
|
30
|
+
charset: this.#config.charset || "utf8mb4",
|
|
26
31
|
waitForConnections: true,
|
|
27
32
|
connectionLimit: this.#config.pool?.max ?? 10,
|
|
28
33
|
queueLimit: this.#config.pool?.queueLimit ?? 100,
|
|
29
34
|
connectTimeout: 10000,
|
|
30
|
-
|
|
31
35
|
enableKeepAlive: true,
|
|
32
36
|
keepAliveInitialDelay: 10000,
|
|
33
37
|
namedPlaceholders: true,
|
|
@@ -35,201 +39,142 @@ class MySQLDriver {
|
|
|
35
39
|
});
|
|
36
40
|
}
|
|
37
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Executes a prepared SQL query with bindings.
|
|
44
|
+
* @param {string} sql - SQL query string
|
|
45
|
+
* @param {Array} bindings - Parameter bindings
|
|
46
|
+
* @returns {Promise<Array>} Query result [rows, fields]
|
|
47
|
+
* @throws {Error} On query failure
|
|
48
|
+
*/
|
|
38
49
|
async query(sql, bindings = []) {
|
|
39
|
-
const
|
|
50
|
+
const connection = await this.#pool.getConnection();
|
|
40
51
|
|
|
41
52
|
try {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
await connection.query('SET SESSION MAX_EXECUTION_TIME = 60000');
|
|
46
|
-
const [rows, fields] = await connection.execute(sql, bindings);
|
|
53
|
+
await connection.query("SET SESSION MAX_EXECUTION_TIME = 60000");
|
|
47
54
|
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
finally {
|
|
51
|
-
connection.release();
|
|
52
|
-
}
|
|
55
|
+
return await connection.execute(sql, bindings);
|
|
53
56
|
}
|
|
54
|
-
|
|
55
57
|
catch (error) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
sqlState: error.sqlState
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const sanitized = new Error('Database query failed');
|
|
64
|
-
sanitized.code = error.code;
|
|
65
|
-
sanitized.driver = 'mysql';
|
|
66
|
-
throw sanitized;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
error.sql = sql;
|
|
70
|
-
error.bindings = this.#maskSensitiveData(bindings);
|
|
71
|
-
error.driver = 'mysql';
|
|
72
|
-
throw error;
|
|
58
|
+
throw this.#handleError(error, sql, bindings);
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
connection.release();
|
|
73
62
|
}
|
|
74
63
|
}
|
|
75
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Executes a raw SQL query without parameter binding.
|
|
67
|
+
* @param {string} sql - Raw SQL string
|
|
68
|
+
* @returns {Promise<Array>} Query result
|
|
69
|
+
*/
|
|
76
70
|
async raw(sql) {
|
|
77
|
-
const isProduction = Environment.isProd;
|
|
78
|
-
|
|
79
71
|
try {
|
|
80
|
-
|
|
81
|
-
return [rows, fields];
|
|
72
|
+
return await this.#pool.query(sql);
|
|
82
73
|
}
|
|
83
|
-
|
|
84
74
|
catch (error) {
|
|
85
|
-
|
|
86
|
-
console.error('[MySQLDriver] Raw query failed', {
|
|
87
|
-
code: error.code,
|
|
88
|
-
errno: error.errno,
|
|
89
|
-
sqlState: error.sqlState
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
const sanitized = new Error('Database query failed');
|
|
93
|
-
sanitized.code = error.code;
|
|
94
|
-
sanitized.driver = 'mysql';
|
|
95
|
-
throw sanitized;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
error.sql = sql;
|
|
99
|
-
error.driver = 'mysql';
|
|
100
|
-
throw error;
|
|
75
|
+
throw this.#handleError(error, sql);
|
|
101
76
|
}
|
|
102
77
|
}
|
|
103
78
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const sensitivePatterns = /password|token|secret|key|auth|bearer|jwt|otp|pin|credential/i;
|
|
115
|
-
const valuePreview = value.substring(0, 30).toLowerCase();
|
|
116
|
-
const looksLikeBase64 = /^[A-Za-z0-9+/]+=*$/.test(value) && value.length > 16;
|
|
117
|
-
|
|
118
|
-
if (sensitivePatterns.test(valuePreview) || value.length > 20 || looksLikeBase64) {
|
|
119
|
-
return '***MASKED***';
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return value;
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async beginTransaction() {
|
|
79
|
+
/**
|
|
80
|
+
* Executes callback within a database transaction.
|
|
81
|
+
* Automatically commits on success or rolls back on failure.
|
|
82
|
+
* @param {Function} callback - Async function receiving connection
|
|
83
|
+
* @param {Object} options - Transaction options
|
|
84
|
+
* @param {AbortSignal} options.signal - Abort signal for timeout
|
|
85
|
+
* @returns {Promise<*>} Callback result
|
|
86
|
+
*/
|
|
87
|
+
async withTransaction(callback, options = {}) {
|
|
127
88
|
const connection = await this.#pool.getConnection();
|
|
128
|
-
await connection.query('SET SESSION MAX_EXECUTION_TIME = 60000');
|
|
129
89
|
|
|
90
|
+
await connection.query("SET SESSION MAX_EXECUTION_TIME = 60000");
|
|
130
91
|
await connection.beginTransaction();
|
|
131
92
|
|
|
132
|
-
return connection;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async commit(connection) {
|
|
136
93
|
try {
|
|
137
|
-
|
|
138
|
-
|
|
94
|
+
if (options.signal?.aborted) {
|
|
95
|
+
throw new Error("Transaction aborted");
|
|
96
|
+
}
|
|
139
97
|
|
|
140
|
-
|
|
141
|
-
connection.release();
|
|
142
|
-
}
|
|
143
|
-
}
|
|
98
|
+
const result = await callback(connection);
|
|
144
99
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
100
|
+
if (options.signal?.aborted) {
|
|
101
|
+
await connection.rollback();
|
|
102
|
+
connection.release();
|
|
149
103
|
|
|
150
|
-
|
|
151
|
-
|
|
104
|
+
throw new Error("Transaction aborted");
|
|
105
|
+
}
|
|
152
106
|
|
|
153
|
-
|
|
107
|
+
await connection.commit();
|
|
154
108
|
connection.release();
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
109
|
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
const [rows, fields] = await connection.execute(sql, bindings);
|
|
161
|
-
return [rows, fields];
|
|
110
|
+
return result;
|
|
162
111
|
}
|
|
163
|
-
|
|
164
112
|
catch (error) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (isProduction) {
|
|
168
|
-
console.error('[MySQLDriver] Transaction query failed', {
|
|
169
|
-
code: error.code,
|
|
170
|
-
errno: error.errno,
|
|
171
|
-
sqlState: error.sqlState,
|
|
172
|
-
inTransaction: true
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
const sanitized = new Error('Database query failed');
|
|
176
|
-
sanitized.code = error.code;
|
|
177
|
-
sanitized.driver = 'mysql';
|
|
178
|
-
sanitized.inTransaction = true;
|
|
179
|
-
throw sanitized;
|
|
113
|
+
try {
|
|
114
|
+
await connection.rollback();
|
|
180
115
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
error.bindings = this.#maskSensitiveData(bindings);
|
|
184
|
-
error.driver = 'mysql';
|
|
185
|
-
error.inTransaction = true;
|
|
186
|
-
throw error;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
async withTransaction(callback, options = {}) {
|
|
191
|
-
const signal = options?.signal;
|
|
192
|
-
const connection = await this.beginTransaction();
|
|
193
|
-
|
|
194
|
-
try {
|
|
195
|
-
if (signal?.aborted) {
|
|
196
|
-
throw new Error('Transaction aborted');
|
|
116
|
+
catch {
|
|
117
|
+
// Ignore rollback errors
|
|
197
118
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if (signal?.aborted) {
|
|
201
|
-
await this.rollback(connection);
|
|
202
|
-
throw new Error('Transaction aborted');
|
|
119
|
+
finally {
|
|
120
|
+
connection.release();
|
|
203
121
|
}
|
|
204
122
|
|
|
205
|
-
await this.commit(connection);
|
|
206
|
-
|
|
207
|
-
return result;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
catch (error) {
|
|
211
|
-
await this.rollback(connection);
|
|
212
123
|
throw error;
|
|
213
124
|
}
|
|
214
125
|
}
|
|
215
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Checks if database connection is healthy.
|
|
129
|
+
* @returns {Promise<boolean>} True if connection is alive
|
|
130
|
+
*/
|
|
216
131
|
async healthCheck() {
|
|
217
132
|
try {
|
|
218
|
-
await this.#pool.query(
|
|
133
|
+
await this.#pool.query("SELECT 1");
|
|
134
|
+
|
|
219
135
|
return true;
|
|
220
136
|
}
|
|
221
|
-
|
|
222
|
-
catch (error) {
|
|
137
|
+
catch {
|
|
223
138
|
return false;
|
|
224
139
|
}
|
|
225
140
|
}
|
|
226
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Closes connection pool and releases resources.
|
|
144
|
+
* @returns {Promise<void>}
|
|
145
|
+
*/
|
|
227
146
|
async close() {
|
|
228
147
|
if (this.#pool) {
|
|
229
148
|
await this.#pool.end();
|
|
230
149
|
this.#pool = null;
|
|
231
150
|
}
|
|
232
151
|
}
|
|
152
|
+
|
|
153
|
+
/** @private */
|
|
154
|
+
#handleError(error, sql, bindings = null) {
|
|
155
|
+
if (Environment.isProd) {
|
|
156
|
+
console.error("[MySQLDriver] Query failed", {
|
|
157
|
+
code: error.code,
|
|
158
|
+
errno: error.errno,
|
|
159
|
+
sqlState: error.sqlState
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const sanitized = new Error("Database query failed");
|
|
163
|
+
sanitized.code = error.code;
|
|
164
|
+
sanitized.driver = "mysql";
|
|
165
|
+
|
|
166
|
+
return sanitized;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
error.sql = sql;
|
|
170
|
+
error.driver = "mysql";
|
|
171
|
+
|
|
172
|
+
if (bindings) {
|
|
173
|
+
error.bindings = bindings;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return error;
|
|
177
|
+
}
|
|
233
178
|
}
|
|
234
179
|
|
|
235
180
|
export default MySQLDriver;
|
|
@@ -2,22 +2,17 @@ import { createHash } from 'crypto';
|
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
|
|
4
4
|
class Checksum {
|
|
5
|
-
|
|
6
5
|
static fromFile(filePath) {
|
|
7
|
-
|
|
8
|
-
return this.fromContent(content);
|
|
6
|
+
return this.fromContent(fs.readFileSync(filePath, 'utf8'));
|
|
9
7
|
}
|
|
10
8
|
|
|
11
9
|
static fromContent(content) {
|
|
12
|
-
|
|
13
|
-
return createHash('sha256').update(normalized, 'utf8').digest('hex');
|
|
10
|
+
return createHash('sha256').update(content.replace(/\r\n/g, '\n').trim(), 'utf8').digest('hex');
|
|
14
11
|
}
|
|
15
12
|
|
|
16
13
|
static verify(filePath, expectedChecksum) {
|
|
17
|
-
|
|
18
|
-
return actualChecksum === expectedChecksum;
|
|
14
|
+
return this.fromFile(filePath) === expectedChecksum;
|
|
19
15
|
}
|
|
20
|
-
|
|
21
16
|
}
|
|
22
17
|
|
|
23
18
|
export default Checksum;
|