crumbtrail-node 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2519 @@
1
+ import {
2
+ BugQueueManager,
3
+ CLOUDFLARE_AUTH_FIELDS,
4
+ CLOUDFLARE_DESCRIPTOR,
5
+ CLOUDFLARE_R2_ACCESS_KEY_ID_ENV,
6
+ CLOUDFLARE_R2_ACCOUNT_ID_ENV,
7
+ CLOUDFLARE_R2_BUCKET_ENV,
8
+ CLOUDFLARE_R2_DATASET_ENV,
9
+ CLOUDFLARE_R2_ENDPOINT_ENV,
10
+ CLOUDFLARE_R2_PREFIX_ENV,
11
+ CLOUDFLARE_R2_SECRET_ACCESS_KEY_ENV,
12
+ CLOUDWATCH_ACCESS_KEY_ID_ENV,
13
+ CLOUDWATCH_AUTH_FIELDS,
14
+ CLOUDWATCH_DESCRIPTOR,
15
+ CLOUDWATCH_ENDPOINT_ENV,
16
+ CLOUDWATCH_LOG_GROUPS_ENV,
17
+ CLOUDWATCH_REGION_ENV,
18
+ CLOUDWATCH_SECRET_ACCESS_KEY_ENV,
19
+ CLOUDWATCH_SESSION_TOKEN_ENV,
20
+ CRUMBTRAIL_REQUEST_HEADER,
21
+ CRUMBTRAIL_USER_AGENT,
22
+ CloudWatchEvidenceSource,
23
+ CloudflareEvidenceSource,
24
+ CompareError,
25
+ DATADOG_API_KEY_ENV,
26
+ DATADOG_APP_KEY_ENV,
27
+ DATADOG_AUTH_FIELDS,
28
+ DATADOG_DEFAULT_SITE,
29
+ DATADOG_DESCRIPTOR,
30
+ DATADOG_SITE_ENV,
31
+ DB_DIFF_BULK_EVENT_KIND,
32
+ DB_DIFF_EVENT_KIND,
33
+ DB_READ_BULK_EVENT_KIND,
34
+ DB_READ_EVENT_KIND,
35
+ DEFAULT_MAX_TOTAL_BYTES,
36
+ DEFAULT_SOURCE_TIMEOUT_MS,
37
+ DEFAULT_SWEEP_CHECKPOINT_MS,
38
+ DEFAULT_SWEEP_IDLE_MS,
39
+ DEFAULT_SWEEP_INTERVAL_MS,
40
+ DISTINCT_BUGS_SCHEMA_VERSION,
41
+ DatadogEvidenceSource,
42
+ EVIDENCE_SOURCE_PROVIDERS,
43
+ FIX_CONTEXT_SCHEMA_VERSION,
44
+ FilesystemSessionStore,
45
+ FixContextError,
46
+ InspectError,
47
+ JiraTicketClient,
48
+ McpServer,
49
+ POSTHOG_API_KEY_ENV,
50
+ POSTHOG_AUTH_FIELDS,
51
+ POSTHOG_DEFAULT_HOST,
52
+ POSTHOG_DESCRIPTOR,
53
+ POSTHOG_HOST_ENV,
54
+ POSTHOG_PROJECT_ID_ENV,
55
+ PROVIDER_IDS,
56
+ PROVIDER_RECIPES,
57
+ PostHogEvidenceSource,
58
+ REDACTED_VALUE,
59
+ REGRESSION_CONTEXT_SCHEMA_VERSION,
60
+ SENTRY_AUTH_FIELDS,
61
+ SENTRY_AUTH_TOKEN_ENV,
62
+ SENTRY_DEFAULT_HOST,
63
+ SENTRY_DESCRIPTOR,
64
+ SENTRY_HOST_ENV,
65
+ SENTRY_ORG_ENV,
66
+ SESSION_COMPARE_SCHEMA_VERSION,
67
+ SPLUNK_AUTH_FIELDS,
68
+ SPLUNK_DESCRIPTOR,
69
+ SPLUNK_HOST_ENV,
70
+ SPLUNK_INDEX_ENV,
71
+ SPLUNK_TOKEN_ENV,
72
+ SPLUNK_WEB_URL_ENV,
73
+ SentryEvidenceSource,
74
+ SessionManager,
75
+ SplunkEvidenceSource,
76
+ TicketError,
77
+ buildBackendRequestEndEvent,
78
+ buildBackendRequestErrorEvent,
79
+ buildBackendRequestStartEvent,
80
+ buildCloudWatchQuery,
81
+ buildCloudflarePlan,
82
+ buildDatadogQuery,
83
+ buildDistinctBugSignature,
84
+ buildFixContext,
85
+ buildPostHogQuery,
86
+ buildRegressionContext,
87
+ buildSentryQuery,
88
+ buildSessionSummary,
89
+ buildSplunkQuery,
90
+ cloudWatchDeepLink,
91
+ cloudWatchEvidenceProvider,
92
+ cloudflareEvidenceProvider,
93
+ compareSessions,
94
+ createServer,
95
+ datadogAppBase,
96
+ datadogEvidenceProvider,
97
+ decodeOtlpLogsProtobuf,
98
+ decodeOtlpTraceProtobuf,
99
+ defaultSessionStore,
100
+ evidenceRequestHeaders,
101
+ evidenceSourcesFromEnv,
102
+ fetchAdapterEvidence,
103
+ formatComparisonSummary,
104
+ formatDuration,
105
+ formatInspection,
106
+ getProviderRecipe,
107
+ groupDistinctBugRecurrences,
108
+ groupDistinctBugs,
109
+ inspectSession,
110
+ isProviderId,
111
+ jiraToSymptom,
112
+ mergeRedactionMetadata,
113
+ normalizeCloudWatchRow,
114
+ normalizeDatadogLog,
115
+ normalizeDatadogSpan,
116
+ normalizePostHogEvent,
117
+ normalizePostHogRecording,
118
+ normalizeSentryIssue,
119
+ normalizeSplunkRow,
120
+ posthogEventDeepLink,
121
+ posthogEvidenceProvider,
122
+ posthogRecordingDeepLink,
123
+ readPackageVersion,
124
+ redactEvidenceGap,
125
+ redactEvidenceItem,
126
+ redactSourceResult,
127
+ redactValue,
128
+ registerEvidenceProvider,
129
+ renderCompareReport,
130
+ renderProviderCliOutput,
131
+ renderProviderConfig,
132
+ renderProviderDoc,
133
+ renderProviderReadme,
134
+ resolveBackendRequestCorrelation,
135
+ sentryEvidenceProvider,
136
+ signSigV4,
137
+ splunkEvidenceProvider,
138
+ splunkSearchDeepLink,
139
+ splunkWebBase,
140
+ startSessionSweeper,
141
+ sweepIdleSessions,
142
+ withBoundedRetry
143
+ } from "./chunk-EASSXKUJ.js";
144
+ import "./chunk-QGM4M3NI.js";
145
+
146
+ // src/db/columns.ts
147
+ var DEFAULT_SENSITIVE_DB_COLUMNS = [
148
+ "password",
149
+ "token",
150
+ "secret",
151
+ "api_key",
152
+ "ssn"
153
+ ];
154
+ function normalizeColumnName(name) {
155
+ return name.toLowerCase().replace(/[^a-z0-9]/g, "");
156
+ }
157
+ function buildSensitiveColumnSet(extra = []) {
158
+ const set = /* @__PURE__ */ new Set();
159
+ for (const name of [...DEFAULT_SENSITIVE_DB_COLUMNS, ...extra]) {
160
+ if (typeof name === "string" && name.trim())
161
+ set.add(normalizeColumnName(name));
162
+ }
163
+ return set;
164
+ }
165
+ function redactColumns(row, sensitive, path) {
166
+ if (!row) return { value: void 0 };
167
+ const masked = {};
168
+ for (const [key, value] of Object.entries(row)) {
169
+ masked[key] = sensitive.has(normalizeColumnName(key)) ? REDACTED_VALUE : value;
170
+ }
171
+ const scrubbed = redactValue(masked, path);
172
+ return {
173
+ value: scrubbed.value,
174
+ metadata: scrubbed.metadata
175
+ };
176
+ }
177
+
178
+ // src/db/diff-event.ts
179
+ var MAX_DB_VALUE_LENGTH = 8 * 1024;
180
+ function boundStringValue(value, max = MAX_DB_VALUE_LENGTH) {
181
+ if (value.length <= max) return value;
182
+ return `${value.slice(0, max)}\u2026[truncated ${value.length} chars]`;
183
+ }
184
+ function boundColumnValue(value) {
185
+ if (typeof value === "string") return boundStringValue(value);
186
+ if (Array.isArray(value)) return value.map(boundColumnValue);
187
+ if (value !== null && typeof value === "object") {
188
+ const out = {};
189
+ for (const [key, inner] of Object.entries(
190
+ value
191
+ )) {
192
+ out[key] = boundColumnValue(inner);
193
+ }
194
+ return out;
195
+ }
196
+ return value;
197
+ }
198
+ function boundColumnRow(row) {
199
+ return row ? boundColumnValue(row) : void 0;
200
+ }
201
+ function buildDbDiffEvent(input) {
202
+ const now = Number.isFinite(input.now) ? Math.round(input.now) : Date.now();
203
+ const sensitive = buildSensitiveColumnSet(input.redactColumns);
204
+ const after = redactColumns(input.after, sensitive, "db.diff.after");
205
+ const before = redactColumns(input.before, sensitive, "db.diff.before");
206
+ const pk = input.pk ? redactColumns(input.pk, sensitive, "db.diff.pk") : { value: null, metadata: void 0 };
207
+ const boundedAfter = boundColumnRow(after.value);
208
+ const boundedBefore = boundColumnRow(before.value);
209
+ const boundedPk = boundColumnRow(pk.value ?? void 0) ?? null;
210
+ const d = {
211
+ engine: input.engine ?? "postgres",
212
+ op: input.op,
213
+ table: input.table,
214
+ pk: boundedPk,
215
+ requestId: input.requestId,
216
+ ...boundedAfter !== void 0 ? { after: boundedAfter } : {},
217
+ ...boundedBefore !== void 0 ? { before: boundedBefore } : {},
218
+ ...input.rowCount !== void 0 ? { rowCount: input.rowCount } : {}
219
+ };
220
+ const redaction = mergeRedactionMetadata(
221
+ after.metadata,
222
+ before.metadata,
223
+ pk.metadata
224
+ );
225
+ if (redaction) d.redaction = redaction;
226
+ const event = {
227
+ t: now,
228
+ k: DB_DIFF_EVENT_KIND,
229
+ d
230
+ };
231
+ if (input.sessionId) event.sessionId = input.sessionId;
232
+ const startedAt = normalizeStartedAt(input.sessionStartedAt);
233
+ if (startedAt !== void 0) event.offsetMs = Math.max(0, now - startedAt);
234
+ return event;
235
+ }
236
+ function normalizeStartedAt(startedAt) {
237
+ if (startedAt instanceof Date) {
238
+ const time = startedAt.getTime();
239
+ return Number.isFinite(time) ? time : void 0;
240
+ }
241
+ return Number.isFinite(startedAt) ? Math.round(startedAt) : void 0;
242
+ }
243
+
244
+ // src/db/read-event.ts
245
+ function buildDbReadEvent(input) {
246
+ const now = Number.isFinite(input.now) ? Math.round(input.now) : Date.now();
247
+ const sensitive = buildSensitiveColumnSet(input.redactColumns);
248
+ const row = redactColumns(input.row, sensitive, "db.read.row");
249
+ const pk = input.pk ? redactColumns(input.pk, sensitive, "db.read.pk") : { value: null, metadata: void 0 };
250
+ const boundedRow = boundColumnRow(row.value) ?? {};
251
+ const boundedPk = boundColumnRow(pk.value ?? void 0) ?? null;
252
+ const d = {
253
+ engine: input.engine ?? "postgres",
254
+ table: input.table,
255
+ pk: boundedPk,
256
+ row: boundedRow,
257
+ requestId: input.requestId
258
+ };
259
+ const redaction = mergeRedactionMetadata(row.metadata, pk.metadata);
260
+ if (redaction) d.redaction = redaction;
261
+ const event = {
262
+ t: now,
263
+ k: DB_READ_EVENT_KIND,
264
+ d
265
+ };
266
+ if (input.sessionId) event.sessionId = input.sessionId;
267
+ const startedAt = normalizeStartedAt2(input.sessionStartedAt);
268
+ if (startedAt !== void 0) event.offsetMs = Math.max(0, now - startedAt);
269
+ return event;
270
+ }
271
+ function buildDbReadBulkEvent(input) {
272
+ const now = Number.isFinite(input.now) ? Math.round(input.now) : Date.now();
273
+ const d = {
274
+ engine: input.engine ?? "postgres",
275
+ table: input.table,
276
+ requestId: input.requestId,
277
+ rowCount: input.rowCount,
278
+ emittedRows: input.emittedRows,
279
+ truncatedRows: Math.max(0, input.rowCount - input.emittedRows),
280
+ samplePks: input.samplePks
281
+ };
282
+ const event = {
283
+ t: now,
284
+ k: DB_READ_BULK_EVENT_KIND,
285
+ d
286
+ };
287
+ if (input.sessionId) event.sessionId = input.sessionId;
288
+ const startedAt = normalizeStartedAt2(input.sessionStartedAt);
289
+ if (startedAt !== void 0) event.offsetMs = Math.max(0, now - startedAt);
290
+ return event;
291
+ }
292
+ function normalizeStartedAt2(startedAt) {
293
+ if (startedAt instanceof Date) {
294
+ const time = startedAt.getTime();
295
+ return Number.isFinite(time) ? time : void 0;
296
+ }
297
+ return Number.isFinite(startedAt) ? Math.round(startedAt) : void 0;
298
+ }
299
+
300
+ // src/db/sql.ts
301
+ var IDENT_PART = String.raw`(?:"[^"]+"|\`[^\`]+\`|\[[^\]]+\]|[\w$]+)`;
302
+ var TABLE_IDENT = String.raw`(${IDENT_PART}(?:\s*\.\s*${IDENT_PART})*)`;
303
+ var TOP_CLAUSE = String.raw`(?:top\s*(?:\(\s*\d+\s*\)|\d+)\s+)?`;
304
+ var INSERT_RE = new RegExp(
305
+ String.raw`^\s*insert\s+into\s+${TABLE_IDENT}`,
306
+ "i"
307
+ );
308
+ var UPDATE_RE = new RegExp(
309
+ String.raw`^\s*update\s+${TOP_CLAUSE}(?:only\s+)?${TABLE_IDENT}\s+set\b`,
310
+ "i"
311
+ );
312
+ var DELETE_RE = new RegExp(
313
+ String.raw`^\s*delete\s+${TOP_CLAUSE}from\s+(?:only\s+)?${TABLE_IDENT}`,
314
+ "i"
315
+ );
316
+ var SELECT_FROM_RE = new RegExp(
317
+ String.raw`^\s*select\b[\s\S]*?\bfrom\s+${TABLE_IDENT}`,
318
+ "i"
319
+ );
320
+ var WHERE_RE = /\bwhere\b[\s\S]*?(?=\breturning\b|$)/i;
321
+ var RETURNING_RE = /\breturning\b/i;
322
+ function parseMutation(sql) {
323
+ const insert = INSERT_RE.exec(sql);
324
+ if (insert) return { op: "insert", table: normalizeTable(insert[1]) };
325
+ const update = UPDATE_RE.exec(sql);
326
+ if (update)
327
+ return {
328
+ op: "update",
329
+ table: normalizeTable(update[1]),
330
+ whereClause: extractWhere(sql)
331
+ };
332
+ const del = DELETE_RE.exec(sql);
333
+ if (del)
334
+ return {
335
+ op: "delete",
336
+ table: normalizeTable(del[1]),
337
+ whereClause: extractWhere(sql)
338
+ };
339
+ return void 0;
340
+ }
341
+ function parseRead(sql) {
342
+ const select = SELECT_FROM_RE.exec(sql);
343
+ if (!select) return void 0;
344
+ return { table: normalizeTable(select[1]) };
345
+ }
346
+ function stripIdentifierQuotes(part) {
347
+ const trimmed = part.trim();
348
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("`") && trimmed.endsWith("`") || trimmed.startsWith("[") && trimmed.endsWith("]")) {
349
+ return trimmed.slice(1, -1);
350
+ }
351
+ return trimmed;
352
+ }
353
+ function normalizeTable(raw) {
354
+ return raw.trim().split(".").map((part) => stripIdentifierQuotes(part)).join(".");
355
+ }
356
+ function extractWhere(sql) {
357
+ const match = WHERE_RE.exec(sql);
358
+ return match ? match[0].trim() : void 0;
359
+ }
360
+ function ensureReturning(sql) {
361
+ return RETURNING_RE.test(sql) ? sql : `${sql.replace(/;\s*$/, "")} RETURNING *`;
362
+ }
363
+ function countPlaceholders(sqlFragment) {
364
+ let count = 0;
365
+ let closing = null;
366
+ for (let index = 0; index < sqlFragment.length; index += 1) {
367
+ const ch = sqlFragment[index];
368
+ if (closing !== null) {
369
+ if (ch === closing) {
370
+ if ((closing === "'" || closing === '"') && sqlFragment[index + 1] === closing) {
371
+ index += 1;
372
+ continue;
373
+ }
374
+ closing = null;
375
+ }
376
+ continue;
377
+ }
378
+ if (ch === "'" || ch === '"' || ch === "`") {
379
+ closing = ch;
380
+ continue;
381
+ }
382
+ if (ch === "[") {
383
+ closing = "]";
384
+ continue;
385
+ }
386
+ if (ch === "?") count += 1;
387
+ }
388
+ return count;
389
+ }
390
+
391
+ // src/db/instrument-shared.ts
392
+ var DEFAULT_MAX_ROWS_PER_STATEMENT = 100;
393
+ var DEFAULT_MAX_READ_ROWS_PER_STATEMENT = 25;
394
+ var DEFAULT_MAX_READ_ROWS_PER_REQUEST = 100;
395
+ function isRecord(value) {
396
+ return value != null && typeof value === "object" && !Array.isArray(value);
397
+ }
398
+ function extractPk(row, table, pkColumns) {
399
+ const cols = pkColumns?.[table] ?? ["id"];
400
+ const pk = {};
401
+ for (const col of cols) {
402
+ if (col in row) pk[col] = row[col];
403
+ }
404
+ return Object.keys(pk).length > 0 ? pk : null;
405
+ }
406
+ function pkKey(pk) {
407
+ return pk ? JSON.stringify(pk) : "";
408
+ }
409
+ function redactPkSample(pk, sensitive) {
410
+ return pk ? redactColumns(pk, sensitive, "db.diff.bulk.samplePks").value ?? null : null;
411
+ }
412
+ function normalizeMaxRowsPerStatement(value) {
413
+ if (value === void 0) return DEFAULT_MAX_ROWS_PER_STATEMENT;
414
+ if (!Number.isFinite(value)) return DEFAULT_MAX_ROWS_PER_STATEMENT;
415
+ return Math.max(0, Math.floor(value));
416
+ }
417
+ function normalizeReadCap(value, fallback) {
418
+ if (value === void 0) return fallback;
419
+ if (!Number.isFinite(value)) return fallback;
420
+ return Math.max(0, Math.floor(value));
421
+ }
422
+ function buildDbDiffBulkEvent(input) {
423
+ const now = Number.isFinite(input.now) ? Math.round(input.now) : Date.now();
424
+ const d = {
425
+ engine: input.engine,
426
+ op: input.op,
427
+ table: input.table,
428
+ requestId: input.requestId,
429
+ rowCount: input.rowCount,
430
+ emittedRows: input.emittedRows,
431
+ truncatedRows: Math.max(0, input.rowCount - input.emittedRows),
432
+ samplePks: input.samplePks
433
+ };
434
+ const event = {
435
+ t: now,
436
+ k: DB_DIFF_BULK_EVENT_KIND,
437
+ d
438
+ };
439
+ if (input.sessionId) event.sessionId = input.sessionId;
440
+ const startedAt = normalizeStartedAt3(input.sessionStartedAt);
441
+ if (startedAt !== void 0) event.offsetMs = Math.max(0, now - startedAt);
442
+ return event;
443
+ }
444
+ function normalizeStartedAt3(startedAt) {
445
+ if (startedAt instanceof Date) {
446
+ const time = startedAt.getTime();
447
+ return Number.isFinite(time) ? time : void 0;
448
+ }
449
+ return Number.isFinite(startedAt) ? Math.round(startedAt) : void 0;
450
+ }
451
+ function emitDbDiffEvents(input) {
452
+ const { engine, op, table, requestId, rows, beforeByPk, rowCount, options } = input;
453
+ const maxRows = normalizeMaxRowsPerStatement(options.maxRowsPerStatement);
454
+ const emittedRows = Math.min(rows.length, maxRows);
455
+ const emitRows = rows.slice(0, emittedRows);
456
+ const samplePks = [];
457
+ const sensitive = buildSensitiveColumnSet(options.redactColumns);
458
+ for (const row of emitRows) {
459
+ const pk = extractPk(row, table, options.pkColumns);
460
+ const samplePk = redactPkSample(pk, sensitive);
461
+ if (samplePks.length < 3 && samplePk) samplePks.push(samplePk);
462
+ const event = buildDbDiffEvent({
463
+ engine,
464
+ op,
465
+ table,
466
+ pk,
467
+ requestId,
468
+ sessionId: options.sessionId,
469
+ redactColumns: options.redactColumns,
470
+ now: options.now?.(),
471
+ sessionStartedAt: options.sessionStartedAt,
472
+ ...op === "delete" ? { before: row } : { after: row, before: beforeByPk?.get(pkKey(pk)) }
473
+ });
474
+ options.emit(event);
475
+ }
476
+ if (rowCount > maxRows) {
477
+ for (let index = emittedRows; index < rows.length && samplePks.length < 3; index += 1) {
478
+ const samplePk = redactPkSample(
479
+ extractPk(rows[index], table, options.pkColumns),
480
+ sensitive
481
+ );
482
+ if (samplePk) samplePks.push(samplePk);
483
+ }
484
+ options.emit(
485
+ buildDbDiffBulkEvent({
486
+ engine,
487
+ op,
488
+ table,
489
+ requestId,
490
+ rowCount,
491
+ emittedRows,
492
+ samplePks,
493
+ sessionId: options.sessionId,
494
+ now: options.now?.(),
495
+ sessionStartedAt: options.sessionStartedAt
496
+ })
497
+ );
498
+ }
499
+ }
500
+ function emitImagelessDbDiff(input) {
501
+ const { engine, op, table, requestId, rowCount, options } = input;
502
+ options.emit(
503
+ buildDbDiffEvent({
504
+ engine,
505
+ op,
506
+ table,
507
+ pk: null,
508
+ rowCount,
509
+ requestId,
510
+ sessionId: options.sessionId,
511
+ redactColumns: options.redactColumns,
512
+ now: options.now?.(),
513
+ sessionStartedAt: options.sessionStartedAt
514
+ })
515
+ );
516
+ const maxRows = normalizeMaxRowsPerStatement(options.maxRowsPerStatement);
517
+ if (rowCount > maxRows) {
518
+ options.emit(
519
+ buildDbDiffBulkEvent({
520
+ engine,
521
+ op,
522
+ table,
523
+ requestId,
524
+ rowCount,
525
+ emittedRows: 0,
526
+ samplePks: [],
527
+ sessionId: options.sessionId,
528
+ now: options.now?.(),
529
+ sessionStartedAt: options.sessionStartedAt
530
+ })
531
+ );
532
+ }
533
+ }
534
+ function emitDbReadEvents(input) {
535
+ const { engine, table, requestId, rows, rowCount, options } = input;
536
+ const emittedReadRowsByRequest = input.emittedReadRowsByRequest;
537
+ const perStatementCap = normalizeReadCap(
538
+ options.maxReadRowsPerStatement,
539
+ DEFAULT_MAX_READ_ROWS_PER_STATEMENT
540
+ );
541
+ const perRequestCap = normalizeReadCap(
542
+ options.maxReadRowsPerRequest,
543
+ DEFAULT_MAX_READ_ROWS_PER_REQUEST
544
+ );
545
+ const emittedForRequest = emittedReadRowsByRequest.get(requestId) ?? 0;
546
+ const remainingForRequest = Math.max(0, perRequestCap - emittedForRequest);
547
+ const emittedRows = Math.min(
548
+ rows.length,
549
+ perStatementCap,
550
+ remainingForRequest
551
+ );
552
+ const emitRows = rows.slice(0, emittedRows);
553
+ const samplePks = [];
554
+ const sensitive = buildSensitiveColumnSet(options.redactColumns);
555
+ for (const row of emitRows) {
556
+ const pk = extractPk(row, table, options.pkColumns);
557
+ const samplePk = redactPkSample(pk, sensitive);
558
+ if (samplePks.length < 3 && samplePk) samplePks.push(samplePk);
559
+ options.emit(
560
+ buildDbReadEvent({
561
+ engine,
562
+ table,
563
+ pk,
564
+ row,
565
+ requestId,
566
+ sessionId: options.sessionId,
567
+ redactColumns: options.redactColumns,
568
+ now: options.now?.(),
569
+ sessionStartedAt: options.sessionStartedAt
570
+ })
571
+ );
572
+ emittedReadRowsByRequest.set(
573
+ requestId,
574
+ (emittedReadRowsByRequest.get(requestId) ?? 0) + 1
575
+ );
576
+ }
577
+ if (rowCount > emittedRows) {
578
+ for (let index = emittedRows; index < rows.length && samplePks.length < 3; index += 1) {
579
+ const samplePk = redactPkSample(
580
+ extractPk(rows[index], table, options.pkColumns),
581
+ sensitive
582
+ );
583
+ if (samplePk) samplePks.push(samplePk);
584
+ }
585
+ options.emit(
586
+ buildDbReadBulkEvent({
587
+ engine,
588
+ table,
589
+ requestId,
590
+ rowCount,
591
+ emittedRows,
592
+ samplePks,
593
+ sessionId: options.sessionId,
594
+ now: options.now?.(),
595
+ sessionStartedAt: options.sessionStartedAt
596
+ })
597
+ );
598
+ }
599
+ }
600
+
601
+ // src/db/pg.ts
602
+ var ENGINE = "postgres";
603
+ function instrumentPgClient(client, options) {
604
+ const emittedReadRowsByRequest = /* @__PURE__ */ new Map();
605
+ const wrappedQuery = async (text2, params) => {
606
+ if (typeof text2 !== "string") return client.query(text2, params);
607
+ let parsed;
608
+ let parsedRead;
609
+ let requestId;
610
+ try {
611
+ parsed = parseMutation(text2);
612
+ parsedRead = parsed ? void 0 : parseRead(text2);
613
+ requestId = options.requestId ?? options.getRequestId?.();
614
+ } catch {
615
+ return client.query(text2, params);
616
+ }
617
+ if (!requestId) return client.query(text2, params);
618
+ if (!parsed) {
619
+ const result2 = await client.query(text2, params);
620
+ if (options.captureReads && parsedRead) {
621
+ try {
622
+ const rows = (result2.rows ?? []).filter(isRecord);
623
+ const rowCount = typeof result2.rowCount === "number" && Number.isFinite(result2.rowCount) ? result2.rowCount : rows.length;
624
+ emitDbReadEvents({
625
+ engine: ENGINE,
626
+ table: parsedRead.table,
627
+ requestId,
628
+ rows,
629
+ rowCount,
630
+ options,
631
+ emittedReadRowsByRequest
632
+ });
633
+ } catch {
634
+ }
635
+ }
636
+ return result2;
637
+ }
638
+ const paramArray = Array.isArray(params) ? params : void 0;
639
+ let beforeByPk;
640
+ if (options.captureBefore && parsed.op === "update" && parsed.whereClause) {
641
+ try {
642
+ const pre = await client.query(
643
+ `SELECT * FROM ${parsed.table} ${parsed.whereClause}`,
644
+ paramArray
645
+ );
646
+ beforeByPk = /* @__PURE__ */ new Map();
647
+ for (const row of pre.rows ?? []) {
648
+ if (!isRecord(row)) continue;
649
+ beforeByPk.set(
650
+ pkKey(extractPk(row, parsed.table, options.pkColumns)),
651
+ row
652
+ );
653
+ }
654
+ } catch {
655
+ beforeByPk = void 0;
656
+ }
657
+ }
658
+ let instrumentedText;
659
+ try {
660
+ instrumentedText = ensureReturning(text2);
661
+ } catch {
662
+ return client.query(text2, paramArray);
663
+ }
664
+ const result = await client.query(instrumentedText, paramArray);
665
+ try {
666
+ const rows = (result.rows ?? []).filter(isRecord);
667
+ const rowCount = typeof result.rowCount === "number" && Number.isFinite(result.rowCount) ? result.rowCount : rows.length;
668
+ emitDbDiffEvents({
669
+ engine: ENGINE,
670
+ op: parsed.op,
671
+ table: parsed.table,
672
+ requestId,
673
+ rows,
674
+ beforeByPk,
675
+ rowCount,
676
+ options
677
+ });
678
+ } catch {
679
+ }
680
+ return result;
681
+ };
682
+ return new Proxy(client, {
683
+ get(target, prop, receiver) {
684
+ if (prop === "query") return wrappedQuery;
685
+ const value = Reflect.get(target, prop, receiver);
686
+ return typeof value === "function" ? value.bind(target) : value;
687
+ }
688
+ });
689
+ }
690
+
691
+ // src/db/mysql.ts
692
+ var ENGINE2 = "mysql";
693
+ function resultPayload(result) {
694
+ return Array.isArray(result) ? result[0] : void 0;
695
+ }
696
+ function mutationHeader(payload) {
697
+ if (isRecord(payload) && typeof payload.affectedRows === "number" && Number.isFinite(payload.affectedRows)) {
698
+ return payload;
699
+ }
700
+ return void 0;
701
+ }
702
+ function isPositiveInt(value) {
703
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
704
+ }
705
+ function extractWhereParams(whereClause, paramArray) {
706
+ if (!whereClause || !paramArray) return [];
707
+ const n = countPlaceholders(whereClause);
708
+ if (n <= 0) return [];
709
+ return paramArray.slice(Math.max(0, paramArray.length - n));
710
+ }
711
+ function buildPkSelect(table, pks) {
712
+ const cols = Object.keys(pks[0]);
713
+ if (cols.length === 1) {
714
+ const col = cols[0];
715
+ const placeholders = pks.map(() => "?").join(", ");
716
+ return {
717
+ text: `SELECT * FROM ${table} WHERE ${col} IN (${placeholders})`,
718
+ params: pks.map((pk) => pk[col])
719
+ };
720
+ }
721
+ const groups = pks.map(() => `(${cols.map((col) => `${col} = ?`).join(" AND ")})`).join(" OR ");
722
+ const params = [];
723
+ for (const pk of pks) for (const col of cols) params.push(pk[col]);
724
+ return { text: `SELECT * FROM ${table} WHERE ${groups}`, params };
725
+ }
726
+ function instrumentMysqlClient(client, options) {
727
+ const emittedReadRowsByRequest = /* @__PURE__ */ new Map();
728
+ const rawQuery = client.query.bind(client);
729
+ const rawExecute = typeof client.execute === "function" ? client.execute.bind(
730
+ client
731
+ ) : void 0;
732
+ const selectRows = async (text2, params) => {
733
+ const result = await rawQuery(text2, params);
734
+ const payload = resultPayload(result);
735
+ return Array.isArray(payload) ? payload.filter(isRecord) : [];
736
+ };
737
+ const captureInsert = async (parsed, result, requestId) => {
738
+ const header = mutationHeader(resultPayload(result));
739
+ if (!header || header.affectedRows <= 0) return;
740
+ const pkCols = options.pkColumns?.[parsed.table] ?? ["id"];
741
+ if (header.affectedRows === 1 && isPositiveInt(header.insertId) && pkCols.length === 1) {
742
+ let rows = [];
743
+ try {
744
+ rows = await selectRows(
745
+ `SELECT * FROM ${parsed.table} WHERE ${pkCols[0]} = ?`,
746
+ [header.insertId]
747
+ );
748
+ } catch {
749
+ rows = [];
750
+ }
751
+ if (rows.length > 0) {
752
+ emitDbDiffEvents({
753
+ engine: ENGINE2,
754
+ op: "insert",
755
+ table: parsed.table,
756
+ requestId,
757
+ rows,
758
+ rowCount: header.affectedRows,
759
+ options
760
+ });
761
+ return;
762
+ }
763
+ }
764
+ emitImagelessDbDiff({
765
+ engine: ENGINE2,
766
+ op: "insert",
767
+ table: parsed.table,
768
+ requestId,
769
+ rowCount: header.affectedRows,
770
+ options
771
+ });
772
+ };
773
+ const captureUpdate = async (parsed, result, requestId, preRows) => {
774
+ const header = mutationHeader(resultPayload(result));
775
+ const affected = header ? header.affectedRows : 0;
776
+ if (affected <= 0) return;
777
+ if (parsed.whereClause && preRows && preRows.length > 0) {
778
+ const beforeByPk = /* @__PURE__ */ new Map();
779
+ const pkList = [];
780
+ for (const row of preRows) {
781
+ const pk = extractPk(row, parsed.table, options.pkColumns);
782
+ beforeByPk.set(pkKey(pk), row);
783
+ if (pk) pkList.push(pk);
784
+ }
785
+ const maxRows = normalizeMaxRowsPerStatement(options.maxRowsPerStatement);
786
+ const cappedPks = pkList.slice(0, maxRows);
787
+ let postRows = [];
788
+ if (cappedPks.length > 0) {
789
+ const pkSelect = buildPkSelect(parsed.table, cappedPks);
790
+ try {
791
+ postRows = await selectRows(pkSelect.text, pkSelect.params);
792
+ } catch {
793
+ postRows = [];
794
+ }
795
+ }
796
+ emitDbDiffEvents({
797
+ engine: ENGINE2,
798
+ op: "update",
799
+ table: parsed.table,
800
+ requestId,
801
+ rows: postRows,
802
+ beforeByPk: options.captureBefore ? beforeByPk : void 0,
803
+ rowCount: affected,
804
+ options
805
+ });
806
+ if (options.captureBefore) {
807
+ const postKeys = new Set(
808
+ postRows.map(
809
+ (row) => pkKey(extractPk(row, parsed.table, options.pkColumns))
810
+ )
811
+ );
812
+ for (const pk of cappedPks) {
813
+ const key = pkKey(pk);
814
+ if (postKeys.has(key)) continue;
815
+ const before = beforeByPk.get(key);
816
+ if (!before) continue;
817
+ options.emit(
818
+ buildDbDiffEvent({
819
+ engine: ENGINE2,
820
+ op: "update",
821
+ table: parsed.table,
822
+ pk,
823
+ before,
824
+ requestId,
825
+ sessionId: options.sessionId,
826
+ redactColumns: options.redactColumns,
827
+ now: options.now?.(),
828
+ sessionStartedAt: options.sessionStartedAt
829
+ })
830
+ );
831
+ }
832
+ }
833
+ return;
834
+ }
835
+ emitImagelessDbDiff({
836
+ engine: ENGINE2,
837
+ op: "update",
838
+ table: parsed.table,
839
+ requestId,
840
+ rowCount: affected,
841
+ options
842
+ });
843
+ };
844
+ const captureDelete = async (parsed, result, requestId, preRows) => {
845
+ const header = mutationHeader(resultPayload(result));
846
+ const affected = header ? header.affectedRows : 0;
847
+ if (affected <= 0) return;
848
+ if (parsed.whereClause && preRows && preRows.length > 0) {
849
+ emitDbDiffEvents({
850
+ engine: ENGINE2,
851
+ op: "delete",
852
+ table: parsed.table,
853
+ requestId,
854
+ rows: preRows,
855
+ rowCount: affected,
856
+ options
857
+ });
858
+ return;
859
+ }
860
+ emitImagelessDbDiff({
861
+ engine: ENGINE2,
862
+ op: "delete",
863
+ table: parsed.table,
864
+ requestId,
865
+ rowCount: affected,
866
+ options
867
+ });
868
+ };
869
+ const makeInstrumented = (run) => async (sql, values) => {
870
+ if (typeof sql !== "string") return run(sql, values);
871
+ let parsed;
872
+ let parsedRead;
873
+ let requestId;
874
+ try {
875
+ parsed = parseMutation(sql);
876
+ parsedRead = parsed ? void 0 : parseRead(sql);
877
+ requestId = options.requestId ?? options.getRequestId?.();
878
+ } catch {
879
+ return run(sql, values);
880
+ }
881
+ if (!requestId) return run(sql, values);
882
+ const paramArray = Array.isArray(values) ? values : void 0;
883
+ if (!parsed) {
884
+ const result2 = await run(sql, values);
885
+ if (options.captureReads && parsedRead) {
886
+ try {
887
+ const payload = resultPayload(result2);
888
+ const rows = Array.isArray(payload) ? payload.filter(isRecord) : [];
889
+ emitDbReadEvents({
890
+ engine: ENGINE2,
891
+ table: parsedRead.table,
892
+ requestId,
893
+ rows,
894
+ rowCount: rows.length,
895
+ options,
896
+ emittedReadRowsByRequest
897
+ });
898
+ } catch {
899
+ }
900
+ }
901
+ return result2;
902
+ }
903
+ if (parsed.op === "insert") {
904
+ const result2 = await run(sql, values);
905
+ try {
906
+ await captureInsert(parsed, result2, requestId);
907
+ } catch {
908
+ }
909
+ return result2;
910
+ }
911
+ const parsedMutation = parsed;
912
+ let preRows;
913
+ if (parsedMutation.whereClause) {
914
+ const whereParams = extractWhereParams(
915
+ parsedMutation.whereClause,
916
+ paramArray
917
+ );
918
+ try {
919
+ preRows = await selectRows(
920
+ `SELECT * FROM ${parsedMutation.table} ${parsedMutation.whereClause}`,
921
+ whereParams
922
+ );
923
+ } catch {
924
+ preRows = void 0;
925
+ }
926
+ }
927
+ const result = await run(sql, values);
928
+ try {
929
+ if (parsedMutation.op === "update") {
930
+ await captureUpdate(parsedMutation, result, requestId, preRows);
931
+ } else {
932
+ await captureDelete(parsedMutation, result, requestId, preRows);
933
+ }
934
+ } catch {
935
+ }
936
+ return result;
937
+ };
938
+ const wrappedQuery = makeInstrumented(rawQuery);
939
+ const wrappedExecute = rawExecute ? makeInstrumented(rawExecute) : void 0;
940
+ return new Proxy(client, {
941
+ get(target, prop, receiver) {
942
+ if (prop === "query") return wrappedQuery;
943
+ if (prop === "execute" && wrappedExecute) return wrappedExecute;
944
+ const value = Reflect.get(target, prop, receiver);
945
+ return typeof value === "function" ? value.bind(target) : value;
946
+ }
947
+ });
948
+ }
949
+
950
+ // src/db/mssql.ts
951
+ var ENGINE3 = "mssql";
952
+ var IDENT_PART2 = String.raw`(?:"[^"]+"|\`[^\`]+\`|\[[^\]]+\]|[\w$]+)`;
953
+ var TABLE_IDENT2 = String.raw`${IDENT_PART2}(?:\s*\.\s*${IDENT_PART2})*`;
954
+ var TOP_CLAUSE2 = String.raw`(?:top\s*(?:\(\s*\d+\s*\)|\d+)\s+)?`;
955
+ var DELETE_TARGET_RE = new RegExp(
956
+ String.raw`^\s*delete\s+${TOP_CLAUSE2}from\s+(?:only\s+)?${TABLE_IDENT2}`,
957
+ "i"
958
+ );
959
+ function matchesKeywordAt(sql, index, keyword) {
960
+ const words = keyword.split(" ");
961
+ let pos = index;
962
+ for (let w = 0; w < words.length; w += 1) {
963
+ const word = words[w];
964
+ if (sql.slice(pos, pos + word.length).toLowerCase() !== word) return false;
965
+ pos += word.length;
966
+ if (w < words.length - 1) {
967
+ const wsStart = pos;
968
+ while (pos < sql.length && /\s/.test(sql[pos])) pos += 1;
969
+ if (pos === wsStart) return false;
970
+ }
971
+ }
972
+ const after = sql[pos];
973
+ return after === void 0 || !/[\w$]/.test(after);
974
+ }
975
+ function skipRegion(sql, i) {
976
+ const ch = sql[i];
977
+ const n = sql.length;
978
+ if (ch === "-" && sql[i + 1] === "-") {
979
+ let j = i + 2;
980
+ while (j < n && sql[j] !== "\n") j += 1;
981
+ return j;
982
+ }
983
+ if (ch === "/" && sql[i + 1] === "*") {
984
+ let j = i + 2;
985
+ while (j < n && !(sql[j] === "*" && sql[j + 1] === "/")) j += 1;
986
+ return j >= n ? n : j + 2;
987
+ }
988
+ if (ch === "'" || ch === '"') {
989
+ let j = i + 1;
990
+ while (j < n) {
991
+ if (sql[j] === ch) {
992
+ if (sql[j + 1] === ch) {
993
+ j += 2;
994
+ continue;
995
+ }
996
+ return j + 1;
997
+ }
998
+ j += 1;
999
+ }
1000
+ return n;
1001
+ }
1002
+ if (ch === "`" || ch === "[") {
1003
+ const close = ch === "`" ? "`" : "]";
1004
+ let j = i + 1;
1005
+ while (j < n && sql[j] !== close) j += 1;
1006
+ return j >= n ? n : j + 1;
1007
+ }
1008
+ return null;
1009
+ }
1010
+ function findTopLevelKeywordIndex(sql, keywords) {
1011
+ let depth = 0;
1012
+ let i = 0;
1013
+ const n = sql.length;
1014
+ while (i < n) {
1015
+ const skipped = skipRegion(sql, i);
1016
+ if (skipped !== null) {
1017
+ i = skipped;
1018
+ continue;
1019
+ }
1020
+ const ch = sql[i];
1021
+ if (ch === "(") {
1022
+ depth += 1;
1023
+ i += 1;
1024
+ continue;
1025
+ }
1026
+ if (ch === ")") {
1027
+ if (depth > 0) depth -= 1;
1028
+ i += 1;
1029
+ continue;
1030
+ }
1031
+ if (depth === 0) {
1032
+ const before = i === 0 ? " " : sql[i - 1];
1033
+ if (!/[\w$]/.test(before)) {
1034
+ for (const keyword of keywords) {
1035
+ if (matchesKeywordAt(sql, i, keyword)) return i;
1036
+ }
1037
+ }
1038
+ }
1039
+ i += 1;
1040
+ }
1041
+ return void 0;
1042
+ }
1043
+ function isQuoteChar(ch) {
1044
+ return ch === "'" || ch === '"' || ch === "`";
1045
+ }
1046
+ function scanStatementSafety(sql) {
1047
+ const n = sql.length;
1048
+ let depth = 0;
1049
+ let sawTopLevelSemicolon = false;
1050
+ let multiStatement = false;
1051
+ let quoteInComment = false;
1052
+ let unterminated = false;
1053
+ let i = 0;
1054
+ while (i < n) {
1055
+ const ch = sql[i];
1056
+ if (ch === "-" && sql[i + 1] === "-") {
1057
+ i += 2;
1058
+ while (i < n && sql[i] !== "\n") {
1059
+ if (isQuoteChar(sql[i])) quoteInComment = true;
1060
+ i += 1;
1061
+ }
1062
+ continue;
1063
+ }
1064
+ if (ch === "/" && sql[i + 1] === "*") {
1065
+ i += 2;
1066
+ let closed = false;
1067
+ while (i < n) {
1068
+ if (sql[i] === "*" && sql[i + 1] === "/") {
1069
+ i += 2;
1070
+ closed = true;
1071
+ break;
1072
+ }
1073
+ if (isQuoteChar(sql[i])) quoteInComment = true;
1074
+ i += 1;
1075
+ }
1076
+ if (!closed) {
1077
+ unterminated = true;
1078
+ break;
1079
+ }
1080
+ continue;
1081
+ }
1082
+ if (sawTopLevelSemicolon && !/\s/.test(ch)) {
1083
+ multiStatement = true;
1084
+ break;
1085
+ }
1086
+ if (ch === "'" || ch === '"') {
1087
+ const quote = ch;
1088
+ i += 1;
1089
+ let closed = false;
1090
+ while (i < n) {
1091
+ if (sql[i] === quote) {
1092
+ if (sql[i + 1] === quote) {
1093
+ i += 2;
1094
+ continue;
1095
+ }
1096
+ i += 1;
1097
+ closed = true;
1098
+ break;
1099
+ }
1100
+ i += 1;
1101
+ }
1102
+ if (!closed) {
1103
+ unterminated = true;
1104
+ break;
1105
+ }
1106
+ continue;
1107
+ }
1108
+ if (ch === "`" || ch === "[") {
1109
+ const close = ch === "`" ? "`" : "]";
1110
+ i += 1;
1111
+ let closed = false;
1112
+ while (i < n) {
1113
+ if (sql[i] === close) {
1114
+ i += 1;
1115
+ closed = true;
1116
+ break;
1117
+ }
1118
+ i += 1;
1119
+ }
1120
+ if (!closed) {
1121
+ unterminated = true;
1122
+ break;
1123
+ }
1124
+ continue;
1125
+ }
1126
+ if (ch === "(") {
1127
+ depth += 1;
1128
+ i += 1;
1129
+ continue;
1130
+ }
1131
+ if (ch === ")") {
1132
+ if (depth > 0) depth -= 1;
1133
+ i += 1;
1134
+ continue;
1135
+ }
1136
+ if (depth === 0 && ch === ";") {
1137
+ sawTopLevelSemicolon = true;
1138
+ i += 1;
1139
+ continue;
1140
+ }
1141
+ i += 1;
1142
+ }
1143
+ return { unterminated, multiStatement, quoteInComment };
1144
+ }
1145
+ function spliceBefore(sql, index, clause) {
1146
+ return `${sql.slice(0, index)}${clause} ${sql.slice(index)}`;
1147
+ }
1148
+ function injectInsertOutput(sql) {
1149
+ const index = findTopLevelKeywordIndex(sql, [
1150
+ "default values",
1151
+ "values",
1152
+ "select"
1153
+ ]);
1154
+ if (index === void 0) return void 0;
1155
+ return spliceBefore(sql, index, "OUTPUT INSERTED.*");
1156
+ }
1157
+ var HAS_FROM_OR_WHERE_RE = /\b(?:from|where)\b/i;
1158
+ function injectUpdateOutput(sql) {
1159
+ const fromIndex = findTopLevelKeywordIndex(sql, ["from"]);
1160
+ if (fromIndex !== void 0)
1161
+ return spliceBefore(sql, fromIndex, "OUTPUT INSERTED.*");
1162
+ const whereIndex = findTopLevelKeywordIndex(sql, ["where"]);
1163
+ if (whereIndex !== void 0)
1164
+ return spliceBefore(sql, whereIndex, "OUTPUT INSERTED.*");
1165
+ if (HAS_FROM_OR_WHERE_RE.test(sql)) return void 0;
1166
+ return `${sql.replace(/;\s*$/, "")} OUTPUT INSERTED.*`;
1167
+ }
1168
+ function injectDeleteOutput(sql) {
1169
+ const match = DELETE_TARGET_RE.exec(sql);
1170
+ if (!match) return void 0;
1171
+ const end = match[0].length;
1172
+ return `${sql.slice(0, end)} OUTPUT DELETED.*${sql.slice(end)}`;
1173
+ }
1174
+ function injectOutput(op, sql) {
1175
+ if (op === "insert") return injectInsertOutput(sql);
1176
+ if (op === "update") return injectUpdateOutput(sql);
1177
+ if (op === "delete") return injectDeleteOutput(sql);
1178
+ return void 0;
1179
+ }
1180
+ function hasOutputClause(sql) {
1181
+ return findTopLevelKeywordIndex(sql, ["output"]) !== void 0;
1182
+ }
1183
+ var COMPILE_RERUN_ERROR_NUMBERS = /* @__PURE__ */ new Set([
1184
+ 334,
1185
+ 156,
1186
+ 102
1187
+ ]);
1188
+ function isSafeCompileRerunError(err) {
1189
+ if (!err || typeof err !== "object") return false;
1190
+ const candidate = err;
1191
+ if (candidate.number !== void 0 && candidate.number !== null) {
1192
+ return typeof candidate.number === "number" && COMPILE_RERUN_ERROR_NUMBERS.has(candidate.number);
1193
+ }
1194
+ const message = typeof candidate.message === "string" ? candidate.message : "";
1195
+ return /output clause/i.test(message) && /trigger/i.test(message);
1196
+ }
1197
+ function recordsetRows(result) {
1198
+ return (Array.isArray(result?.recordset) ? result.recordset : []).filter(
1199
+ isRecord
1200
+ );
1201
+ }
1202
+ function rowCountFromResult(result, fallback) {
1203
+ const affected = result?.rowsAffected;
1204
+ if (Array.isArray(affected) && Number.isFinite(affected[0]))
1205
+ return affected[0];
1206
+ return fallback;
1207
+ }
1208
+ function stripInjectedRows(result) {
1209
+ return { ...result, recordset: void 0, recordsets: [] };
1210
+ }
1211
+ function replayInputs(request, recordedInputs) {
1212
+ for (const args of recordedInputs) {
1213
+ request.input(...args);
1214
+ }
1215
+ }
1216
+ function instrumentMssqlPool(pool, options) {
1217
+ const emittedReadRowsByRequest = /* @__PURE__ */ new Map();
1218
+ const runInstrumentedQuery = async (request, recordedInputs, text2) => {
1219
+ if (typeof text2 !== "string") return request.query(text2);
1220
+ const sql = text2;
1221
+ let parsed;
1222
+ let parsedRead;
1223
+ let requestId;
1224
+ try {
1225
+ parsed = parseMutation(sql);
1226
+ parsedRead = parsed ? void 0 : parseRead(sql);
1227
+ requestId = options.requestId ?? options.getRequestId?.();
1228
+ } catch {
1229
+ return request.query(sql);
1230
+ }
1231
+ if (!requestId) return request.query(sql);
1232
+ if (!parsed) {
1233
+ const result2 = await request.query(sql);
1234
+ if (options.captureReads && parsedRead) {
1235
+ try {
1236
+ const rows = recordsetRows(result2);
1237
+ emitDbReadEvents({
1238
+ engine: ENGINE3,
1239
+ table: parsedRead.table,
1240
+ requestId,
1241
+ rows,
1242
+ rowCount: rowCountFromResult(result2, rows.length),
1243
+ options,
1244
+ emittedReadRowsByRequest
1245
+ });
1246
+ } catch {
1247
+ }
1248
+ }
1249
+ return result2;
1250
+ }
1251
+ const mutation = parsed;
1252
+ const reqId = requestId;
1253
+ const runOriginalWithImagelessDiff = async () => {
1254
+ const result2 = await request.query(sql);
1255
+ try {
1256
+ const rowCount = rowCountFromResult(result2, 0);
1257
+ if (rowCount > 0) {
1258
+ emitImagelessDbDiff({
1259
+ engine: ENGINE3,
1260
+ op: mutation.op,
1261
+ table: mutation.table,
1262
+ requestId: reqId,
1263
+ rowCount,
1264
+ options
1265
+ });
1266
+ }
1267
+ } catch {
1268
+ }
1269
+ return result2;
1270
+ };
1271
+ if (hasOutputClause(sql)) return request.query(sql);
1272
+ const safety = scanStatementSafety(sql);
1273
+ if (safety.unterminated || safety.multiStatement || safety.quoteInComment) {
1274
+ return runOriginalWithImagelessDiff();
1275
+ }
1276
+ let beforeByPk;
1277
+ if (options.captureBefore && mutation.op === "update" && mutation.whereClause) {
1278
+ try {
1279
+ const pre = pool.request();
1280
+ replayInputs(pre, recordedInputs);
1281
+ const preResult = await pre.query(
1282
+ `SELECT * FROM ${mutation.table} ${mutation.whereClause}`
1283
+ );
1284
+ beforeByPk = /* @__PURE__ */ new Map();
1285
+ for (const row of recordsetRows(preResult)) {
1286
+ beforeByPk.set(
1287
+ pkKey(extractPk(row, mutation.table, options.pkColumns)),
1288
+ row
1289
+ );
1290
+ }
1291
+ } catch {
1292
+ beforeByPk = void 0;
1293
+ }
1294
+ }
1295
+ let injectedText;
1296
+ try {
1297
+ injectedText = injectOutput(mutation.op, sql);
1298
+ } catch {
1299
+ injectedText = void 0;
1300
+ }
1301
+ if (injectedText === void 0) {
1302
+ return runOriginalWithImagelessDiff();
1303
+ }
1304
+ let result;
1305
+ try {
1306
+ result = await request.query(injectedText);
1307
+ } catch (err) {
1308
+ if (!isSafeCompileRerunError(err)) throw err;
1309
+ const fresh = pool.request();
1310
+ replayInputs(fresh, recordedInputs);
1311
+ const fallbackResult = await fresh.query(sql);
1312
+ try {
1313
+ const rowCount = rowCountFromResult(fallbackResult, 0);
1314
+ if (rowCount > 0) {
1315
+ emitImagelessDbDiff({
1316
+ engine: ENGINE3,
1317
+ op: mutation.op,
1318
+ table: mutation.table,
1319
+ requestId: reqId,
1320
+ rowCount,
1321
+ options
1322
+ });
1323
+ }
1324
+ } catch {
1325
+ }
1326
+ return fallbackResult;
1327
+ }
1328
+ try {
1329
+ const rows = recordsetRows(result);
1330
+ const rowCount = rowCountFromResult(result, rows.length);
1331
+ if (rows.length > 0) {
1332
+ emitDbDiffEvents({
1333
+ engine: ENGINE3,
1334
+ op: mutation.op,
1335
+ table: mutation.table,
1336
+ requestId: reqId,
1337
+ rows,
1338
+ beforeByPk,
1339
+ rowCount,
1340
+ options
1341
+ });
1342
+ } else if (rowCount > 0) {
1343
+ emitImagelessDbDiff({
1344
+ engine: ENGINE3,
1345
+ op: mutation.op,
1346
+ table: mutation.table,
1347
+ requestId: reqId,
1348
+ rowCount,
1349
+ options
1350
+ });
1351
+ }
1352
+ } catch {
1353
+ }
1354
+ return stripInjectedRows(result);
1355
+ };
1356
+ const wrapRequest = (raw) => {
1357
+ const recordedInputs = [];
1358
+ const proxy = new Proxy(raw, {
1359
+ get(target, prop, receiver) {
1360
+ if (prop === "input") {
1361
+ return (...args) => {
1362
+ target.input.apply(target, args);
1363
+ recordedInputs.push(args);
1364
+ return proxy;
1365
+ };
1366
+ }
1367
+ if (prop === "query") {
1368
+ return (text2) => runInstrumentedQuery(target, recordedInputs, text2);
1369
+ }
1370
+ const value = Reflect.get(target, prop, receiver);
1371
+ return typeof value === "function" ? value.bind(target) : value;
1372
+ }
1373
+ });
1374
+ return proxy;
1375
+ };
1376
+ return new Proxy(pool, {
1377
+ get(target, prop, receiver) {
1378
+ if (prop === "request") {
1379
+ return (...args) => wrapRequest(
1380
+ target.request.apply(target, args)
1381
+ );
1382
+ }
1383
+ if (prop === "query" && typeof target.query === "function") {
1384
+ return (text2, ...rest) => {
1385
+ if (typeof text2 === "string" && rest.length === 0) {
1386
+ return wrapRequest(target.request()).query(text2);
1387
+ }
1388
+ return target.query.call(
1389
+ target,
1390
+ text2,
1391
+ ...rest
1392
+ );
1393
+ };
1394
+ }
1395
+ const value = Reflect.get(target, prop, receiver);
1396
+ return typeof value === "function" ? value.bind(target) : value;
1397
+ }
1398
+ });
1399
+ }
1400
+
1401
+ // src/db/sqlite.ts
1402
+ var ENGINE4 = "sqlite";
1403
+ function resolveParams(args) {
1404
+ if (args.length === 1 && isRecord(args[0])) {
1405
+ return { kind: "named", value: args[0] };
1406
+ }
1407
+ const values = [];
1408
+ for (const arg of args) {
1409
+ if (Array.isArray(arg)) values.push(...arg);
1410
+ else values.push(arg);
1411
+ }
1412
+ return { kind: "positional", values };
1413
+ }
1414
+ function bindReusedParams(resolved, whereClause) {
1415
+ if (resolved.kind === "named") return [resolved.value];
1416
+ const count = countPlaceholders(whereClause);
1417
+ const start = Math.max(0, resolved.values.length - count);
1418
+ return resolved.values.slice(start);
1419
+ }
1420
+ function buildPostSelectByPk(table, pkCols, pks) {
1421
+ if (pks.length === 0 || pkCols.length === 0) return null;
1422
+ if (pkCols.length === 1) {
1423
+ const col = pkCols[0];
1424
+ const params2 = pks.map((pk) => pk[col]);
1425
+ const placeholders = params2.map(() => "?").join(", ");
1426
+ return {
1427
+ sql: `SELECT * FROM ${table} WHERE ${col} IN (${placeholders})`,
1428
+ params: params2
1429
+ };
1430
+ }
1431
+ const clause = pks.map(() => `(${pkCols.map((col) => `${col} = ?`).join(" AND ")})`).join(" OR ");
1432
+ const params = pks.flatMap((pk) => pkCols.map((col) => pk[col]));
1433
+ return { sql: `SELECT * FROM ${table} WHERE ${clause}`, params };
1434
+ }
1435
+ function toChangeCount(info) {
1436
+ if (isRecord(info) && typeof info.changes === "number" && Number.isFinite(info.changes)) {
1437
+ return Math.max(0, Math.floor(info.changes));
1438
+ }
1439
+ return 0;
1440
+ }
1441
+ function validRowid(info) {
1442
+ if (!isRecord(info)) return void 0;
1443
+ const rowid = info.lastInsertRowid;
1444
+ if (typeof rowid === "number" && Number.isFinite(rowid) && rowid > 0)
1445
+ return rowid;
1446
+ if (typeof rowid === "bigint" && rowid > 0n) return rowid;
1447
+ return void 0;
1448
+ }
1449
+ function instrumentSqliteDatabase(db, options) {
1450
+ const emittedReadRowsByRequest = /* @__PURE__ */ new Map();
1451
+ const resolveRequestId = () => options.requestId ?? options.getRequestId?.();
1452
+ const handleInsert = (realStmt, parsed, args, requestId) => {
1453
+ const info = realStmt.run(...args);
1454
+ try {
1455
+ const changes = toChangeCount(info);
1456
+ const rowid = validRowid(info);
1457
+ if (changes === 1 && rowid !== void 0) {
1458
+ let afterRow;
1459
+ try {
1460
+ const row = db.prepare(`SELECT * FROM ${parsed.table} WHERE rowid = ?`).get(rowid);
1461
+ if (isRecord(row)) afterRow = row;
1462
+ } catch {
1463
+ afterRow = void 0;
1464
+ }
1465
+ if (afterRow) {
1466
+ emitDbDiffEvents({
1467
+ engine: ENGINE4,
1468
+ op: "insert",
1469
+ table: parsed.table,
1470
+ requestId,
1471
+ rows: [afterRow],
1472
+ rowCount: 1,
1473
+ options
1474
+ });
1475
+ return info;
1476
+ }
1477
+ }
1478
+ if (changes > 0) {
1479
+ emitImagelessDbDiff({
1480
+ engine: ENGINE4,
1481
+ op: "insert",
1482
+ table: parsed.table,
1483
+ requestId,
1484
+ rowCount: changes,
1485
+ options
1486
+ });
1487
+ }
1488
+ } catch {
1489
+ }
1490
+ return info;
1491
+ };
1492
+ const handleUpdate = (realStmt, parsed, args, requestId) => {
1493
+ const resolved = resolveParams(args);
1494
+ const pkCols = options.pkColumns?.[parsed.table] ?? ["id"];
1495
+ let usablePks;
1496
+ let beforeByPk;
1497
+ if (parsed.whereClause) {
1498
+ try {
1499
+ const preRows = db.prepare(`SELECT * FROM ${parsed.table} ${parsed.whereClause}`).all(...bindReusedParams(resolved, parsed.whereClause));
1500
+ const pks = [];
1501
+ const beforeMap = /* @__PURE__ */ new Map();
1502
+ for (const row of Array.isArray(preRows) ? preRows : []) {
1503
+ if (!isRecord(row)) continue;
1504
+ const pk = extractPk(row, parsed.table, options.pkColumns);
1505
+ if (pk && pkCols.every((col) => col in pk)) pks.push(pk);
1506
+ if (pk) beforeMap.set(pkKey(pk), row);
1507
+ }
1508
+ usablePks = pks;
1509
+ if (options.captureBefore) beforeByPk = beforeMap;
1510
+ } catch {
1511
+ usablePks = void 0;
1512
+ beforeByPk = void 0;
1513
+ }
1514
+ }
1515
+ const info = realStmt.run(...args);
1516
+ try {
1517
+ const changes = toChangeCount(info);
1518
+ if (usablePks && usablePks.length > 0) {
1519
+ const maxRows = normalizeMaxRowsPerStatement(
1520
+ options.maxRowsPerStatement
1521
+ );
1522
+ const capped = usablePks.slice(0, maxRows);
1523
+ let afterRows;
1524
+ try {
1525
+ const post = buildPostSelectByPk(parsed.table, pkCols, capped);
1526
+ if (post) {
1527
+ const rows = db.prepare(post.sql).all(...post.params);
1528
+ afterRows = (Array.isArray(rows) ? rows : []).filter(isRecord);
1529
+ }
1530
+ } catch {
1531
+ afterRows = void 0;
1532
+ }
1533
+ if (afterRows && afterRows.length > 0) {
1534
+ emitDbDiffEvents({
1535
+ engine: ENGINE4,
1536
+ op: "update",
1537
+ table: parsed.table,
1538
+ requestId,
1539
+ rows: afterRows,
1540
+ beforeByPk,
1541
+ rowCount: changes,
1542
+ options
1543
+ });
1544
+ return info;
1545
+ }
1546
+ }
1547
+ if (changes > 0) {
1548
+ emitImagelessDbDiff({
1549
+ engine: ENGINE4,
1550
+ op: "update",
1551
+ table: parsed.table,
1552
+ requestId,
1553
+ rowCount: changes,
1554
+ options
1555
+ });
1556
+ }
1557
+ } catch {
1558
+ }
1559
+ return info;
1560
+ };
1561
+ const handleDelete = (realStmt, parsed, args, requestId) => {
1562
+ const resolved = resolveParams(args);
1563
+ let beforeRows;
1564
+ if (parsed.whereClause) {
1565
+ try {
1566
+ const preRows = db.prepare(`SELECT * FROM ${parsed.table} ${parsed.whereClause}`).all(...bindReusedParams(resolved, parsed.whereClause));
1567
+ beforeRows = (Array.isArray(preRows) ? preRows : []).filter(isRecord);
1568
+ } catch {
1569
+ beforeRows = void 0;
1570
+ }
1571
+ }
1572
+ const info = realStmt.run(...args);
1573
+ try {
1574
+ const changes = toChangeCount(info);
1575
+ if (beforeRows && beforeRows.length > 0) {
1576
+ emitDbDiffEvents({
1577
+ engine: ENGINE4,
1578
+ op: "delete",
1579
+ table: parsed.table,
1580
+ requestId,
1581
+ rows: beforeRows,
1582
+ rowCount: changes,
1583
+ options
1584
+ });
1585
+ return info;
1586
+ }
1587
+ if (changes > 0) {
1588
+ emitImagelessDbDiff({
1589
+ engine: ENGINE4,
1590
+ op: "delete",
1591
+ table: parsed.table,
1592
+ requestId,
1593
+ rowCount: changes,
1594
+ options
1595
+ });
1596
+ }
1597
+ } catch {
1598
+ }
1599
+ return info;
1600
+ };
1601
+ const instrumentedRun = (realStmt, parsed, args) => {
1602
+ let requestId;
1603
+ try {
1604
+ requestId = resolveRequestId();
1605
+ } catch {
1606
+ return realStmt.run(...args);
1607
+ }
1608
+ if (!requestId) return realStmt.run(...args);
1609
+ switch (parsed.op) {
1610
+ case "insert":
1611
+ return handleInsert(realStmt, parsed, args, requestId);
1612
+ case "update":
1613
+ return handleUpdate(realStmt, parsed, args, requestId);
1614
+ case "delete":
1615
+ return handleDelete(realStmt, parsed, args, requestId);
1616
+ default:
1617
+ return realStmt.run(...args);
1618
+ }
1619
+ };
1620
+ const instrumentedAll = (realStmt, parsedRead, args) => {
1621
+ const result = realStmt.all(...args);
1622
+ let requestId;
1623
+ try {
1624
+ requestId = resolveRequestId();
1625
+ } catch {
1626
+ return result;
1627
+ }
1628
+ if (!requestId) return result;
1629
+ try {
1630
+ const rows = (Array.isArray(result) ? result : []).filter(isRecord);
1631
+ emitDbReadEvents({
1632
+ engine: ENGINE4,
1633
+ table: parsedRead.table,
1634
+ requestId,
1635
+ rows,
1636
+ rowCount: rows.length,
1637
+ options,
1638
+ emittedReadRowsByRequest
1639
+ });
1640
+ } catch {
1641
+ }
1642
+ return result;
1643
+ };
1644
+ const wrapStatement = (stmt, parsed, parsedRead) => new Proxy(stmt, {
1645
+ get(target, prop, _receiver) {
1646
+ if (prop === "run" && parsed) {
1647
+ return (...args) => instrumentedRun(target, parsed, args);
1648
+ }
1649
+ if (prop === "all" && parsedRead && options.captureReads) {
1650
+ return (...args) => instrumentedAll(target, parsedRead, args);
1651
+ }
1652
+ const value = Reflect.get(target, prop, target);
1653
+ return typeof value === "function" ? value.bind(target) : value;
1654
+ }
1655
+ });
1656
+ return new Proxy(db, {
1657
+ get(target, prop, _receiver) {
1658
+ if (prop === "prepare") {
1659
+ return (...prepareArgs) => {
1660
+ const realStmt = target.prepare(
1661
+ ...prepareArgs
1662
+ );
1663
+ const sql = prepareArgs[0];
1664
+ if (typeof sql !== "string") return realStmt;
1665
+ let parsed;
1666
+ let parsedRead;
1667
+ try {
1668
+ parsed = parseMutation(sql);
1669
+ parsedRead = parsed ? void 0 : parseRead(sql);
1670
+ } catch {
1671
+ return realStmt;
1672
+ }
1673
+ if (!parsed && !parsedRead) return realStmt;
1674
+ return wrapStatement(realStmt, parsed, parsedRead);
1675
+ };
1676
+ }
1677
+ const value = Reflect.get(target, prop, target);
1678
+ return typeof value === "function" ? value.bind(target) : value;
1679
+ }
1680
+ });
1681
+ }
1682
+
1683
+ // src/db/index.ts
1684
+ function resolveDbRequestContext(input) {
1685
+ const correlation = resolveBackendRequestCorrelation(input);
1686
+ return {
1687
+ requestId: correlation.requestId,
1688
+ ...correlation.sessionId ? { sessionId: correlation.sessionId } : {}
1689
+ };
1690
+ }
1691
+
1692
+ // src/backend-intake.ts
1693
+ var DEFAULT_BACKEND_INTAKE_ENDPOINT = "http://localhost:9898";
1694
+ var MAX_SAFE_STRING_LENGTH = 200;
1695
+ var MAX_WARNING_MESSAGE_LENGTH = 300;
1696
+ async function sendBackendEvent(options) {
1697
+ const event = options.event;
1698
+ const sessionId = safeString(options.sessionId) ?? safeString(event.sessionId);
1699
+ const requestId = safeString(event.d.requestId);
1700
+ const eventKind = safeString(event.k);
1701
+ const warningContext = { sessionId, requestId, eventKind };
1702
+ if (!sessionId) {
1703
+ reportWarning(options.onWarning, {
1704
+ kind: "missing-session",
1705
+ message: "Backend event was not sent because no usable session ID was available.",
1706
+ requestId,
1707
+ eventKind
1708
+ });
1709
+ return;
1710
+ }
1711
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1712
+ if (typeof fetchImpl !== "function") {
1713
+ reportWarning(options.onWarning, {
1714
+ kind: "missing-fetch",
1715
+ message: "Backend event was not sent because no fetch implementation is available.",
1716
+ ...warningContext
1717
+ });
1718
+ return;
1719
+ }
1720
+ const endpoint = normalizeEndpoint(options.endpoint);
1721
+ const headers = { "Content-Type": "application/json" };
1722
+ const authToken = options.authToken?.trim();
1723
+ if (authToken) headers["X-Crumbtrail-Auth"] = authToken;
1724
+ try {
1725
+ const response = await fetchImpl(`${endpoint}/api/events`, {
1726
+ method: "POST",
1727
+ headers,
1728
+ body: JSON.stringify({ sessionId, events: [event] }),
1729
+ signal: options.signal
1730
+ });
1731
+ if (!response.ok) {
1732
+ reportWarning(options.onWarning, {
1733
+ kind: "http-error",
1734
+ message: `Backend intake returned HTTP ${safeStatus(response.status) ?? "error"}.`,
1735
+ status: safeStatus(response.status),
1736
+ ...warningContext
1737
+ });
1738
+ return;
1739
+ }
1740
+ await readAndValidateResponse(response);
1741
+ } catch (error) {
1742
+ reportWarning(options.onWarning, {
1743
+ kind: classifyCaughtError(error),
1744
+ message: safeErrorMessage(error),
1745
+ ...warningContext
1746
+ });
1747
+ }
1748
+ }
1749
+ async function readAndValidateResponse(response) {
1750
+ if (typeof response.text === "function") {
1751
+ const text2 = await response.text();
1752
+ if (!text2.trim()) return;
1753
+ const parsed = JSON.parse(text2);
1754
+ if (!isRecord2(parsed) || parsed.ok !== true) {
1755
+ throw new MalformedBackendResponseError(
1756
+ "Backend intake response did not contain ok: true."
1757
+ );
1758
+ }
1759
+ return;
1760
+ }
1761
+ if (typeof response.json === "function") {
1762
+ const parsed = await response.json();
1763
+ if (!isRecord2(parsed) || parsed.ok !== true) {
1764
+ throw new MalformedBackendResponseError(
1765
+ "Backend intake response did not contain ok: true."
1766
+ );
1767
+ }
1768
+ }
1769
+ }
1770
+ function normalizeEndpoint(endpoint) {
1771
+ const trimmed = endpoint?.trim() || DEFAULT_BACKEND_INTAKE_ENDPOINT;
1772
+ return trimmed.replace(/\/+$/, "");
1773
+ }
1774
+ function reportWarning(onWarning, warning) {
1775
+ if (!onWarning) return;
1776
+ try {
1777
+ onWarning(
1778
+ removeUndefined({
1779
+ kind: warning.kind,
1780
+ message: boundString(warning.message, MAX_WARNING_MESSAGE_LENGTH) ?? "Backend intake warning.",
1781
+ status: warning.status,
1782
+ sessionId: safeString(warning.sessionId),
1783
+ requestId: safeString(warning.requestId),
1784
+ eventKind: safeString(warning.eventKind)
1785
+ })
1786
+ );
1787
+ } catch {
1788
+ }
1789
+ }
1790
+ function classifyCaughtError(error) {
1791
+ return error instanceof SyntaxError || error instanceof MalformedBackendResponseError ? "malformed-response" : "fetch-rejected";
1792
+ }
1793
+ function safeErrorMessage(error) {
1794
+ if (error instanceof SyntaxError || error instanceof MalformedBackendResponseError) {
1795
+ return "Backend intake response was malformed.";
1796
+ }
1797
+ if (error instanceof Error) {
1798
+ if (error.name === "AbortError")
1799
+ return "Backend intake request was aborted.";
1800
+ return boundString(error.name, MAX_WARNING_MESSAGE_LENGTH) ?? "Backend intake request failed.";
1801
+ }
1802
+ return "Backend intake request failed.";
1803
+ }
1804
+ function safeStatus(status) {
1805
+ return Number.isInteger(status) && status >= 100 && status <= 599 ? status : void 0;
1806
+ }
1807
+ function safeString(value) {
1808
+ if (typeof value !== "string" && typeof value !== "number") return void 0;
1809
+ const text2 = String(value).trim();
1810
+ return boundString(text2, MAX_SAFE_STRING_LENGTH);
1811
+ }
1812
+ function boundString(value, maxLength) {
1813
+ if (!value) return void 0;
1814
+ return value.length > maxLength ? `${value.slice(0, maxLength)}\u2026` : value;
1815
+ }
1816
+ function removeUndefined(value) {
1817
+ return Object.fromEntries(
1818
+ Object.entries(value).filter(([, entry]) => entry !== void 0)
1819
+ );
1820
+ }
1821
+ function isRecord2(value) {
1822
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1823
+ }
1824
+ var MalformedBackendResponseError = class extends Error {
1825
+ constructor(message) {
1826
+ super(message);
1827
+ this.name = "MalformedBackendResponseError";
1828
+ }
1829
+ };
1830
+
1831
+ // src/express.ts
1832
+ var requestStates = /* @__PURE__ */ new WeakMap();
1833
+ function createCrumbtrailExpressMiddleware(options = {}) {
1834
+ return function crumbtrailExpressMiddleware(req, res, next) {
1835
+ const startedAtMs = readNow(options);
1836
+ const startEvent = buildBackendRequestStartEvent({
1837
+ ...readRequestInput(req, options),
1838
+ now: startedAtMs
1839
+ });
1840
+ const state = stateFromEvent(startEvent, startedAtMs);
1841
+ requestStates.set(req, state);
1842
+ exposeRequestIdHeader(req, state);
1843
+ attemptSend(startEvent, options, state.sessionId);
1844
+ attachFinishListener(req, res, options, state);
1845
+ next();
1846
+ };
1847
+ }
1848
+ function createCrumbtrailExpressErrorMiddleware(options = {}) {
1849
+ return function crumbtrailExpressErrorMiddleware(error, req, res, next) {
1850
+ const now = readNow(options);
1851
+ const existingState = requestStates.get(req);
1852
+ const state = existingState ?? createMinimalState(req, options, now);
1853
+ if (!existingState) requestStates.set(req, state);
1854
+ exposeRequestIdHeader(req, state);
1855
+ const errorEvent = buildBackendRequestErrorEvent({
1856
+ ...readRequestInput(req, options, state),
1857
+ now,
1858
+ statusCode: safeStatusCode(res.statusCode),
1859
+ durationMs: now - state.startedAtMs,
1860
+ error
1861
+ });
1862
+ attemptSend(errorEvent, options, state.sessionId);
1863
+ next(error);
1864
+ };
1865
+ }
1866
+ function attachFinishListener(req, res, options, state) {
1867
+ if (typeof res.once !== "function") return;
1868
+ res.once("finish", () => {
1869
+ const now = readNow(options);
1870
+ const endEvent = buildBackendRequestEndEvent({
1871
+ ...readRequestInput(req, options, state),
1872
+ now,
1873
+ statusCode: safeStatusCode(res.statusCode),
1874
+ durationMs: now - state.startedAtMs
1875
+ });
1876
+ attemptSend(endEvent, options, state.sessionId);
1877
+ });
1878
+ }
1879
+ function exposeRequestIdHeader(req, state) {
1880
+ if (!state.requestId) return;
1881
+ req.headers ??= {};
1882
+ const existingKey = Object.keys(req.headers).find(
1883
+ (key) => key.toLowerCase() === CRUMBTRAIL_REQUEST_HEADER
1884
+ );
1885
+ if (!existingKey) req.headers[CRUMBTRAIL_REQUEST_HEADER] = state.requestId;
1886
+ }
1887
+ function createMinimalState(req, options, now) {
1888
+ const event = buildBackendRequestStartEvent({
1889
+ ...readRequestInput(req, options),
1890
+ now
1891
+ });
1892
+ return stateFromEvent(event, now);
1893
+ }
1894
+ function stateFromEvent(event, startedAtMs) {
1895
+ const requestId = typeof event.d.requestId === "string" ? event.d.requestId : "unknown";
1896
+ const sessionId = typeof event.sessionId === "string" ? event.sessionId : typeof event.d.sessionId === "string" ? event.d.sessionId : void 0;
1897
+ return { startedAtMs, requestId, sessionId };
1898
+ }
1899
+ function readRequestInput(req, options, state) {
1900
+ return {
1901
+ method: req.method,
1902
+ url: req.url,
1903
+ originalUrl: req.originalUrl,
1904
+ path: req.path,
1905
+ route: readRoute(req),
1906
+ headers: req.headers,
1907
+ sessionId: state?.sessionId ?? resolveRequestValue(options.sessionId, req),
1908
+ requestId: state?.requestId ?? resolveRequestValue(options.requestId, req),
1909
+ sessionStartedAt: resolveSessionStartedAt(options.sessionStartedAt, req)
1910
+ };
1911
+ }
1912
+ function readRoute(req) {
1913
+ if (typeof req.route === "string") return req.route;
1914
+ if (req.route && typeof req.route.path === "string") return req.route.path;
1915
+ return void 0;
1916
+ }
1917
+ function resolveRequestValue(value, req) {
1918
+ try {
1919
+ return typeof value === "function" ? value(req) : value;
1920
+ } catch {
1921
+ return void 0;
1922
+ }
1923
+ }
1924
+ function resolveSessionStartedAt(value, req) {
1925
+ try {
1926
+ return typeof value === "function" ? value(req) : value;
1927
+ } catch {
1928
+ return void 0;
1929
+ }
1930
+ }
1931
+ function readNow(options) {
1932
+ try {
1933
+ const value = options.now?.() ?? Date.now();
1934
+ return Number.isFinite(value) ? Math.round(value) : Date.now();
1935
+ } catch {
1936
+ return Date.now();
1937
+ }
1938
+ }
1939
+ function safeStatusCode(statusCode) {
1940
+ return Number.isFinite(statusCode) ? statusCode : void 0;
1941
+ }
1942
+ function attemptSend(event, options, sessionId) {
1943
+ void sendBackendEvent({
1944
+ event,
1945
+ sessionId,
1946
+ endpoint: options.endpoint,
1947
+ authToken: options.authToken,
1948
+ fetch: options.fetch,
1949
+ signal: options.signal,
1950
+ onWarning: options.onWarning
1951
+ }).catch(() => {
1952
+ });
1953
+ }
1954
+
1955
+ // src/headless-session.ts
1956
+ async function startHeadlessSession(options) {
1957
+ const fetcher = options.fetchImpl ?? fetch;
1958
+ const endpoint = options.endpoint.replace(/\/+$/, "");
1959
+ const headers = buildHeaders(options.authToken);
1960
+ await postJson(fetcher, `${endpoint}/api/session/start`, headers, {
1961
+ sessionId: options.sessionId,
1962
+ metadata: {
1963
+ ...options.metadata,
1964
+ source: "headless"
1965
+ }
1966
+ });
1967
+ return {
1968
+ sessionId: options.sessionId,
1969
+ async record(events) {
1970
+ const batch = Array.isArray(events) ? events : [events];
1971
+ await postJson(fetcher, `${endpoint}/api/events`, headers, {
1972
+ sessionId: options.sessionId,
1973
+ events: batch
1974
+ });
1975
+ },
1976
+ async end() {
1977
+ return postJson(fetcher, `${endpoint}/api/session/end`, headers, {
1978
+ sessionId: options.sessionId
1979
+ });
1980
+ }
1981
+ };
1982
+ }
1983
+ function buildHeaders(authToken) {
1984
+ return {
1985
+ "content-type": "application/json",
1986
+ ...authToken ? { "x-crumbtrail-auth": authToken } : {}
1987
+ };
1988
+ }
1989
+ async function postJson(fetcher, url, headers, body) {
1990
+ const response = await fetcher(url, {
1991
+ method: "POST",
1992
+ headers,
1993
+ body: JSON.stringify(body)
1994
+ });
1995
+ const text2 = await response.text();
1996
+ let parsed = {};
1997
+ try {
1998
+ parsed = text2 ? JSON.parse(text2) : {};
1999
+ } catch {
2000
+ parsed = { error: text2 || `HTTP ${response.status}` };
2001
+ }
2002
+ if (!response.ok) {
2003
+ const message = isRecord3(parsed) && typeof parsed.error === "string" ? parsed.error : `HTTP ${response.status}`;
2004
+ throw new Error(`Crumbtrail headless session request failed: ${message}`);
2005
+ }
2006
+ return isRecord3(parsed) ? parsed : {};
2007
+ }
2008
+ function isRecord3(value) {
2009
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2010
+ }
2011
+
2012
+ // src/ticket/comment.ts
2013
+ var REASON_PHRASES = {
2014
+ semantic: "wording overlap with the captured incident",
2015
+ "same-route": "same route",
2016
+ "same-error": "same error signature",
2017
+ "env-overlap": "shared environment or configuration",
2018
+ "time-proximity": "occurred near the report time",
2019
+ "release-hint": "same release"
2020
+ };
2021
+ function humanizeReason(reason) {
2022
+ return REASON_PHRASES[reason] ?? reason;
2023
+ }
2024
+ function text(value) {
2025
+ return { type: "text", text: value };
2026
+ }
2027
+ function code(value) {
2028
+ return { type: "text", text: value, marks: [{ type: "code" }] };
2029
+ }
2030
+ function link(label, href) {
2031
+ return {
2032
+ type: "text",
2033
+ text: label,
2034
+ marks: [{ type: "link", attrs: { href } }]
2035
+ };
2036
+ }
2037
+ function paragraph(...content) {
2038
+ return { type: "paragraph", content };
2039
+ }
2040
+ function bulletList(items) {
2041
+ return {
2042
+ type: "bulletList",
2043
+ content: items.map((content) => ({
2044
+ type: "listItem",
2045
+ content: [paragraph(...content)]
2046
+ }))
2047
+ };
2048
+ }
2049
+ function confidencePercent(confidence) {
2050
+ const pct = Math.round(confidence * 100);
2051
+ if (Number.isNaN(pct)) return 0;
2052
+ return Math.min(100, Math.max(0, pct));
2053
+ }
2054
+ function linkParagraph(label, bundleUrl) {
2055
+ return paragraph(text(`${label} `), link(bundleUrl, bundleUrl));
2056
+ }
2057
+ function correlationLines(correlation) {
2058
+ if (!correlation) return [];
2059
+ const items = [];
2060
+ const sessionId = typeof correlation.sessionId === "string" ? correlation.sessionId.trim() : "";
2061
+ if (sessionId) items.push([text("Session: "), code(sessionId)]);
2062
+ const seen = /* @__PURE__ */ new Set();
2063
+ for (const raw of correlation.requestIds ?? []) {
2064
+ if (typeof raw !== "string") continue;
2065
+ const id = raw.trim();
2066
+ if (!id || seen.has(id)) continue;
2067
+ seen.add(id);
2068
+ items.push([text("Request: "), code(id)]);
2069
+ if (seen.size >= 3) break;
2070
+ }
2071
+ return items;
2072
+ }
2073
+ function buildAdvisoryComment(input) {
2074
+ const { match, bundleUrl } = input;
2075
+ const gaps = input.gaps ?? [];
2076
+ const content = [];
2077
+ if (match.outcome === "matched") {
2078
+ content.push(
2079
+ paragraph(
2080
+ text(
2081
+ "Crumbtrail located a candidate incident that likely matches this ticket."
2082
+ )
2083
+ )
2084
+ );
2085
+ content.push(
2086
+ paragraph(
2087
+ text(
2088
+ `Match confidence: ${confidencePercent(match.confidence)}% (advisory \u2014 review the evidence before acting).`
2089
+ )
2090
+ )
2091
+ );
2092
+ const reasons = (match.reasons ?? []).filter(
2093
+ (reason) => typeof reason === "string" && reason.trim().length > 0
2094
+ );
2095
+ if (reasons.length > 0) {
2096
+ content.push(paragraph(text("Why this was matched:")));
2097
+ content.push(
2098
+ bulletList(reasons.map((reason) => [text(humanizeReason(reason))]))
2099
+ );
2100
+ }
2101
+ const correlationItems = correlationLines(input.correlation);
2102
+ if (correlationItems.length > 0) {
2103
+ content.push(
2104
+ paragraph(
2105
+ text("Correlation keys (match these against your logs and traces):")
2106
+ )
2107
+ );
2108
+ content.push(bulletList(correlationItems));
2109
+ }
2110
+ content.push(linkParagraph("View the full evidence bundle:", bundleUrl));
2111
+ return { version: 1, type: "doc", content };
2112
+ }
2113
+ content.push(
2114
+ paragraph(
2115
+ text(
2116
+ "Crumbtrail could not locate a recorded incident matching this ticket yet."
2117
+ )
2118
+ )
2119
+ );
2120
+ const namedGaps = gaps.filter(
2121
+ (gap) => gap && typeof gap.reason === "string" && gap.reason.trim().length > 0
2122
+ );
2123
+ if (namedGaps.length > 0) {
2124
+ content.push(paragraph(text("What is missing:")));
2125
+ content.push(
2126
+ bulletList(
2127
+ namedGaps.map((gap) => {
2128
+ const label = gap.suggestion ? `${gap.reason} \u2014 ${gap.suggestion}` : gap.reason;
2129
+ return [text(label)];
2130
+ })
2131
+ )
2132
+ );
2133
+ }
2134
+ content.push(linkParagraph("Open the evidence bundle:", bundleUrl));
2135
+ return { version: 1, type: "doc", content };
2136
+ }
2137
+
2138
+ // src/replay/result.ts
2139
+ import fs from "fs";
2140
+ var REPLAY_RESULT_SCHEMA_VERSION = "replay-result.v1";
2141
+ var RESOLUTIONS = /* @__PURE__ */ new Set(["exact", "role-label", "structural", "failed"]);
2142
+ function buildReplayResult(input) {
2143
+ return {
2144
+ schemaVersion: REPLAY_RESULT_SCHEMA_VERSION,
2145
+ sourceSessionId: input.sourceSessionId,
2146
+ actuatedSessionId: input.actuatedSessionId,
2147
+ steps: input.steps,
2148
+ divergences: input.divergences,
2149
+ completed: input.completed
2150
+ };
2151
+ }
2152
+ function parseReplayResult(raw) {
2153
+ if (!isRecord4(raw)) {
2154
+ throw new Error("replay-result.v1: payload must be an object");
2155
+ }
2156
+ if (raw.schemaVersion !== REPLAY_RESULT_SCHEMA_VERSION) {
2157
+ throw new Error(`replay-result.v1: unsupported schemaVersion ${JSON.stringify(raw.schemaVersion)}`);
2158
+ }
2159
+ if (!Array.isArray(raw.steps)) {
2160
+ throw new Error("replay-result.v1: steps must be an array");
2161
+ }
2162
+ if (!Array.isArray(raw.divergences)) {
2163
+ throw new Error("replay-result.v1: divergences must be an array");
2164
+ }
2165
+ if (typeof raw.completed !== "boolean") {
2166
+ throw new Error("replay-result.v1: completed must be a boolean");
2167
+ }
2168
+ return {
2169
+ schemaVersion: REPLAY_RESULT_SCHEMA_VERSION,
2170
+ sourceSessionId: reqString(raw, "sourceSessionId", "result"),
2171
+ actuatedSessionId: reqString(raw, "actuatedSessionId", "result"),
2172
+ steps: raw.steps.map(parseStep),
2173
+ divergences: raw.divergences.map(parseDivergence),
2174
+ completed: raw.completed
2175
+ };
2176
+ }
2177
+ function writeReplayResult(filePath, result) {
2178
+ fs.writeFileSync(filePath, `${JSON.stringify(result, null, 2)}
2179
+ `);
2180
+ }
2181
+ function parseStep(step, index) {
2182
+ const context = `steps[${index}]`;
2183
+ if (!isRecord4(step)) {
2184
+ throw new Error(`replay-result.v1: ${context} must be an object`);
2185
+ }
2186
+ const resolution = step.resolution;
2187
+ if (typeof resolution !== "string" || !RESOLUTIONS.has(resolution)) {
2188
+ throw new Error(`replay-result.v1: ${context}.resolution must be one of exact|role-label|structural|failed`);
2189
+ }
2190
+ return {
2191
+ index: reqNumber(step, "index", context),
2192
+ sig: reqString(step, "sig", context),
2193
+ action: reqString(step, "action", context),
2194
+ resolution,
2195
+ durationMs: reqNumber(step, "durationMs", context)
2196
+ };
2197
+ }
2198
+ function parseDivergence(divergence, index) {
2199
+ const context = `divergences[${index}]`;
2200
+ if (!isRecord4(divergence)) {
2201
+ throw new Error(`replay-result.v1: ${context} must be an object`);
2202
+ }
2203
+ return {
2204
+ index: reqNumber(divergence, "index", context),
2205
+ sig: reqString(divergence, "sig", context),
2206
+ reason: reqString(divergence, "reason", context)
2207
+ };
2208
+ }
2209
+ function isRecord4(value) {
2210
+ return value != null && typeof value === "object" && !Array.isArray(value);
2211
+ }
2212
+ function reqString(obj, key, context) {
2213
+ const value = obj[key];
2214
+ if (typeof value !== "string" || value.length === 0) {
2215
+ throw new Error(`replay-result.v1: ${context}.${key} must be a non-empty string`);
2216
+ }
2217
+ return value;
2218
+ }
2219
+ function reqNumber(obj, key, context) {
2220
+ const value = obj[key];
2221
+ if (typeof value !== "number" || !Number.isFinite(value)) {
2222
+ throw new Error(`replay-result.v1: ${context}.${key} must be a finite number`);
2223
+ }
2224
+ return value;
2225
+ }
2226
+
2227
+ // src/auto-capture.ts
2228
+ var AUTO_CAPTURE_ERROR_EVENT = "backend.uncaught";
2229
+ var MAX_MESSAGE = 500;
2230
+ var MAX_STACK = 4e3;
2231
+ var CRASH_FLUSH_MS = 150;
2232
+ function sleep(ms) {
2233
+ return new Promise((resolve) => {
2234
+ const timer = setTimeout(resolve, ms);
2235
+ timer.unref?.();
2236
+ });
2237
+ }
2238
+ var installed = false;
2239
+ async function autoCapture(options) {
2240
+ if (installed) {
2241
+ return { stop() {
2242
+ } };
2243
+ }
2244
+ installed = true;
2245
+ const proc = options.processImpl ?? process;
2246
+ const consoleRef = options.consoleImpl ?? console;
2247
+ if (options.loadEnv !== false) {
2248
+ try {
2249
+ const loader = proc.loadEnvFile;
2250
+ if (typeof loader === "function") loader.call(proc);
2251
+ } catch {
2252
+ }
2253
+ }
2254
+ const authToken = options.authToken ?? proc.env.CRUMBTRAIL_KEY;
2255
+ let session;
2256
+ try {
2257
+ session = await startHeadlessSession({
2258
+ endpoint: options.endpoint,
2259
+ sessionId: options.sessionId ?? generateSessionId(),
2260
+ authToken,
2261
+ metadata: { ...options.metadata, capture: "auto" },
2262
+ fetchImpl: options.fetchImpl
2263
+ });
2264
+ } catch {
2265
+ session = void 0;
2266
+ }
2267
+ let capturing = false;
2268
+ const record = (error, source) => {
2269
+ if (!session || capturing) return void 0;
2270
+ capturing = true;
2271
+ try {
2272
+ return session.record(buildErrorEvent(error, source)).catch(() => {
2273
+ });
2274
+ } catch {
2275
+ return void 0;
2276
+ } finally {
2277
+ capturing = false;
2278
+ }
2279
+ };
2280
+ const originalError = consoleRef.error;
2281
+ const patchedError = (...args) => {
2282
+ const errorArg = args.find((a) => a instanceof Error);
2283
+ record(errorArg ?? args.map((a) => String(a)).join(" "), "console.error");
2284
+ originalError.apply(consoleRef, args);
2285
+ };
2286
+ consoleRef.error = patchedError;
2287
+ const exit = (code2) => {
2288
+ const exiter = options.onCrashExit ?? ((c) => proc.exit(c));
2289
+ exiter(code2);
2290
+ };
2291
+ let crashing = false;
2292
+ const flushThenExit = async (error, source) => {
2293
+ if (crashing) return;
2294
+ crashing = true;
2295
+ try {
2296
+ const recordPromise = record(error, source);
2297
+ if (recordPromise) {
2298
+ await Promise.race([recordPromise, sleep(CRASH_FLUSH_MS)]);
2299
+ }
2300
+ } catch {
2301
+ } finally {
2302
+ exit(1);
2303
+ }
2304
+ };
2305
+ const onUncaught = (error) => {
2306
+ void flushThenExit(error, "uncaughtException");
2307
+ };
2308
+ proc.on("uncaughtException", onUncaught);
2309
+ const onUnhandled = (reason) => {
2310
+ void flushThenExit(reason, "unhandledRejection");
2311
+ };
2312
+ proc.on("unhandledRejection", onUnhandled);
2313
+ let stopped = false;
2314
+ const stop = () => {
2315
+ if (stopped) return;
2316
+ stopped = true;
2317
+ if (consoleRef.error === patchedError) {
2318
+ consoleRef.error = originalError;
2319
+ }
2320
+ proc.removeListener("uncaughtException", onUncaught);
2321
+ proc.removeListener("unhandledRejection", onUnhandled);
2322
+ installed = false;
2323
+ };
2324
+ return { sessionId: session?.sessionId, stop };
2325
+ }
2326
+ function generateSessionId() {
2327
+ const random = Math.random().toString(36).slice(2, 10);
2328
+ return `auto_${Date.now().toString(36)}_${random}`;
2329
+ }
2330
+ function buildErrorEvent(error, source) {
2331
+ const normalized = normalizeError(error);
2332
+ return {
2333
+ t: Date.now(),
2334
+ k: AUTO_CAPTURE_ERROR_EVENT,
2335
+ d: {
2336
+ source,
2337
+ error: normalized
2338
+ }
2339
+ };
2340
+ }
2341
+ function normalizeError(error) {
2342
+ if (error instanceof Error) {
2343
+ return {
2344
+ name: error.name || "Error",
2345
+ message: bounded(error.message, MAX_MESSAGE),
2346
+ ...error.stack ? { stack: bounded(error.stack, MAX_STACK) } : {}
2347
+ };
2348
+ }
2349
+ if (typeof error === "string") {
2350
+ return { name: "Error", message: bounded(error, MAX_MESSAGE) };
2351
+ }
2352
+ return {
2353
+ name: typeof error,
2354
+ message: bounded(safeString2(error), MAX_MESSAGE)
2355
+ };
2356
+ }
2357
+ function safeString2(value) {
2358
+ try {
2359
+ return typeof value === "object" ? JSON.stringify(value) : String(value);
2360
+ } catch {
2361
+ return "Non-serializable value";
2362
+ }
2363
+ }
2364
+ function bounded(value, max) {
2365
+ return value.length > max ? `${value.slice(0, max)}\u2026` : value;
2366
+ }
2367
+ export {
2368
+ AUTO_CAPTURE_ERROR_EVENT,
2369
+ BugQueueManager,
2370
+ CLOUDFLARE_AUTH_FIELDS,
2371
+ CLOUDFLARE_DESCRIPTOR,
2372
+ CLOUDFLARE_R2_ACCESS_KEY_ID_ENV,
2373
+ CLOUDFLARE_R2_ACCOUNT_ID_ENV,
2374
+ CLOUDFLARE_R2_BUCKET_ENV,
2375
+ CLOUDFLARE_R2_DATASET_ENV,
2376
+ CLOUDFLARE_R2_ENDPOINT_ENV,
2377
+ CLOUDFLARE_R2_PREFIX_ENV,
2378
+ CLOUDFLARE_R2_SECRET_ACCESS_KEY_ENV,
2379
+ CLOUDWATCH_ACCESS_KEY_ID_ENV,
2380
+ CLOUDWATCH_AUTH_FIELDS,
2381
+ CLOUDWATCH_DESCRIPTOR,
2382
+ CLOUDWATCH_ENDPOINT_ENV,
2383
+ CLOUDWATCH_LOG_GROUPS_ENV,
2384
+ CLOUDWATCH_REGION_ENV,
2385
+ CLOUDWATCH_SECRET_ACCESS_KEY_ENV,
2386
+ CLOUDWATCH_SESSION_TOKEN_ENV,
2387
+ CRUMBTRAIL_USER_AGENT,
2388
+ CloudWatchEvidenceSource,
2389
+ CloudflareEvidenceSource,
2390
+ CompareError,
2391
+ DATADOG_API_KEY_ENV,
2392
+ DATADOG_APP_KEY_ENV,
2393
+ DATADOG_AUTH_FIELDS,
2394
+ DATADOG_DEFAULT_SITE,
2395
+ DATADOG_DESCRIPTOR,
2396
+ DATADOG_SITE_ENV,
2397
+ DEFAULT_MAX_TOTAL_BYTES,
2398
+ DEFAULT_SENSITIVE_DB_COLUMNS,
2399
+ DEFAULT_SOURCE_TIMEOUT_MS,
2400
+ DEFAULT_SWEEP_CHECKPOINT_MS,
2401
+ DEFAULT_SWEEP_IDLE_MS,
2402
+ DEFAULT_SWEEP_INTERVAL_MS,
2403
+ DISTINCT_BUGS_SCHEMA_VERSION,
2404
+ DatadogEvidenceSource,
2405
+ EVIDENCE_SOURCE_PROVIDERS,
2406
+ CRUMBTRAIL_USER_AGENT as EVIDENCE_SOURCE_USER_AGENT,
2407
+ FIX_CONTEXT_SCHEMA_VERSION,
2408
+ FilesystemSessionStore,
2409
+ FixContextError,
2410
+ InspectError,
2411
+ JiraTicketClient,
2412
+ McpServer,
2413
+ POSTHOG_API_KEY_ENV,
2414
+ POSTHOG_AUTH_FIELDS,
2415
+ POSTHOG_DEFAULT_HOST,
2416
+ POSTHOG_DESCRIPTOR,
2417
+ POSTHOG_HOST_ENV,
2418
+ POSTHOG_PROJECT_ID_ENV,
2419
+ PROVIDER_IDS,
2420
+ PROVIDER_RECIPES,
2421
+ PostHogEvidenceSource,
2422
+ REGRESSION_CONTEXT_SCHEMA_VERSION,
2423
+ REPLAY_RESULT_SCHEMA_VERSION,
2424
+ SENTRY_AUTH_FIELDS,
2425
+ SENTRY_AUTH_TOKEN_ENV,
2426
+ SENTRY_DEFAULT_HOST,
2427
+ SENTRY_DESCRIPTOR,
2428
+ SENTRY_HOST_ENV,
2429
+ SENTRY_ORG_ENV,
2430
+ SESSION_COMPARE_SCHEMA_VERSION,
2431
+ SPLUNK_AUTH_FIELDS,
2432
+ SPLUNK_DESCRIPTOR,
2433
+ SPLUNK_HOST_ENV,
2434
+ SPLUNK_INDEX_ENV,
2435
+ SPLUNK_TOKEN_ENV,
2436
+ SPLUNK_WEB_URL_ENV,
2437
+ SentryEvidenceSource,
2438
+ SessionManager,
2439
+ SplunkEvidenceSource,
2440
+ TicketError,
2441
+ autoCapture,
2442
+ buildAdvisoryComment,
2443
+ buildCloudWatchQuery,
2444
+ buildCloudflarePlan,
2445
+ buildDatadogQuery,
2446
+ buildDbDiffEvent,
2447
+ buildDbReadBulkEvent,
2448
+ buildDbReadEvent,
2449
+ buildDistinctBugSignature,
2450
+ buildFixContext,
2451
+ buildPostHogQuery,
2452
+ buildRegressionContext,
2453
+ buildReplayResult,
2454
+ buildSentryQuery,
2455
+ buildSessionSummary,
2456
+ buildSplunkQuery,
2457
+ cloudWatchDeepLink,
2458
+ cloudWatchEvidenceProvider,
2459
+ cloudflareEvidenceProvider,
2460
+ compareSessions,
2461
+ createCrumbtrailExpressErrorMiddleware,
2462
+ createCrumbtrailExpressMiddleware,
2463
+ createServer,
2464
+ datadogAppBase,
2465
+ datadogEvidenceProvider,
2466
+ decodeOtlpLogsProtobuf,
2467
+ decodeOtlpTraceProtobuf,
2468
+ defaultSessionStore,
2469
+ evidenceRequestHeaders,
2470
+ evidenceSourcesFromEnv,
2471
+ fetchAdapterEvidence,
2472
+ formatComparisonSummary,
2473
+ formatDuration,
2474
+ formatInspection,
2475
+ getProviderRecipe,
2476
+ groupDistinctBugRecurrences,
2477
+ groupDistinctBugs,
2478
+ inspectSession,
2479
+ instrumentMssqlPool,
2480
+ instrumentMysqlClient,
2481
+ instrumentPgClient,
2482
+ instrumentSqliteDatabase,
2483
+ isProviderId,
2484
+ jiraToSymptom,
2485
+ normalizeCloudWatchRow,
2486
+ normalizeDatadogLog,
2487
+ normalizeDatadogSpan,
2488
+ normalizePostHogEvent,
2489
+ normalizePostHogRecording,
2490
+ normalizeSentryIssue,
2491
+ normalizeSplunkRow,
2492
+ parseMutation,
2493
+ parseRead,
2494
+ parseReplayResult,
2495
+ posthogEventDeepLink,
2496
+ posthogEvidenceProvider,
2497
+ posthogRecordingDeepLink,
2498
+ readPackageVersion,
2499
+ redactEvidenceGap,
2500
+ redactEvidenceItem,
2501
+ redactSourceResult,
2502
+ registerEvidenceProvider,
2503
+ renderCompareReport,
2504
+ renderProviderCliOutput,
2505
+ renderProviderConfig,
2506
+ renderProviderDoc,
2507
+ renderProviderReadme,
2508
+ resolveDbRequestContext,
2509
+ sentryEvidenceProvider,
2510
+ signSigV4,
2511
+ splunkEvidenceProvider,
2512
+ splunkSearchDeepLink,
2513
+ splunkWebBase,
2514
+ startHeadlessSession,
2515
+ startSessionSweeper,
2516
+ sweepIdleSessions,
2517
+ withBoundedRetry,
2518
+ writeReplayResult
2519
+ };