trickle-observe 0.2.94 → 0.2.95

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.
@@ -13,3 +13,11 @@
13
13
  * Called from observe-register when pg is required.
14
14
  */
15
15
  export declare function patchPg(pgModule: any, debug: boolean): void;
16
+ /**
17
+ * Patch mysql2 to capture queries.
18
+ */
19
+ export declare function patchMysql2(mysqlModule: any, debug: boolean): void;
20
+ /**
21
+ * Patch better-sqlite3 to capture queries.
22
+ */
23
+ export declare function patchBetterSqlite3(dbConstructor: any, debug: boolean): void;
@@ -44,6 +44,8 @@ var __importStar = (this && this.__importStar) || (function () {
44
44
  })();
45
45
  Object.defineProperty(exports, "__esModule", { value: true });
46
46
  exports.patchPg = patchPg;
47
+ exports.patchMysql2 = patchMysql2;
48
+ exports.patchBetterSqlite3 = patchBetterSqlite3;
47
49
  const fs = __importStar(require("fs"));
48
50
  const path = __importStar(require("path"));
49
51
  let queriesFile = null;
@@ -197,3 +199,94 @@ function patchPg(pgModule, debug) {
197
199
  console.log('[trickle/db] PostgreSQL query tracing enabled');
198
200
  }
199
201
  }
202
+ /**
203
+ * Patch mysql2 to capture queries.
204
+ */
205
+ function patchMysql2(mysqlModule, debug) {
206
+ debugMode = debug;
207
+ // Patch Connection.prototype.query and .execute
208
+ const Connection = mysqlModule.Connection;
209
+ if (!Connection || !Connection.prototype)
210
+ return;
211
+ for (const method of ['query', 'execute']) {
212
+ const original = Connection.prototype[method];
213
+ if (!original || original.__trickle_patched)
214
+ continue;
215
+ Connection.prototype[method] = function patchedMethod(...args) {
216
+ const startTime = performance.now();
217
+ let queryText = typeof args[0] === 'string' ? args[0] : args[0]?.sql || '';
218
+ const truncated = queryText.length > MAX_QUERY_LENGTH ? queryText.substring(0, MAX_QUERY_LENGTH) + '...' : queryText;
219
+ const result = original.apply(this, args);
220
+ if (result && typeof result.then === 'function') {
221
+ return result.then((res) => {
222
+ const durationMs = Math.round((performance.now() - startTime) * 100) / 100;
223
+ const rows = Array.isArray(res) ? res[0] : res;
224
+ writeQuery({
225
+ kind: 'query', query: truncated, durationMs,
226
+ rowCount: Array.isArray(rows) ? rows.length : 0,
227
+ columns: Array.isArray(rows) && rows[0] ? Object.keys(rows[0]) : undefined,
228
+ timestamp: Date.now(),
229
+ });
230
+ return res;
231
+ }, (err) => {
232
+ writeQuery({
233
+ kind: 'query', query: truncated,
234
+ durationMs: Math.round((performance.now() - startTime) * 100) / 100,
235
+ rowCount: 0, error: err.message?.substring(0, 200), timestamp: Date.now(),
236
+ });
237
+ throw err;
238
+ });
239
+ }
240
+ return result;
241
+ };
242
+ Connection.prototype[method].__trickle_patched = true;
243
+ }
244
+ if (debug)
245
+ console.log('[trickle/db] MySQL query tracing enabled');
246
+ }
247
+ /**
248
+ * Patch better-sqlite3 to capture queries.
249
+ */
250
+ function patchBetterSqlite3(dbConstructor, debug) {
251
+ debugMode = debug;
252
+ // better-sqlite3 returns a Database constructor — patch its prototype
253
+ const origPrepare = dbConstructor.prototype?.prepare;
254
+ if (!origPrepare || origPrepare.__trickle_patched)
255
+ return;
256
+ dbConstructor.prototype.prepare = function patchedPrepare(sql) {
257
+ const stmt = origPrepare.call(this, sql);
258
+ const truncated = sql.length > MAX_QUERY_LENGTH ? sql.substring(0, MAX_QUERY_LENGTH) + '...' : sql;
259
+ // Patch run, get, all methods on the statement
260
+ for (const method of ['run', 'get', 'all']) {
261
+ const origMethod = stmt[method];
262
+ if (!origMethod)
263
+ continue;
264
+ stmt[method] = function (...args) {
265
+ const startTime = performance.now();
266
+ try {
267
+ const result = origMethod.apply(this, args);
268
+ const durationMs = Math.round((performance.now() - startTime) * 100) / 100;
269
+ const rowCount = method === 'all' ? (Array.isArray(result) ? result.length : 0)
270
+ : method === 'get' ? (result ? 1 : 0)
271
+ : (result?.changes || 0);
272
+ writeQuery({
273
+ kind: 'query', query: truncated, durationMs, rowCount, timestamp: Date.now(),
274
+ });
275
+ return result;
276
+ }
277
+ catch (err) {
278
+ writeQuery({
279
+ kind: 'query', query: truncated,
280
+ durationMs: Math.round((performance.now() - startTime) * 100) / 100,
281
+ rowCount: 0, error: err.message?.substring(0, 200), timestamp: Date.now(),
282
+ });
283
+ throw err;
284
+ }
285
+ };
286
+ }
287
+ return stmt;
288
+ };
289
+ dbConstructor.prototype.prepare.__trickle_patched = true;
290
+ if (debug)
291
+ console.log('[trickle/db] SQLite query tracing enabled');
292
+ }
@@ -1123,14 +1123,30 @@ if (enabled) {
1123
1123
  }
1124
1124
  catch { /* fall through to normal processing */ }
1125
1125
  }
1126
- // ── PostgreSQL auto-detection: patch pg to capture SQL queries ──
1126
+ // ── Database auto-detection: patch database drivers to capture SQL queries ──
1127
1127
  if (request === 'pg' && !expressPatched.has('pg')) {
1128
1128
  expressPatched.add('pg');
1129
1129
  try {
1130
1130
  const { patchPg } = require(path_1.default.join(__dirname, 'db-observer.js'));
1131
1131
  patchPg(exports, debug);
1132
1132
  }
1133
- catch { /* pg observer not critical */ }
1133
+ catch { /* not critical */ }
1134
+ }
1135
+ if ((request === 'mysql2' || request === 'mysql2/promise') && !expressPatched.has('mysql2')) {
1136
+ expressPatched.add('mysql2');
1137
+ try {
1138
+ const { patchMysql2 } = require(path_1.default.join(__dirname, 'db-observer.js'));
1139
+ patchMysql2(exports, debug);
1140
+ }
1141
+ catch { /* not critical */ }
1142
+ }
1143
+ if (request === 'better-sqlite3' && !expressPatched.has('better-sqlite3')) {
1144
+ expressPatched.add('better-sqlite3');
1145
+ try {
1146
+ const { patchBetterSqlite3 } = require(path_1.default.join(__dirname, 'db-observer.js'));
1147
+ patchBetterSqlite3(exports, debug);
1148
+ }
1149
+ catch { /* not critical */ }
1134
1150
  }
1135
1151
  // Resolve to absolute path for dedup — do this FIRST since bundlers like
1136
1152
  // tsx/esbuild may use path aliases (e.g., @config/env) that don't start
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trickle-observe",
3
- "version": "0.2.94",
3
+ "version": "0.2.95",
4
4
  "description": "Runtime type observability for JavaScript applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -183,3 +183,101 @@ export function patchPg(pgModule: any, debug: boolean): void {
183
183
  console.log('[trickle/db] PostgreSQL query tracing enabled');
184
184
  }
185
185
  }
186
+
187
+ /**
188
+ * Patch mysql2 to capture queries.
189
+ */
190
+ export function patchMysql2(mysqlModule: any, debug: boolean): void {
191
+ debugMode = debug;
192
+
193
+ // Patch Connection.prototype.query and .execute
194
+ const Connection = mysqlModule.Connection;
195
+ if (!Connection || !Connection.prototype) return;
196
+
197
+ for (const method of ['query', 'execute'] as const) {
198
+ const original = Connection.prototype[method];
199
+ if (!original || (original as any).__trickle_patched) continue;
200
+
201
+ Connection.prototype[method] = function patchedMethod(...args: any[]): any {
202
+ const startTime = performance.now();
203
+ let queryText = typeof args[0] === 'string' ? args[0] : args[0]?.sql || '';
204
+ const truncated = queryText.length > MAX_QUERY_LENGTH ? queryText.substring(0, MAX_QUERY_LENGTH) + '...' : queryText;
205
+
206
+ const result = original.apply(this, args);
207
+ if (result && typeof result.then === 'function') {
208
+ return result.then(
209
+ (res: any) => {
210
+ const durationMs = Math.round((performance.now() - startTime) * 100) / 100;
211
+ const rows = Array.isArray(res) ? res[0] : res;
212
+ writeQuery({
213
+ kind: 'query', query: truncated, durationMs,
214
+ rowCount: Array.isArray(rows) ? rows.length : 0,
215
+ columns: Array.isArray(rows) && rows[0] ? Object.keys(rows[0]) : undefined,
216
+ timestamp: Date.now(),
217
+ });
218
+ return res;
219
+ },
220
+ (err: any) => {
221
+ writeQuery({
222
+ kind: 'query', query: truncated,
223
+ durationMs: Math.round((performance.now() - startTime) * 100) / 100,
224
+ rowCount: 0, error: err.message?.substring(0, 200), timestamp: Date.now(),
225
+ });
226
+ throw err;
227
+ },
228
+ );
229
+ }
230
+ return result;
231
+ };
232
+ (Connection.prototype[method] as any).__trickle_patched = true;
233
+ }
234
+
235
+ if (debug) console.log('[trickle/db] MySQL query tracing enabled');
236
+ }
237
+
238
+ /**
239
+ * Patch better-sqlite3 to capture queries.
240
+ */
241
+ export function patchBetterSqlite3(dbConstructor: any, debug: boolean): void {
242
+ debugMode = debug;
243
+
244
+ // better-sqlite3 returns a Database constructor — patch its prototype
245
+ const origPrepare = dbConstructor.prototype?.prepare;
246
+ if (!origPrepare || (origPrepare as any).__trickle_patched) return;
247
+
248
+ dbConstructor.prototype.prepare = function patchedPrepare(sql: string): any {
249
+ const stmt = origPrepare.call(this, sql);
250
+ const truncated = sql.length > MAX_QUERY_LENGTH ? sql.substring(0, MAX_QUERY_LENGTH) + '...' : sql;
251
+
252
+ // Patch run, get, all methods on the statement
253
+ for (const method of ['run', 'get', 'all'] as const) {
254
+ const origMethod = stmt[method];
255
+ if (!origMethod) continue;
256
+ stmt[method] = function (...args: any[]): any {
257
+ const startTime = performance.now();
258
+ try {
259
+ const result = origMethod.apply(this, args);
260
+ const durationMs = Math.round((performance.now() - startTime) * 100) / 100;
261
+ const rowCount = method === 'all' ? (Array.isArray(result) ? result.length : 0)
262
+ : method === 'get' ? (result ? 1 : 0)
263
+ : (result?.changes || 0);
264
+ writeQuery({
265
+ kind: 'query', query: truncated, durationMs, rowCount, timestamp: Date.now(),
266
+ });
267
+ return result;
268
+ } catch (err: any) {
269
+ writeQuery({
270
+ kind: 'query', query: truncated,
271
+ durationMs: Math.round((performance.now() - startTime) * 100) / 100,
272
+ rowCount: 0, error: err.message?.substring(0, 200), timestamp: Date.now(),
273
+ });
274
+ throw err;
275
+ }
276
+ };
277
+ }
278
+ return stmt;
279
+ };
280
+ (dbConstructor.prototype.prepare as any).__trickle_patched = true;
281
+
282
+ if (debug) console.log('[trickle/db] SQLite query tracing enabled');
283
+ }
@@ -1113,13 +1113,27 @@ if (enabled) {
1113
1113
  } catch { /* fall through to normal processing */ }
1114
1114
  }
1115
1115
 
1116
- // ── PostgreSQL auto-detection: patch pg to capture SQL queries ──
1116
+ // ── Database auto-detection: patch database drivers to capture SQL queries ──
1117
1117
  if (request === 'pg' && !expressPatched.has('pg')) {
1118
1118
  expressPatched.add('pg');
1119
1119
  try {
1120
1120
  const { patchPg } = require(path.join(__dirname, 'db-observer.js'));
1121
1121
  patchPg(exports, debug);
1122
- } catch { /* pg observer not critical */ }
1122
+ } catch { /* not critical */ }
1123
+ }
1124
+ if ((request === 'mysql2' || request === 'mysql2/promise') && !expressPatched.has('mysql2')) {
1125
+ expressPatched.add('mysql2');
1126
+ try {
1127
+ const { patchMysql2 } = require(path.join(__dirname, 'db-observer.js'));
1128
+ patchMysql2(exports, debug);
1129
+ } catch { /* not critical */ }
1130
+ }
1131
+ if (request === 'better-sqlite3' && !expressPatched.has('better-sqlite3')) {
1132
+ expressPatched.add('better-sqlite3');
1133
+ try {
1134
+ const { patchBetterSqlite3 } = require(path.join(__dirname, 'db-observer.js'));
1135
+ patchBetterSqlite3(exports, debug);
1136
+ } catch { /* not critical */ }
1123
1137
  }
1124
1138
 
1125
1139
  // Resolve to absolute path for dedup — do this FIRST since bundlers like