proteum 2.2.0 → 2.2.2-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.
@@ -16,6 +16,9 @@ import {
16
16
  type TTraceSqlQueryCallerOrigin,
17
17
  type TTraceSqlQueryKind,
18
18
  type TTraceSummaryValue,
19
+ type TRequestProfiling,
20
+ type TRequestProfilingApiCall,
21
+ type TRequestProfilingSqlQuery,
19
22
  type TRequestTrace,
20
23
  type TTraceMemorySnapshot,
21
24
  type TRequestTraceListItem,
@@ -24,6 +27,7 @@ import type { TProteumManifest } from '@common/dev/proteumManifest';
24
27
 
25
28
  export type Config = {
26
29
  enable: boolean;
30
+ profilerEnable: boolean;
27
31
  requestsLimit: number;
28
32
  eventsLimit: number;
29
33
  capture: TTraceCaptureMode;
@@ -32,6 +36,12 @@ export type Config = {
32
36
 
33
37
  type TTraceInspectable = object | PrimitiveValue | bigint | symbol | null | undefined | (() => void);
34
38
  type TTraceDetails = { [key: string]: TTraceInspectable };
39
+ type TSerializeJsonValueOptions = { redactSensitive: boolean };
40
+ type TActiveRequestRecord = {
41
+ profiling: TRequestProfiling;
42
+ trace?: TRequestTrace;
43
+ capture?: TTraceCaptureMode;
44
+ };
35
45
 
36
46
  const capturePriority: Record<TTraceCaptureMode, number> = { summary: 0, resolve: 1, deep: 2 };
37
47
  const sensitiveKeyPattern =
@@ -50,8 +60,13 @@ const isSensitiveKeyPath = (keyPath: string[]) => sensitiveKeyPattern.test(keyPa
50
60
  const summarizeString = (value: string) =>
51
61
  value.length <= maxStringLength ? value : `${value.slice(0, maxStringLength)}…`;
52
62
 
53
- const serializeJsonValue = (value: unknown, keyPath: string[], seen: WeakSet<object>): unknown => {
54
- if (isSensitiveKeyPath(keyPath)) return `[redacted: Sensitive key ${keyPath[keyPath.length - 1] || 'value'}]`;
63
+ const serializeJsonValue = (
64
+ value: unknown,
65
+ keyPath: string[],
66
+ seen: WeakSet<object>,
67
+ { redactSensitive }: TSerializeJsonValueOptions,
68
+ ): unknown => {
69
+ if (redactSensitive && isSensitiveKeyPath(keyPath)) return `[redacted: Sensitive key ${keyPath[keyPath.length - 1] || 'value'}]`;
55
70
  if (value === undefined || value === null) return value;
56
71
  if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') return value;
57
72
  if (typeof value === 'bigint') return `${value.toString()}n`;
@@ -61,11 +76,14 @@ const serializeJsonValue = (value: unknown, keyPath: string[], seen: WeakSet<obj
61
76
  if (value instanceof Date) return value.toISOString();
62
77
  if (value instanceof Error) return { name: value.name, message: value.message, stack: value.stack };
63
78
  if (Buffer.isBuffer(value)) return `[Buffer ${value.byteLength} bytes]`;
64
- if (value instanceof Map) return Array.from(value.entries()).map(([entryKey, entryValue], index) =>
65
- serializeJsonValue([entryKey, entryValue], [...keyPath, `[${index}]`], seen),
66
- );
79
+ if (value instanceof Map)
80
+ return Array.from(value.entries()).map(([entryKey, entryValue], index) =>
81
+ serializeJsonValue([entryKey, entryValue], [...keyPath, `[${index}]`], seen, { redactSensitive }),
82
+ );
67
83
  if (value instanceof Set) {
68
- return Array.from(value.values()).map((entryValue, index) => serializeJsonValue(entryValue, [...keyPath, `[${index}]`], seen));
84
+ return Array.from(value.values()).map((entryValue, index) =>
85
+ serializeJsonValue(entryValue, [...keyPath, `[${index}]`], seen, { redactSensitive }),
86
+ );
69
87
  }
70
88
 
71
89
  if (typeof value !== 'object') return String(value);
@@ -74,19 +92,22 @@ const serializeJsonValue = (value: unknown, keyPath: string[], seen: WeakSet<obj
74
92
  seen.add(value);
75
93
 
76
94
  if (Array.isArray(value)) {
77
- return value.map((item, index) => serializeJsonValue(item, [...keyPath, `[${index}]`], seen));
95
+ return value.map((item, index) => serializeJsonValue(item, [...keyPath, `[${index}]`], seen, { redactSensitive }));
78
96
  }
79
97
 
80
98
  const serialized: Record<string, unknown> = {};
81
99
  for (const [entryKey, entryValue] of Object.entries(value)) {
82
- const nextValue = serializeJsonValue(entryValue, [...keyPath, entryKey], seen);
100
+ const nextValue = serializeJsonValue(entryValue, [...keyPath, entryKey], seen, { redactSensitive });
83
101
  if (nextValue !== undefined) serialized[entryKey] = nextValue;
84
102
  }
85
103
 
86
104
  return serialized;
87
105
  };
88
106
 
89
- const serializeCaptureValue = (value: TTraceInspectable, key: string) => serializeJsonValue(value, [key], new WeakSet<object>());
107
+ const serializeCaptureValue = (value: TTraceInspectable, key: string) =>
108
+ serializeJsonValue(value, [key], new WeakSet<object>(), { redactSensitive: true });
109
+ const serializeRawCaptureValue = (value: TTraceInspectable, key: string) =>
110
+ serializeJsonValue(value, [key], new WeakSet<object>(), { redactSensitive: false });
90
111
 
91
112
  const summarizeError = (error: Error): TTraceSummaryValue => ({
92
113
  kind: 'error',
@@ -181,7 +202,7 @@ const snapshotMemory = (): TTraceMemorySnapshot => {
181
202
  };
182
203
 
183
204
  export default class Trace {
184
- private requests = new Map<string, TRequestTrace>();
205
+ private requests = new Map<string, TActiveRequestRecord>();
185
206
  private order: string[] = [];
186
207
  private armedCapture?: TTraceCaptureMode;
187
208
  private manifestCache?: {
@@ -202,10 +223,18 @@ export default class Trace {
202
223
  private config: Config,
203
224
  ) {}
204
225
 
205
- public isEnabled() {
226
+ public isDevTraceEnabled() {
206
227
  return __DEV__ && this.config.enable && this.container.Environment.profile === 'dev';
207
228
  }
208
229
 
230
+ public isProfilingEnabled() {
231
+ return this.config.profilerEnable;
232
+ }
233
+
234
+ public shouldInstrumentRequests() {
235
+ return this.isDevTraceEnabled() || this.isProfilingEnabled();
236
+ }
237
+
209
238
  private getContextChannel() {
210
239
  return context.getStore() as ChannelInfos | undefined;
211
240
  }
@@ -270,8 +299,8 @@ export default class Trace {
270
299
  return undefined;
271
300
  }
272
301
 
273
- private createSqlFingerprint(query: string) {
274
- const normalized = query
302
+ private normalizeSqlQuery(query: string) {
303
+ return query
275
304
  .replace(sqlCommentPattern, ' ')
276
305
  .replace(sqlLineCommentPattern, ' ')
277
306
  .replace(/'([^']|'')*'/g, '?')
@@ -280,12 +309,43 @@ export default class Trace {
280
309
  .replace(/\s+/g, ' ')
281
310
  .trim()
282
311
  .toUpperCase();
312
+ }
313
+
314
+ private createSqlFingerprint(query: string) {
315
+ const normalized = this.normalizeSqlQuery(query);
283
316
 
284
317
  if (!normalized) return undefined;
285
318
 
286
319
  return createHash('sha1').update(normalized).digest('hex').slice(0, 12);
287
320
  }
288
321
 
322
+ private createProfiling(input: {
323
+ enabled: boolean;
324
+ id: string;
325
+ method: string;
326
+ path: string;
327
+ url: string;
328
+ profilerOrigin?: string;
329
+ profilerParentRequestId?: string;
330
+ }): TRequestProfiling {
331
+ return {
332
+ enabled: input.enabled,
333
+ requestId: input.id,
334
+ method: input.method,
335
+ path: input.path,
336
+ url: input.url,
337
+ startedAt: nowIso(),
338
+ profilerOrigin: input.profilerOrigin,
339
+ profilerParentRequestId: input.profilerParentRequestId,
340
+ apiCalls: [],
341
+ sqlQueries: [],
342
+ };
343
+ }
344
+
345
+ private getRecord(requestId: string) {
346
+ return this.requests.get(requestId);
347
+ }
348
+
289
349
  public armNextRequest(capture: string) {
290
350
  if (!isTraceCaptureMode(capture)) {
291
351
  throw new Error(`Unsupported trace capture mode "${capture}". Expected one of: ${traceCaptureModes.join(', ')}.`);
@@ -306,46 +366,72 @@ export default class Trace {
306
366
  profilerOrigin?: string;
307
367
  profilerParentRequestId?: string;
308
368
  }) {
309
- if (!this.isEnabled()) return;
310
-
311
- const capture = this.armedCapture ?? this.config.capture;
312
- this.armedCapture = undefined;
313
-
314
- const trace: TRequestTrace = {
369
+ const profilingEnabled = this.shouldInstrumentRequests();
370
+ const profiling = this.createProfiling({
371
+ enabled: profilingEnabled,
315
372
  id: input.id,
316
373
  method: input.method,
317
374
  path: input.path,
318
375
  url: input.url,
319
- capture,
320
- profilerSessionId: input.profilerSessionId,
321
376
  profilerOrigin: input.profilerOrigin,
322
377
  profilerParentRequestId: input.profilerParentRequestId,
323
- startedAt: nowIso(),
324
- droppedEvents: 0,
325
- requestDataJson: serializeCaptureValue(input.data, 'requestData'),
326
- calls: [],
327
- sqlQueries: [],
328
- events: [],
329
- };
378
+ });
379
+ if (!profilingEnabled) return profiling;
380
+
381
+ const traceEnabled = this.isDevTraceEnabled();
382
+ const capture = traceEnabled ? this.armedCapture ?? this.config.capture : undefined;
383
+ this.armedCapture = undefined;
384
+
385
+ const trace =
386
+ traceEnabled
387
+ ? ({
388
+ id: input.id,
389
+ method: input.method,
390
+ path: input.path,
391
+ url: input.url,
392
+ capture: capture as TTraceCaptureMode,
393
+ profilerSessionId: input.profilerSessionId,
394
+ profilerOrigin: input.profilerOrigin,
395
+ profilerParentRequestId: input.profilerParentRequestId,
396
+ startedAt: profiling.startedAt,
397
+ droppedEvents: 0,
398
+ requestDataJson: serializeCaptureValue(input.data, 'requestData'),
399
+ calls: [],
400
+ sqlQueries: [],
401
+ events: [],
402
+ } satisfies TRequestTrace)
403
+ : undefined;
404
+
405
+ this.requests.set(input.id, {
406
+ profiling,
407
+ trace,
408
+ capture,
409
+ });
410
+ this.activeMeasurements.set(input.id, { cpu: process.cpuUsage(), memory: snapshotMemory() });
330
411
 
331
- this.requests.set(trace.id, trace);
332
- this.activeMeasurements.set(trace.id, { cpu: process.cpuUsage(), memory: snapshotMemory() });
333
- this.order.push(trace.id);
334
- this.trimRequestBuffer();
412
+ if (trace) {
413
+ this.order.push(input.id);
414
+ this.trimRequestBuffer();
415
+ this.record(input.id, 'request.start', { method: input.method, path: input.path, url: input.url, headers: input.headers, data: input.data });
416
+ }
335
417
 
336
- this.record(trace.id, 'request.start', { method: input.method, path: input.path, url: input.url, headers: input.headers, data: input.data });
418
+ return profiling;
337
419
  }
338
420
 
339
421
  public setRequestUser(requestId: string, user?: string) {
340
- const trace = this.requests.get(requestId);
341
- if (!trace) return;
422
+ const record = this.getRecord(requestId);
423
+ if (!record) return;
424
+
425
+ record.profiling.user = user;
426
+ if (!record.trace) return;
342
427
 
428
+ const trace = record.trace;
343
429
  trace.user = user;
344
430
  if (user) this.record(requestId, 'request.user', { user });
345
431
  }
346
432
 
347
433
  public getCapture(requestId: string) {
348
- return this.requests.get(requestId)?.capture;
434
+ return this.getRecord(requestId)?.capture;
349
435
  }
350
436
 
351
437
  public shouldCapture(requestId: string, minimumCapture: TTraceCaptureMode) {
@@ -356,7 +442,8 @@ export default class Trace {
356
442
  }
357
443
 
358
444
  public record(requestId: string, type: TTraceEventType, details: TTraceDetails, minimumCapture: TTraceCaptureMode = 'summary') {
359
- const trace = this.requests.get(requestId);
445
+ const record = this.getRecord(requestId);
446
+ const trace = record?.trace;
360
447
  if (!trace || !this.shouldCapture(requestId, minimumCapture)) return;
361
448
 
362
449
  if (trace.events.length >= this.config.eventsLimit) {
@@ -376,28 +463,41 @@ export default class Trace {
376
463
  }
377
464
 
378
465
  public finishRequest(requestId: string, output: { statusCode: number; user?: string; errorMessage?: string }) {
379
- const trace = this.requests.get(requestId);
380
- if (!trace) return;
466
+ const record = this.getRecord(requestId);
467
+ if (!record) return;
468
+
469
+ const { profiling, trace } = record;
470
+ if (output.user) profiling.user = output.user;
471
+ profiling.statusCode = output.statusCode;
472
+ profiling.errorMessage = output.errorMessage;
381
473
 
382
- if (output.user) trace.user = output.user;
383
- trace.statusCode = output.statusCode;
384
- trace.errorMessage = output.errorMessage;
385
474
  const measurement = this.activeMeasurements.get(requestId);
386
475
  if (measurement) {
387
- const cpu = process.cpuUsage(measurement.cpu);
388
- trace.performance = {
389
- cpu: {
390
- systemMicros: cpu.system,
391
- userMicros: cpu.user,
392
- },
393
- memory: {
394
- after: snapshotMemory(),
395
- before: measurement.memory,
396
- },
397
- };
398
476
  this.activeMeasurements.delete(requestId);
477
+
478
+ if (trace) {
479
+ const cpu = process.cpuUsage(measurement.cpu);
480
+ trace.performance = {
481
+ cpu: {
482
+ systemMicros: cpu.system,
483
+ userMicros: cpu.user,
484
+ },
485
+ memory: {
486
+ after: snapshotMemory(),
487
+ before: measurement.memory,
488
+ },
489
+ };
490
+ }
399
491
  }
400
492
 
493
+ profiling.finishedAt = nowIso();
494
+ profiling.durationMs = Math.max(0, Date.parse(profiling.finishedAt) - Date.parse(profiling.startedAt));
495
+
496
+ if (!trace) return;
497
+
498
+ if (output.user) trace.user = output.user;
499
+ trace.statusCode = output.statusCode;
500
+ trace.errorMessage = output.errorMessage;
401
501
  this.record(
402
502
  requestId,
403
503
  'request.finish',
@@ -405,8 +505,8 @@ export default class Trace {
405
505
  'summary',
406
506
  );
407
507
 
408
- trace.finishedAt = nowIso();
409
- trace.durationMs = Math.max(0, Date.parse(trace.finishedAt) - Date.parse(trace.startedAt));
508
+ trace.finishedAt = profiling.finishedAt;
509
+ trace.durationMs = profiling.durationMs;
410
510
 
411
511
  if (this.config.persistOnError && trace.statusCode >= 500) {
412
512
  trace.persistedFilepath = this.exportRequest(requestId);
@@ -433,13 +533,37 @@ export default class Trace {
433
533
  requestData?: TTraceInspectable;
434
534
  },
435
535
  ) {
436
- const trace = this.requests.get(requestId);
437
- if (!trace) return undefined;
536
+ const record = this.getRecord(requestId);
537
+ if (!record) return undefined;
438
538
  const channel = this.getContextChannel();
439
539
  const inferredServiceLabel = input.serviceLabel || channel?.serviceLabel || this.inferServiceLabelFromStack(new Error().stack);
540
+ const callIndex = record.profiling.apiCalls.length;
541
+ const startedAt = nowIso();
542
+ const callId = `${requestId}:call:${callIndex}`;
543
+
544
+ const profilingCall: TRequestProfilingApiCall = {
545
+ id: callId,
546
+ origin: input.origin,
547
+ label: input.label,
548
+ method: input.method || '',
549
+ path: input.path || '',
550
+ fetcherId: input.fetcherId,
551
+ connectedProjectNamespace: input.connectedProjectNamespace,
552
+ connectedControllerAccessor: input.connectedControllerAccessor,
553
+ ownerLabel: input.ownerLabel || channel?.ownerLabel,
554
+ ownerFilepath: input.ownerFilepath || channel?.ownerFilepath,
555
+ serviceLabel: inferredServiceLabel,
556
+ startedAt,
557
+ requestBodyJson: input.requestData !== undefined ? serializeRawCaptureValue(input.requestData, 'requestData') : undefined,
558
+ };
559
+
560
+ record.profiling.apiCalls.push(profilingCall);
561
+
562
+ const trace = record.trace;
563
+ if (!trace) return callId;
440
564
 
441
565
  const call: TTraceCall = {
442
- id: `${requestId}:call:${trace.calls.length}`,
566
+ id: callId,
443
567
  parentId: input.parentId,
444
568
  origin: input.origin,
445
569
  label: input.label,
@@ -453,7 +577,7 @@ export default class Trace {
453
577
  serviceLabel: inferredServiceLabel,
454
578
  cacheKey: input.cacheKey || channel?.cacheKey,
455
579
  cachePhase: input.cachePhase || channel?.cachePhase,
456
- startedAt: nowIso(),
580
+ startedAt,
457
581
  requestDataKeys: input.requestDataKeys || [],
458
582
  requestData: input.requestData !== undefined ? summarizeCaptureValue(input.requestData, trace.capture, 'requestData') : undefined,
459
583
  requestDataJson: input.requestData !== undefined ? serializeCaptureValue(input.requestData, 'requestData') : undefined,
@@ -461,7 +585,7 @@ export default class Trace {
461
585
  };
462
586
 
463
587
  trace.calls.push(call);
464
- return call.id;
588
+ return callId;
465
589
  }
466
590
 
467
591
  public finishCall(
@@ -476,12 +600,24 @@ export default class Trace {
476
600
  ) {
477
601
  if (!callId) return;
478
602
 
479
- const trace = this.requests.get(requestId);
480
- const call = trace?.calls.find((candidate) => candidate.id === callId);
481
- if (!trace || !call) return;
603
+ const record = this.getRecord(requestId);
604
+ const profilingCall = record?.profiling.apiCalls.find((candidate) => candidate.id === callId);
605
+ if (!record || !profilingCall) return;
606
+
607
+ profilingCall.finishedAt = nowIso();
608
+ profilingCall.durationMs = Math.max(0, Date.parse(profilingCall.finishedAt) - Date.parse(profilingCall.startedAt));
609
+ profilingCall.statusCode = output.statusCode;
610
+ profilingCall.errorMessage = output.errorMessage;
611
+ profilingCall.responseBodyJson = output.result !== undefined ? serializeRawCaptureValue(output.result, 'result') : undefined;
482
612
 
483
- call.finishedAt = nowIso();
484
- call.durationMs = Math.max(0, Date.parse(call.finishedAt) - Date.parse(call.startedAt));
613
+ const trace = record.trace;
614
+ if (!trace) return;
615
+
616
+ const call = trace.calls.find((candidate) => candidate.id === callId);
617
+ if (!call) return;
618
+
619
+ call.finishedAt = profilingCall.finishedAt;
620
+ call.durationMs = profilingCall.durationMs;
485
621
  call.statusCode = output.statusCode;
486
622
  call.errorMessage = output.errorMessage;
487
623
  call.resultKeys = output.resultKeys || [];
@@ -490,7 +626,7 @@ export default class Trace {
490
626
  }
491
627
 
492
628
  public setRequestResult(requestId: string, result: TTraceInspectable) {
493
- const trace = this.requests.get(requestId);
629
+ const trace = this.getRecord(requestId)?.trace;
494
630
  if (!trace) return;
495
631
 
496
632
  trace.resultJson = serializeCaptureValue(result, 'result');
@@ -520,8 +656,8 @@ export default class Trace {
520
656
  target?: string;
521
657
  },
522
658
  ) {
523
- const trace = this.requests.get(requestId);
524
- if (!trace) return;
659
+ const record = this.getRecord(requestId);
660
+ if (!record) return;
525
661
  const channel = this.getContextChannel();
526
662
 
527
663
  const durationMs = Math.max(0, input.durationMs || 0);
@@ -530,10 +666,41 @@ export default class Trace {
530
666
  const startedAt =
531
667
  Number.isFinite(finishedAtMs) && durationMs > 0 ? new Date(finishedAtMs - durationMs).toISOString() : finishedAt;
532
668
  const fingerprint = this.createSqlFingerprint(input.query);
669
+ const normalizedQuery = this.normalizeSqlQuery(input.query) || undefined;
533
670
  const inferredServiceLabel = input.serviceLabel || channel?.serviceLabel || this.inferServiceLabelFromStack(new Error().stack);
534
671
 
672
+ const profilingQuery: TRequestProfilingSqlQuery = {
673
+ id: `${requestId}:sql:${record.profiling.sqlQueries.length}`,
674
+ callerCallId: input.callerCallId,
675
+ callerFetcherId: input.callerFetcherId,
676
+ callerLabel: input.callerLabel,
677
+ callerMethod: input.callerMethod || '',
678
+ callerOrigin: input.callerOrigin || 'request',
679
+ callerPath: input.callerPath || '',
680
+ durationMs,
681
+ finishedAt,
682
+ kind: input.kind,
683
+ model: input.model,
684
+ operation: input.operation,
685
+ fingerprint,
686
+ normalizedQuery,
687
+ ownerLabel: input.ownerLabel || channel?.ownerLabel,
688
+ ownerFilepath: input.ownerFilepath || channel?.ownerFilepath,
689
+ serviceLabel: inferredServiceLabel,
690
+ connectedNamespace: input.connectedNamespace || channel?.connectedNamespace,
691
+ paramsJson: input.paramsJson,
692
+ paramsText: input.paramsText,
693
+ query: input.query.trim(),
694
+ startedAt,
695
+ target: input.target,
696
+ };
697
+
698
+ record.profiling.sqlQueries.push(profilingQuery);
699
+ const trace = record.trace;
700
+ if (!trace) return;
701
+
535
702
  const sqlQuery: TTraceSqlQuery = {
536
- id: `${requestId}:sql:${trace.sqlQueries.length}`,
703
+ id: profilingQuery.id,
537
704
  callerCallId: input.callerCallId,
538
705
  callerFetcherId: input.callerFetcherId,
539
706
  callerLabel: input.callerLabel,
@@ -546,6 +713,7 @@ export default class Trace {
546
713
  model: input.model,
547
714
  operation: input.operation,
548
715
  fingerprint,
716
+ normalizedQuery,
549
717
  ownerLabel: input.ownerLabel || channel?.ownerLabel,
550
718
  ownerFilepath: input.ownerFilepath || channel?.ownerFilepath,
551
719
  serviceLabel: inferredServiceLabel,
@@ -564,7 +732,7 @@ export default class Trace {
564
732
  return [...this.order]
565
733
  .reverse()
566
734
  .slice(0, limit)
567
- .map((requestId) => this.requests.get(requestId))
735
+ .map((requestId) => this.getRecord(requestId)?.trace)
568
736
  .filter((trace): trace is TRequestTrace => trace !== undefined)
569
737
  .map((trace) => ({
570
738
  id: trace.id,
@@ -593,21 +761,25 @@ export default class Trace {
593
761
  return [...this.order]
594
762
  .reverse()
595
763
  .slice(0, Math.max(1, limit))
596
- .map((requestId) => this.requests.get(requestId))
764
+ .map((requestId) => this.getRecord(requestId)?.trace)
597
765
  .filter((trace): trace is TRequestTrace => trace !== undefined);
598
766
  }
599
767
 
600
768
  public getLatestRequest() {
601
769
  const latestRequestId = this.order[this.order.length - 1];
602
- return latestRequestId ? this.requests.get(latestRequestId) : undefined;
770
+ return latestRequestId ? this.getRecord(latestRequestId)?.trace : undefined;
603
771
  }
604
772
 
605
773
  public getRequest(requestId: string) {
606
- return this.requests.get(requestId);
774
+ return this.getRecord(requestId)?.trace;
775
+ }
776
+
777
+ public getProfiling(requestId: string) {
778
+ return this.getRecord(requestId)?.profiling;
607
779
  }
608
780
 
609
781
  public exportRequest(requestId: string, filepath?: string) {
610
- const trace = this.requests.get(requestId);
782
+ const trace = this.getRecord(requestId)?.trace;
611
783
  if (!trace) throw new Error(`Trace ${requestId} was not found.`);
612
784
 
613
785
  const outputFilepath =
@@ -622,6 +794,15 @@ export default class Trace {
622
794
  return outputFilepath;
623
795
  }
624
796
 
797
+ public releaseRequest(requestId: string) {
798
+ const record = this.getRecord(requestId);
799
+ if (!record) return;
800
+ if (record.trace) return;
801
+
802
+ this.requests.delete(requestId);
803
+ this.activeMeasurements.delete(requestId);
804
+ }
805
+
625
806
  private trimRequestBuffer() {
626
807
  const overflow = this.order.length - this.config.requestsLimit;
627
808
  if (overflow <= 0) return;
@@ -46,6 +46,12 @@ type TPrismaExtensionOperation = {
46
46
  query: (args: unknown) => Promise<unknown>;
47
47
  };
48
48
 
49
+ declare global {
50
+ interface BigInt {
51
+ toJSON: () => number | string;
52
+ }
53
+ }
54
+
49
55
  /*----------------------------------
50
56
  - HELPERS
51
57
  ----------------------------------*/
@@ -173,21 +179,18 @@ export default class ModelsManager extends Service<Config, Hooks, Application, A
173
179
  'DATABASE_URL is required before starting the Models service. Prisma 7 no longer auto-loads runtime env files.',
174
180
  );
175
181
 
176
- const shouldTraceQueries = this.app.container.Trace.isEnabled();
177
- const prismaClient = shouldTraceQueries
178
- ? new PrismaClient({
179
- adapter: createMariaDbAdapter(databaseUrl),
180
- log: [{ emit: 'event', level: 'query' }],
181
- })
182
- : new PrismaClient({
183
- adapter: createMariaDbAdapter(databaseUrl),
184
- });
185
-
186
- if (!shouldTraceQueries) {
187
- this.client = prismaClient;
182
+ if (!this.app.container.Trace.shouldInstrumentRequests()) {
183
+ this.client = new PrismaClient({
184
+ adapter: createMariaDbAdapter(databaseUrl),
185
+ });
188
186
  return;
189
187
  }
190
188
 
189
+ const prismaClient = new PrismaClient({
190
+ adapter: createMariaDbAdapter(databaseUrl),
191
+ log: [{ emit: 'event', level: 'query' }],
192
+ });
193
+
191
194
  prismaClient.$on('query', (event: TPrismaQueryEvent) => this.traceQuery(event));
192
195
 
193
196
  this.client = prismaClient.$extends(
@@ -510,7 +510,7 @@ export default class HttpServer<TRouter extends TServerRouter = TServerRouter> {
510
510
  private registerDevTraceRoutes(routes: express.Express) {
511
511
  if (!__DEV__ || this.app.env.profile !== 'dev') return;
512
512
 
513
- if (this.app.container.Trace.isEnabled()) {
513
+ if (this.app.container.Trace.isDevTraceEnabled()) {
514
514
  routes.get('/__proteum/trace/requests', (req, res) => {
515
515
  const rawLimit = Array.isArray(req.query.limit) ? req.query.limit[0] : req.query.limit;
516
516
  const parsedLimit = typeof rawLimit === 'string' ? Number.parseInt(rawLimit, 10) : NaN;