@hacimertgokhan/next-audit 1.0.3 → 1.0.4
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/lib/mysql.js +110 -109
- package/package.json +1 -1
- package/src/lib/mysql.ts +133 -126
package/dist/lib/mysql.js
CHANGED
|
@@ -3,139 +3,140 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.getDbConnection = getDbConnection;
|
|
4
4
|
exports.saveAuditLog = saveAuditLog;
|
|
5
5
|
const promise_1 = require("mysql2/promise");
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
// Use global to persist pool across HMR reloads in Next.js development
|
|
7
|
+
const globalForAudit = global;
|
|
8
8
|
async function getDbConnection(config) {
|
|
9
9
|
const configKey = JSON.stringify(config.database);
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const url = new URL(dbConfig.url.replace('mysql://', 'http://')); // URL parser needs a known protocol
|
|
35
|
-
const parsedConfig = {
|
|
36
|
-
...poolOptions,
|
|
37
|
-
host: url.hostname,
|
|
38
|
-
port: parseInt(url.port) || 3306,
|
|
39
|
-
user: url.username,
|
|
40
|
-
password: url.password,
|
|
41
|
-
database: url.pathname.substring(1),
|
|
42
|
-
};
|
|
43
|
-
// If the user's IP issue is real, maybe passing it as an object fixed it
|
|
44
|
-
pool = (0, promise_1.createPool)(parsedConfig);
|
|
45
|
-
}
|
|
46
|
-
catch (e) {
|
|
47
|
-
// If parsing fails (e.g. invalid URL), fallback to direct string
|
|
48
|
-
pool = (0, promise_1.createPool)(dbConfig.url);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
10
|
+
if (globalForAudit._nextAuditPool && globalForAudit._nextAuditConfig === configKey) {
|
|
11
|
+
return globalForAudit._nextAuditPool;
|
|
12
|
+
}
|
|
13
|
+
// Cleanup old pool if config changed
|
|
14
|
+
if (globalForAudit._nextAuditPool) {
|
|
15
|
+
await globalForAudit._nextAuditPool.end().catch(() => { });
|
|
16
|
+
globalForAudit._nextAuditTableInitialized = false;
|
|
17
|
+
}
|
|
18
|
+
const { database: dbConfig } = config;
|
|
19
|
+
const poolOptions = {
|
|
20
|
+
waitForConnections: true,
|
|
21
|
+
connectionLimit: 10,
|
|
22
|
+
maxIdle: 10,
|
|
23
|
+
idleTimeout: 60000,
|
|
24
|
+
queueLimit: 0,
|
|
25
|
+
enableKeepAlive: true,
|
|
26
|
+
keepAliveInitialDelay: 10000,
|
|
27
|
+
connectTimeout: 10000,
|
|
28
|
+
};
|
|
29
|
+
let pool;
|
|
30
|
+
if (dbConfig.url) {
|
|
31
|
+
// Try to parse URL to avoid driver quirks with some IP formats
|
|
32
|
+
try {
|
|
33
|
+
const mysqlUrl = new URL(dbConfig.url.replace('mysql://', 'http://'));
|
|
52
34
|
pool = (0, promise_1.createPool)({
|
|
53
35
|
...poolOptions,
|
|
54
|
-
host:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
36
|
+
host: mysqlUrl.hostname,
|
|
37
|
+
port: parseInt(mysqlUrl.port) || 3306,
|
|
38
|
+
user: mysqlUrl.username,
|
|
39
|
+
password: mysqlUrl.password,
|
|
40
|
+
database: mysqlUrl.pathname.substring(1),
|
|
59
41
|
});
|
|
60
42
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
43
|
+
catch (e) {
|
|
44
|
+
pool = (0, promise_1.createPool)(dbConfig.url);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
pool = (0, promise_1.createPool)({
|
|
49
|
+
...poolOptions,
|
|
50
|
+
host: dbConfig.host,
|
|
51
|
+
user: dbConfig.user,
|
|
52
|
+
password: dbConfig.password,
|
|
53
|
+
database: dbConfig.database,
|
|
54
|
+
port: dbConfig.port || 3306,
|
|
68
55
|
});
|
|
69
56
|
}
|
|
57
|
+
// Explicit error listener to clear global pool on fatal errors
|
|
58
|
+
pool.on('error', (err) => {
|
|
59
|
+
if (err.code === 'PROTOCOL_CONNECTION_LOST' || err.fatal) {
|
|
60
|
+
globalForAudit._nextAuditPool = undefined;
|
|
61
|
+
globalForAudit._nextAuditTableInitialized = false;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
globalForAudit._nextAuditPool = pool;
|
|
65
|
+
globalForAudit._nextAuditConfig = configKey;
|
|
66
|
+
globalForAudit._nextAuditTableInitialized = false;
|
|
70
67
|
return pool;
|
|
71
68
|
}
|
|
69
|
+
async function ensureTableExists(connection, tableName) {
|
|
70
|
+
if (globalForAudit._nextAuditTableInitialized)
|
|
71
|
+
return;
|
|
72
|
+
await connection.query(`
|
|
73
|
+
CREATE TABLE IF NOT EXISTS \`${tableName}\` (
|
|
74
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
75
|
+
timestamp DATETIME,
|
|
76
|
+
method VARCHAR(10),
|
|
77
|
+
url VARCHAR(255),
|
|
78
|
+
user_id VARCHAR(50),
|
|
79
|
+
entity VARCHAR(50),
|
|
80
|
+
entity_id VARCHAR(50),
|
|
81
|
+
old_value JSON,
|
|
82
|
+
new_value JSON,
|
|
83
|
+
action_type VARCHAR(50),
|
|
84
|
+
status INT,
|
|
85
|
+
duration_ms INT,
|
|
86
|
+
ip VARCHAR(45),
|
|
87
|
+
user_agent VARCHAR(255)
|
|
88
|
+
)
|
|
89
|
+
`);
|
|
90
|
+
globalForAudit._nextAuditTableInitialized = true;
|
|
91
|
+
}
|
|
72
92
|
async function saveAuditLog(entry, config) {
|
|
93
|
+
if (config.testMode && config.debug) {
|
|
94
|
+
console.log(`[Next-Audit] (Test Mode) Log: ${entry.method} ${entry.url}`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
let connection = null;
|
|
73
98
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
const tableName = config.tableName || 'audit_logs';
|
|
82
|
-
// Check connection before proceeding
|
|
83
|
-
try {
|
|
84
|
-
await db.query('SELECT 1');
|
|
85
|
-
}
|
|
86
|
-
catch (e) {
|
|
87
|
-
if (e.code === 'PROTOCOL_CONNECTION_LOST' || e.code === 'ECONNRESET') {
|
|
88
|
-
pool = null; // Reset pool
|
|
89
|
-
const retryDb = await getDbConnection(config);
|
|
90
|
-
// Continue with retryDb
|
|
91
|
-
}
|
|
92
|
-
else {
|
|
93
|
-
throw e;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
const finalDb = pool || (await getDbConnection(config));
|
|
97
|
-
await finalDb.query(`
|
|
98
|
-
CREATE TABLE IF NOT EXISTS \`${tableName}\` (
|
|
99
|
-
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
100
|
-
timestamp DATETIME,
|
|
101
|
-
method VARCHAR(10),
|
|
102
|
-
url VARCHAR(255),
|
|
103
|
-
user_id VARCHAR(50),
|
|
104
|
-
entity VARCHAR(50),
|
|
105
|
-
entity_id VARCHAR(50),
|
|
106
|
-
old_value JSON,
|
|
107
|
-
new_value JSON,
|
|
108
|
-
action_type VARCHAR(50),
|
|
109
|
-
status INT,
|
|
110
|
-
duration_ms INT,
|
|
111
|
-
ip VARCHAR(45),
|
|
112
|
-
user_agent VARCHAR(255)
|
|
113
|
-
)
|
|
114
|
-
`);
|
|
115
|
-
await finalDb.query(`INSERT INTO \`${tableName}\`
|
|
116
|
-
(timestamp, method, url, user_id, entity, entity_id, old_value, new_value, action_type, status, duration_ms, ip, user_agent)
|
|
117
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
99
|
+
const pool = await getDbConnection(config);
|
|
100
|
+
// Checkout connection from pool
|
|
101
|
+
connection = await pool.getConnection();
|
|
102
|
+
// One-time table initialization per pool lifecycle
|
|
103
|
+
await ensureTableExists(connection, config.tableName || 'audit_logs');
|
|
104
|
+
// Prepare values - ensure JSON is stringified or null
|
|
105
|
+
const values = [
|
|
118
106
|
entry.timestamp,
|
|
119
107
|
entry.method,
|
|
120
108
|
entry.url,
|
|
121
|
-
entry.user_id,
|
|
122
|
-
entry.entity,
|
|
123
|
-
entry.entity_id,
|
|
109
|
+
entry.user_id || null,
|
|
110
|
+
entry.entity || null,
|
|
111
|
+
entry.entity_id || null,
|
|
124
112
|
entry.old_value ? JSON.stringify(entry.old_value) : null,
|
|
125
113
|
entry.new_value ? JSON.stringify(entry.new_value) : null,
|
|
126
114
|
entry.action_type,
|
|
127
115
|
entry.status,
|
|
128
116
|
entry.duration_ms,
|
|
129
|
-
entry.ip,
|
|
130
|
-
entry.user_agent
|
|
131
|
-
]
|
|
117
|
+
entry.ip || null,
|
|
118
|
+
entry.user_agent || null
|
|
119
|
+
];
|
|
120
|
+
const tableName = config.tableName || 'audit_logs';
|
|
121
|
+
await connection.query(`INSERT INTO \`${tableName}\`
|
|
122
|
+
(timestamp, method, url, user_id, entity, entity_id, old_value, new_value, action_type, status, duration_ms, ip, user_agent)
|
|
123
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, values);
|
|
132
124
|
if (config.debug) {
|
|
133
125
|
console.log(`[Next-Audit] Log saved: ${entry.method} ${entry.url}`);
|
|
134
126
|
}
|
|
135
127
|
}
|
|
136
128
|
catch (error) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
pool
|
|
129
|
+
const dbHost = config.database.url || config.database.host || 'unknown';
|
|
130
|
+
console.error(`[Next-Audit] Failed to save audit log to ${dbHost}:`, error.message || error);
|
|
131
|
+
// If it's a connection error, clear the pool so it recreates next time
|
|
132
|
+
if (error.code === 'PROTOCOL_CONNECTION_LOST' || error.code === 'ETIMEDOUT' || error.fatal) {
|
|
133
|
+
globalForAudit._nextAuditPool = undefined;
|
|
134
|
+
globalForAudit._nextAuditTableInitialized = false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
if (connection) {
|
|
139
|
+
connection.release();
|
|
140
|
+
}
|
|
140
141
|
}
|
|
141
142
|
}
|
package/package.json
CHANGED
package/src/lib/mysql.ts
CHANGED
|
@@ -1,155 +1,162 @@
|
|
|
1
|
-
import { createPool, Pool } from 'mysql2/promise';
|
|
1
|
+
import { createPool, Pool, PoolConnection } from 'mysql2/promise';
|
|
2
2
|
import { AuditConfig, AuditLogEntry } from '../types';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
// Use global to persist pool across HMR reloads in Next.js development
|
|
5
|
+
const globalForAudit = global as unknown as {
|
|
6
|
+
_nextAuditPool?: Pool;
|
|
7
|
+
_nextAuditConfig?: string;
|
|
8
|
+
_nextAuditTableInitialized?: boolean;
|
|
9
|
+
};
|
|
6
10
|
|
|
7
|
-
export async function getDbConnection(config: AuditConfig) {
|
|
11
|
+
export async function getDbConnection(config: AuditConfig): Promise<Pool> {
|
|
8
12
|
const configKey = JSON.stringify(config.database);
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
await pool.end().catch(() => { });
|
|
14
|
-
}
|
|
14
|
+
if (globalForAudit._nextAuditPool && globalForAudit._nextAuditConfig === configKey) {
|
|
15
|
+
return globalForAudit._nextAuditPool;
|
|
16
|
+
}
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
enableKeepAlive: true,
|
|
22
|
-
keepAliveInitialDelay: 10000,
|
|
23
|
-
connectTimeout: 10000,
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
if (dbConfig.url) {
|
|
27
|
-
// Some environments have issues with URL strings, let's try to parse it if it looks suspicious
|
|
28
|
-
// but mysql2 generally handles it. We'll pass it directly first.
|
|
29
|
-
pool = createPool(dbConfig.url);
|
|
18
|
+
// Cleanup old pool if config changed
|
|
19
|
+
if (globalForAudit._nextAuditPool) {
|
|
20
|
+
await globalForAudit._nextAuditPool.end().catch(() => { });
|
|
21
|
+
globalForAudit._nextAuditTableInitialized = false;
|
|
22
|
+
}
|
|
30
23
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
pool = createPool(parsedConfig);
|
|
50
|
-
} catch (e) {
|
|
51
|
-
// If parsing fails (e.g. invalid URL), fallback to direct string
|
|
52
|
-
pool = createPool(dbConfig.url);
|
|
53
|
-
}
|
|
54
|
-
} else {
|
|
24
|
+
const { database: dbConfig } = config;
|
|
25
|
+
const poolOptions: any = {
|
|
26
|
+
waitForConnections: true,
|
|
27
|
+
connectionLimit: 10,
|
|
28
|
+
maxIdle: 10,
|
|
29
|
+
idleTimeout: 60000,
|
|
30
|
+
queueLimit: 0,
|
|
31
|
+
enableKeepAlive: true,
|
|
32
|
+
keepAliveInitialDelay: 10000,
|
|
33
|
+
connectTimeout: 10000,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
let pool: Pool;
|
|
37
|
+
|
|
38
|
+
if (dbConfig.url) {
|
|
39
|
+
// Try to parse URL to avoid driver quirks with some IP formats
|
|
40
|
+
try {
|
|
41
|
+
const mysqlUrl = new URL(dbConfig.url.replace('mysql://', 'http://'));
|
|
55
42
|
pool = createPool({
|
|
56
43
|
...poolOptions,
|
|
57
|
-
host:
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
44
|
+
host: mysqlUrl.hostname,
|
|
45
|
+
port: parseInt(mysqlUrl.port) || 3306,
|
|
46
|
+
user: mysqlUrl.username,
|
|
47
|
+
password: mysqlUrl.password,
|
|
48
|
+
database: mysqlUrl.pathname.substring(1),
|
|
62
49
|
});
|
|
50
|
+
} catch (e) {
|
|
51
|
+
pool = createPool(dbConfig.url);
|
|
63
52
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
53
|
+
} else {
|
|
54
|
+
pool = createPool({
|
|
55
|
+
...poolOptions,
|
|
56
|
+
host: dbConfig.host,
|
|
57
|
+
user: dbConfig.user,
|
|
58
|
+
password: dbConfig.password,
|
|
59
|
+
database: dbConfig.database,
|
|
60
|
+
port: dbConfig.port || 3306,
|
|
73
61
|
});
|
|
74
62
|
}
|
|
75
63
|
|
|
64
|
+
// Explicit error listener to clear global pool on fatal errors
|
|
65
|
+
(pool as any).on('error', (err: any) => {
|
|
66
|
+
if (err.code === 'PROTOCOL_CONNECTION_LOST' || err.fatal) {
|
|
67
|
+
globalForAudit._nextAuditPool = undefined;
|
|
68
|
+
globalForAudit._nextAuditTableInitialized = false;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
globalForAudit._nextAuditPool = pool;
|
|
73
|
+
globalForAudit._nextAuditConfig = configKey;
|
|
74
|
+
globalForAudit._nextAuditTableInitialized = false;
|
|
75
|
+
|
|
76
76
|
return pool;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
79
|
+
async function ensureTableExists(connection: PoolConnection, tableName: string) {
|
|
80
|
+
if (globalForAudit._nextAuditTableInitialized) return;
|
|
81
|
+
|
|
82
|
+
await connection.query(`
|
|
83
|
+
CREATE TABLE IF NOT EXISTS \`${tableName}\` (
|
|
84
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
85
|
+
timestamp DATETIME,
|
|
86
|
+
method VARCHAR(10),
|
|
87
|
+
url VARCHAR(255),
|
|
88
|
+
user_id VARCHAR(50),
|
|
89
|
+
entity VARCHAR(50),
|
|
90
|
+
entity_id VARCHAR(50),
|
|
91
|
+
old_value JSON,
|
|
92
|
+
new_value JSON,
|
|
93
|
+
action_type VARCHAR(50),
|
|
94
|
+
status INT,
|
|
95
|
+
duration_ms INT,
|
|
96
|
+
ip VARCHAR(45),
|
|
97
|
+
user_agent VARCHAR(255)
|
|
98
|
+
)
|
|
99
|
+
`);
|
|
87
100
|
|
|
88
|
-
|
|
89
|
-
|
|
101
|
+
globalForAudit._nextAuditTableInitialized = true;
|
|
102
|
+
}
|
|
90
103
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
pool = null; // Reset pool
|
|
97
|
-
const retryDb = await getDbConnection(config);
|
|
98
|
-
// Continue with retryDb
|
|
99
|
-
} else {
|
|
100
|
-
throw e;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
104
|
+
export async function saveAuditLog(entry: AuditLogEntry, config: AuditConfig) {
|
|
105
|
+
if (config.testMode && config.debug) {
|
|
106
|
+
console.log(`[Next-Audit] (Test Mode) Log: ${entry.method} ${entry.url}`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
103
109
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
await
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
110
|
+
let connection: PoolConnection | null = null;
|
|
111
|
+
try {
|
|
112
|
+
const pool = await getDbConnection(config);
|
|
113
|
+
|
|
114
|
+
// Checkout connection from pool
|
|
115
|
+
connection = await pool.getConnection();
|
|
116
|
+
|
|
117
|
+
// One-time table initialization per pool lifecycle
|
|
118
|
+
await ensureTableExists(connection, config.tableName || 'audit_logs');
|
|
119
|
+
|
|
120
|
+
// Prepare values - ensure JSON is stringified or null
|
|
121
|
+
const values = [
|
|
122
|
+
entry.timestamp,
|
|
123
|
+
entry.method,
|
|
124
|
+
entry.url,
|
|
125
|
+
entry.user_id || null,
|
|
126
|
+
entry.entity || null,
|
|
127
|
+
entry.entity_id || null,
|
|
128
|
+
entry.old_value ? JSON.stringify(entry.old_value) : null,
|
|
129
|
+
entry.new_value ? JSON.stringify(entry.new_value) : null,
|
|
130
|
+
entry.action_type,
|
|
131
|
+
entry.status,
|
|
132
|
+
entry.duration_ms,
|
|
133
|
+
entry.ip || null,
|
|
134
|
+
entry.user_agent || null
|
|
135
|
+
];
|
|
124
136
|
|
|
125
|
-
|
|
137
|
+
const tableName = config.tableName || 'audit_logs';
|
|
138
|
+
await connection.query(
|
|
126
139
|
`INSERT INTO \`${tableName}\`
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
entry.timestamp,
|
|
131
|
-
entry.method,
|
|
132
|
-
entry.url,
|
|
133
|
-
entry.user_id,
|
|
134
|
-
entry.entity,
|
|
135
|
-
entry.entity_id,
|
|
136
|
-
entry.old_value ? JSON.stringify(entry.old_value) : null,
|
|
137
|
-
entry.new_value ? JSON.stringify(entry.new_value) : null,
|
|
138
|
-
entry.action_type,
|
|
139
|
-
entry.status,
|
|
140
|
-
entry.duration_ms,
|
|
141
|
-
entry.ip,
|
|
142
|
-
entry.user_agent
|
|
143
|
-
]
|
|
140
|
+
(timestamp, method, url, user_id, entity, entity_id, old_value, new_value, action_type, status, duration_ms, ip, user_agent)
|
|
141
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
142
|
+
values
|
|
144
143
|
);
|
|
145
144
|
|
|
146
145
|
if (config.debug) {
|
|
147
146
|
console.log(`[Next-Audit] Log saved: ${entry.method} ${entry.url}`);
|
|
148
147
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
console.error(
|
|
152
|
-
|
|
153
|
-
pool
|
|
148
|
+
} catch (error: any) {
|
|
149
|
+
const dbHost = config.database.url || config.database.host || 'unknown';
|
|
150
|
+
console.error(`[Next-Audit] Failed to save audit log to ${dbHost}:`, error.message || error);
|
|
151
|
+
|
|
152
|
+
// If it's a connection error, clear the pool so it recreates next time
|
|
153
|
+
if (error.code === 'PROTOCOL_CONNECTION_LOST' || error.code === 'ETIMEDOUT' || error.fatal) {
|
|
154
|
+
globalForAudit._nextAuditPool = undefined;
|
|
155
|
+
globalForAudit._nextAuditTableInitialized = false;
|
|
156
|
+
}
|
|
157
|
+
} finally {
|
|
158
|
+
if (connection) {
|
|
159
|
+
connection.release();
|
|
160
|
+
}
|
|
154
161
|
}
|
|
155
162
|
}
|