@dyrected/db-mysql 2.12.0 → 2.13.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/dist/index.cjs +256 -73
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +255 -73
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -31,11 +31,24 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
MysqlAdapter: () => MysqlAdapter,
|
|
34
|
+
closeAllMysqlClients: () => closeAllMysqlClients,
|
|
34
35
|
mysqlAdapter: () => mysqlAdapter
|
|
35
36
|
});
|
|
36
37
|
module.exports = __toCommonJS(index_exports);
|
|
37
38
|
var import_core = require("@dyrected/core");
|
|
38
39
|
var import_promise = __toESM(require("mysql2/promise"), 1);
|
|
40
|
+
var MYSQL_CLIENT_CACHE_KEY = "__dyrectedMysqlClientCache";
|
|
41
|
+
function getSharedMysqlClientCache() {
|
|
42
|
+
const globalScope = globalThis;
|
|
43
|
+
if (!globalScope[MYSQL_CLIENT_CACHE_KEY]) {
|
|
44
|
+
globalScope[MYSQL_CLIENT_CACHE_KEY] = /* @__PURE__ */ new Map();
|
|
45
|
+
}
|
|
46
|
+
return globalScope[MYSQL_CLIENT_CACHE_KEY];
|
|
47
|
+
}
|
|
48
|
+
function getMysqlCacheKey(config) {
|
|
49
|
+
if (config.url) return config.url;
|
|
50
|
+
return `${config.host || "localhost"}:${config.port || 3306}:${config.database || ""}:${config.user || ""}`;
|
|
51
|
+
}
|
|
39
52
|
function escapeMysqlIdentifier(identifier) {
|
|
40
53
|
return `\`${identifier.replace(/`/g, "``")}\``;
|
|
41
54
|
}
|
|
@@ -54,6 +67,9 @@ var MysqlAdapter = class {
|
|
|
54
67
|
config;
|
|
55
68
|
initPromise = null;
|
|
56
69
|
inTransaction = false;
|
|
70
|
+
tableLocks = /* @__PURE__ */ new Map();
|
|
71
|
+
ensuredTables = /* @__PURE__ */ new Set();
|
|
72
|
+
tableColumnsCache = /* @__PURE__ */ new Map();
|
|
57
73
|
constructor(config) {
|
|
58
74
|
this.config = config;
|
|
59
75
|
}
|
|
@@ -76,6 +92,25 @@ FIX INSTRUCTIONS:
|
|
|
76
92
|
}
|
|
77
93
|
throw err;
|
|
78
94
|
}
|
|
95
|
+
async retryOperation(operation, maxRetries = 3, initialDelay = 1e3) {
|
|
96
|
+
let lastError;
|
|
97
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
98
|
+
try {
|
|
99
|
+
return await operation();
|
|
100
|
+
} catch (err) {
|
|
101
|
+
lastError = err;
|
|
102
|
+
const isNetworkOrTimeout = err.code === "ETIMEDOUT" || err.code === "ECONNREFUSED" || err.code === "PROTOCOL_CONNECTION_LOST" || err.message?.includes("ETIMEDOUT") || err.message?.includes("Connection lost");
|
|
103
|
+
if (attempt < maxRetries && isNetworkOrTimeout) {
|
|
104
|
+
const delay = initialDelay * Math.pow(2, attempt - 1);
|
|
105
|
+
console.warn(`[dyrected/db-mysql] Connection attempt ${attempt} failed (${err.code || err.message}), retrying in ${delay}ms...`);
|
|
106
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
107
|
+
} else {
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
throw lastError;
|
|
113
|
+
}
|
|
79
114
|
async ensureInitialized() {
|
|
80
115
|
if (!this.initPromise) {
|
|
81
116
|
this.initPromise = this.initialize();
|
|
@@ -83,59 +118,126 @@ FIX INSTRUCTIONS:
|
|
|
83
118
|
await this.initPromise;
|
|
84
119
|
}
|
|
85
120
|
async initialize() {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
121
|
+
const cache = getSharedMysqlClientCache();
|
|
122
|
+
const cacheKey = getMysqlCacheKey(this.config);
|
|
123
|
+
const cached = cache.get(cacheKey);
|
|
124
|
+
if (cached?.pool) {
|
|
125
|
+
this.pool = cached.pool;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (cached?.initPromise) {
|
|
129
|
+
this.pool = await cached.initPromise;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const initPromise = (async () => {
|
|
133
|
+
const config = this.config;
|
|
134
|
+
let dbName = config.database;
|
|
135
|
+
let serverConfig = null;
|
|
136
|
+
if (config.url) {
|
|
137
|
+
try {
|
|
138
|
+
const parsed = new URL(config.url);
|
|
139
|
+
dbName = parsed.pathname.replace(/^\//, "");
|
|
140
|
+
const serverUrl = `${parsed.protocol}//${parsed.username}:${parsed.password}@${parsed.host}`;
|
|
141
|
+
serverConfig = serverUrl;
|
|
142
|
+
} catch (err) {
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
serverConfig = {
|
|
146
|
+
host: config.host ?? "localhost",
|
|
147
|
+
port: config.port ?? 3306,
|
|
148
|
+
user: config.user,
|
|
149
|
+
password: config.password
|
|
150
|
+
};
|
|
96
151
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
152
|
+
if (dbName && serverConfig) {
|
|
153
|
+
try {
|
|
154
|
+
await this.retryOperation(async () => {
|
|
155
|
+
const tempConn = await import_promise.default.createConnection(serverConfig);
|
|
156
|
+
try {
|
|
157
|
+
await tempConn.query(`CREATE DATABASE IF NOT EXISTS \`${dbName}\``);
|
|
158
|
+
} finally {
|
|
159
|
+
await tempConn.end().catch(() => {
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}, 3, 1e3);
|
|
163
|
+
console.log(`[dyrected/db-mysql] Database "${dbName}" checked/created successfully`);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
console.warn(`[dyrected/db-mysql] Auto-creation of database "${dbName}" skipped/failed:`, err.message);
|
|
166
|
+
if (err.code === "EADDRNOTAVAIL" || err.message?.includes("EADDRNOTAVAIL")) {
|
|
167
|
+
this.handleConnectionError(err);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const defaultPoolOptions = {
|
|
172
|
+
enableKeepAlive: true,
|
|
173
|
+
keepAliveInitialDelay: 1e4,
|
|
174
|
+
connectTimeout: 2e4,
|
|
175
|
+
dateStrings: true,
|
|
176
|
+
waitForConnections: true,
|
|
177
|
+
connectionLimit: 10,
|
|
178
|
+
queueLimit: 0,
|
|
179
|
+
...config.poolOptions
|
|
103
180
|
};
|
|
104
|
-
|
|
105
|
-
|
|
181
|
+
let pool;
|
|
182
|
+
if (config.url) {
|
|
183
|
+
pool = import_promise.default.createPool({
|
|
184
|
+
uri: config.url,
|
|
185
|
+
...defaultPoolOptions
|
|
186
|
+
});
|
|
187
|
+
} else {
|
|
188
|
+
pool = import_promise.default.createPool({
|
|
189
|
+
host: config.host ?? "localhost",
|
|
190
|
+
port: config.port ?? 3306,
|
|
191
|
+
user: config.user,
|
|
192
|
+
password: config.password,
|
|
193
|
+
database: config.database,
|
|
194
|
+
...defaultPoolOptions
|
|
195
|
+
});
|
|
196
|
+
}
|
|
106
197
|
try {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
198
|
+
await this.retryOperation(async () => {
|
|
199
|
+
await pool.query(`
|
|
200
|
+
CREATE TABLE IF NOT EXISTS dyrected_internal (
|
|
201
|
+
\`key\` VARCHAR(255) PRIMARY KEY,
|
|
202
|
+
value JSON NOT NULL
|
|
203
|
+
)
|
|
204
|
+
`);
|
|
205
|
+
await pool.query(`
|
|
206
|
+
CREATE TABLE IF NOT EXISTS _dyrected_ai_threads (
|
|
207
|
+
id VARCHAR(36) PRIMARY KEY,
|
|
208
|
+
project_id VARCHAR(255) NOT NULL,
|
|
209
|
+
user_id VARCHAR(255) NOT NULL,
|
|
210
|
+
title TEXT,
|
|
211
|
+
created_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3),
|
|
212
|
+
updated_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
|
213
|
+
INDEX idx_ai_threads_user_project (user_id, project_id)
|
|
214
|
+
)
|
|
215
|
+
`);
|
|
216
|
+
await pool.query(`
|
|
217
|
+
CREATE TABLE IF NOT EXISTS _dyrected_ai_messages (
|
|
218
|
+
id VARCHAR(36) PRIMARY KEY,
|
|
219
|
+
thread_id VARCHAR(36) NOT NULL,
|
|
220
|
+
role VARCHAR(50) NOT NULL,
|
|
221
|
+
content LONGTEXT NOT NULL,
|
|
222
|
+
created_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3),
|
|
223
|
+
metadata JSON,
|
|
224
|
+
INDEX idx_ai_messages_thread_id (thread_id),
|
|
225
|
+
CONSTRAINT fk_ai_messages_thread FOREIGN KEY (thread_id) REFERENCES _dyrected_ai_threads(id) ON DELETE CASCADE
|
|
226
|
+
)
|
|
227
|
+
`);
|
|
228
|
+
}, 3, 1e3);
|
|
111
229
|
} catch (err) {
|
|
112
|
-
|
|
113
|
-
if (err.code === "EADDRNOTAVAIL" || err.message?.includes("EADDRNOTAVAIL")) {
|
|
114
|
-
this.handleConnectionError(err);
|
|
115
|
-
}
|
|
230
|
+
this.handleConnectionError(err);
|
|
116
231
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
} else {
|
|
121
|
-
this.pool = import_promise.default.createPool({
|
|
122
|
-
host: config.host ?? "localhost",
|
|
123
|
-
port: config.port ?? 3306,
|
|
124
|
-
user: config.user,
|
|
125
|
-
password: config.password,
|
|
126
|
-
database: config.database,
|
|
127
|
-
dateStrings: true
|
|
128
|
-
});
|
|
129
|
-
}
|
|
232
|
+
return pool;
|
|
233
|
+
})();
|
|
234
|
+
cache.set(cacheKey, { initPromise });
|
|
130
235
|
try {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
`);
|
|
137
|
-
} catch (err) {
|
|
138
|
-
this.handleConnectionError(err);
|
|
236
|
+
this.pool = await initPromise;
|
|
237
|
+
cache.set(cacheKey, { pool: this.pool });
|
|
238
|
+
} catch (error) {
|
|
239
|
+
cache.delete(cacheKey);
|
|
240
|
+
throw error;
|
|
139
241
|
}
|
|
140
242
|
}
|
|
141
243
|
async query(sql, params) {
|
|
@@ -155,28 +257,92 @@ FIX INSTRUCTIONS:
|
|
|
155
257
|
}
|
|
156
258
|
}
|
|
157
259
|
getTableName(slug) {
|
|
158
|
-
|
|
260
|
+
if (slug.includes(".")) {
|
|
261
|
+
const parts = slug.split(".");
|
|
262
|
+
return parts[parts.length - 1].replace(/`/g, "");
|
|
263
|
+
}
|
|
264
|
+
return slug.startsWith("collection_") ? slug : `collection_${slug}`;
|
|
159
265
|
}
|
|
160
|
-
async
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
CREATE TABLE IF NOT EXISTS \`${tableName}\` (
|
|
164
|
-
id VARCHAR(36) PRIMARY KEY,
|
|
165
|
-
data JSON NOT NULL,
|
|
166
|
-
created_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3),
|
|
167
|
-
updated_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)
|
|
168
|
-
)
|
|
169
|
-
`);
|
|
266
|
+
async getTableColumns(tableName) {
|
|
267
|
+
const cached = this.tableColumnsCache.get(tableName);
|
|
268
|
+
if (cached) return cached;
|
|
170
269
|
const [cols] = await this.query(`SHOW COLUMNS FROM \`${tableName}\``);
|
|
171
270
|
const existingCols = cols.map((c) => c.Field);
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
271
|
+
this.tableColumnsCache.set(tableName, existingCols);
|
|
272
|
+
return existingCols;
|
|
273
|
+
}
|
|
274
|
+
async ensureTable(slug, fields = []) {
|
|
275
|
+
if (slug === "_dyrected_ai_threads" || slug === "_dyrected_ai_messages") {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (fields.length === 0 && this.ensuredTables.has(slug)) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (this.tableLocks.has(slug)) {
|
|
282
|
+
await this.tableLocks.get(slug);
|
|
283
|
+
if (fields.length === 0 && this.ensuredTables.has(slug)) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
let resolveLock;
|
|
288
|
+
let rejectLock;
|
|
289
|
+
const lockPromise = new Promise((resolve, reject) => {
|
|
290
|
+
resolveLock = resolve;
|
|
291
|
+
rejectLock = reject;
|
|
292
|
+
});
|
|
293
|
+
this.tableLocks.set(slug, lockPromise);
|
|
294
|
+
try {
|
|
295
|
+
const tableName = this.getTableName(slug);
|
|
296
|
+
await this.query(`
|
|
297
|
+
CREATE TABLE IF NOT EXISTS \`${tableName}\` (
|
|
298
|
+
id VARCHAR(36) PRIMARY KEY,
|
|
299
|
+
data JSON NOT NULL,
|
|
300
|
+
created_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3),
|
|
301
|
+
updated_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)
|
|
302
|
+
)
|
|
303
|
+
`);
|
|
304
|
+
const existingCols = await this.getTableColumns(tableName);
|
|
305
|
+
for (const field of fields) {
|
|
306
|
+
if (field.promoted && !existingCols.includes(field.name)) {
|
|
307
|
+
console.log(`[dyrected/mysql] Promoting field "${field.name}" to column in ${tableName}`);
|
|
308
|
+
let sqlType = "TEXT";
|
|
309
|
+
if (field.type === "number") sqlType = "DECIMAL(19,4)";
|
|
310
|
+
if (field.type === "boolean") sqlType = "TINYINT(1)";
|
|
311
|
+
if (field.type === "date" || field.type === "datetime") sqlType = "DATETIME(3)";
|
|
312
|
+
try {
|
|
313
|
+
await this.query(`ALTER TABLE \`${tableName}\` ADD COLUMN \`${field.name}\` ${sqlType}`);
|
|
314
|
+
existingCols.push(field.name);
|
|
315
|
+
this.tableColumnsCache.set(tableName, existingCols);
|
|
316
|
+
} catch (err) {
|
|
317
|
+
if (err.code === "ER_DUP_FIELDNAME" || err.errno === 1060 || err.message?.includes("Duplicate column name")) {
|
|
318
|
+
} else {
|
|
319
|
+
throw err;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const escapedField = field.name.replace(/`/g, "``");
|
|
323
|
+
let castExpr = `JSON_UNQUOTE(JSON_EXTRACT(data, '$.${field.name}'))`;
|
|
324
|
+
if (field.type === "number") {
|
|
325
|
+
castExpr = `IF(JSON_UNQUOTE(JSON_EXTRACT(data, '$.${field.name}')) REGEXP '^-?[0-9]+(\\\\.[0-9]+)?([eE][+-]?[0-9]+)?$', CAST(JSON_UNQUOTE(JSON_EXTRACT(data, '$.${field.name}')) AS DECIMAL(19,4)), NULL)`;
|
|
326
|
+
} else if (field.type === "boolean") {
|
|
327
|
+
castExpr = `IF(JSON_UNQUOTE(JSON_EXTRACT(data, '$.${field.name}')) IN ('true', '1'), 1, 0)`;
|
|
328
|
+
} else if (field.type === "date" || field.type === "datetime") {
|
|
329
|
+
castExpr = `STR_TO_DATE(LEFT(JSON_UNQUOTE(JSON_EXTRACT(data, '$.${field.name}')), 23), '%Y-%m-%d %H:%i:%s.%f')`;
|
|
330
|
+
}
|
|
331
|
+
try {
|
|
332
|
+
await this.query(
|
|
333
|
+
`UPDATE \`${tableName}\` SET \`${escapedField}\` = ${castExpr} WHERE \`${escapedField}\` IS NULL AND JSON_CONTAINS_PATH(data, 'one', '$.${field.name}')`
|
|
334
|
+
);
|
|
335
|
+
} catch {
|
|
336
|
+
}
|
|
337
|
+
}
|
|
179
338
|
}
|
|
339
|
+
this.ensuredTables.add(slug);
|
|
340
|
+
resolveLock();
|
|
341
|
+
} catch (err) {
|
|
342
|
+
rejectLock(err);
|
|
343
|
+
throw err;
|
|
344
|
+
} finally {
|
|
345
|
+
this.tableLocks.delete(slug);
|
|
180
346
|
}
|
|
181
347
|
}
|
|
182
348
|
async find(args) {
|
|
@@ -185,8 +351,7 @@ FIX INSTRUCTIONS:
|
|
|
185
351
|
const limit = args.limit ?? 10;
|
|
186
352
|
const page = args.page ?? 1;
|
|
187
353
|
const offset = (page - 1) * limit;
|
|
188
|
-
const
|
|
189
|
-
const existingCols = cols.map((c) => c.Field);
|
|
354
|
+
const existingCols = await this.getTableColumns(tableName);
|
|
190
355
|
let whereSql = "";
|
|
191
356
|
let whereParams = [];
|
|
192
357
|
if (args.where && Object.keys(args.where).length > 0) {
|
|
@@ -249,8 +414,7 @@ FIX INSTRUCTIONS:
|
|
|
249
414
|
async create(params) {
|
|
250
415
|
if (!this.inTransaction) await this.ensureTable(params.collection);
|
|
251
416
|
const tableName = this.getTableName(params.collection);
|
|
252
|
-
const
|
|
253
|
-
const existingCols = cols.map((c) => c.Field);
|
|
417
|
+
const existingCols = await this.getTableColumns(tableName);
|
|
254
418
|
const id = params.data.id ?? Math.random().toString(36).substring(7);
|
|
255
419
|
const now = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace("Z", "");
|
|
256
420
|
const data = { ...params.data };
|
|
@@ -273,8 +437,7 @@ FIX INSTRUCTIONS:
|
|
|
273
437
|
async update(params) {
|
|
274
438
|
if (!this.inTransaction) await this.ensureTable(params.collection);
|
|
275
439
|
const tableName = this.getTableName(params.collection);
|
|
276
|
-
const
|
|
277
|
-
const existingCols = cols.map((c) => c.Field);
|
|
440
|
+
const existingCols = await this.getTableColumns(tableName);
|
|
278
441
|
const existing = await this.findOne({ collection: params.collection, id: params.id });
|
|
279
442
|
const now = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace("Z", "");
|
|
280
443
|
const merged = { ...existing ?? {}, ...params.data };
|
|
@@ -349,8 +512,7 @@ FIX INSTRUCTIONS:
|
|
|
349
512
|
}
|
|
350
513
|
const tableName = this.getTableName(args.collection);
|
|
351
514
|
if (!this.inTransaction) await this.ensureTable(args.collection);
|
|
352
|
-
const
|
|
353
|
-
const existingCols = cols.map((c) => c.Field);
|
|
515
|
+
const existingCols = await this.getTableColumns(tableName);
|
|
354
516
|
const toFieldExpr = (field) => {
|
|
355
517
|
if (field === "createdAt") return "`created_at`";
|
|
356
518
|
if (field === "updatedAt") return "`updated_at`";
|
|
@@ -443,6 +605,11 @@ FIX INSTRUCTIONS:
|
|
|
443
605
|
return result;
|
|
444
606
|
}
|
|
445
607
|
async disconnect() {
|
|
608
|
+
const cache = getSharedMysqlClientCache();
|
|
609
|
+
const cacheKey = getMysqlCacheKey(this.config);
|
|
610
|
+
cache.delete(cacheKey);
|
|
611
|
+
this.ensuredTables.clear();
|
|
612
|
+
this.tableColumnsCache.clear();
|
|
446
613
|
if (this.initPromise) {
|
|
447
614
|
try {
|
|
448
615
|
await this.initPromise;
|
|
@@ -457,9 +624,25 @@ FIX INSTRUCTIONS:
|
|
|
457
624
|
this.pool = null;
|
|
458
625
|
}
|
|
459
626
|
};
|
|
627
|
+
async function closeAllMysqlClients() {
|
|
628
|
+
const cache = getSharedMysqlClientCache();
|
|
629
|
+
const entries = Array.from(cache.values());
|
|
630
|
+
cache.clear();
|
|
631
|
+
for (const entry of entries) {
|
|
632
|
+
try {
|
|
633
|
+
const pool = entry.pool || await entry.initPromise;
|
|
634
|
+
if (pool && typeof pool.end === "function") {
|
|
635
|
+
await pool.end().catch(() => {
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
} catch {
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
460
642
|
var mysqlAdapter = (config) => new MysqlAdapter(config);
|
|
461
643
|
// Annotate the CommonJS export names for ESM import in node:
|
|
462
644
|
0 && (module.exports = {
|
|
463
645
|
MysqlAdapter,
|
|
646
|
+
closeAllMysqlClients,
|
|
464
647
|
mysqlAdapter
|
|
465
648
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -9,19 +9,26 @@ interface MysqlAdapterConfig {
|
|
|
9
9
|
user?: string;
|
|
10
10
|
password?: string;
|
|
11
11
|
database?: string;
|
|
12
|
+
/** Optional pool options passed directly to mysql2 createPool */
|
|
13
|
+
poolOptions?: Record<string, any>;
|
|
12
14
|
}
|
|
13
15
|
declare class MysqlAdapter implements DatabaseAdapter {
|
|
14
16
|
private pool;
|
|
15
17
|
private config;
|
|
16
18
|
private initPromise;
|
|
17
19
|
private inTransaction;
|
|
20
|
+
private tableLocks;
|
|
21
|
+
private ensuredTables;
|
|
22
|
+
private tableColumnsCache;
|
|
18
23
|
constructor(config: MysqlAdapterConfig);
|
|
19
24
|
private handleConnectionError;
|
|
25
|
+
private retryOperation;
|
|
20
26
|
private ensureInitialized;
|
|
21
27
|
private initialize;
|
|
22
28
|
private query;
|
|
23
29
|
execute(sql: string, params?: any[]): Promise<any>;
|
|
24
30
|
private getTableName;
|
|
31
|
+
private getTableColumns;
|
|
25
32
|
private ensureTable;
|
|
26
33
|
find(args: {
|
|
27
34
|
collection: string;
|
|
@@ -65,6 +72,7 @@ declare class MysqlAdapter implements DatabaseAdapter {
|
|
|
65
72
|
}): Promise<Record<string, any>>;
|
|
66
73
|
disconnect(): Promise<void>;
|
|
67
74
|
}
|
|
75
|
+
declare function closeAllMysqlClients(): Promise<void>;
|
|
68
76
|
declare const mysqlAdapter: (config: MysqlAdapterConfig) => MysqlAdapter;
|
|
69
77
|
|
|
70
|
-
export { MysqlAdapter, type MysqlAdapterConfig, mysqlAdapter };
|
|
78
|
+
export { MysqlAdapter, type MysqlAdapterConfig, closeAllMysqlClients, mysqlAdapter };
|
package/dist/index.d.ts
CHANGED
|
@@ -9,19 +9,26 @@ interface MysqlAdapterConfig {
|
|
|
9
9
|
user?: string;
|
|
10
10
|
password?: string;
|
|
11
11
|
database?: string;
|
|
12
|
+
/** Optional pool options passed directly to mysql2 createPool */
|
|
13
|
+
poolOptions?: Record<string, any>;
|
|
12
14
|
}
|
|
13
15
|
declare class MysqlAdapter implements DatabaseAdapter {
|
|
14
16
|
private pool;
|
|
15
17
|
private config;
|
|
16
18
|
private initPromise;
|
|
17
19
|
private inTransaction;
|
|
20
|
+
private tableLocks;
|
|
21
|
+
private ensuredTables;
|
|
22
|
+
private tableColumnsCache;
|
|
18
23
|
constructor(config: MysqlAdapterConfig);
|
|
19
24
|
private handleConnectionError;
|
|
25
|
+
private retryOperation;
|
|
20
26
|
private ensureInitialized;
|
|
21
27
|
private initialize;
|
|
22
28
|
private query;
|
|
23
29
|
execute(sql: string, params?: any[]): Promise<any>;
|
|
24
30
|
private getTableName;
|
|
31
|
+
private getTableColumns;
|
|
25
32
|
private ensureTable;
|
|
26
33
|
find(args: {
|
|
27
34
|
collection: string;
|
|
@@ -65,6 +72,7 @@ declare class MysqlAdapter implements DatabaseAdapter {
|
|
|
65
72
|
}): Promise<Record<string, any>>;
|
|
66
73
|
disconnect(): Promise<void>;
|
|
67
74
|
}
|
|
75
|
+
declare function closeAllMysqlClients(): Promise<void>;
|
|
68
76
|
declare const mysqlAdapter: (config: MysqlAdapterConfig) => MysqlAdapter;
|
|
69
77
|
|
|
70
|
-
export { MysqlAdapter, type MysqlAdapterConfig, mysqlAdapter };
|
|
78
|
+
export { MysqlAdapter, type MysqlAdapterConfig, closeAllMysqlClients, mysqlAdapter };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { parseSort, parseSqlWhere } from "@dyrected/core";
|
|
3
3
|
import mysql from "mysql2/promise";
|
|
4
|
+
var MYSQL_CLIENT_CACHE_KEY = "__dyrectedMysqlClientCache";
|
|
5
|
+
function getSharedMysqlClientCache() {
|
|
6
|
+
const globalScope = globalThis;
|
|
7
|
+
if (!globalScope[MYSQL_CLIENT_CACHE_KEY]) {
|
|
8
|
+
globalScope[MYSQL_CLIENT_CACHE_KEY] = /* @__PURE__ */ new Map();
|
|
9
|
+
}
|
|
10
|
+
return globalScope[MYSQL_CLIENT_CACHE_KEY];
|
|
11
|
+
}
|
|
12
|
+
function getMysqlCacheKey(config) {
|
|
13
|
+
if (config.url) return config.url;
|
|
14
|
+
return `${config.host || "localhost"}:${config.port || 3306}:${config.database || ""}:${config.user || ""}`;
|
|
15
|
+
}
|
|
4
16
|
function escapeMysqlIdentifier(identifier) {
|
|
5
17
|
return `\`${identifier.replace(/`/g, "``")}\``;
|
|
6
18
|
}
|
|
@@ -19,6 +31,9 @@ var MysqlAdapter = class {
|
|
|
19
31
|
config;
|
|
20
32
|
initPromise = null;
|
|
21
33
|
inTransaction = false;
|
|
34
|
+
tableLocks = /* @__PURE__ */ new Map();
|
|
35
|
+
ensuredTables = /* @__PURE__ */ new Set();
|
|
36
|
+
tableColumnsCache = /* @__PURE__ */ new Map();
|
|
22
37
|
constructor(config) {
|
|
23
38
|
this.config = config;
|
|
24
39
|
}
|
|
@@ -41,6 +56,25 @@ FIX INSTRUCTIONS:
|
|
|
41
56
|
}
|
|
42
57
|
throw err;
|
|
43
58
|
}
|
|
59
|
+
async retryOperation(operation, maxRetries = 3, initialDelay = 1e3) {
|
|
60
|
+
let lastError;
|
|
61
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
62
|
+
try {
|
|
63
|
+
return await operation();
|
|
64
|
+
} catch (err) {
|
|
65
|
+
lastError = err;
|
|
66
|
+
const isNetworkOrTimeout = err.code === "ETIMEDOUT" || err.code === "ECONNREFUSED" || err.code === "PROTOCOL_CONNECTION_LOST" || err.message?.includes("ETIMEDOUT") || err.message?.includes("Connection lost");
|
|
67
|
+
if (attempt < maxRetries && isNetworkOrTimeout) {
|
|
68
|
+
const delay = initialDelay * Math.pow(2, attempt - 1);
|
|
69
|
+
console.warn(`[dyrected/db-mysql] Connection attempt ${attempt} failed (${err.code || err.message}), retrying in ${delay}ms...`);
|
|
70
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
71
|
+
} else {
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
throw lastError;
|
|
77
|
+
}
|
|
44
78
|
async ensureInitialized() {
|
|
45
79
|
if (!this.initPromise) {
|
|
46
80
|
this.initPromise = this.initialize();
|
|
@@ -48,59 +82,126 @@ FIX INSTRUCTIONS:
|
|
|
48
82
|
await this.initPromise;
|
|
49
83
|
}
|
|
50
84
|
async initialize() {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
85
|
+
const cache = getSharedMysqlClientCache();
|
|
86
|
+
const cacheKey = getMysqlCacheKey(this.config);
|
|
87
|
+
const cached = cache.get(cacheKey);
|
|
88
|
+
if (cached?.pool) {
|
|
89
|
+
this.pool = cached.pool;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (cached?.initPromise) {
|
|
93
|
+
this.pool = await cached.initPromise;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const initPromise = (async () => {
|
|
97
|
+
const config = this.config;
|
|
98
|
+
let dbName = config.database;
|
|
99
|
+
let serverConfig = null;
|
|
100
|
+
if (config.url) {
|
|
101
|
+
try {
|
|
102
|
+
const parsed = new URL(config.url);
|
|
103
|
+
dbName = parsed.pathname.replace(/^\//, "");
|
|
104
|
+
const serverUrl = `${parsed.protocol}//${parsed.username}:${parsed.password}@${parsed.host}`;
|
|
105
|
+
serverConfig = serverUrl;
|
|
106
|
+
} catch (err) {
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
serverConfig = {
|
|
110
|
+
host: config.host ?? "localhost",
|
|
111
|
+
port: config.port ?? 3306,
|
|
112
|
+
user: config.user,
|
|
113
|
+
password: config.password
|
|
114
|
+
};
|
|
61
115
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
116
|
+
if (dbName && serverConfig) {
|
|
117
|
+
try {
|
|
118
|
+
await this.retryOperation(async () => {
|
|
119
|
+
const tempConn = await mysql.createConnection(serverConfig);
|
|
120
|
+
try {
|
|
121
|
+
await tempConn.query(`CREATE DATABASE IF NOT EXISTS \`${dbName}\``);
|
|
122
|
+
} finally {
|
|
123
|
+
await tempConn.end().catch(() => {
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}, 3, 1e3);
|
|
127
|
+
console.log(`[dyrected/db-mysql] Database "${dbName}" checked/created successfully`);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
console.warn(`[dyrected/db-mysql] Auto-creation of database "${dbName}" skipped/failed:`, err.message);
|
|
130
|
+
if (err.code === "EADDRNOTAVAIL" || err.message?.includes("EADDRNOTAVAIL")) {
|
|
131
|
+
this.handleConnectionError(err);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const defaultPoolOptions = {
|
|
136
|
+
enableKeepAlive: true,
|
|
137
|
+
keepAliveInitialDelay: 1e4,
|
|
138
|
+
connectTimeout: 2e4,
|
|
139
|
+
dateStrings: true,
|
|
140
|
+
waitForConnections: true,
|
|
141
|
+
connectionLimit: 10,
|
|
142
|
+
queueLimit: 0,
|
|
143
|
+
...config.poolOptions
|
|
68
144
|
};
|
|
69
|
-
|
|
70
|
-
|
|
145
|
+
let pool;
|
|
146
|
+
if (config.url) {
|
|
147
|
+
pool = mysql.createPool({
|
|
148
|
+
uri: config.url,
|
|
149
|
+
...defaultPoolOptions
|
|
150
|
+
});
|
|
151
|
+
} else {
|
|
152
|
+
pool = mysql.createPool({
|
|
153
|
+
host: config.host ?? "localhost",
|
|
154
|
+
port: config.port ?? 3306,
|
|
155
|
+
user: config.user,
|
|
156
|
+
password: config.password,
|
|
157
|
+
database: config.database,
|
|
158
|
+
...defaultPoolOptions
|
|
159
|
+
});
|
|
160
|
+
}
|
|
71
161
|
try {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
162
|
+
await this.retryOperation(async () => {
|
|
163
|
+
await pool.query(`
|
|
164
|
+
CREATE TABLE IF NOT EXISTS dyrected_internal (
|
|
165
|
+
\`key\` VARCHAR(255) PRIMARY KEY,
|
|
166
|
+
value JSON NOT NULL
|
|
167
|
+
)
|
|
168
|
+
`);
|
|
169
|
+
await pool.query(`
|
|
170
|
+
CREATE TABLE IF NOT EXISTS _dyrected_ai_threads (
|
|
171
|
+
id VARCHAR(36) PRIMARY KEY,
|
|
172
|
+
project_id VARCHAR(255) NOT NULL,
|
|
173
|
+
user_id VARCHAR(255) NOT NULL,
|
|
174
|
+
title TEXT,
|
|
175
|
+
created_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3),
|
|
176
|
+
updated_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
|
177
|
+
INDEX idx_ai_threads_user_project (user_id, project_id)
|
|
178
|
+
)
|
|
179
|
+
`);
|
|
180
|
+
await pool.query(`
|
|
181
|
+
CREATE TABLE IF NOT EXISTS _dyrected_ai_messages (
|
|
182
|
+
id VARCHAR(36) PRIMARY KEY,
|
|
183
|
+
thread_id VARCHAR(36) NOT NULL,
|
|
184
|
+
role VARCHAR(50) NOT NULL,
|
|
185
|
+
content LONGTEXT NOT NULL,
|
|
186
|
+
created_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3),
|
|
187
|
+
metadata JSON,
|
|
188
|
+
INDEX idx_ai_messages_thread_id (thread_id),
|
|
189
|
+
CONSTRAINT fk_ai_messages_thread FOREIGN KEY (thread_id) REFERENCES _dyrected_ai_threads(id) ON DELETE CASCADE
|
|
190
|
+
)
|
|
191
|
+
`);
|
|
192
|
+
}, 3, 1e3);
|
|
76
193
|
} catch (err) {
|
|
77
|
-
|
|
78
|
-
if (err.code === "EADDRNOTAVAIL" || err.message?.includes("EADDRNOTAVAIL")) {
|
|
79
|
-
this.handleConnectionError(err);
|
|
80
|
-
}
|
|
194
|
+
this.handleConnectionError(err);
|
|
81
195
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
} else {
|
|
86
|
-
this.pool = mysql.createPool({
|
|
87
|
-
host: config.host ?? "localhost",
|
|
88
|
-
port: config.port ?? 3306,
|
|
89
|
-
user: config.user,
|
|
90
|
-
password: config.password,
|
|
91
|
-
database: config.database,
|
|
92
|
-
dateStrings: true
|
|
93
|
-
});
|
|
94
|
-
}
|
|
196
|
+
return pool;
|
|
197
|
+
})();
|
|
198
|
+
cache.set(cacheKey, { initPromise });
|
|
95
199
|
try {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
`);
|
|
102
|
-
} catch (err) {
|
|
103
|
-
this.handleConnectionError(err);
|
|
200
|
+
this.pool = await initPromise;
|
|
201
|
+
cache.set(cacheKey, { pool: this.pool });
|
|
202
|
+
} catch (error) {
|
|
203
|
+
cache.delete(cacheKey);
|
|
204
|
+
throw error;
|
|
104
205
|
}
|
|
105
206
|
}
|
|
106
207
|
async query(sql, params) {
|
|
@@ -120,28 +221,92 @@ FIX INSTRUCTIONS:
|
|
|
120
221
|
}
|
|
121
222
|
}
|
|
122
223
|
getTableName(slug) {
|
|
123
|
-
|
|
224
|
+
if (slug.includes(".")) {
|
|
225
|
+
const parts = slug.split(".");
|
|
226
|
+
return parts[parts.length - 1].replace(/`/g, "");
|
|
227
|
+
}
|
|
228
|
+
return slug.startsWith("collection_") ? slug : `collection_${slug}`;
|
|
124
229
|
}
|
|
125
|
-
async
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
CREATE TABLE IF NOT EXISTS \`${tableName}\` (
|
|
129
|
-
id VARCHAR(36) PRIMARY KEY,
|
|
130
|
-
data JSON NOT NULL,
|
|
131
|
-
created_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3),
|
|
132
|
-
updated_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)
|
|
133
|
-
)
|
|
134
|
-
`);
|
|
230
|
+
async getTableColumns(tableName) {
|
|
231
|
+
const cached = this.tableColumnsCache.get(tableName);
|
|
232
|
+
if (cached) return cached;
|
|
135
233
|
const [cols] = await this.query(`SHOW COLUMNS FROM \`${tableName}\``);
|
|
136
234
|
const existingCols = cols.map((c) => c.Field);
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
235
|
+
this.tableColumnsCache.set(tableName, existingCols);
|
|
236
|
+
return existingCols;
|
|
237
|
+
}
|
|
238
|
+
async ensureTable(slug, fields = []) {
|
|
239
|
+
if (slug === "_dyrected_ai_threads" || slug === "_dyrected_ai_messages") {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (fields.length === 0 && this.ensuredTables.has(slug)) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (this.tableLocks.has(slug)) {
|
|
246
|
+
await this.tableLocks.get(slug);
|
|
247
|
+
if (fields.length === 0 && this.ensuredTables.has(slug)) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
let resolveLock;
|
|
252
|
+
let rejectLock;
|
|
253
|
+
const lockPromise = new Promise((resolve, reject) => {
|
|
254
|
+
resolveLock = resolve;
|
|
255
|
+
rejectLock = reject;
|
|
256
|
+
});
|
|
257
|
+
this.tableLocks.set(slug, lockPromise);
|
|
258
|
+
try {
|
|
259
|
+
const tableName = this.getTableName(slug);
|
|
260
|
+
await this.query(`
|
|
261
|
+
CREATE TABLE IF NOT EXISTS \`${tableName}\` (
|
|
262
|
+
id VARCHAR(36) PRIMARY KEY,
|
|
263
|
+
data JSON NOT NULL,
|
|
264
|
+
created_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3),
|
|
265
|
+
updated_at DATETIME(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)
|
|
266
|
+
)
|
|
267
|
+
`);
|
|
268
|
+
const existingCols = await this.getTableColumns(tableName);
|
|
269
|
+
for (const field of fields) {
|
|
270
|
+
if (field.promoted && !existingCols.includes(field.name)) {
|
|
271
|
+
console.log(`[dyrected/mysql] Promoting field "${field.name}" to column in ${tableName}`);
|
|
272
|
+
let sqlType = "TEXT";
|
|
273
|
+
if (field.type === "number") sqlType = "DECIMAL(19,4)";
|
|
274
|
+
if (field.type === "boolean") sqlType = "TINYINT(1)";
|
|
275
|
+
if (field.type === "date" || field.type === "datetime") sqlType = "DATETIME(3)";
|
|
276
|
+
try {
|
|
277
|
+
await this.query(`ALTER TABLE \`${tableName}\` ADD COLUMN \`${field.name}\` ${sqlType}`);
|
|
278
|
+
existingCols.push(field.name);
|
|
279
|
+
this.tableColumnsCache.set(tableName, existingCols);
|
|
280
|
+
} catch (err) {
|
|
281
|
+
if (err.code === "ER_DUP_FIELDNAME" || err.errno === 1060 || err.message?.includes("Duplicate column name")) {
|
|
282
|
+
} else {
|
|
283
|
+
throw err;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const escapedField = field.name.replace(/`/g, "``");
|
|
287
|
+
let castExpr = `JSON_UNQUOTE(JSON_EXTRACT(data, '$.${field.name}'))`;
|
|
288
|
+
if (field.type === "number") {
|
|
289
|
+
castExpr = `IF(JSON_UNQUOTE(JSON_EXTRACT(data, '$.${field.name}')) REGEXP '^-?[0-9]+(\\\\.[0-9]+)?([eE][+-]?[0-9]+)?$', CAST(JSON_UNQUOTE(JSON_EXTRACT(data, '$.${field.name}')) AS DECIMAL(19,4)), NULL)`;
|
|
290
|
+
} else if (field.type === "boolean") {
|
|
291
|
+
castExpr = `IF(JSON_UNQUOTE(JSON_EXTRACT(data, '$.${field.name}')) IN ('true', '1'), 1, 0)`;
|
|
292
|
+
} else if (field.type === "date" || field.type === "datetime") {
|
|
293
|
+
castExpr = `STR_TO_DATE(LEFT(JSON_UNQUOTE(JSON_EXTRACT(data, '$.${field.name}')), 23), '%Y-%m-%d %H:%i:%s.%f')`;
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
await this.query(
|
|
297
|
+
`UPDATE \`${tableName}\` SET \`${escapedField}\` = ${castExpr} WHERE \`${escapedField}\` IS NULL AND JSON_CONTAINS_PATH(data, 'one', '$.${field.name}')`
|
|
298
|
+
);
|
|
299
|
+
} catch {
|
|
300
|
+
}
|
|
301
|
+
}
|
|
144
302
|
}
|
|
303
|
+
this.ensuredTables.add(slug);
|
|
304
|
+
resolveLock();
|
|
305
|
+
} catch (err) {
|
|
306
|
+
rejectLock(err);
|
|
307
|
+
throw err;
|
|
308
|
+
} finally {
|
|
309
|
+
this.tableLocks.delete(slug);
|
|
145
310
|
}
|
|
146
311
|
}
|
|
147
312
|
async find(args) {
|
|
@@ -150,8 +315,7 @@ FIX INSTRUCTIONS:
|
|
|
150
315
|
const limit = args.limit ?? 10;
|
|
151
316
|
const page = args.page ?? 1;
|
|
152
317
|
const offset = (page - 1) * limit;
|
|
153
|
-
const
|
|
154
|
-
const existingCols = cols.map((c) => c.Field);
|
|
318
|
+
const existingCols = await this.getTableColumns(tableName);
|
|
155
319
|
let whereSql = "";
|
|
156
320
|
let whereParams = [];
|
|
157
321
|
if (args.where && Object.keys(args.where).length > 0) {
|
|
@@ -214,8 +378,7 @@ FIX INSTRUCTIONS:
|
|
|
214
378
|
async create(params) {
|
|
215
379
|
if (!this.inTransaction) await this.ensureTable(params.collection);
|
|
216
380
|
const tableName = this.getTableName(params.collection);
|
|
217
|
-
const
|
|
218
|
-
const existingCols = cols.map((c) => c.Field);
|
|
381
|
+
const existingCols = await this.getTableColumns(tableName);
|
|
219
382
|
const id = params.data.id ?? Math.random().toString(36).substring(7);
|
|
220
383
|
const now = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace("Z", "");
|
|
221
384
|
const data = { ...params.data };
|
|
@@ -238,8 +401,7 @@ FIX INSTRUCTIONS:
|
|
|
238
401
|
async update(params) {
|
|
239
402
|
if (!this.inTransaction) await this.ensureTable(params.collection);
|
|
240
403
|
const tableName = this.getTableName(params.collection);
|
|
241
|
-
const
|
|
242
|
-
const existingCols = cols.map((c) => c.Field);
|
|
404
|
+
const existingCols = await this.getTableColumns(tableName);
|
|
243
405
|
const existing = await this.findOne({ collection: params.collection, id: params.id });
|
|
244
406
|
const now = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").replace("Z", "");
|
|
245
407
|
const merged = { ...existing ?? {}, ...params.data };
|
|
@@ -314,8 +476,7 @@ FIX INSTRUCTIONS:
|
|
|
314
476
|
}
|
|
315
477
|
const tableName = this.getTableName(args.collection);
|
|
316
478
|
if (!this.inTransaction) await this.ensureTable(args.collection);
|
|
317
|
-
const
|
|
318
|
-
const existingCols = cols.map((c) => c.Field);
|
|
479
|
+
const existingCols = await this.getTableColumns(tableName);
|
|
319
480
|
const toFieldExpr = (field) => {
|
|
320
481
|
if (field === "createdAt") return "`created_at`";
|
|
321
482
|
if (field === "updatedAt") return "`updated_at`";
|
|
@@ -408,6 +569,11 @@ FIX INSTRUCTIONS:
|
|
|
408
569
|
return result;
|
|
409
570
|
}
|
|
410
571
|
async disconnect() {
|
|
572
|
+
const cache = getSharedMysqlClientCache();
|
|
573
|
+
const cacheKey = getMysqlCacheKey(this.config);
|
|
574
|
+
cache.delete(cacheKey);
|
|
575
|
+
this.ensuredTables.clear();
|
|
576
|
+
this.tableColumnsCache.clear();
|
|
411
577
|
if (this.initPromise) {
|
|
412
578
|
try {
|
|
413
579
|
await this.initPromise;
|
|
@@ -422,8 +588,24 @@ FIX INSTRUCTIONS:
|
|
|
422
588
|
this.pool = null;
|
|
423
589
|
}
|
|
424
590
|
};
|
|
591
|
+
async function closeAllMysqlClients() {
|
|
592
|
+
const cache = getSharedMysqlClientCache();
|
|
593
|
+
const entries = Array.from(cache.values());
|
|
594
|
+
cache.clear();
|
|
595
|
+
for (const entry of entries) {
|
|
596
|
+
try {
|
|
597
|
+
const pool = entry.pool || await entry.initPromise;
|
|
598
|
+
if (pool && typeof pool.end === "function") {
|
|
599
|
+
await pool.end().catch(() => {
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
} catch {
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
425
606
|
var mysqlAdapter = (config) => new MysqlAdapter(config);
|
|
426
607
|
export {
|
|
427
608
|
MysqlAdapter,
|
|
609
|
+
closeAllMysqlClients,
|
|
428
610
|
mysqlAdapter
|
|
429
611
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dyrected/db-mysql",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.1",
|
|
4
4
|
"description": "MySQL adapter for Dyrected CMS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"mysql2": "^3.22.3",
|
|
21
|
-
"@dyrected/core": "^2.
|
|
21
|
+
"@dyrected/core": "^2.13.1"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"tsup": "^8.0.0",
|