@zintrust/trace 0.4.82 → 0.4.83

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/README.md CHANGED
@@ -29,6 +29,7 @@ You can still import the package migrations manually if you prefer to keep them
29
29
  ```env
30
30
  TRACE_ENABLED=true
31
31
  TRACE_DB_CONNECTION=d1 # optional — omit to inherit DB_CONNECTION
32
+ TRACE_QUERY_CONNECTION=main # optional — app DB to observe for SQL traces
32
33
  TRACE_PRUNE_HOURS=24 # how long entries are kept (default: 24)
33
34
  TRACE_SLOW_QUERY_MS=100 # slow-query threshold in ms (default: 100)
34
35
  TRACE_LOG_LEVEL=info # minimum log level captured (default: info)
@@ -77,6 +78,7 @@ import type { TraceConfigOverrides } from '@zintrust/trace';
77
78
  export default {
78
79
  enabled: Env.getBool('TRACE_ENABLED', false),
79
80
  connection: Env.get('TRACE_DB_CONNECTION', '') || undefined,
81
+ observeConnection: Env.get('TRACE_QUERY_CONNECTION', '') || undefined,
80
82
  pruneAfterHours: Env.getInt('TRACE_PRUNE_HOURS', 24),
81
83
  slowQueryThreshold: Env.getInt('TRACE_SLOW_QUERY_MS', 100),
82
84
  logMinLevel: Env.get('TRACE_LOG_LEVEL', 'info') as TraceConfigOverrides['logMinLevel'],
@@ -102,6 +104,10 @@ export default {
102
104
 
103
105
  All include/exclude matching is contains-based, so a term like `report` matches `/reports/daily`, `monthly-report`, or any other trace content containing that fragment.
104
106
 
107
+ When trace storage lives on a different connection than your application data, keep `TRACE_DB_CONNECTION` pointed at the trace tables and set `TRACE_QUERY_CONNECTION` to the app connection you want SQL traces to observe. If you omit `TRACE_QUERY_CONNECTION`, trace automatically falls back to the main `DB_CONNECTION` whenever the storage connection is different.
108
+
109
+ When ZinTrust proxy runtimes also have trace enabled, proxy-executed SQL and SMTP operations are traced at the proxy boundary too. That means MySQL, PostgreSQL, SQL Server, and D1 proxy requests can still record the final SQL plus bound values, and the SMTP proxy can persist rendered mail text and HTML, even when the consumer application is talking to the proxy rather than executing the transport locally.
110
+
105
111
  ### 3. Mount the dashboard
106
112
 
107
113
  Register the dashboard explicitly in your route file when you want the UI. Restrict access with middleware — the trace does **not** apply auth automatically.
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@zintrust/trace",
3
- "version": "0.4.82",
4
- "buildDate": "2026-04-08T15:23:56.210Z",
3
+ "version": "0.4.83",
4
+ "buildDate": "2026-04-08T17:13:19.081Z",
5
5
  "buildEnvironment": {
6
6
  "node": "v22.22.1",
7
7
  "platform": "darwin",
8
8
  "arch": "arm64"
9
9
  },
10
10
  "git": {
11
- "commit": "54ac1e60",
11
+ "commit": "3c792064",
12
12
  "branch": "release"
13
13
  },
14
14
  "package": {
@@ -23,7 +23,7 @@
23
23
  "files": {
24
24
  "build-manifest.json": {
25
25
  "size": 14439,
26
- "sha256": "b94e2cbd290c3992dabf8673fba6a501186ebe5386ee6bd2686a42f750881d00"
26
+ "sha256": "dab00c1ade5c0b5fe2b10b5eeeae31dfd9a017f198794e18e7c2b69cfb5560d8"
27
27
  },
28
28
  "cli-register.d.ts": {
29
29
  "size": 255,
@@ -38,8 +38,8 @@
38
38
  "sha256": "b034cbef0c71fb868071363624ef7a9f8d7acc20f8be8c895dd5db5a75e81f37"
39
39
  },
40
40
  "config.js": {
41
- "size": 5927,
42
- "sha256": "24f446c25ca4aae4ebc48ab54824c46e2042f1afbf41dd88b769fde4dced5886"
41
+ "size": 5961,
42
+ "sha256": "1402cf8ad7c850da99e429c8c937385b7b5cf401fa3714d972d4e6bd94f1663e"
43
43
  },
44
44
  "context.d.ts": {
45
45
  "size": 596,
@@ -79,7 +79,7 @@
79
79
  },
80
80
  "index.js": {
81
81
  "size": 3255,
82
- "sha256": "ab97f252e49aa760657c10b96ea3fc0857d626c128d70cbb423cf6b72d6a67fb"
82
+ "sha256": "a293d38a9e56a2ab802f99143eb1eaebb18f854e610f29bc95878746ffe26326"
83
83
  },
84
84
  "migrations/20260331000001_create_zin_trace_entries_table.d.ts": {
85
85
  "size": 304,
@@ -134,8 +134,8 @@
134
134
  "sha256": "71d366165dd36f1675aa253a76262b226fb6c62e5ab632746b8aea61c0c625fc"
135
135
  },
136
136
  "register.js": {
137
- "size": 11496,
138
- "sha256": "46a85cc9e9fd14a48c66c0cdf348a84608ffc628e731c0d2cc129fc38d1fa9b6"
137
+ "size": 12522,
138
+ "sha256": "66bccec7fac4d669702fa18e439788325d36ce17f228639005ffcde2cd968d70"
139
139
  },
140
140
  "storage/DebuggerStorage.d.ts": {
141
141
  "size": 517,
@@ -186,8 +186,8 @@
186
186
  "sha256": "d916e8e3abb1b1087f6b184851b0e6265e53380d7857b008e745d566aad15d44"
187
187
  },
188
188
  "types.d.ts": {
189
- "size": 8416,
190
- "sha256": "03c17d7f3759890062d36089311d3c464ff833d90c1048dd8c35a07a33388862"
189
+ "size": 8448,
190
+ "sha256": "de994120c04696e08afb428cff27a99fb2918f5c4277b8190b8fb3d36d139f0d"
191
191
  },
192
192
  "types.js": {
193
193
  "size": 696,
@@ -378,12 +378,12 @@
378
378
  "sha256": "b0ecc1df6a49dc8c5ffeb8dff0f1e3594ba016c4de3424dc8b5d7832e2f4cd11"
379
379
  },
380
380
  "watchers/QueryWatcher.d.ts": {
381
- "size": 97,
382
- "sha256": "6832a282b1658398264ede770d41c6aa86cb13625a3a87dac27fbaf7d2f7be6a"
381
+ "size": 240,
382
+ "sha256": "5d5046c65e5b683369c7709f1acd09b60aec3e7f44748fd1baeb35498836465b"
383
383
  },
384
384
  "watchers/QueryWatcher.js": {
385
- "size": 2899,
386
- "sha256": "a5bb991149846b67edf9658ce448172ec0b6f2f1bfb1cab25d9b2831a5e19a64"
385
+ "size": 2935,
386
+ "sha256": "577c6fec0282d2290db5c4b6c606b9b6ecdd64209af2b09f3205a15bf656bbef"
387
387
  },
388
388
  "watchers/RedisWatcher.d.ts": {
389
389
  "size": 294,
package/dist/config.js CHANGED
@@ -76,6 +76,7 @@ const mergeWatchers = (base, override) => {
76
76
  const DEFAULTS = Object.freeze({
77
77
  enabled: false,
78
78
  connection: undefined,
79
+ observeConnection: undefined,
79
80
  pruneAfterHours: 24,
80
81
  ignoreRoutes: ['/trace', '/health', '/ping'],
81
82
  slowQueryThreshold: 100,
package/dist/register.js CHANGED
@@ -64,6 +64,17 @@ const resolveTraceConnectionName = (env, configuredConnection) => {
64
64
  }
65
65
  return resolveDefaultConnection();
66
66
  };
67
+ const resolveObservedConnectionName = (env, configuredObservedConnection, storageConnectionName) => {
68
+ if (typeof configuredObservedConnection === 'string' &&
69
+ configuredObservedConnection.trim() !== '') {
70
+ return resolveTraceConnectionName(env, configuredObservedConnection);
71
+ }
72
+ const defaultConnectionName = resolveTraceConnectionName(env, undefined);
73
+ if (storageConnectionName !== defaultConnectionName) {
74
+ return defaultConnectionName;
75
+ }
76
+ return storageConnectionName;
77
+ };
67
78
  const isObjectValue = (value) => {
68
79
  return typeof value === 'object' && value !== null && !Array.isArray(value);
69
80
  };
@@ -146,6 +157,7 @@ if (!traceAlreadyInitialized && Env) {
146
157
  const enabled = startupOverrides?.enabled === true || Env.getBool('TRACE_ENABLED', false);
147
158
  if (enabled) {
148
159
  const connectionRaw = Env.get('TRACE_DB_CONNECTION', '').trim();
160
+ const observeConnectionRaw = Env.get('TRACE_QUERY_CONNECTION', '').trim();
149
161
  const pruneAfterHoursRaw = Env.get('TRACE_PRUNE_HOURS', '').trim();
150
162
  const slowQueryThresholdRaw = Env.get('TRACE_SLOW_QUERY_MS', '').trim();
151
163
  const logMinLevelRaw = Env.get('TRACE_LOG_LEVEL', '').trim();
@@ -156,6 +168,7 @@ if (!traceAlreadyInitialized && Env) {
156
168
  const redactionBody = parseEnvList(Env.get('TRACE_REDACT_BODY', ''));
157
169
  const redactionQuery = parseEnvList(Env.get('TRACE_REDACT_QUERY', ''));
158
170
  const connection = connectionRaw === '' ? startupOverrides?.connection : connectionRaw;
171
+ const observeConnection = observeConnectionRaw === '' ? startupOverrides?.observeConnection : observeConnectionRaw;
159
172
  const pruneAfterHours = pruneAfterHoursRaw === ''
160
173
  ? startupOverrides?.pruneAfterHours
161
174
  : Number.parseInt(pruneAfterHoursRaw, 10);
@@ -176,6 +189,7 @@ if (!traceAlreadyInitialized && Env) {
176
189
  ...startupOverrides,
177
190
  enabled,
178
191
  connection,
192
+ observeConnection,
179
193
  ...(typeof pruneAfterHours === 'number' && Number.isFinite(pruneAfterHours)
180
194
  ? { pruneAfterHours }
181
195
  : {}),
@@ -188,9 +202,11 @@ if (!traceAlreadyInitialized && Env) {
188
202
  ...(redaction === undefined ? {} : { redaction }),
189
203
  });
190
204
  const resolvedConnectionName = resolveTraceConnectionName(Env, config.connection);
191
- const db = core.useDatabase?.(undefined, resolvedConnectionName);
192
- if (db) {
193
- const storage = TraceWriteDiagnostics.wrapStorage(TraceContentRedaction.wrapStorage(TraceEntryFiltering.wrapStorage(TraceStorage.resolveStorage(db), config), config.redaction), {
205
+ const resolvedObservedConnectionName = resolveObservedConnectionName(Env, config.observeConnection, resolvedConnectionName);
206
+ const storageDb = core.useDatabase?.(undefined, resolvedConnectionName);
207
+ const observedDb = core.useDatabase?.(undefined, resolvedObservedConnectionName);
208
+ if (storageDb && observedDb) {
209
+ const storage = TraceWriteDiagnostics.wrapStorage(TraceContentRedaction.wrapStorage(TraceEntryFiltering.wrapStorage(TraceStorage.resolveStorage(storageDb), config), config.redaction), {
194
210
  connectionName: resolvedConnectionName,
195
211
  logger: core.Logger,
196
212
  });
@@ -219,7 +235,7 @@ if (!traceAlreadyInitialized && Env) {
219
235
  import('./watchers/ViewWatcher.js'),
220
236
  import('./watchers/HttpClientWatcher.js'),
221
237
  ]);
222
- const watcherArgs = { storage, config, db };
238
+ const watcherArgs = { storage, config, db: observedDb };
223
239
  HttpWatcher.register({ ...watcherArgs, registerMiddleware: resolveRegisterMiddleware() });
224
240
  QueryWatcher.register(watcherArgs);
225
241
  LogWatcher.register(watcherArgs);
package/dist/types.d.ts CHANGED
@@ -303,6 +303,7 @@ export type WatcherToggles = {
303
303
  export interface ITraceConfig {
304
304
  enabled: boolean;
305
305
  connection?: string;
306
+ observeConnection?: string;
306
307
  pruneAfterHours: number;
307
308
  ignoreRoutes: string[];
308
309
  slowQueryThreshold: number;
@@ -1,2 +1,6 @@
1
1
  import type { ITraceWatcher } from '../types';
2
- export declare const QueryWatcher: ITraceWatcher;
2
+ declare const emit: (query: string, params: unknown[], duration: number, connection?: string) => void;
3
+ export declare const QueryWatcher: ITraceWatcher & {
4
+ emit: typeof emit;
5
+ };
6
+ export {};
@@ -6,6 +6,8 @@ import { TraceStorage } from '../storage/index.js';
6
6
  import { EntryType } from '../types.js';
7
7
  import { AuthTag } from '../utils/authTag.js';
8
8
  import { RequestFilter } from '../utils/requestFilter.js';
9
+ let _storage = null;
10
+ let _config = null;
9
11
  const bindingsInterpolated = (sql, params) => {
10
12
  // Inline params for display only — safe, not for re-execution.
11
13
  let i = 0;
@@ -22,54 +24,64 @@ const isTraceStorageQuery = (sql) => {
22
24
  const normalized = sql.toLowerCase();
23
25
  return normalized.includes('zin_trace_entries') || normalized.includes('zin_trace_monitoring');
24
26
  };
27
+ const emit = (query, params, duration, connection = 'default') => {
28
+ if (_storage === null || _config === null)
29
+ return;
30
+ if (RequestFilter.shouldIgnoreCurrentRequest(_config.ignoreRoutes))
31
+ return;
32
+ if (isTraceStorageQuery(query))
33
+ return;
34
+ const batchId = TraceContext.getBatchId();
35
+ const includeBindings = _config.captureQueryBindings !== false;
36
+ const sql = includeBindings ? bindingsInterpolated(query, params) : query;
37
+ const roundedDuration = Math.round(duration * 100) / 100;
38
+ const hash = TraceStorage.familyHash(query);
39
+ const slow = roundedDuration >= _config.slowQueryThreshold;
40
+ const content = {
41
+ connection,
42
+ sql,
43
+ statement: query,
44
+ ...(includeBindings ? { bindings: [...params] } : {}),
45
+ bindingsIncluded: includeBindings,
46
+ time: roundedDuration,
47
+ duration: roundedDuration,
48
+ slow,
49
+ hash,
50
+ hostname: TraceContext.getHostname(),
51
+ };
52
+ const tags = AuthTag.append([]);
53
+ if (slow)
54
+ tags.push('slow');
55
+ _storage
56
+ .writeEntry({
57
+ uuid: crypto.randomUUID(),
58
+ batchId,
59
+ familyHash: hash,
60
+ type: EntryType.QUERY,
61
+ content,
62
+ tags,
63
+ isLatest: true,
64
+ createdAt: TraceContext.now(),
65
+ })
66
+ .catch(() => undefined);
67
+ };
25
68
  export const QueryWatcher = Object.freeze({
69
+ emit,
26
70
  register({ storage, config, db: injectedDb }) {
27
71
  if (config.watchers.query === false)
28
72
  return () => undefined;
29
73
  if (!injectedDb)
30
74
  return () => undefined; // no db available
75
+ _storage = storage;
76
+ _config = config;
31
77
  const db = injectedDb;
32
78
  const handler = (query, params, duration) => {
33
- if (RequestFilter.shouldIgnoreCurrentRequest(config.ignoreRoutes))
34
- return;
35
- if (isTraceStorageQuery(query))
36
- return;
37
- const batchId = TraceContext.getBatchId();
38
- const includeBindings = config.captureQueryBindings !== false;
39
- const sql = includeBindings ? bindingsInterpolated(query, params) : query;
40
- const roundedDuration = Math.round(duration * 100) / 100;
41
- const hash = TraceStorage.familyHash(query);
42
- const slow = roundedDuration >= config.slowQueryThreshold;
43
- const content = {
44
- connection: 'default',
45
- sql,
46
- statement: query,
47
- ...(includeBindings ? { bindings: [...params] } : {}),
48
- bindingsIncluded: includeBindings,
49
- time: roundedDuration,
50
- duration: roundedDuration,
51
- slow,
52
- hash,
53
- hostname: TraceContext.getHostname(),
54
- };
55
- const tags = AuthTag.append([]);
56
- if (slow)
57
- tags.push('slow');
58
- storage
59
- .writeEntry({
60
- uuid: crypto.randomUUID(),
61
- batchId,
62
- familyHash: hash,
63
- type: EntryType.QUERY,
64
- content,
65
- tags,
66
- isLatest: true,
67
- createdAt: TraceContext.now(),
68
- })
69
- .catch(() => undefined);
79
+ emit(query, params, duration);
70
80
  };
71
81
  db.onAfterQuery?.(handler);
72
82
  return () => {
83
+ _storage = null;
84
+ _config = null;
73
85
  db.offAfterQuery?.(handler);
74
86
  };
75
87
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zintrust/trace",
3
- "version": "0.4.82",
3
+ "version": "0.4.83",
4
4
  "description": "Trace assistant for ZinTrust: logs requests, queries, exceptions, jobs, and more.",
5
5
  "private": false,
6
6
  "type": "module",
package/src/config.ts CHANGED
@@ -106,6 +106,7 @@ const mergeWatchers = (
106
106
  const DEFAULTS: ITraceConfig = Object.freeze({
107
107
  enabled: false,
108
108
  connection: undefined,
109
+ observeConnection: undefined,
109
110
  pruneAfterHours: 24,
110
111
  ignoreRoutes: ['/trace', '/health', '/ping'],
111
112
  slowQueryThreshold: 100,
package/src/register.ts CHANGED
@@ -113,6 +113,26 @@ const resolveTraceConnectionName = (
113
113
  return resolveDefaultConnection();
114
114
  };
115
115
 
116
+ const resolveObservedConnectionName = (
117
+ env: Pick<NonNullable<CoreApi['Env']>, 'get'> | undefined,
118
+ configuredObservedConnection: string | undefined,
119
+ storageConnectionName: string
120
+ ): string => {
121
+ if (
122
+ typeof configuredObservedConnection === 'string' &&
123
+ configuredObservedConnection.trim() !== ''
124
+ ) {
125
+ return resolveTraceConnectionName(env, configuredObservedConnection);
126
+ }
127
+
128
+ const defaultConnectionName = resolveTraceConnectionName(env, undefined);
129
+ if (storageConnectionName !== defaultConnectionName) {
130
+ return defaultConnectionName;
131
+ }
132
+
133
+ return storageConnectionName;
134
+ };
135
+
116
136
  const isObjectValue = (value: unknown): value is Record<string, unknown> => {
117
137
  return typeof value === 'object' && value !== null && !Array.isArray(value);
118
138
  };
@@ -206,6 +226,7 @@ if (!traceAlreadyInitialized && Env) {
206
226
 
207
227
  if (enabled) {
208
228
  const connectionRaw = Env.get('TRACE_DB_CONNECTION', '').trim();
229
+ const observeConnectionRaw = Env.get('TRACE_QUERY_CONNECTION', '').trim();
209
230
  const pruneAfterHoursRaw = Env.get('TRACE_PRUNE_HOURS', '').trim();
210
231
  const slowQueryThresholdRaw = Env.get('TRACE_SLOW_QUERY_MS', '').trim();
211
232
  const logMinLevelRaw = Env.get('TRACE_LOG_LEVEL', '').trim();
@@ -217,6 +238,8 @@ if (!traceAlreadyInitialized && Env) {
217
238
  const redactionQuery = parseEnvList(Env.get('TRACE_REDACT_QUERY', ''));
218
239
 
219
240
  const connection = connectionRaw === '' ? startupOverrides?.connection : connectionRaw;
241
+ const observeConnection =
242
+ observeConnectionRaw === '' ? startupOverrides?.observeConnection : observeConnectionRaw;
220
243
  const pruneAfterHours =
221
244
  pruneAfterHoursRaw === ''
222
245
  ? startupOverrides?.pruneAfterHours
@@ -247,6 +270,7 @@ if (!traceAlreadyInitialized && Env) {
247
270
  ...startupOverrides,
248
271
  enabled,
249
272
  connection,
273
+ observeConnection,
250
274
  ...(typeof pruneAfterHours === 'number' && Number.isFinite(pruneAfterHours)
251
275
  ? { pruneAfterHours }
252
276
  : {}),
@@ -260,12 +284,18 @@ if (!traceAlreadyInitialized && Env) {
260
284
  });
261
285
 
262
286
  const resolvedConnectionName = resolveTraceConnectionName(Env, config.connection);
263
- const db = core.useDatabase?.(undefined, resolvedConnectionName);
264
-
265
- if (db) {
287
+ const resolvedObservedConnectionName = resolveObservedConnectionName(
288
+ Env,
289
+ config.observeConnection,
290
+ resolvedConnectionName
291
+ );
292
+ const storageDb = core.useDatabase?.(undefined, resolvedConnectionName);
293
+ const observedDb = core.useDatabase?.(undefined, resolvedObservedConnectionName);
294
+
295
+ if (storageDb && observedDb) {
266
296
  const storage = TraceWriteDiagnostics.wrapStorage(
267
297
  TraceContentRedaction.wrapStorage(
268
- TraceEntryFiltering.wrapStorage(TraceStorage.resolveStorage(db), config),
298
+ TraceEntryFiltering.wrapStorage(TraceStorage.resolveStorage(storageDb), config),
269
299
  config.redaction
270
300
  ),
271
301
  {
@@ -327,7 +357,7 @@ if (!traceAlreadyInitialized && Env) {
327
357
  import('./watchers/HttpClientWatcher'),
328
358
  ]);
329
359
 
330
- const watcherArgs = { storage, config, db };
360
+ const watcherArgs = { storage, config, db: observedDb };
331
361
 
332
362
  HttpWatcher.register({ ...watcherArgs, registerMiddleware: resolveRegisterMiddleware() });
333
363
 
package/src/types.ts CHANGED
@@ -353,6 +353,7 @@ export type WatcherToggles = {
353
353
  export interface ITraceConfig {
354
354
  enabled: boolean;
355
355
  connection?: string;
356
+ observeConnection?: string;
356
357
  pruneAfterHours: number;
357
358
  ignoreRoutes: string[];
358
359
  slowQueryThreshold: number;
@@ -8,6 +8,9 @@ import { EntryType } from '../types';
8
8
  import { AuthTag } from '../utils/authTag';
9
9
  import { RequestFilter } from '../utils/requestFilter';
10
10
 
11
+ let _storage: ITraceWatcherConfig['storage'] | null = null;
12
+ let _config: ITraceWatcherConfig['config'] | null = null;
13
+
11
14
  const bindingsInterpolated = (sql: string, params: unknown[]): string => {
12
15
  // Inline params for display only — safe, not for re-execution.
13
16
  let i = 0;
@@ -24,52 +27,61 @@ const isTraceStorageQuery = (sql: string): boolean => {
24
27
  return normalized.includes('zin_trace_entries') || normalized.includes('zin_trace_monitoring');
25
28
  };
26
29
 
27
- export const QueryWatcher: ITraceWatcher = Object.freeze({
30
+ const emit = (query: string, params: unknown[], duration: number, connection = 'default'): void => {
31
+ if (_storage === null || _config === null) return;
32
+ if (RequestFilter.shouldIgnoreCurrentRequest(_config.ignoreRoutes)) return;
33
+ if (isTraceStorageQuery(query)) return;
34
+
35
+ const batchId = TraceContext.getBatchId();
36
+ const includeBindings = _config.captureQueryBindings !== false;
37
+ const sql = includeBindings ? bindingsInterpolated(query, params) : query;
38
+ const roundedDuration = Math.round(duration * 100) / 100;
39
+ const hash = TraceStorage.familyHash(query);
40
+ const slow = roundedDuration >= _config.slowQueryThreshold;
41
+
42
+ const content: QueryContent = {
43
+ connection,
44
+ sql,
45
+ statement: query,
46
+ ...(includeBindings ? { bindings: [...params] } : {}),
47
+ bindingsIncluded: includeBindings,
48
+ time: roundedDuration,
49
+ duration: roundedDuration,
50
+ slow,
51
+ hash,
52
+ hostname: TraceContext.getHostname(),
53
+ };
54
+
55
+ const tags = AuthTag.append([]);
56
+ if (slow) tags.push('slow');
57
+
58
+ _storage
59
+ .writeEntry({
60
+ uuid: crypto.randomUUID(),
61
+ batchId,
62
+ familyHash: hash,
63
+ type: EntryType.QUERY,
64
+ content,
65
+ tags,
66
+ isLatest: true,
67
+ createdAt: TraceContext.now(),
68
+ })
69
+ .catch(() => undefined);
70
+ };
71
+
72
+ export const QueryWatcher: ITraceWatcher & { emit: typeof emit } = Object.freeze({
73
+ emit,
74
+
28
75
  register({ storage, config, db: injectedDb }: ITraceWatcherConfig): () => void {
29
76
  if (config.watchers.query === false) return () => undefined;
30
77
  if (!injectedDb) return () => undefined; // no db available
31
78
 
79
+ _storage = storage;
80
+ _config = config;
32
81
  const db = injectedDb;
33
82
 
34
83
  const handler = (query: string, params: unknown[], duration: number): void => {
35
- if (RequestFilter.shouldIgnoreCurrentRequest(config.ignoreRoutes)) return;
36
- if (isTraceStorageQuery(query)) return;
37
-
38
- const batchId = TraceContext.getBatchId();
39
- const includeBindings = config.captureQueryBindings !== false;
40
- const sql = includeBindings ? bindingsInterpolated(query, params) : query;
41
- const roundedDuration = Math.round(duration * 100) / 100;
42
- const hash = TraceStorage.familyHash(query);
43
- const slow = roundedDuration >= config.slowQueryThreshold;
44
-
45
- const content: QueryContent = {
46
- connection: 'default',
47
- sql,
48
- statement: query,
49
- ...(includeBindings ? { bindings: [...params] } : {}),
50
- bindingsIncluded: includeBindings,
51
- time: roundedDuration,
52
- duration: roundedDuration,
53
- slow,
54
- hash,
55
- hostname: TraceContext.getHostname(),
56
- };
57
-
58
- const tags = AuthTag.append([]);
59
- if (slow) tags.push('slow');
60
-
61
- storage
62
- .writeEntry({
63
- uuid: crypto.randomUUID(),
64
- batchId,
65
- familyHash: hash,
66
- type: EntryType.QUERY,
67
- content,
68
- tags,
69
- isLatest: true,
70
- createdAt: TraceContext.now(),
71
- })
72
- .catch(() => undefined);
84
+ emit(query, params, duration);
73
85
  };
74
86
 
75
87
  (
@@ -79,6 +91,8 @@ export const QueryWatcher: ITraceWatcher = Object.freeze({
79
91
  ).onAfterQuery?.(handler);
80
92
 
81
93
  return () => {
94
+ _storage = null;
95
+ _config = null;
82
96
  (
83
97
  db as {
84
98
  offAfterQuery?: (h: (sql: string, params: unknown[], duration: number) => void) => void;