@reproapp/node-sdk 0.0.39 → 0.0.42
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 +4 -1
- package/dist/index.d.ts +13 -0
- package/dist/index.js +592 -2
- package/dist/ingest/client.d.ts +1 -1
- package/dist/ingest/client.js +156 -8
- package/dist/ingest/mapper.js +2 -0
- package/dist/ingest/types.d.ts +1 -0
- package/package.json +10 -4
- package/src/index.ts +651 -1
- package/src/ingest/client.ts +187 -10
- package/src/ingest/mapper.ts +1 -0
- package/src/ingest/types.ts +1 -0
- package/test/backend-live-mode.test.js +20 -2
- package/test/client-performance-regression.test.js +5 -0
- package/test/ingest-batching.test.js +67 -0
- package/test/postgresql-integration.test.js +302 -0
- package/test/postgresql-integrations.testcontainers.test.js +261 -0
- package/test/request-capture-background.test.js +6 -1
- package/test/request-manifest-priority.test.js +6 -0
package/src/index.ts
CHANGED
|
@@ -1223,6 +1223,35 @@ function buildRequestCaptureManifestPayload(
|
|
|
1223
1223
|
};
|
|
1224
1224
|
}
|
|
1225
1225
|
|
|
1226
|
+
function emitRequestCaptureStart(
|
|
1227
|
+
cfg: IngestClientConfig,
|
|
1228
|
+
sessionId: string | null | undefined,
|
|
1229
|
+
actionId: string | null | undefined,
|
|
1230
|
+
scope: RequestCaptureScopeState,
|
|
1231
|
+
openedAtMs?: number | null,
|
|
1232
|
+
): void {
|
|
1233
|
+
if (!sessionId) {
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
const openedAt =
|
|
1237
|
+
typeof openedAtMs === 'number' && Number.isFinite(openedAtMs)
|
|
1238
|
+
? openedAtMs
|
|
1239
|
+
: alignedNow();
|
|
1240
|
+
post(cfg, sessionId, {
|
|
1241
|
+
entries: [{
|
|
1242
|
+
actionId: actionId ?? null,
|
|
1243
|
+
requestRid: scope.requestRid,
|
|
1244
|
+
requestCaptureStart: {
|
|
1245
|
+
scopeId: `req:${scope.requestRid}`,
|
|
1246
|
+
requestRid: scope.requestRid,
|
|
1247
|
+
transport: scope.transport,
|
|
1248
|
+
openedAt,
|
|
1249
|
+
},
|
|
1250
|
+
t: openedAt,
|
|
1251
|
+
}],
|
|
1252
|
+
}, { priority: 'critical' });
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1226
1255
|
const als = new AsyncLocalStorage<Ctx>();
|
|
1227
1256
|
const getCtx = () => als.getStore() || {};
|
|
1228
1257
|
|
|
@@ -1364,7 +1393,7 @@ const SDK_BACKGROUND_BUSY_IDLE_DELAY_MS = (() => {
|
|
|
1364
1393
|
const SDK_BACKGROUND_MAX_DEFER_MS = (() => {
|
|
1365
1394
|
const raw = String(process.env.REPRO_SDK_BACKGROUND_MAX_DEFER_MS ?? '').trim().toLowerCase();
|
|
1366
1395
|
if (!raw) {
|
|
1367
|
-
return
|
|
1396
|
+
return 30000;
|
|
1368
1397
|
}
|
|
1369
1398
|
if (raw === 'off' || raw === 'never' || raw === 'infinite' || raw === 'infinity' || raw === '-1') {
|
|
1370
1399
|
return Number.POSITIVE_INFINITY;
|
|
@@ -5712,6 +5741,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5712
5741
|
const clockSkewMs = headerTs !== null ? headerTs - requestStartRaw : 0;
|
|
5713
5742
|
const rid = nextRequestId(requestEpochMs);
|
|
5714
5743
|
const captureScope = createRequestCaptureScope(rid, 'http');
|
|
5744
|
+
emitRequestCaptureStart(captureCfg, sid, aid, captureScope, requestEpochMs);
|
|
5715
5745
|
const t0 = requestStartRaw;
|
|
5716
5746
|
const url = (req as any).originalUrl || req.url || '/';
|
|
5717
5747
|
const urlPathOnly = (url || '/').split('?')[0] || '/';
|
|
@@ -7810,6 +7840,7 @@ function patchKafkaProducerInstance(producer: any, cfg: KafkaJsPatchConfig) {
|
|
|
7810
7840
|
const span = captureSpanContextFromTracer();
|
|
7811
7841
|
const publishRid = nextRequestId(alignedNow());
|
|
7812
7842
|
const captureScope = createRequestCaptureScope(publishRid, 'kafka');
|
|
7843
|
+
emitRequestCaptureStart(cfg, sid, aid, captureScope);
|
|
7813
7844
|
const payloadObj = payload && typeof payload === 'object' ? payload : {};
|
|
7814
7845
|
const topic = sanitizeKafkaTopic((payloadObj as any).topic);
|
|
7815
7846
|
const rawMessages = Array.isArray((payloadObj as any).messages) ? (payloadObj as any).messages : [];
|
|
@@ -7924,6 +7955,7 @@ function patchKafkaProducerInstance(producer: any, cfg: KafkaJsPatchConfig) {
|
|
|
7924
7955
|
const span = captureSpanContextFromTracer();
|
|
7925
7956
|
const publishRid = nextRequestId(alignedNow());
|
|
7926
7957
|
const captureScope = createRequestCaptureScope(publishRid, 'kafka');
|
|
7958
|
+
emitRequestCaptureStart(cfg, sid, aid, captureScope);
|
|
7927
7959
|
|
|
7928
7960
|
const payloadObj = payload && typeof payload === 'object' ? payload : {};
|
|
7929
7961
|
const topicMessages = Array.isArray((payloadObj as any).topicMessages)
|
|
@@ -8074,6 +8106,7 @@ function wrapKafkaEachMessageHandler(eachMessage: any, cfg: KafkaJsPatchConfig,
|
|
|
8074
8106
|
const consumeRid = nextRequestId(`${topic}-${message?.offset ?? alignedNow()}`);
|
|
8075
8107
|
const traceId = consumeRid;
|
|
8076
8108
|
const captureScope = createRequestCaptureScope(consumeRid, 'kafka');
|
|
8109
|
+
emitRequestCaptureStart(cfg, sid, aid, captureScope);
|
|
8077
8110
|
const rawTraceEvents: any[] = [];
|
|
8078
8111
|
const maskReq: MaskRequestContext = {
|
|
8079
8112
|
method: 'KAFKA_CONSUME',
|
|
@@ -8184,6 +8217,7 @@ function wrapKafkaEachBatchHandler(eachBatch: any, cfg: KafkaJsPatchConfig, cons
|
|
|
8184
8217
|
const consumeRid = nextRequestId(`${topic}-batch-${alignedNow()}`);
|
|
8185
8218
|
const traceId = consumeRid;
|
|
8186
8219
|
const captureScope = createRequestCaptureScope(consumeRid, 'kafka');
|
|
8220
|
+
emitRequestCaptureStart(cfg, sid, aid, captureScope);
|
|
8187
8221
|
const rawTraceEvents: any[] = [];
|
|
8188
8222
|
const maskReq: MaskRequestContext = {
|
|
8189
8223
|
method: 'KAFKA_CONSUME_BATCH',
|
|
@@ -8891,6 +8925,7 @@ function patchAmqplibPublishMethod(
|
|
|
8891
8925
|
const key = `${method} ${methodName === 'publish' ? `${exchange || 'default'}/${routingKey}` : queue}`;
|
|
8892
8926
|
const content = methodName === 'publish' ? args[2] : args[1];
|
|
8893
8927
|
const captureScope = createRequestCaptureScope(publishRid, 'amqp');
|
|
8928
|
+
emitRequestCaptureStart(cfg, sid, aid, captureScope);
|
|
8894
8929
|
const t0 = Date.now();
|
|
8895
8930
|
let finished = false;
|
|
8896
8931
|
|
|
@@ -8980,6 +9015,7 @@ function wrapAmqplibConsumeHandler(onMessage: any, cfg: AmqplibPatchConfig, queu
|
|
|
8980
9015
|
const consumeRid = nextRequestId(`${queueName}-${deliveryTag}`);
|
|
8981
9016
|
const traceId = consumeRid;
|
|
8982
9017
|
const captureScope = createRequestCaptureScope(consumeRid, 'amqp');
|
|
9018
|
+
emitRequestCaptureStart(cfg, sid, aid, captureScope);
|
|
8983
9019
|
const rawTraceEvents: any[] = [];
|
|
8984
9020
|
const maskReq: MaskRequestContext = {
|
|
8985
9021
|
method: 'AMQP_CONSUME',
|
|
@@ -9583,6 +9619,616 @@ export function patchRedis(cfg: RedisPatchConfig) {
|
|
|
9583
9619
|
}
|
|
9584
9620
|
}
|
|
9585
9621
|
|
|
9622
|
+
// ===================================================================
|
|
9623
|
+
// PostgreSQL integrations: pg, postgres.js, and TypeORM
|
|
9624
|
+
// ===================================================================
|
|
9625
|
+
export type PostgreSqlPatchConfig = ReproMiddlewareConfig & {
|
|
9626
|
+
resolveContext?: () => { sid?: string; aid?: string } | undefined;
|
|
9627
|
+
};
|
|
9628
|
+
|
|
9629
|
+
type PostgreSqlSource = {
|
|
9630
|
+
library: 'pg' | 'postgres' | 'typeorm';
|
|
9631
|
+
driver: 'pg' | 'postgres';
|
|
9632
|
+
};
|
|
9633
|
+
|
|
9634
|
+
type PostgreSqlCaptureContext = {
|
|
9635
|
+
cfg: PostgreSqlPatchConfig;
|
|
9636
|
+
sid: string;
|
|
9637
|
+
aid: string | null;
|
|
9638
|
+
spanContext: SpanContext | null;
|
|
9639
|
+
source: PostgreSqlSource;
|
|
9640
|
+
};
|
|
9641
|
+
|
|
9642
|
+
const postgresSqlPatchConfigs: PostgreSqlPatchConfig[] = [];
|
|
9643
|
+
const postgresSqlSourceAls = new AsyncLocalStorage<PostgreSqlSource>();
|
|
9644
|
+
const pgPoolQueryAls = new AsyncLocalStorage<boolean>();
|
|
9645
|
+
let pgRequireHookInstalled = false;
|
|
9646
|
+
let postgresJsRequireHookInstalled = false;
|
|
9647
|
+
let typeOrmRequireHookInstalled = false;
|
|
9648
|
+
|
|
9649
|
+
const PG_CLIENT_PATCHED = Symbol.for('@reproapp/node-sdk/pg-client-patched');
|
|
9650
|
+
const PG_POOL_PATCHED = Symbol.for('@reproapp/node-sdk/pg-pool-patched');
|
|
9651
|
+
const POSTGRES_JS_QUERY_PATCHED = Symbol.for('@reproapp/node-sdk/postgres-js-query-patched');
|
|
9652
|
+
const TYPEORM_DATASOURCE_PATCHED = Symbol.for('@reproapp/node-sdk/typeorm-datasource-patched');
|
|
9653
|
+
const TYPEORM_QUERY_RUNNER_PATCHED = Symbol.for('@reproapp/node-sdk/typeorm-query-runner-patched');
|
|
9654
|
+
|
|
9655
|
+
function registerPostgreSqlPatchConfig(cfg: PostgreSqlPatchConfig): void {
|
|
9656
|
+
if (!postgresSqlPatchConfigs.includes(cfg)) {
|
|
9657
|
+
postgresSqlPatchConfigs.push(cfg);
|
|
9658
|
+
}
|
|
9659
|
+
}
|
|
9660
|
+
|
|
9661
|
+
function resolvePostgreSqlCaptureContext(defaultSource: PostgreSqlSource, source?: any): PostgreSqlCaptureContext | null {
|
|
9662
|
+
const ctx = getCtx();
|
|
9663
|
+
const contextualSource = postgresSqlSourceAls.getStore();
|
|
9664
|
+
for (let i = postgresSqlPatchConfigs.length - 1; i >= 0; i -= 1) {
|
|
9665
|
+
const cfg = postgresSqlPatchConfigs[i];
|
|
9666
|
+
if (!cfg) continue;
|
|
9667
|
+
const fallback = cfg.resolveContext?.() ?? {};
|
|
9668
|
+
const sid = ctx.sid ?? fallback.sid ?? null;
|
|
9669
|
+
if (!sid) continue;
|
|
9670
|
+
return {
|
|
9671
|
+
cfg,
|
|
9672
|
+
sid,
|
|
9673
|
+
aid: ctx.aid ?? fallback.aid ?? null,
|
|
9674
|
+
spanContext: captureSpanContextFromTracer(source),
|
|
9675
|
+
source: contextualSource ?? defaultSource,
|
|
9676
|
+
};
|
|
9677
|
+
}
|
|
9678
|
+
return null;
|
|
9679
|
+
}
|
|
9680
|
+
|
|
9681
|
+
function stripSqlComments(sql: string): string {
|
|
9682
|
+
let remaining = sql.trimStart();
|
|
9683
|
+
while (remaining.length) {
|
|
9684
|
+
if (remaining.startsWith('--')) {
|
|
9685
|
+
const newline = remaining.indexOf('\n');
|
|
9686
|
+
remaining = newline < 0 ? '' : remaining.slice(newline + 1).trimStart();
|
|
9687
|
+
continue;
|
|
9688
|
+
}
|
|
9689
|
+
if (remaining.startsWith('/*')) {
|
|
9690
|
+
const end = remaining.indexOf('*/', 2);
|
|
9691
|
+
remaining = end < 0 ? '' : remaining.slice(end + 2).trimStart();
|
|
9692
|
+
continue;
|
|
9693
|
+
}
|
|
9694
|
+
break;
|
|
9695
|
+
}
|
|
9696
|
+
return remaining;
|
|
9697
|
+
}
|
|
9698
|
+
|
|
9699
|
+
function normalizeSqlOperation(sql: string): string {
|
|
9700
|
+
const normalized = stripSqlComments(sql);
|
|
9701
|
+
const first = normalized.match(/^([a-z]+)/i)?.[1]?.toLowerCase() ?? 'query';
|
|
9702
|
+
if (first !== 'with') return first;
|
|
9703
|
+
|
|
9704
|
+
// CTEs can wrap any DML statement. The last top-level DML keyword is the
|
|
9705
|
+
// operation users care about, while nested SELECTs only build the CTE.
|
|
9706
|
+
const matches = Array.from(normalized.matchAll(/\b(select|insert|update|delete|merge)\b/gi));
|
|
9707
|
+
return matches.length ? String(matches[matches.length - 1][1]).toLowerCase() : 'query';
|
|
9708
|
+
}
|
|
9709
|
+
|
|
9710
|
+
function cleanSqlIdentifier(value: string | undefined): string | undefined {
|
|
9711
|
+
if (!value) return undefined;
|
|
9712
|
+
const cleaned = value
|
|
9713
|
+
.trim()
|
|
9714
|
+
.replace(/[;,]$/, '')
|
|
9715
|
+
.split('.')
|
|
9716
|
+
.map((part) => part.replace(/^['"`]|['"`]$/g, ''))
|
|
9717
|
+
.join('.');
|
|
9718
|
+
return cleaned || undefined;
|
|
9719
|
+
}
|
|
9720
|
+
|
|
9721
|
+
function resolveSqlTable(sql: string, op: string): string {
|
|
9722
|
+
const normalized = stripSqlComments(sql);
|
|
9723
|
+
let match: RegExpMatchArray | null = null;
|
|
9724
|
+
if (op === 'insert') match = normalized.match(/\binsert\s+into\s+([^\s(]+)/i);
|
|
9725
|
+
else if (op === 'update') match = normalized.match(/\bupdate\s+([^\s(]+)/i);
|
|
9726
|
+
else if (op === 'delete') match = normalized.match(/\bdelete\s+from\s+([^\s(]+)/i);
|
|
9727
|
+
else if (op === 'merge') match = normalized.match(/\bmerge\s+into\s+([^\s(]+)/i);
|
|
9728
|
+
else match = normalized.match(/\bfrom\s+([^\s(]+)/i);
|
|
9729
|
+
return cleanSqlIdentifier(match?.[1]) ?? 'unknown';
|
|
9730
|
+
}
|
|
9731
|
+
|
|
9732
|
+
function buildPostgresQueryPayload(
|
|
9733
|
+
sql: string,
|
|
9734
|
+
parameters: any[],
|
|
9735
|
+
source: PostgreSqlSource,
|
|
9736
|
+
extra?: Record<string, any>,
|
|
9737
|
+
): Record<string, any> {
|
|
9738
|
+
return {
|
|
9739
|
+
sql,
|
|
9740
|
+
parameters,
|
|
9741
|
+
library: source.library,
|
|
9742
|
+
driver: source.driver,
|
|
9743
|
+
...(extra ?? {}),
|
|
9744
|
+
};
|
|
9745
|
+
}
|
|
9746
|
+
|
|
9747
|
+
function summarizePostgresError(error: any): Record<string, any> {
|
|
9748
|
+
return {
|
|
9749
|
+
name: error?.name ?? 'Error',
|
|
9750
|
+
message: error?.message ?? String(error),
|
|
9751
|
+
code: error?.code,
|
|
9752
|
+
severity: error?.severity,
|
|
9753
|
+
detail: error?.detail,
|
|
9754
|
+
hint: error?.hint,
|
|
9755
|
+
position: error?.position,
|
|
9756
|
+
internalPosition: error?.internalPosition,
|
|
9757
|
+
internalQuery: error?.internalQuery,
|
|
9758
|
+
where: error?.where,
|
|
9759
|
+
schema: error?.schema,
|
|
9760
|
+
table: error?.table,
|
|
9761
|
+
column: error?.column,
|
|
9762
|
+
dataType: error?.dataType,
|
|
9763
|
+
constraint: error?.constraint,
|
|
9764
|
+
file: error?.file,
|
|
9765
|
+
line: error?.line,
|
|
9766
|
+
routine: error?.routine,
|
|
9767
|
+
};
|
|
9768
|
+
}
|
|
9769
|
+
|
|
9770
|
+
function normalizePgQueryInput(config: any, values: any): { sql: string; parameters: any[]; extra?: Record<string, any> } {
|
|
9771
|
+
if (typeof config === 'string') {
|
|
9772
|
+
return { sql: config, parameters: Array.isArray(values) ? values : [] };
|
|
9773
|
+
}
|
|
9774
|
+
const sql = typeof config?.text === 'string' ? config.text : '';
|
|
9775
|
+
const parameters = Array.isArray(config?.values)
|
|
9776
|
+
? config.values
|
|
9777
|
+
: Array.isArray(values)
|
|
9778
|
+
? values
|
|
9779
|
+
: [];
|
|
9780
|
+
return {
|
|
9781
|
+
sql,
|
|
9782
|
+
parameters,
|
|
9783
|
+
extra: {
|
|
9784
|
+
name: typeof config?.name === 'string' ? config.name : undefined,
|
|
9785
|
+
rowMode: config?.rowMode,
|
|
9786
|
+
binary: config?.binary,
|
|
9787
|
+
},
|
|
9788
|
+
};
|
|
9789
|
+
}
|
|
9790
|
+
|
|
9791
|
+
function normalizePgResult(result: any): any {
|
|
9792
|
+
if (Array.isArray(result)) return result.map(normalizePgResult);
|
|
9793
|
+
if (!result || typeof result !== 'object') return result;
|
|
9794
|
+
return {
|
|
9795
|
+
command: result.command,
|
|
9796
|
+
rowCount: result.rowCount,
|
|
9797
|
+
oid: result.oid,
|
|
9798
|
+
fields: Array.isArray(result.fields)
|
|
9799
|
+
? result.fields.map((field: any) => ({
|
|
9800
|
+
name: field?.name,
|
|
9801
|
+
tableID: field?.tableID,
|
|
9802
|
+
columnID: field?.columnID,
|
|
9803
|
+
dataTypeID: field?.dataTypeID,
|
|
9804
|
+
dataTypeSize: field?.dataTypeSize,
|
|
9805
|
+
dataTypeModifier: field?.dataTypeModifier,
|
|
9806
|
+
format: field?.format,
|
|
9807
|
+
}))
|
|
9808
|
+
: undefined,
|
|
9809
|
+
rows: result.rows,
|
|
9810
|
+
};
|
|
9811
|
+
}
|
|
9812
|
+
|
|
9813
|
+
function emitPostgreSqlResult(
|
|
9814
|
+
capture: PostgreSqlCaptureContext,
|
|
9815
|
+
sql: string,
|
|
9816
|
+
parameters: any[],
|
|
9817
|
+
startedAt: number,
|
|
9818
|
+
result: any,
|
|
9819
|
+
error?: any,
|
|
9820
|
+
extra?: Record<string, any>,
|
|
9821
|
+
): void {
|
|
9822
|
+
const op = normalizeSqlOperation(sql);
|
|
9823
|
+
emitDbQuery(capture.cfg, capture.sid, capture.aid ?? undefined, {
|
|
9824
|
+
type: 'postgresql',
|
|
9825
|
+
collection: resolveSqlTable(sql, op),
|
|
9826
|
+
op,
|
|
9827
|
+
queryRaw: buildPostgresQueryPayload(sql, parameters, capture.source, extra),
|
|
9828
|
+
resultMetaRaw: error ? undefined : result,
|
|
9829
|
+
error: error ? summarizePostgresError(error) : undefined,
|
|
9830
|
+
durMs: Date.now() - startedAt,
|
|
9831
|
+
t: alignedNow(),
|
|
9832
|
+
spanContext: capture.spanContext,
|
|
9833
|
+
});
|
|
9834
|
+
}
|
|
9835
|
+
|
|
9836
|
+
function findPgCallbackIndex(args: any[]): number {
|
|
9837
|
+
for (let i = args.length - 1; i >= 1; i -= 1) {
|
|
9838
|
+
if (typeof args[i] === 'function') return i;
|
|
9839
|
+
}
|
|
9840
|
+
return -1;
|
|
9841
|
+
}
|
|
9842
|
+
|
|
9843
|
+
function patchPgClientPrototype(proto: any): void {
|
|
9844
|
+
if (!proto || proto[PG_CLIENT_PATCHED]) return;
|
|
9845
|
+
const original = proto.query;
|
|
9846
|
+
if (typeof original !== 'function') return;
|
|
9847
|
+
try {
|
|
9848
|
+
Object.defineProperty(proto, PG_CLIENT_PATCHED, { value: true, configurable: true });
|
|
9849
|
+
} catch {
|
|
9850
|
+
return;
|
|
9851
|
+
}
|
|
9852
|
+
|
|
9853
|
+
proto.query = function reproPatchedPgQuery(this: any, ...args: any[]) {
|
|
9854
|
+
if (pgPoolQueryAls.getStore()) return original.apply(this, args);
|
|
9855
|
+
const capture = resolvePostgreSqlCaptureContext({ library: 'pg', driver: 'pg' }, this);
|
|
9856
|
+
if (!capture) return original.apply(this, args);
|
|
9857
|
+
|
|
9858
|
+
const input = normalizePgQueryInput(args[0], args[1]);
|
|
9859
|
+
const startedAt = Date.now();
|
|
9860
|
+
const callbackIndex = findPgCallbackIndex(args);
|
|
9861
|
+
const actualArgs = callbackIndex >= 0 ? [...args] : args;
|
|
9862
|
+
let emitted = false;
|
|
9863
|
+
const emitSuccess = (result: any) => {
|
|
9864
|
+
if (emitted) return;
|
|
9865
|
+
emitted = true;
|
|
9866
|
+
emitPostgreSqlResult(
|
|
9867
|
+
capture,
|
|
9868
|
+
input.sql,
|
|
9869
|
+
input.parameters,
|
|
9870
|
+
startedAt,
|
|
9871
|
+
normalizePgResult(result),
|
|
9872
|
+
undefined,
|
|
9873
|
+
input.extra,
|
|
9874
|
+
);
|
|
9875
|
+
};
|
|
9876
|
+
const emitFailure = (error: any) => {
|
|
9877
|
+
if (emitted) return;
|
|
9878
|
+
emitted = true;
|
|
9879
|
+
emitPostgreSqlResult(capture, input.sql, input.parameters, startedAt, undefined, error, input.extra);
|
|
9880
|
+
};
|
|
9881
|
+
|
|
9882
|
+
if (callbackIndex >= 0) {
|
|
9883
|
+
const callback = args[callbackIndex];
|
|
9884
|
+
actualArgs[callbackIndex] = function reproPgQueryCallback(this: any, error: any, result: any, ...rest: any[]) {
|
|
9885
|
+
if (error) emitFailure(error);
|
|
9886
|
+
else emitSuccess(result);
|
|
9887
|
+
return callback.call(this, error, result, ...rest);
|
|
9888
|
+
};
|
|
9889
|
+
}
|
|
9890
|
+
|
|
9891
|
+
try {
|
|
9892
|
+
const returned = original.apply(this, actualArgs);
|
|
9893
|
+
if (returned && typeof returned.then === 'function') {
|
|
9894
|
+
return returned.then(
|
|
9895
|
+
(result: any) => {
|
|
9896
|
+
emitSuccess(result);
|
|
9897
|
+
return result;
|
|
9898
|
+
},
|
|
9899
|
+
(error: any) => {
|
|
9900
|
+
emitFailure(error);
|
|
9901
|
+
throw error;
|
|
9902
|
+
},
|
|
9903
|
+
);
|
|
9904
|
+
}
|
|
9905
|
+
if (returned && callbackIndex < 0 && typeof returned.once === 'function') {
|
|
9906
|
+
returned.once('end', emitSuccess);
|
|
9907
|
+
returned.once('error', emitFailure);
|
|
9908
|
+
}
|
|
9909
|
+
return returned;
|
|
9910
|
+
} catch (error) {
|
|
9911
|
+
emitFailure(error);
|
|
9912
|
+
throw error;
|
|
9913
|
+
}
|
|
9914
|
+
};
|
|
9915
|
+
}
|
|
9916
|
+
|
|
9917
|
+
function patchPgPoolPrototype(proto: any): void {
|
|
9918
|
+
if (!proto || proto[PG_POOL_PATCHED]) return;
|
|
9919
|
+
const original = proto.query;
|
|
9920
|
+
if (typeof original !== 'function') return;
|
|
9921
|
+
try {
|
|
9922
|
+
Object.defineProperty(proto, PG_POOL_PATCHED, { value: true, configurable: true });
|
|
9923
|
+
} catch {
|
|
9924
|
+
return;
|
|
9925
|
+
}
|
|
9926
|
+
|
|
9927
|
+
proto.query = function reproPatchedPgPoolQuery(this: any, ...args: any[]) {
|
|
9928
|
+
const capture = resolvePostgreSqlCaptureContext({ library: 'pg', driver: 'pg' }, this);
|
|
9929
|
+
if (!capture) return original.apply(this, args);
|
|
9930
|
+
|
|
9931
|
+
const input = normalizePgQueryInput(args[0], args[1]);
|
|
9932
|
+
const startedAt = Date.now();
|
|
9933
|
+
const callbackIndex = findPgCallbackIndex(args);
|
|
9934
|
+
const actualArgs = callbackIndex >= 0 ? [...args] : args;
|
|
9935
|
+
let emitted = false;
|
|
9936
|
+
const emitSuccess = (result: any) => {
|
|
9937
|
+
if (emitted) return;
|
|
9938
|
+
emitted = true;
|
|
9939
|
+
emitPostgreSqlResult(
|
|
9940
|
+
capture,
|
|
9941
|
+
input.sql,
|
|
9942
|
+
input.parameters,
|
|
9943
|
+
startedAt,
|
|
9944
|
+
normalizePgResult(result),
|
|
9945
|
+
undefined,
|
|
9946
|
+
input.extra,
|
|
9947
|
+
);
|
|
9948
|
+
};
|
|
9949
|
+
const emitFailure = (error: any) => {
|
|
9950
|
+
if (emitted) return;
|
|
9951
|
+
emitted = true;
|
|
9952
|
+
emitPostgreSqlResult(capture, input.sql, input.parameters, startedAt, undefined, error, input.extra);
|
|
9953
|
+
};
|
|
9954
|
+
|
|
9955
|
+
if (callbackIndex >= 0) {
|
|
9956
|
+
const callback = args[callbackIndex];
|
|
9957
|
+
actualArgs[callbackIndex] = function reproPgPoolQueryCallback(this: any, error: any, result: any, ...rest: any[]) {
|
|
9958
|
+
if (error) emitFailure(error);
|
|
9959
|
+
else emitSuccess(result);
|
|
9960
|
+
return callback.call(this, error, result, ...rest);
|
|
9961
|
+
};
|
|
9962
|
+
}
|
|
9963
|
+
|
|
9964
|
+
try {
|
|
9965
|
+
const returned = pgPoolQueryAls.run(true, () => original.apply(this, actualArgs));
|
|
9966
|
+
if (returned && typeof returned.then === 'function') {
|
|
9967
|
+
return returned.then(
|
|
9968
|
+
(result: any) => {
|
|
9969
|
+
emitSuccess(result);
|
|
9970
|
+
return result;
|
|
9971
|
+
},
|
|
9972
|
+
(error: any) => {
|
|
9973
|
+
emitFailure(error);
|
|
9974
|
+
throw error;
|
|
9975
|
+
},
|
|
9976
|
+
);
|
|
9977
|
+
}
|
|
9978
|
+
return returned;
|
|
9979
|
+
} catch (error) {
|
|
9980
|
+
emitFailure(error);
|
|
9981
|
+
throw error;
|
|
9982
|
+
}
|
|
9983
|
+
};
|
|
9984
|
+
}
|
|
9985
|
+
|
|
9986
|
+
function patchPgFromModuleExports(pgModule: any): void {
|
|
9987
|
+
if (!pgModule) return;
|
|
9988
|
+
const candidates = [pgModule, pgModule.default];
|
|
9989
|
+
for (const candidate of candidates) {
|
|
9990
|
+
patchPgClientPrototype(candidate?.Client?.prototype);
|
|
9991
|
+
patchPgClientPrototype(candidate?.native?.Client?.prototype);
|
|
9992
|
+
patchPgPoolPrototype(candidate?.Pool?.prototype);
|
|
9993
|
+
patchPgPoolPrototype(candidate?.native?.Pool?.prototype);
|
|
9994
|
+
}
|
|
9995
|
+
}
|
|
9996
|
+
|
|
9997
|
+
function installPgRequireHook(): void {
|
|
9998
|
+
if (pgRequireHookInstalled) return;
|
|
9999
|
+
pgRequireHookInstalled = true;
|
|
10000
|
+
try {
|
|
10001
|
+
const rim = requireOptionalHostModule('require-in-the-middle');
|
|
10002
|
+
const Hook = rim?.Hook ?? rim;
|
|
10003
|
+
if (typeof Hook !== 'function') return;
|
|
10004
|
+
// eslint-disable-next-line no-new
|
|
10005
|
+
new Hook(['pg'], (exports: any) => {
|
|
10006
|
+
patchPgFromModuleExports(exports);
|
|
10007
|
+
return exports;
|
|
10008
|
+
});
|
|
10009
|
+
} catch {}
|
|
10010
|
+
}
|
|
10011
|
+
|
|
10012
|
+
function normalizePostgresJsInput(query: any): { sql: string; parameters: any[]; extra: Record<string, any> } {
|
|
10013
|
+
if (typeof query?.statement?.string === 'string') {
|
|
10014
|
+
return {
|
|
10015
|
+
sql: query.statement.string,
|
|
10016
|
+
parameters: Array.isArray(query?.parameters) ? query.parameters : [],
|
|
10017
|
+
extra: {
|
|
10018
|
+
tagged: query?.tagged === true,
|
|
10019
|
+
parameterTypes: Array.isArray(query?.statement?.types) ? query.statement.types : undefined,
|
|
10020
|
+
},
|
|
10021
|
+
};
|
|
10022
|
+
}
|
|
10023
|
+
const strings = Array.isArray(query?.strings) ? query.strings : [];
|
|
10024
|
+
const parameters = Array.isArray(query?.args) ? query.args : [];
|
|
10025
|
+
if (query?.tagged) {
|
|
10026
|
+
return {
|
|
10027
|
+
sql: strings.map((part: any, index: number) => `${String(part)}${index < parameters.length ? `$${index + 1}` : ''}`).join(''),
|
|
10028
|
+
parameters,
|
|
10029
|
+
extra: { tagged: true },
|
|
10030
|
+
};
|
|
10031
|
+
}
|
|
10032
|
+
return {
|
|
10033
|
+
sql: typeof strings[0] === 'string' ? strings[0] : '',
|
|
10034
|
+
parameters,
|
|
10035
|
+
extra: { tagged: false },
|
|
10036
|
+
};
|
|
10037
|
+
}
|
|
10038
|
+
|
|
10039
|
+
function normalizePostgresJsResult(result: any): any {
|
|
10040
|
+
if (!Array.isArray(result)) return result;
|
|
10041
|
+
const metadata = result as any;
|
|
10042
|
+
return {
|
|
10043
|
+
command: metadata.command,
|
|
10044
|
+
count: metadata.count,
|
|
10045
|
+
columns: Array.isArray(metadata.columns)
|
|
10046
|
+
? metadata.columns.map((column: any) => ({
|
|
10047
|
+
name: column?.name,
|
|
10048
|
+
type: column?.type,
|
|
10049
|
+
table: column?.table,
|
|
10050
|
+
number: column?.number,
|
|
10051
|
+
}))
|
|
10052
|
+
: undefined,
|
|
10053
|
+
rows: Array.from(result),
|
|
10054
|
+
};
|
|
10055
|
+
}
|
|
10056
|
+
|
|
10057
|
+
function patchPostgresJsQueryPrototype(proto: any): void {
|
|
10058
|
+
if (!proto || proto[POSTGRES_JS_QUERY_PATCHED]) return;
|
|
10059
|
+
const originalHandle = proto.handle;
|
|
10060
|
+
if (typeof originalHandle !== 'function') return;
|
|
10061
|
+
try {
|
|
10062
|
+
Object.defineProperty(proto, POSTGRES_JS_QUERY_PATCHED, { value: true, configurable: true });
|
|
10063
|
+
} catch {
|
|
10064
|
+
return;
|
|
10065
|
+
}
|
|
10066
|
+
|
|
10067
|
+
proto.handle = function reproPatchedPostgresJsHandle(this: any, ...args: any[]) {
|
|
10068
|
+
if (!this.__repro_postgres_capture_attached) {
|
|
10069
|
+
const capture = resolvePostgreSqlCaptureContext({ library: 'postgres', driver: 'postgres' }, this);
|
|
10070
|
+
if (capture) {
|
|
10071
|
+
try {
|
|
10072
|
+
Object.defineProperty(this, '__repro_postgres_capture_attached', { value: true, configurable: true });
|
|
10073
|
+
} catch {
|
|
10074
|
+
this.__repro_postgres_capture_attached = true;
|
|
10075
|
+
}
|
|
10076
|
+
const input = normalizePostgresJsInput(this);
|
|
10077
|
+
const startedAt = Date.now();
|
|
10078
|
+
Promise.prototype.then.call(
|
|
10079
|
+
this,
|
|
10080
|
+
(result: any) => {
|
|
10081
|
+
const executedInput = normalizePostgresJsInput(this);
|
|
10082
|
+
emitPostgreSqlResult(
|
|
10083
|
+
capture,
|
|
10084
|
+
executedInput.sql || input.sql,
|
|
10085
|
+
executedInput.parameters,
|
|
10086
|
+
startedAt,
|
|
10087
|
+
normalizePostgresJsResult(result),
|
|
10088
|
+
undefined,
|
|
10089
|
+
executedInput.extra,
|
|
10090
|
+
);
|
|
10091
|
+
return result;
|
|
10092
|
+
},
|
|
10093
|
+
(error: any) => {
|
|
10094
|
+
const executedInput = normalizePostgresJsInput(this);
|
|
10095
|
+
emitPostgreSqlResult(
|
|
10096
|
+
capture,
|
|
10097
|
+
executedInput.sql || input.sql,
|
|
10098
|
+
executedInput.parameters.length ? executedInput.parameters : input.parameters,
|
|
10099
|
+
startedAt,
|
|
10100
|
+
undefined,
|
|
10101
|
+
error,
|
|
10102
|
+
executedInput.extra,
|
|
10103
|
+
);
|
|
10104
|
+
},
|
|
10105
|
+
);
|
|
10106
|
+
}
|
|
10107
|
+
}
|
|
10108
|
+
return originalHandle.apply(this, args);
|
|
10109
|
+
};
|
|
10110
|
+
}
|
|
10111
|
+
|
|
10112
|
+
function patchPostgresJsFromModuleExports(postgresModule: any): void {
|
|
10113
|
+
const factories = new Set<any>();
|
|
10114
|
+
const addFactory = (candidate: any) => {
|
|
10115
|
+
if (typeof candidate !== 'function') return;
|
|
10116
|
+
// getLoadedModuleExportsByPackage also returns internal postgres.js
|
|
10117
|
+
// helpers. Only the public factory carries these static exports.
|
|
10118
|
+
if (!candidate.PostgresError && !candidate.BigInt && !candidate.__repro_postgres_factory) return;
|
|
10119
|
+
factories.add(candidate);
|
|
10120
|
+
};
|
|
10121
|
+
addFactory(postgresModule);
|
|
10122
|
+
addFactory(postgresModule?.default);
|
|
10123
|
+
|
|
10124
|
+
for (const factory of factories) {
|
|
10125
|
+
let sql: any;
|
|
10126
|
+
try {
|
|
10127
|
+
sql = factory({ max: 1, idle_timeout: 1, connect_timeout: 1 });
|
|
10128
|
+
const query = sql.unsafe('select 1');
|
|
10129
|
+
patchPostgresJsQueryPrototype(Object.getPrototypeOf(query));
|
|
10130
|
+
} catch {}
|
|
10131
|
+
try {
|
|
10132
|
+
const ended = sql?.end?.({ timeout: 0 });
|
|
10133
|
+
if (ended && typeof ended.catch === 'function') ended.catch(() => undefined);
|
|
10134
|
+
} catch {}
|
|
10135
|
+
}
|
|
10136
|
+
}
|
|
10137
|
+
|
|
10138
|
+
function installPostgresJsRequireHook(): void {
|
|
10139
|
+
if (postgresJsRequireHookInstalled) return;
|
|
10140
|
+
postgresJsRequireHookInstalled = true;
|
|
10141
|
+
try {
|
|
10142
|
+
const rim = requireOptionalHostModule('require-in-the-middle');
|
|
10143
|
+
const Hook = rim?.Hook ?? rim;
|
|
10144
|
+
if (typeof Hook !== 'function') return;
|
|
10145
|
+
// eslint-disable-next-line no-new
|
|
10146
|
+
new Hook(['postgres'], (exports: any) => {
|
|
10147
|
+
patchPostgresJsFromModuleExports(exports);
|
|
10148
|
+
return exports;
|
|
10149
|
+
});
|
|
10150
|
+
} catch {}
|
|
10151
|
+
}
|
|
10152
|
+
|
|
10153
|
+
function patchTypeOrmQueryRunner(queryRunner: any): any {
|
|
10154
|
+
if (!queryRunner || queryRunner[TYPEORM_QUERY_RUNNER_PATCHED]) return queryRunner;
|
|
10155
|
+
const original = queryRunner.query;
|
|
10156
|
+
if (typeof original !== 'function') return queryRunner;
|
|
10157
|
+
try {
|
|
10158
|
+
Object.defineProperty(queryRunner, TYPEORM_QUERY_RUNNER_PATCHED, { value: true, configurable: true });
|
|
10159
|
+
} catch {
|
|
10160
|
+
return queryRunner;
|
|
10161
|
+
}
|
|
10162
|
+
queryRunner.query = function reproPatchedTypeOrmQuery(this: any, ...args: any[]) {
|
|
10163
|
+
return postgresSqlSourceAls.run(
|
|
10164
|
+
{ library: 'typeorm', driver: 'pg' },
|
|
10165
|
+
() => original.apply(this, args),
|
|
10166
|
+
);
|
|
10167
|
+
};
|
|
10168
|
+
return queryRunner;
|
|
10169
|
+
}
|
|
10170
|
+
|
|
10171
|
+
function patchTypeOrmDataSourcePrototype(proto: any): void {
|
|
10172
|
+
if (!proto || proto[TYPEORM_DATASOURCE_PATCHED]) return;
|
|
10173
|
+
const original = proto.createQueryRunner;
|
|
10174
|
+
if (typeof original !== 'function') return;
|
|
10175
|
+
try {
|
|
10176
|
+
Object.defineProperty(proto, TYPEORM_DATASOURCE_PATCHED, { value: true, configurable: true });
|
|
10177
|
+
} catch {
|
|
10178
|
+
return;
|
|
10179
|
+
}
|
|
10180
|
+
proto.createQueryRunner = function reproPatchedCreateQueryRunner(this: any, ...args: any[]) {
|
|
10181
|
+
return patchTypeOrmQueryRunner(original.apply(this, args));
|
|
10182
|
+
};
|
|
10183
|
+
}
|
|
10184
|
+
|
|
10185
|
+
function patchTypeOrmFromModuleExports(typeOrmModule: any): void {
|
|
10186
|
+
if (!typeOrmModule) return;
|
|
10187
|
+
patchTypeOrmDataSourcePrototype(typeOrmModule?.DataSource?.prototype);
|
|
10188
|
+
patchTypeOrmDataSourcePrototype(typeOrmModule?.Connection?.prototype);
|
|
10189
|
+
patchTypeOrmDataSourcePrototype(typeOrmModule?.default?.DataSource?.prototype);
|
|
10190
|
+
}
|
|
10191
|
+
|
|
10192
|
+
function installTypeOrmRequireHook(): void {
|
|
10193
|
+
if (typeOrmRequireHookInstalled) return;
|
|
10194
|
+
typeOrmRequireHookInstalled = true;
|
|
10195
|
+
try {
|
|
10196
|
+
const rim = requireOptionalHostModule('require-in-the-middle');
|
|
10197
|
+
const Hook = rim?.Hook ?? rim;
|
|
10198
|
+
if (typeof Hook !== 'function') return;
|
|
10199
|
+
// eslint-disable-next-line no-new
|
|
10200
|
+
new Hook(['typeorm'], (exports: any) => {
|
|
10201
|
+
patchTypeOrmFromModuleExports(exports);
|
|
10202
|
+
return exports;
|
|
10203
|
+
});
|
|
10204
|
+
} catch {}
|
|
10205
|
+
}
|
|
10206
|
+
|
|
10207
|
+
export function patchPostgreSql(cfg: PostgreSqlPatchConfig): void {
|
|
10208
|
+
registerPostgreSqlPatchConfig(cfg);
|
|
10209
|
+
installPgRequireHook();
|
|
10210
|
+
installPostgresJsRequireHook();
|
|
10211
|
+
installTypeOrmRequireHook();
|
|
10212
|
+
|
|
10213
|
+
const pgModules = new Set<any>();
|
|
10214
|
+
const pg = requireOptionalHostModule('pg');
|
|
10215
|
+
if (pg) pgModules.add(pg);
|
|
10216
|
+
for (const loaded of getLoadedModuleExportsByPackage('pg')) pgModules.add(loaded);
|
|
10217
|
+
for (const loaded of pgModules) patchPgFromModuleExports(loaded);
|
|
10218
|
+
|
|
10219
|
+
const postgresModules = new Set<any>();
|
|
10220
|
+
const postgres = requireOptionalHostModule('postgres');
|
|
10221
|
+
if (postgres) postgresModules.add(postgres);
|
|
10222
|
+
for (const loaded of getLoadedModuleExportsByPackage('postgres')) postgresModules.add(loaded);
|
|
10223
|
+
for (const loaded of postgresModules) patchPostgresJsFromModuleExports(loaded);
|
|
10224
|
+
|
|
10225
|
+
const typeOrmModules = new Set<any>();
|
|
10226
|
+
const typeOrm = requireOptionalHostModule('typeorm');
|
|
10227
|
+
if (typeOrm) typeOrmModules.add(typeOrm);
|
|
10228
|
+
for (const loaded of getLoadedModuleExportsByPackage('typeorm')) typeOrmModules.add(loaded);
|
|
10229
|
+
for (const loaded of typeOrmModules) patchTypeOrmFromModuleExports(loaded);
|
|
10230
|
+
}
|
|
10231
|
+
|
|
9586
10232
|
// ===================================================================
|
|
9587
10233
|
// Native MongoDB driver integration
|
|
9588
10234
|
// ===================================================================
|
|
@@ -10713,6 +11359,7 @@ export async function initRepro(cfg: ReproInitConfig): Promise<void> {
|
|
|
10713
11359
|
patchKafkaJs(integrationCfg);
|
|
10714
11360
|
patchAmqplib(integrationCfg);
|
|
10715
11361
|
patchRedis(integrationCfg);
|
|
11362
|
+
patchPostgreSql(integrationCfg);
|
|
10716
11363
|
patchMongoDb(integrationCfg);
|
|
10717
11364
|
await installMongoDbImportHook();
|
|
10718
11365
|
patchSendgridMail(integrationCfg);
|
|
@@ -10779,6 +11426,9 @@ export const __reproTestHooks = {
|
|
|
10779
11426
|
patchAmqplibConnectionForTest: patchAmqplibConnection,
|
|
10780
11427
|
patchAmqplibChannelForTest: patchAmqplibChannel,
|
|
10781
11428
|
wrapAmqplibConsumeHandlerForTest: wrapAmqplibConsumeHandler,
|
|
11429
|
+
patchPgModuleForTest: patchPgFromModuleExports,
|
|
11430
|
+
patchPostgresJsModuleForTest: patchPostgresJsFromModuleExports,
|
|
11431
|
+
patchTypeOrmModuleForTest: patchTypeOrmFromModuleExports,
|
|
10782
11432
|
patchMongoDbModuleForTest: patchMongoDbFromModuleExports,
|
|
10783
11433
|
patchMongoCollectionPrototypeForTest: patchMongoCollectionPrototype,
|
|
10784
11434
|
chunkTraceEventsForTransportForTest: chunkTraceEventsForTransport,
|