@zintrust/db-mysql 1.8.0 → 1.8.8
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.
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { ErrorFactory, Logger } from '@zintrust/core';
|
|
2
|
+
import { CREATE_MIGRATIONS_TABLE_SQL, MYSQL_PLACEHOLDER, MYSQL_TYPE } from './common.js';
|
|
3
|
+
const getDoTimeoutMs = () => {
|
|
4
|
+
const globalEnv = globalThis.env;
|
|
5
|
+
const raw = globalEnv?.['MYSQL_DO_TIMEOUT_MS'] ?? globalEnv?.['DO_REQUEST_TIMEOUT_MS'];
|
|
6
|
+
const parsed = Number(raw);
|
|
7
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 15000;
|
|
8
|
+
};
|
|
9
|
+
const createTimeoutSignal = (timeoutMs) => {
|
|
10
|
+
if (timeoutMs <= 0)
|
|
11
|
+
return undefined;
|
|
12
|
+
const timeout = AbortSignal.timeout;
|
|
13
|
+
return typeof timeout === 'function' ? timeout(timeoutMs) : undefined;
|
|
14
|
+
};
|
|
15
|
+
const createSendQueryFunction = (getStub, connect) => {
|
|
16
|
+
return async (sql, params, method) => {
|
|
17
|
+
await connect();
|
|
18
|
+
const stub = getStub();
|
|
19
|
+
const executePath = 'http://do/execute'; //NOSONAR
|
|
20
|
+
const queryPath = 'http://do/query'; //NOSONAR
|
|
21
|
+
const payload = JSON.stringify({
|
|
22
|
+
command: sql,
|
|
23
|
+
sql,
|
|
24
|
+
params,
|
|
25
|
+
method,
|
|
26
|
+
});
|
|
27
|
+
const timeoutMs = getDoTimeoutMs();
|
|
28
|
+
const send = async (path) => {
|
|
29
|
+
const startedAt = Date.now();
|
|
30
|
+
try {
|
|
31
|
+
const response = await stub.fetch(path, {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: {
|
|
34
|
+
'Content-Type': 'application/json',
|
|
35
|
+
},
|
|
36
|
+
body: payload,
|
|
37
|
+
signal: createTimeoutSignal(timeoutMs),
|
|
38
|
+
});
|
|
39
|
+
Logger.debug('[MySqlWorkersDurableObjectAdapter] DO request completed', {
|
|
40
|
+
path,
|
|
41
|
+
status: response.status,
|
|
42
|
+
durationMs: Date.now() - startedAt,
|
|
43
|
+
timeoutMs,
|
|
44
|
+
sqlPreview: sql.slice(0, 80),
|
|
45
|
+
});
|
|
46
|
+
return response;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
Logger.error('[MySqlWorkersDurableObjectAdapter] DO request failed', {
|
|
50
|
+
path,
|
|
51
|
+
durationMs: Date.now() - startedAt,
|
|
52
|
+
timeoutMs,
|
|
53
|
+
error: error instanceof Error ? error.message : String(error),
|
|
54
|
+
});
|
|
55
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
56
|
+
throw ErrorFactory.createGeneralError(`MySQL DO request timed out after ${timeoutMs}ms (${path})`, error);
|
|
57
|
+
}
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
let response = await send(executePath);
|
|
62
|
+
if (!response.ok && (response.status === 404 || response.status === 405)) {
|
|
63
|
+
response = await send(queryPath);
|
|
64
|
+
}
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
const text = await response.text();
|
|
67
|
+
let errDetail;
|
|
68
|
+
try {
|
|
69
|
+
errDetail = JSON.parse(text);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
errDetail = { error: text };
|
|
73
|
+
}
|
|
74
|
+
const msg = errDetail.error ||
|
|
75
|
+
errDetail.message ||
|
|
76
|
+
response.statusText;
|
|
77
|
+
throw ErrorFactory.createGeneralError(`DO Query Failed: ${msg}`);
|
|
78
|
+
}
|
|
79
|
+
const json = (await response.json());
|
|
80
|
+
if (json !== null && typeof json === 'object' && 'result' in json) {
|
|
81
|
+
return json.result;
|
|
82
|
+
}
|
|
83
|
+
return json;
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
const createConnectionManager = (getNamespace) => {
|
|
87
|
+
let connected = false;
|
|
88
|
+
const getStub = () => {
|
|
89
|
+
const namespace = getNamespace();
|
|
90
|
+
if (!namespace) {
|
|
91
|
+
throw ErrorFactory.createConfigError('MYSQL_POOL binding not found. Cannot connect to Durable Object pool.');
|
|
92
|
+
}
|
|
93
|
+
const id = namespace.idFromName('default');
|
|
94
|
+
return namespace.get(id);
|
|
95
|
+
};
|
|
96
|
+
const connect = async () => {
|
|
97
|
+
if (connected)
|
|
98
|
+
return;
|
|
99
|
+
try {
|
|
100
|
+
const stub = getStub();
|
|
101
|
+
const health = 'http://do/health'; //NOSONAR
|
|
102
|
+
const timeoutMs = getDoTimeoutMs();
|
|
103
|
+
const res = await stub.fetch(health, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
signal: createTimeoutSignal(timeoutMs),
|
|
106
|
+
});
|
|
107
|
+
if (!res.ok) {
|
|
108
|
+
throw ErrorFactory.createGeneralError(`DO health check failed: ${res.status}`);
|
|
109
|
+
}
|
|
110
|
+
const body = (await res.json());
|
|
111
|
+
if (!body.connected) {
|
|
112
|
+
Logger.info('[MySqlWorkersDurableObjectAdapter] DO not connected yet, will init on first query');
|
|
113
|
+
}
|
|
114
|
+
connected = true;
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
Logger.error('[MySqlWorkersDurableObjectAdapter] Connection failed', err);
|
|
118
|
+
throw ErrorFactory.createGeneralError('Failed to connect to MySQL DO', err);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
const sendQuery = createSendQueryFunction(getStub, connect);
|
|
122
|
+
return {
|
|
123
|
+
connect,
|
|
124
|
+
sendQuery,
|
|
125
|
+
disconnect: () => {
|
|
126
|
+
connected = false;
|
|
127
|
+
},
|
|
128
|
+
isConnected: () => connected,
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
export const MySqlWorkersDurableObjectAdapter = Object.freeze({
|
|
132
|
+
create(_config) {
|
|
133
|
+
const connectionManager = createConnectionManager(() => {
|
|
134
|
+
const globalEnv = globalThis.env;
|
|
135
|
+
return globalEnv?.['MYSQL_POOL'];
|
|
136
|
+
});
|
|
137
|
+
return {
|
|
138
|
+
async connect() {
|
|
139
|
+
return connectionManager.connect();
|
|
140
|
+
},
|
|
141
|
+
async disconnect() {
|
|
142
|
+
connectionManager.disconnect();
|
|
143
|
+
},
|
|
144
|
+
async query(sql, parameters = []) {
|
|
145
|
+
return connectionManager.sendQuery(sql, parameters, 'query');
|
|
146
|
+
},
|
|
147
|
+
async queryOne(sql, parameters = []) {
|
|
148
|
+
const result = await connectionManager.sendQuery(sql, parameters, 'query');
|
|
149
|
+
if (result.rows.length === 0)
|
|
150
|
+
return null;
|
|
151
|
+
return result.rows[0];
|
|
152
|
+
},
|
|
153
|
+
async rawQuery(sql, parameters) {
|
|
154
|
+
const result = await connectionManager.sendQuery(sql, parameters || [], 'query');
|
|
155
|
+
return result.rows;
|
|
156
|
+
},
|
|
157
|
+
async ping() {
|
|
158
|
+
await connectionManager.connect();
|
|
159
|
+
await connectionManager.sendQuery('SELECT 1', [], 'query');
|
|
160
|
+
},
|
|
161
|
+
async transaction(_callback) {
|
|
162
|
+
throw ErrorFactory.createGeneralError('Transactions are not yet supported in MySqlWorkersDurableObjectAdapter');
|
|
163
|
+
},
|
|
164
|
+
getType() {
|
|
165
|
+
return MYSQL_TYPE;
|
|
166
|
+
},
|
|
167
|
+
isConnected() {
|
|
168
|
+
return connectionManager.isConnected();
|
|
169
|
+
},
|
|
170
|
+
getPlaceholder(_index) {
|
|
171
|
+
return MYSQL_PLACEHOLDER;
|
|
172
|
+
},
|
|
173
|
+
async ensureMigrationsTable() {
|
|
174
|
+
await connectionManager.sendQuery(CREATE_MIGRATIONS_TABLE_SQL, [], 'query');
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
});
|
package/dist/build-manifest.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zintrust/db-mysql",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"buildDate": "2026-05-
|
|
3
|
+
"version": "1.6.0",
|
|
4
|
+
"buildDate": "2026-05-01T05:25:51.958Z",
|
|
5
5
|
"buildEnvironment": {
|
|
6
|
-
"node": "
|
|
7
|
-
"platform": "
|
|
8
|
-
"arch": "
|
|
6
|
+
"node": "v22.22.1",
|
|
7
|
+
"platform": "darwin",
|
|
8
|
+
"arch": "arm64"
|
|
9
9
|
},
|
|
10
10
|
"git": {
|
|
11
|
-
"commit": "
|
|
12
|
-
"branch": "
|
|
11
|
+
"commit": "ef4e9bec",
|
|
12
|
+
"branch": "release"
|
|
13
13
|
},
|
|
14
14
|
"package": {
|
|
15
15
|
"engines": {
|
|
@@ -23,6 +23,18 @@
|
|
|
23
23
|
]
|
|
24
24
|
},
|
|
25
25
|
"files": {
|
|
26
|
+
"MySqlWorkersDurableObjectAdapter.d.ts": {
|
|
27
|
+
"size": 170,
|
|
28
|
+
"sha256": "ed336493205ab6e060ad2ad6737a0aab9a0c41dfcdee8d5cd89f18b8ffb58f7e"
|
|
29
|
+
},
|
|
30
|
+
"MySqlWorkersDurableObjectAdapter.js": {
|
|
31
|
+
"size": 6810,
|
|
32
|
+
"sha256": "775b5e5efb6aa58be1a83ea9c54c182a44060f70ab1e293fae20998d35766447"
|
|
33
|
+
},
|
|
34
|
+
"build-manifest.json": {
|
|
35
|
+
"size": 1678,
|
|
36
|
+
"sha256": "7e3b42382b430baf50c0278598ec13ebc1fea1091baa9b76207715f684825a22"
|
|
37
|
+
},
|
|
26
38
|
"common.d.ts": {
|
|
27
39
|
"size": 615,
|
|
28
40
|
"sha256": "fafe13a87e5239d41b537dcdf23d7fc84c25d7b34207d272f149cbadfebfb417"
|
|
@@ -37,7 +49,7 @@
|
|
|
37
49
|
},
|
|
38
50
|
"index.js": {
|
|
39
51
|
"size": 10003,
|
|
40
|
-
"sha256": "
|
|
52
|
+
"sha256": "877f2f95488505148da95707ccacbf08fa042744ab5255bef016687a220a2c95"
|
|
41
53
|
},
|
|
42
54
|
"register.d.ts": {
|
|
43
55
|
"size": 180,
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,66 @@ const getInjectedMysqlModule = () => {
|
|
|
4
4
|
const globalAny = globalThis;
|
|
5
5
|
return globalAny.__zintrustMysqlModule;
|
|
6
6
|
};
|
|
7
|
+
const getNodeMysqlSslConfig = (tlsEnabled) => {
|
|
8
|
+
if (!tlsEnabled) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
rejectUnauthorized: false,
|
|
13
|
+
minVersion: 'TLSv1.2',
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
const getSocketTimeoutMs = (config) => {
|
|
17
|
+
if (typeof config.socketTimeoutMs === 'number' && config.socketTimeoutMs > 0) {
|
|
18
|
+
return config.socketTimeoutMs;
|
|
19
|
+
}
|
|
20
|
+
return 30000;
|
|
21
|
+
};
|
|
22
|
+
const createWorkersPool = async ({ mysql, host, port, database, user, password, tlsEnabled, timeoutMs, }) => {
|
|
23
|
+
if (!Cloudflare.isCloudflareSocketsEnabled()) {
|
|
24
|
+
throw ErrorFactory.createConfigError('Cloudflare sockets are disabled. Set ENABLE_CLOUDFLARE_SOCKETS=true to use MySQL sockets on Workers.');
|
|
25
|
+
}
|
|
26
|
+
const createSocket = await loadCloudflareSocketFactory();
|
|
27
|
+
return mysql.createPool({
|
|
28
|
+
host,
|
|
29
|
+
port,
|
|
30
|
+
database,
|
|
31
|
+
user,
|
|
32
|
+
password,
|
|
33
|
+
waitForConnections: true,
|
|
34
|
+
connectionLimit: 10,
|
|
35
|
+
namedPlaceholders: false,
|
|
36
|
+
disableEval: true,
|
|
37
|
+
stream: () => createSocket({ host, port, tls: tlsEnabled, timeoutMs }),
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
const createNodePool = (mysql, host, port, database, user, password, nodeMysqlSslConfig) => {
|
|
41
|
+
return mysql.createPool({
|
|
42
|
+
host,
|
|
43
|
+
port,
|
|
44
|
+
database,
|
|
45
|
+
user,
|
|
46
|
+
password,
|
|
47
|
+
ssl: nodeMysqlSslConfig,
|
|
48
|
+
waitForConnections: true,
|
|
49
|
+
connectionLimit: 10,
|
|
50
|
+
namedPlaceholders: false,
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
const describeDriverError = (error) => {
|
|
54
|
+
if (error === null || typeof error !== 'object') {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
const details = error;
|
|
58
|
+
const parts = [
|
|
59
|
+
typeof details.code === 'string' ? `code=${details.code}` : undefined,
|
|
60
|
+
typeof details.errno === 'number' ? `errno=${details.errno}` : undefined,
|
|
61
|
+
typeof details.sqlState === 'string' ? `sqlState=${details.sqlState}` : undefined,
|
|
62
|
+
typeof details.sqlMessage === 'string' ? `sqlMessage=${details.sqlMessage}` : undefined,
|
|
63
|
+
typeof details.fatal === 'boolean' ? `fatal=${details.fatal}` : undefined,
|
|
64
|
+
].filter((part) => part !== undefined);
|
|
65
|
+
return parts.length > 0 ? parts.join(', ') : undefined;
|
|
66
|
+
};
|
|
7
67
|
function isMissingEsmPackage(error, packageName) {
|
|
8
68
|
if (error === null || typeof error !== 'object')
|
|
9
69
|
return false;
|
|
@@ -97,42 +157,23 @@ async function connect(state, config) {
|
|
|
97
157
|
const { host, port, database, user, password } = getConnectionParams(config);
|
|
98
158
|
const isWorkersRuntime = Cloudflare.getWorkersEnv() !== null;
|
|
99
159
|
const tlsEnabled = Boolean(config.ssl);
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
timeoutMs = 30000; // default 30s
|
|
106
|
-
}
|
|
160
|
+
const nodeMysqlSslConfig = getNodeMysqlSslConfig(tlsEnabled);
|
|
161
|
+
Logger.info(`[db-mysql] Effective connection params: host=${host}, port=${port}, database=${database}, user=${user}, password_len=${password.length}, ssl=${tlsEnabled}`);
|
|
162
|
+
const timeoutMs = getSocketTimeoutMs(config);
|
|
107
163
|
if (isWorkersRuntime) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
const createSocket = await loadCloudflareSocketFactory();
|
|
112
|
-
state.pool = mysql.createPool({
|
|
164
|
+
state.pool = await createWorkersPool({
|
|
165
|
+
mysql,
|
|
113
166
|
host,
|
|
114
167
|
port,
|
|
115
168
|
database,
|
|
116
169
|
user,
|
|
117
170
|
password,
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
namedPlaceholders: false,
|
|
121
|
-
disableEval: true,
|
|
122
|
-
stream: () => createSocket({ host, port, tls: tlsEnabled, timeoutMs }),
|
|
171
|
+
tlsEnabled,
|
|
172
|
+
timeoutMs,
|
|
123
173
|
});
|
|
124
174
|
}
|
|
125
175
|
else {
|
|
126
|
-
state.pool = mysql
|
|
127
|
-
host,
|
|
128
|
-
port,
|
|
129
|
-
database,
|
|
130
|
-
user,
|
|
131
|
-
password,
|
|
132
|
-
waitForConnections: true,
|
|
133
|
-
connectionLimit: 10,
|
|
134
|
-
namedPlaceholders: false,
|
|
135
|
-
});
|
|
176
|
+
state.pool = createNodePool(mysql, host, port, database, user, password, nodeMysqlSslConfig);
|
|
136
177
|
}
|
|
137
178
|
// Probe.
|
|
138
179
|
await state.pool.execute('SELECT 1');
|
|
@@ -143,6 +184,13 @@ async function connect(state, config) {
|
|
|
143
184
|
if (isMissingEsmPackage(error, 'mysql2')) {
|
|
144
185
|
throw ErrorFactory.createConfigError("MySQL adapter requires the 'mysql2' package (run `npm install mysql2` or `zin add db:mysql`).");
|
|
145
186
|
}
|
|
187
|
+
const driverError = describeDriverError(error);
|
|
188
|
+
if (driverError !== undefined) {
|
|
189
|
+
Logger.error(`[db-mysql] MySQL driver error: ${driverError}`);
|
|
190
|
+
}
|
|
191
|
+
if (error instanceof Error && typeof error.stack === 'string' && error.stack.trim() !== '') {
|
|
192
|
+
Logger.error(`[db-mysql] MySQL driver stack: ${error.stack}`);
|
|
193
|
+
}
|
|
146
194
|
throw ErrorFactory.createTryCatchError('Failed to connect to MySQL', error);
|
|
147
195
|
}
|
|
148
196
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zintrust/db-mysql",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.8",
|
|
4
4
|
"description": "MySQL and MariaDB database adapter for ZinTrust.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -42,4 +42,4 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"mysql2": "^3.22.3"
|
|
44
44
|
}
|
|
45
|
-
}
|
|
45
|
+
}
|