@lara-node/db 0.1.0
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/MakeMigration.cjs +215 -0
- package/dist/MakeMigration.js +215 -0
- package/dist/MakeMigration.js.map +1 -0
- package/dist/chunk.cjs +43 -0
- package/dist/connection.cjs +286 -0
- package/dist/connection.js +226 -0
- package/dist/connection.js.map +1 -0
- package/dist/index.cjs +7790 -0
- package/dist/index.d.cts +1635 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +1635 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7732 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
const require_chunk = require("./chunk.cjs");
|
|
2
|
+
let mysql2_promise = require("mysql2/promise");
|
|
3
|
+
mysql2_promise = require_chunk.__toESM(mysql2_promise, 1);
|
|
4
|
+
let fs = require("fs");
|
|
5
|
+
fs = require_chunk.__toESM(fs, 1);
|
|
6
|
+
let mongodb = require("mongodb");
|
|
7
|
+
//#region src/QueryInstrumentation.ts
|
|
8
|
+
/** Compact JSON string of a MongoDB filter/pipeline argument (max 300 chars). */
|
|
9
|
+
function mongoArgSummary(val) {
|
|
10
|
+
try {
|
|
11
|
+
return JSON.stringify(val ?? {}).slice(0, 300);
|
|
12
|
+
} catch {
|
|
13
|
+
return "{}";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** Optional hook registered by Telescope/events layer — avoids circular deps. */
|
|
17
|
+
let _queryEventHook = null;
|
|
18
|
+
/** Called by @lara-node/telescope or @lara-node/events to wire up query event emission. */
|
|
19
|
+
function setQueryEventHook(hook) {
|
|
20
|
+
_queryEventHook = hook;
|
|
21
|
+
}
|
|
22
|
+
/** Emit a query event via the registered hook (no-op if none registered). */
|
|
23
|
+
async function emitQueryEvent(payload) {
|
|
24
|
+
if (_queryEventHook) await _queryEventHook(payload).catch(() => {});
|
|
25
|
+
}
|
|
26
|
+
/** Promise-based MongoDB operations that resolve with a result directly. */
|
|
27
|
+
const MONGO_PROMISE_OPS = new Set([
|
|
28
|
+
"insertOne",
|
|
29
|
+
"insertMany",
|
|
30
|
+
"updateOne",
|
|
31
|
+
"updateMany",
|
|
32
|
+
"replaceOne",
|
|
33
|
+
"deleteOne",
|
|
34
|
+
"deleteMany",
|
|
35
|
+
"findOne",
|
|
36
|
+
"findOneAndUpdate",
|
|
37
|
+
"findOneAndDelete",
|
|
38
|
+
"findOneAndReplace",
|
|
39
|
+
"countDocuments",
|
|
40
|
+
"estimatedDocumentCount",
|
|
41
|
+
"distinct",
|
|
42
|
+
"bulkWrite"
|
|
43
|
+
]);
|
|
44
|
+
/** Cursor-returning MongoDB operations — we instrument toArray(). */
|
|
45
|
+
const MONGO_CURSOR_OPS = new Set([
|
|
46
|
+
"find",
|
|
47
|
+
"aggregate",
|
|
48
|
+
"listIndexes"
|
|
49
|
+
]);
|
|
50
|
+
/**
|
|
51
|
+
* Wraps a MongoCollection in a Proxy that times every operation and calls
|
|
52
|
+
* emitQueryEvent() so Telescope captures it regardless of which process runs it.
|
|
53
|
+
*/
|
|
54
|
+
function createMongoQueryProxy(collectionName, col) {
|
|
55
|
+
const emit = (op, filter, durationMs, rows, error) => {
|
|
56
|
+
emitQueryEvent({
|
|
57
|
+
sql: `${collectionName}.${op}(${mongoArgSummary(filter)})`,
|
|
58
|
+
duration: durationMs,
|
|
59
|
+
rows,
|
|
60
|
+
error,
|
|
61
|
+
connection: "mongodb",
|
|
62
|
+
collection: collectionName
|
|
63
|
+
}).catch(() => {});
|
|
64
|
+
};
|
|
65
|
+
return new Proxy(col, { get(target, prop) {
|
|
66
|
+
const value = target[prop];
|
|
67
|
+
if (typeof prop !== "string" || typeof value !== "function") return value;
|
|
68
|
+
if (MONGO_PROMISE_OPS.has(prop)) return (...args) => {
|
|
69
|
+
const start = process.hrtime.bigint();
|
|
70
|
+
const filter = args[0];
|
|
71
|
+
return value.apply(target, args).then((res) => {
|
|
72
|
+
emit(prop, filter, Number((process.hrtime.bigint() - start) / 1000000n), res == null ? 0 : Array.isArray(res) ? res.length : res.insertedCount ?? res.modifiedCount ?? res.deletedCount ?? 1);
|
|
73
|
+
return res;
|
|
74
|
+
}).catch((err) => {
|
|
75
|
+
emit(prop, filter, Number((process.hrtime.bigint() - start) / 1000000n), 0, err.message);
|
|
76
|
+
throw err;
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
if (MONGO_CURSOR_OPS.has(prop)) return (...args) => {
|
|
80
|
+
const start = process.hrtime.bigint();
|
|
81
|
+
const filter = args[0];
|
|
82
|
+
const cursor = value.apply(target, args);
|
|
83
|
+
const origToArray = cursor.toArray.bind(cursor);
|
|
84
|
+
cursor.toArray = async () => {
|
|
85
|
+
try {
|
|
86
|
+
const rows = await origToArray();
|
|
87
|
+
emit(prop, filter, Number((process.hrtime.bigint() - start) / 1000000n), rows.length);
|
|
88
|
+
return rows;
|
|
89
|
+
} catch (err) {
|
|
90
|
+
emit(prop, filter, Number((process.hrtime.bigint() - start) / 1000000n), 0, err.message);
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
return cursor;
|
|
95
|
+
};
|
|
96
|
+
return value.bind(target);
|
|
97
|
+
} });
|
|
98
|
+
}
|
|
99
|
+
/** The Cache key pattern used for cross-process query entries. */
|
|
100
|
+
const TELESCOPE_QUERY_CACHE_KEY = `${process.env.APP_NAME || "app"}:telescope:queries`;
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/connection.ts
|
|
103
|
+
const dbType = (process.env.DB_CONNECTION || process.env.DB_DRIVER || "mysql").toLowerCase();
|
|
104
|
+
const dbHost = process.env.DB_HOST || "localhost";
|
|
105
|
+
const dbUser = process.env.DB_USER || "root";
|
|
106
|
+
const dbPassword = process.env.DB_PASSWORD || "";
|
|
107
|
+
const dbName = process.env.DB_NAME || "test";
|
|
108
|
+
const dbPort = process.env.DB_PORT ? parseInt(process.env.DB_PORT, 10) : void 0;
|
|
109
|
+
const socketPath = process.env.DB_SOCKET_PATH || process.env.DB_SOCKET || void 0;
|
|
110
|
+
const baseOptions = {
|
|
111
|
+
waitForConnections: true,
|
|
112
|
+
connectionLimit: parseInt(process.env.DB_POOL_LIMIT || "10", 10),
|
|
113
|
+
queueLimit: 0,
|
|
114
|
+
multipleStatements: true
|
|
115
|
+
};
|
|
116
|
+
let pool;
|
|
117
|
+
let mongoDb;
|
|
118
|
+
function ensureMysqlPool() {
|
|
119
|
+
if (pool) return pool;
|
|
120
|
+
pool = mysql2_promise.default.createPool(socketPath ? {
|
|
121
|
+
...baseOptions,
|
|
122
|
+
user: dbUser,
|
|
123
|
+
password: dbPassword,
|
|
124
|
+
database: dbName,
|
|
125
|
+
socketPath
|
|
126
|
+
} : {
|
|
127
|
+
...baseOptions,
|
|
128
|
+
host: dbHost,
|
|
129
|
+
user: dbUser,
|
|
130
|
+
password: dbPassword,
|
|
131
|
+
database: dbName,
|
|
132
|
+
port: dbPort
|
|
133
|
+
});
|
|
134
|
+
return pool;
|
|
135
|
+
}
|
|
136
|
+
function getDbType() {
|
|
137
|
+
return dbType;
|
|
138
|
+
}
|
|
139
|
+
async function query(sql, params = []) {
|
|
140
|
+
if (dbType === "mysql") {
|
|
141
|
+
const [rows] = await ensureMysqlPool().query(sql, params);
|
|
142
|
+
return rows;
|
|
143
|
+
}
|
|
144
|
+
throw new Error("query(sql, params) is not supported for MongoDB. Use the Mongo helpers (collection, getMongoDb).");
|
|
145
|
+
}
|
|
146
|
+
async function initDatabase() {
|
|
147
|
+
if (dbType === "mysql") {
|
|
148
|
+
let conn;
|
|
149
|
+
try {
|
|
150
|
+
conn = await ensureMysqlPool().getConnection();
|
|
151
|
+
await conn.ping();
|
|
152
|
+
} catch (err) {
|
|
153
|
+
const triedSockets = [];
|
|
154
|
+
if (!socketPath) {
|
|
155
|
+
for (const pth of [
|
|
156
|
+
"/var/run/mysqld/mysqld.sock",
|
|
157
|
+
"/tmp/mysql.sock",
|
|
158
|
+
"/var/lib/mysql/mysql.sock"
|
|
159
|
+
]) try {
|
|
160
|
+
if (!fs.default.existsSync(pth)) continue;
|
|
161
|
+
triedSockets.push(pth);
|
|
162
|
+
pool = mysql2_promise.default.createPool({
|
|
163
|
+
...baseOptions,
|
|
164
|
+
user: dbUser,
|
|
165
|
+
password: dbPassword,
|
|
166
|
+
database: dbName,
|
|
167
|
+
socketPath: pth
|
|
168
|
+
});
|
|
169
|
+
conn = await pool.getConnection();
|
|
170
|
+
await conn.ping();
|
|
171
|
+
return;
|
|
172
|
+
} catch (e) {
|
|
173
|
+
continue;
|
|
174
|
+
} finally {
|
|
175
|
+
if (conn) {
|
|
176
|
+
try {
|
|
177
|
+
conn.release();
|
|
178
|
+
} catch (_) {}
|
|
179
|
+
conn = void 0;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (triedSockets.length) err.message = `${err.message} (attempted sockets: ${triedSockets.join(", ")})`;
|
|
183
|
+
}
|
|
184
|
+
const hintLines = [];
|
|
185
|
+
if (err && err.code && String(err.code).startsWith("ER_ACCESS_DENIED")) hintLines.push("Access denied: verify DB_USER/DB_PASSWORD and that the user has permissions on DB_NAME.", "On many Linux setups, MySQL 'root' uses auth_socket and can't login with a blank password.", "Either create a dedicated user, set a proper password, or use DB_SOCKET_PATH if using a local socket.");
|
|
186
|
+
if (!process.env.DB_USER) hintLines.push("DB_USER is not set in your environment/.env; set it (e.g., DB_USER=rentivo)");
|
|
187
|
+
if (!dbName) hintLines.push("DB_NAME is empty; set it in your .env (e.g., DB_NAME=rentivo).");
|
|
188
|
+
if (!socketPath && !dbHost) hintLines.push("DB_HOST is empty; set DB_HOST or DB_SOCKET_PATH in your .env.");
|
|
189
|
+
const help = hintLines.length ? `\nHints:\n- ${hintLines.join("\n- ")}` : "";
|
|
190
|
+
const safeTarget = socketPath ? `socket ${socketPath}` : `${dbHost}${dbPort ? ":" + dbPort : ""}`;
|
|
191
|
+
throw new Error(`Database ping failed for ${safeTarget} as '${dbUser}' on schema '${dbName}'. Original: ${err?.message || err}${help}`);
|
|
192
|
+
} finally {
|
|
193
|
+
if (conn) conn.release();
|
|
194
|
+
}
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (mongoDb) return;
|
|
198
|
+
const mongoUri = process.env.MONGO_URI || process.env.MONGODB_URI || `mongodb://${dbHost}:${dbPort || 27017}`;
|
|
199
|
+
const replicaSet = process.env.MONGO_REPLICA_SET || void 0;
|
|
200
|
+
const isReplicaSet = !!replicaSet || process.env.MONGO_DIRECT_CONNECTION === "false";
|
|
201
|
+
const directConnection = process.env.MONGO_DIRECT_CONNECTION === "true" ? true : process.env.MONGO_DIRECT_CONNECTION === "false" ? false : !isReplicaSet;
|
|
202
|
+
const clientOptions = {
|
|
203
|
+
serverSelectionTimeoutMS: 1e4,
|
|
204
|
+
retryWrites: process.env.MONGO_RETRY_WRITES === "true" ? true : process.env.MONGO_RETRY_WRITES === "false" ? false : isReplicaSet
|
|
205
|
+
};
|
|
206
|
+
if (!isReplicaSet) clientOptions.directConnection = directConnection;
|
|
207
|
+
if (replicaSet) clientOptions.replicaSet = replicaSet;
|
|
208
|
+
const client = new mongodb.MongoClient(mongoUri, clientOptions);
|
|
209
|
+
await client.connect();
|
|
210
|
+
mongoDb = client.db(dbName);
|
|
211
|
+
await mongoDb.command({ ping: 1 });
|
|
212
|
+
}
|
|
213
|
+
function getPool() {
|
|
214
|
+
if (dbType !== "mysql") throw new Error("getPool() only valid for MySQL");
|
|
215
|
+
return ensureMysqlPool();
|
|
216
|
+
}
|
|
217
|
+
function getMongoDb() {
|
|
218
|
+
if (dbType !== "mongodb") throw new Error("getMongoDb() only valid for MongoDB");
|
|
219
|
+
if (!mongoDb) throw new Error("MongoDB not initialized. Call initDatabase() first.");
|
|
220
|
+
return mongoDb;
|
|
221
|
+
}
|
|
222
|
+
function collection(name) {
|
|
223
|
+
return createMongoQueryProxy(name, getMongoDb().collection(name));
|
|
224
|
+
}
|
|
225
|
+
dbType === "mysql" && ensureMysqlPool();
|
|
226
|
+
//#endregion
|
|
227
|
+
Object.defineProperty(exports, "TELESCOPE_QUERY_CACHE_KEY", {
|
|
228
|
+
enumerable: true,
|
|
229
|
+
get: function() {
|
|
230
|
+
return TELESCOPE_QUERY_CACHE_KEY;
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
Object.defineProperty(exports, "collection", {
|
|
234
|
+
enumerable: true,
|
|
235
|
+
get: function() {
|
|
236
|
+
return collection;
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
Object.defineProperty(exports, "createMongoQueryProxy", {
|
|
240
|
+
enumerable: true,
|
|
241
|
+
get: function() {
|
|
242
|
+
return createMongoQueryProxy;
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
Object.defineProperty(exports, "emitQueryEvent", {
|
|
246
|
+
enumerable: true,
|
|
247
|
+
get: function() {
|
|
248
|
+
return emitQueryEvent;
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
Object.defineProperty(exports, "getDbType", {
|
|
252
|
+
enumerable: true,
|
|
253
|
+
get: function() {
|
|
254
|
+
return getDbType;
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
Object.defineProperty(exports, "getMongoDb", {
|
|
258
|
+
enumerable: true,
|
|
259
|
+
get: function() {
|
|
260
|
+
return getMongoDb;
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
Object.defineProperty(exports, "getPool", {
|
|
264
|
+
enumerable: true,
|
|
265
|
+
get: function() {
|
|
266
|
+
return getPool;
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
Object.defineProperty(exports, "initDatabase", {
|
|
270
|
+
enumerable: true,
|
|
271
|
+
get: function() {
|
|
272
|
+
return initDatabase;
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
Object.defineProperty(exports, "query", {
|
|
276
|
+
enumerable: true,
|
|
277
|
+
get: function() {
|
|
278
|
+
return query;
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
Object.defineProperty(exports, "setQueryEventHook", {
|
|
282
|
+
enumerable: true,
|
|
283
|
+
get: function() {
|
|
284
|
+
return setQueryEventHook;
|
|
285
|
+
}
|
|
286
|
+
});
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import mysql from "mysql2/promise";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { MongoClient } from "mongodb";
|
|
4
|
+
//#region src/QueryInstrumentation.ts
|
|
5
|
+
/** Compact JSON string of a MongoDB filter/pipeline argument (max 300 chars). */
|
|
6
|
+
function mongoArgSummary(val) {
|
|
7
|
+
try {
|
|
8
|
+
return JSON.stringify(val ?? {}).slice(0, 300);
|
|
9
|
+
} catch {
|
|
10
|
+
return "{}";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** Optional hook registered by Telescope/events layer — avoids circular deps. */
|
|
14
|
+
let _queryEventHook = null;
|
|
15
|
+
/** Called by @lara-node/telescope or @lara-node/events to wire up query event emission. */
|
|
16
|
+
function setQueryEventHook(hook) {
|
|
17
|
+
_queryEventHook = hook;
|
|
18
|
+
}
|
|
19
|
+
/** Emit a query event via the registered hook (no-op if none registered). */
|
|
20
|
+
async function emitQueryEvent(payload) {
|
|
21
|
+
if (_queryEventHook) await _queryEventHook(payload).catch(() => {});
|
|
22
|
+
}
|
|
23
|
+
/** Promise-based MongoDB operations that resolve with a result directly. */
|
|
24
|
+
const MONGO_PROMISE_OPS = new Set([
|
|
25
|
+
"insertOne",
|
|
26
|
+
"insertMany",
|
|
27
|
+
"updateOne",
|
|
28
|
+
"updateMany",
|
|
29
|
+
"replaceOne",
|
|
30
|
+
"deleteOne",
|
|
31
|
+
"deleteMany",
|
|
32
|
+
"findOne",
|
|
33
|
+
"findOneAndUpdate",
|
|
34
|
+
"findOneAndDelete",
|
|
35
|
+
"findOneAndReplace",
|
|
36
|
+
"countDocuments",
|
|
37
|
+
"estimatedDocumentCount",
|
|
38
|
+
"distinct",
|
|
39
|
+
"bulkWrite"
|
|
40
|
+
]);
|
|
41
|
+
/** Cursor-returning MongoDB operations — we instrument toArray(). */
|
|
42
|
+
const MONGO_CURSOR_OPS = new Set([
|
|
43
|
+
"find",
|
|
44
|
+
"aggregate",
|
|
45
|
+
"listIndexes"
|
|
46
|
+
]);
|
|
47
|
+
/**
|
|
48
|
+
* Wraps a MongoCollection in a Proxy that times every operation and calls
|
|
49
|
+
* emitQueryEvent() so Telescope captures it regardless of which process runs it.
|
|
50
|
+
*/
|
|
51
|
+
function createMongoQueryProxy(collectionName, col) {
|
|
52
|
+
const emit = (op, filter, durationMs, rows, error) => {
|
|
53
|
+
emitQueryEvent({
|
|
54
|
+
sql: `${collectionName}.${op}(${mongoArgSummary(filter)})`,
|
|
55
|
+
duration: durationMs,
|
|
56
|
+
rows,
|
|
57
|
+
error,
|
|
58
|
+
connection: "mongodb",
|
|
59
|
+
collection: collectionName
|
|
60
|
+
}).catch(() => {});
|
|
61
|
+
};
|
|
62
|
+
return new Proxy(col, { get(target, prop) {
|
|
63
|
+
const value = target[prop];
|
|
64
|
+
if (typeof prop !== "string" || typeof value !== "function") return value;
|
|
65
|
+
if (MONGO_PROMISE_OPS.has(prop)) return (...args) => {
|
|
66
|
+
const start = process.hrtime.bigint();
|
|
67
|
+
const filter = args[0];
|
|
68
|
+
return value.apply(target, args).then((res) => {
|
|
69
|
+
emit(prop, filter, Number((process.hrtime.bigint() - start) / 1000000n), res == null ? 0 : Array.isArray(res) ? res.length : res.insertedCount ?? res.modifiedCount ?? res.deletedCount ?? 1);
|
|
70
|
+
return res;
|
|
71
|
+
}).catch((err) => {
|
|
72
|
+
emit(prop, filter, Number((process.hrtime.bigint() - start) / 1000000n), 0, err.message);
|
|
73
|
+
throw err;
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
if (MONGO_CURSOR_OPS.has(prop)) return (...args) => {
|
|
77
|
+
const start = process.hrtime.bigint();
|
|
78
|
+
const filter = args[0];
|
|
79
|
+
const cursor = value.apply(target, args);
|
|
80
|
+
const origToArray = cursor.toArray.bind(cursor);
|
|
81
|
+
cursor.toArray = async () => {
|
|
82
|
+
try {
|
|
83
|
+
const rows = await origToArray();
|
|
84
|
+
emit(prop, filter, Number((process.hrtime.bigint() - start) / 1000000n), rows.length);
|
|
85
|
+
return rows;
|
|
86
|
+
} catch (err) {
|
|
87
|
+
emit(prop, filter, Number((process.hrtime.bigint() - start) / 1000000n), 0, err.message);
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
return cursor;
|
|
92
|
+
};
|
|
93
|
+
return value.bind(target);
|
|
94
|
+
} });
|
|
95
|
+
}
|
|
96
|
+
/** The Cache key pattern used for cross-process query entries. */
|
|
97
|
+
const TELESCOPE_QUERY_CACHE_KEY = `${process.env.APP_NAME || "app"}:telescope:queries`;
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/connection.ts
|
|
100
|
+
const dbType = (process.env.DB_CONNECTION || process.env.DB_DRIVER || "mysql").toLowerCase();
|
|
101
|
+
const dbHost = process.env.DB_HOST || "localhost";
|
|
102
|
+
const dbUser = process.env.DB_USER || "root";
|
|
103
|
+
const dbPassword = process.env.DB_PASSWORD || "";
|
|
104
|
+
const dbName = process.env.DB_NAME || "test";
|
|
105
|
+
const dbPort = process.env.DB_PORT ? parseInt(process.env.DB_PORT, 10) : void 0;
|
|
106
|
+
const socketPath = process.env.DB_SOCKET_PATH || process.env.DB_SOCKET || void 0;
|
|
107
|
+
const baseOptions = {
|
|
108
|
+
waitForConnections: true,
|
|
109
|
+
connectionLimit: parseInt(process.env.DB_POOL_LIMIT || "10", 10),
|
|
110
|
+
queueLimit: 0,
|
|
111
|
+
multipleStatements: true
|
|
112
|
+
};
|
|
113
|
+
let pool;
|
|
114
|
+
let mongoDb;
|
|
115
|
+
function ensureMysqlPool() {
|
|
116
|
+
if (pool) return pool;
|
|
117
|
+
pool = mysql.createPool(socketPath ? {
|
|
118
|
+
...baseOptions,
|
|
119
|
+
user: dbUser,
|
|
120
|
+
password: dbPassword,
|
|
121
|
+
database: dbName,
|
|
122
|
+
socketPath
|
|
123
|
+
} : {
|
|
124
|
+
...baseOptions,
|
|
125
|
+
host: dbHost,
|
|
126
|
+
user: dbUser,
|
|
127
|
+
password: dbPassword,
|
|
128
|
+
database: dbName,
|
|
129
|
+
port: dbPort
|
|
130
|
+
});
|
|
131
|
+
return pool;
|
|
132
|
+
}
|
|
133
|
+
function getDbType() {
|
|
134
|
+
return dbType;
|
|
135
|
+
}
|
|
136
|
+
async function query(sql, params = []) {
|
|
137
|
+
if (dbType === "mysql") {
|
|
138
|
+
const [rows] = await ensureMysqlPool().query(sql, params);
|
|
139
|
+
return rows;
|
|
140
|
+
}
|
|
141
|
+
throw new Error("query(sql, params) is not supported for MongoDB. Use the Mongo helpers (collection, getMongoDb).");
|
|
142
|
+
}
|
|
143
|
+
async function initDatabase() {
|
|
144
|
+
if (dbType === "mysql") {
|
|
145
|
+
let conn;
|
|
146
|
+
try {
|
|
147
|
+
conn = await ensureMysqlPool().getConnection();
|
|
148
|
+
await conn.ping();
|
|
149
|
+
} catch (err) {
|
|
150
|
+
const triedSockets = [];
|
|
151
|
+
if (!socketPath) {
|
|
152
|
+
for (const pth of [
|
|
153
|
+
"/var/run/mysqld/mysqld.sock",
|
|
154
|
+
"/tmp/mysql.sock",
|
|
155
|
+
"/var/lib/mysql/mysql.sock"
|
|
156
|
+
]) try {
|
|
157
|
+
if (!fs.existsSync(pth)) continue;
|
|
158
|
+
triedSockets.push(pth);
|
|
159
|
+
pool = mysql.createPool({
|
|
160
|
+
...baseOptions,
|
|
161
|
+
user: dbUser,
|
|
162
|
+
password: dbPassword,
|
|
163
|
+
database: dbName,
|
|
164
|
+
socketPath: pth
|
|
165
|
+
});
|
|
166
|
+
conn = await pool.getConnection();
|
|
167
|
+
await conn.ping();
|
|
168
|
+
return;
|
|
169
|
+
} catch (e) {
|
|
170
|
+
continue;
|
|
171
|
+
} finally {
|
|
172
|
+
if (conn) {
|
|
173
|
+
try {
|
|
174
|
+
conn.release();
|
|
175
|
+
} catch (_) {}
|
|
176
|
+
conn = void 0;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (triedSockets.length) err.message = `${err.message} (attempted sockets: ${triedSockets.join(", ")})`;
|
|
180
|
+
}
|
|
181
|
+
const hintLines = [];
|
|
182
|
+
if (err && err.code && String(err.code).startsWith("ER_ACCESS_DENIED")) hintLines.push("Access denied: verify DB_USER/DB_PASSWORD and that the user has permissions on DB_NAME.", "On many Linux setups, MySQL 'root' uses auth_socket and can't login with a blank password.", "Either create a dedicated user, set a proper password, or use DB_SOCKET_PATH if using a local socket.");
|
|
183
|
+
if (!process.env.DB_USER) hintLines.push("DB_USER is not set in your environment/.env; set it (e.g., DB_USER=rentivo)");
|
|
184
|
+
if (!dbName) hintLines.push("DB_NAME is empty; set it in your .env (e.g., DB_NAME=rentivo).");
|
|
185
|
+
if (!socketPath && !dbHost) hintLines.push("DB_HOST is empty; set DB_HOST or DB_SOCKET_PATH in your .env.");
|
|
186
|
+
const help = hintLines.length ? `\nHints:\n- ${hintLines.join("\n- ")}` : "";
|
|
187
|
+
const safeTarget = socketPath ? `socket ${socketPath}` : `${dbHost}${dbPort ? ":" + dbPort : ""}`;
|
|
188
|
+
throw new Error(`Database ping failed for ${safeTarget} as '${dbUser}' on schema '${dbName}'. Original: ${err?.message || err}${help}`);
|
|
189
|
+
} finally {
|
|
190
|
+
if (conn) conn.release();
|
|
191
|
+
}
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (mongoDb) return;
|
|
195
|
+
const mongoUri = process.env.MONGO_URI || process.env.MONGODB_URI || `mongodb://${dbHost}:${dbPort || 27017}`;
|
|
196
|
+
const replicaSet = process.env.MONGO_REPLICA_SET || void 0;
|
|
197
|
+
const isReplicaSet = !!replicaSet || process.env.MONGO_DIRECT_CONNECTION === "false";
|
|
198
|
+
const directConnection = process.env.MONGO_DIRECT_CONNECTION === "true" ? true : process.env.MONGO_DIRECT_CONNECTION === "false" ? false : !isReplicaSet;
|
|
199
|
+
const clientOptions = {
|
|
200
|
+
serverSelectionTimeoutMS: 1e4,
|
|
201
|
+
retryWrites: process.env.MONGO_RETRY_WRITES === "true" ? true : process.env.MONGO_RETRY_WRITES === "false" ? false : isReplicaSet
|
|
202
|
+
};
|
|
203
|
+
if (!isReplicaSet) clientOptions.directConnection = directConnection;
|
|
204
|
+
if (replicaSet) clientOptions.replicaSet = replicaSet;
|
|
205
|
+
const client = new MongoClient(mongoUri, clientOptions);
|
|
206
|
+
await client.connect();
|
|
207
|
+
mongoDb = client.db(dbName);
|
|
208
|
+
await mongoDb.command({ ping: 1 });
|
|
209
|
+
}
|
|
210
|
+
function getPool() {
|
|
211
|
+
if (dbType !== "mysql") throw new Error("getPool() only valid for MySQL");
|
|
212
|
+
return ensureMysqlPool();
|
|
213
|
+
}
|
|
214
|
+
function getMongoDb() {
|
|
215
|
+
if (dbType !== "mongodb") throw new Error("getMongoDb() only valid for MongoDB");
|
|
216
|
+
if (!mongoDb) throw new Error("MongoDB not initialized. Call initDatabase() first.");
|
|
217
|
+
return mongoDb;
|
|
218
|
+
}
|
|
219
|
+
function collection(name) {
|
|
220
|
+
return createMongoQueryProxy(name, getMongoDb().collection(name));
|
|
221
|
+
}
|
|
222
|
+
dbType === "mysql" && ensureMysqlPool();
|
|
223
|
+
//#endregion
|
|
224
|
+
export { initDatabase as a, createMongoQueryProxy as c, getPool as i, emitQueryEvent as l, getDbType as n, query as o, getMongoDb as r, TELESCOPE_QUERY_CACHE_KEY as s, collection as t, setQueryEventHook as u };
|
|
225
|
+
|
|
226
|
+
//# sourceMappingURL=connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.js","names":[],"sources":["../src/QueryInstrumentation.ts","../src/connection.ts"],"sourcesContent":["/*\n|--------------------------------------------------------------------------\n| Query Instrumentation — shared utility\n|--------------------------------------------------------------------------\n|\n| Used by both DB.ts (MySQL executeQuery + MongoDB collection) and\n| db.config.ts (mongoCollection shorthand used by EloquentBuilder/Model).\n|\n| emitQueryEvent()\n| If the application EventDispatcher already has a `db:query` listener\n| (i.e., Telescope is active in this process), fires the event there.\n| Otherwise — we're in a worker process — writes the entry to the shared\n| Cache so the HTTP server's Telescope can poll it.\n|\n| createMongoQueryProxy()\n| Wraps any MongoCollection in a Proxy that captures timing and calls\n| emitQueryEvent() for every meaningful operation.\n|\n*/\n\n/** Compact JSON string of a MongoDB filter/pipeline argument (max 300 chars). */\nfunction mongoArgSummary(val: unknown): string {\n try {\n return JSON.stringify(val ?? {}).slice(0, 300);\n } catch {\n return \"{}\";\n }\n}\n\nexport interface QueryEventPayload {\n sql: string;\n bindings?: any[];\n duration: number;\n rows?: number;\n error?: string;\n connection: string;\n collection?: string;\n}\n\n/** Optional hook registered by Telescope/events layer — avoids circular deps. */\nlet _queryEventHook: ((payload: QueryEventPayload) => Promise<void>) | null = null;\n\n/** Called by @lara-node/telescope or @lara-node/events to wire up query event emission. */\nexport function setQueryEventHook(hook: (payload: QueryEventPayload) => Promise<void>): void {\n _queryEventHook = hook;\n}\n\n/** Emit a query event via the registered hook (no-op if none registered). */\nexport async function emitQueryEvent(payload: QueryEventPayload): Promise<void> {\n if (_queryEventHook) {\n await _queryEventHook(payload).catch(() => {});\n }\n}\n\n/** Promise-based MongoDB operations that resolve with a result directly. */\nconst MONGO_PROMISE_OPS = new Set([\n \"insertOne\",\n \"insertMany\",\n \"updateOne\",\n \"updateMany\",\n \"replaceOne\",\n \"deleteOne\",\n \"deleteMany\",\n \"findOne\",\n \"findOneAndUpdate\",\n \"findOneAndDelete\",\n \"findOneAndReplace\",\n \"countDocuments\",\n \"estimatedDocumentCount\",\n \"distinct\",\n \"bulkWrite\",\n]);\n\n/** Cursor-returning MongoDB operations — we instrument toArray(). */\nconst MONGO_CURSOR_OPS = new Set([\"find\", \"aggregate\", \"listIndexes\"]);\n\n/**\n * Wraps a MongoCollection in a Proxy that times every operation and calls\n * emitQueryEvent() so Telescope captures it regardless of which process runs it.\n */\nexport function createMongoQueryProxy<T extends object>(collectionName: string, col: T): T {\n const emit = (\n op: string,\n filter: unknown,\n durationMs: number,\n rows?: number,\n error?: string,\n ): void => {\n emitQueryEvent({\n sql: `${collectionName}.${op}(${mongoArgSummary(filter)})`,\n duration: durationMs,\n rows,\n error,\n connection: \"mongodb\",\n collection: collectionName,\n }).catch(() => {});\n };\n\n return new Proxy(col, {\n get(target, prop: string | symbol) {\n const value = (target as any)[prop];\n\n if (typeof prop !== \"string\" || typeof value !== \"function\") {\n return value;\n }\n\n if (MONGO_PROMISE_OPS.has(prop)) {\n return (...args: any[]): Promise<any> => {\n const start = process.hrtime.bigint();\n const filter = args[0];\n return (value.apply(target, args) as Promise<any>)\n .then((res: any) => {\n const dur = Number((process.hrtime.bigint() - start) / 1_000_000n);\n const rowCount =\n res == null\n ? 0\n : Array.isArray(res)\n ? res.length\n : (res.insertedCount ?? res.modifiedCount ?? res.deletedCount ?? 1);\n emit(prop, filter, dur, rowCount);\n return res;\n })\n .catch((err: Error) => {\n const dur = Number((process.hrtime.bigint() - start) / 1_000_000n);\n emit(prop, filter, dur, 0, err.message);\n throw err;\n });\n };\n }\n\n if (MONGO_CURSOR_OPS.has(prop)) {\n return (...args: any[]) => {\n const start = process.hrtime.bigint();\n const filter = args[0];\n const cursor = value.apply(target, args);\n const origToArray = cursor.toArray.bind(cursor);\n cursor.toArray = async (): Promise<any[]> => {\n try {\n const rows: any[] = await origToArray();\n const dur = Number((process.hrtime.bigint() - start) / 1_000_000n);\n emit(prop, filter, dur, rows.length);\n return rows;\n } catch (err: any) {\n const dur = Number((process.hrtime.bigint() - start) / 1_000_000n);\n emit(prop, filter, dur, 0, err.message);\n throw err;\n }\n };\n return cursor;\n };\n }\n\n return value.bind(target);\n },\n }) as T;\n}\n\n/** The Cache key pattern used for cross-process query entries. */\nexport const TELESCOPE_QUERY_CACHE_KEY = `${process.env.APP_NAME || \"app\"}:telescope:queries`;\n","import mysql from \"mysql2/promise\";\nimport fs from \"fs\";\nimport { MongoClient, Db, Collection, Document } from \"mongodb\";\nimport { createMongoQueryProxy } from \"./QueryInstrumentation.js\";\n\n// Ensure .env is loaded if this module is imported directly (safety for various import orders)\n\nconst dbType = (process.env.DB_CONNECTION || process.env.DB_DRIVER || \"mysql\").toLowerCase();\n\n// Common envs\nconst dbHost = process.env.DB_HOST || \"localhost\";\nconst dbUser = process.env.DB_USER || \"root\";\nconst dbPassword = process.env.DB_PASSWORD || \"\";\nconst dbName = process.env.DB_NAME || \"test\";\nconst dbPort = process.env.DB_PORT ? parseInt(process.env.DB_PORT, 10) : undefined;\nconst socketPath = process.env.DB_SOCKET_PATH || process.env.DB_SOCKET || undefined;\n\n// MySQL state\nconst baseOptions: mysql.PoolOptions = {\n waitForConnections: true,\n connectionLimit: parseInt(process.env.DB_POOL_LIMIT || \"10\", 10),\n queueLimit: 0,\n multipleStatements: true,\n};\n\nlet pool: mysql.Pool | undefined;\n\n// Mongo state\nlet mongoClient: MongoClient | undefined;\nlet mongoDb: Db | undefined;\n\nfunction ensureMysqlPool() {\n if (pool) return pool;\n pool = mysql.createPool(\n socketPath\n ? { ...baseOptions, user: dbUser, password: dbPassword, database: dbName, socketPath }\n : {\n ...baseOptions,\n host: dbHost,\n user: dbUser,\n password: dbPassword,\n database: dbName,\n port: dbPort,\n },\n );\n return pool;\n}\n\nexport function getDbType() {\n return dbType as \"mysql\" | \"mongodb\";\n}\n\nexport async function query<T = any>(sql: string, params: any[] = []): Promise<T[]> {\n if (dbType === \"mysql\") {\n const p = ensureMysqlPool();\n const [rows] = await p.query(sql, params);\n return rows as T[];\n }\n // Intentionally throw for Mongo to avoid accidental SQL usage\n throw new Error(\n \"query(sql, params) is not supported for MongoDB. Use the Mongo helpers (collection, getMongoDb).\",\n );\n}\n\nexport async function initDatabase(): Promise<void> {\n if (dbType === \"mysql\") {\n let conn: mysql.PoolConnection | undefined;\n try {\n const p = ensureMysqlPool();\n conn = await p.getConnection();\n await conn.ping();\n } catch (err: any) {\n const triedSockets: string[] = [];\n if (!socketPath) {\n const commonSocketPaths = [\n \"/var/run/mysqld/mysqld.sock\",\n \"/tmp/mysql.sock\",\n \"/var/lib/mysql/mysql.sock\",\n ];\n for (const pth of commonSocketPaths) {\n try {\n if (!fs.existsSync(pth)) continue;\n triedSockets.push(pth);\n pool = mysql.createPool({\n ...baseOptions,\n user: dbUser,\n password: dbPassword,\n database: dbName,\n socketPath: pth,\n });\n conn = await pool.getConnection();\n await conn.ping();\n return;\n } catch (e) {\n continue;\n } finally {\n if (conn) {\n try {\n conn.release();\n } catch (_) {}\n conn = undefined;\n }\n }\n }\n if (triedSockets.length) {\n (err as any).message =\n `${(err as any).message} (attempted sockets: ${triedSockets.join(\", \")})`;\n }\n }\n const hintLines: string[] = [];\n if (err && err.code && String(err.code).startsWith(\"ER_ACCESS_DENIED\")) {\n hintLines.push(\n \"Access denied: verify DB_USER/DB_PASSWORD and that the user has permissions on DB_NAME.\",\n \"On many Linux setups, MySQL 'root' uses auth_socket and can't login with a blank password.\",\n \"Either create a dedicated user, set a proper password, or use DB_SOCKET_PATH if using a local socket.\",\n );\n }\n if (!process.env.DB_USER)\n hintLines.push(\n \"DB_USER is not set in your environment/.env; set it (e.g., DB_USER=rentivo)\",\n );\n if (!dbName) hintLines.push(\"DB_NAME is empty; set it in your .env (e.g., DB_NAME=rentivo).\");\n if (!socketPath && !dbHost)\n hintLines.push(\"DB_HOST is empty; set DB_HOST or DB_SOCKET_PATH in your .env.\");\n const help = hintLines.length ? `\\nHints:\\n- ${hintLines.join(\"\\n- \")}` : \"\";\n const safeTarget = socketPath\n ? `socket ${socketPath}`\n : `${dbHost}${dbPort ? \":\" + dbPort : \"\"}`;\n throw new Error(\n `Database ping failed for ${safeTarget} as '${dbUser}' on schema '${dbName}'. Original: ${err?.message || err}${help}`,\n );\n } finally {\n if (conn) conn.release();\n }\n return;\n }\n\n // MongoDB init\n if (mongoDb) return;\n const mongoUri =\n process.env.MONGO_URI || process.env.MONGODB_URI || `mongodb://${dbHost}:${dbPort || 27017}`;\n\n // Replica set configuration\n // If MONGO_REPLICA_SET is set, we're using a replica set\n const replicaSet = process.env.MONGO_REPLICA_SET || undefined;\n const isReplicaSet = !!replicaSet || process.env.MONGO_DIRECT_CONNECTION === \"false\";\n\n // directConnection: For standalone MongoDB, set to true (default for local dev)\n // For replica sets, must be false or undefined to allow driver to discover all nodes\n // Default: true for standalone (safe local dev), false if replica set is configured\n const directConnection =\n process.env.MONGO_DIRECT_CONNECTION === \"true\"\n ? true\n : process.env.MONGO_DIRECT_CONNECTION === \"false\"\n ? false\n : !isReplicaSet; // Default: true for standalone, false for replica set\n\n // retryWrites: Requires replica set. MongoDB 7.x uses transaction numbers for retryable writes\n // Default: true for replica sets, false for standalone (to avoid \"Transaction numbers\" error)\n const retryWrites =\n process.env.MONGO_RETRY_WRITES === \"true\"\n ? true\n : process.env.MONGO_RETRY_WRITES === \"false\"\n ? false\n : isReplicaSet; // Default based on replica set detection\n\n const clientOptions: any = {\n serverSelectionTimeoutMS: 10000,\n retryWrites,\n };\n\n // Only set directConnection if not using replica set (it's incompatible with replicaSet option)\n if (!isReplicaSet) {\n clientOptions.directConnection = directConnection;\n }\n\n // Add replica set name if configured\n if (replicaSet) {\n clientOptions.replicaSet = replicaSet;\n }\n\n const client = new MongoClient(mongoUri, clientOptions);\n await client.connect();\n mongoClient = client;\n mongoDb = client.db(dbName);\n // Simple ping\n await mongoDb.command({ ping: 1 });\n}\n\nexport function getPool() {\n if (dbType !== \"mysql\") throw new Error(\"getPool() only valid for MySQL\");\n return ensureMysqlPool();\n}\n\nexport function getMongoDb(): Db {\n if (dbType !== \"mongodb\") throw new Error(\"getMongoDb() only valid for MongoDB\");\n if (!mongoDb) throw new Error(\"MongoDB not initialized. Call initDatabase() first.\");\n return mongoDb;\n}\n\nexport function collection(name: string): Collection<Document> {\n const col: Collection<Document> = getMongoDb().collection(name);\n return createMongoQueryProxy(name, col) as Collection<Document>;\n}\n\nexport async function closeDatabase(): Promise<void> {\n if (dbType === \"mysql\") {\n if (pool) await pool.end();\n pool = undefined;\n return;\n }\n if (mongoClient) await mongoClient.close();\n mongoClient = undefined;\n mongoDb = undefined;\n}\n\nexport default (dbType === \"mysql\" ? ensureMysqlPool() : undefined) as any;\n"],"mappings":"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"}
|