@saptools/cf-inspector 0.3.19 → 0.4.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.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { DebuggerHandle } from '@saptools/cf-debugger';
2
2
 
3
- type CfInspectorErrorCode = "INVALID_ARGUMENT" | "INVALID_BREAKPOINT" | "INVALID_REMOTE_ROOT" | "INVALID_EXPRESSION" | "BREAKPOINT_DID_NOT_BIND" | "INSPECTOR_DISCOVERY_FAILED" | "INSPECTOR_CONNECTION_FAILED" | "CDP_REQUEST_FAILED" | "BREAKPOINT_NOT_HIT" | "UNRELATED_PAUSE" | "UNRELATED_PAUSE_TIMEOUT" | "EVALUATION_FAILED" | "MISSING_TARGET" | "ABORTED";
3
+ type CfInspectorErrorCode = "INVALID_ARGUMENT" | "INVALID_BREAKPOINT" | "INVALID_REMOTE_ROOT" | "INVALID_EXPRESSION" | "INVALID_HIT_COUNT" | "INVALID_PAUSE_TYPE" | "BREAKPOINT_DID_NOT_BIND" | "INSPECTOR_DISCOVERY_FAILED" | "INSPECTOR_CONNECTION_FAILED" | "CDP_REQUEST_FAILED" | "BREAKPOINT_NOT_HIT" | "UNRELATED_PAUSE" | "UNRELATED_PAUSE_TIMEOUT" | "EVALUATION_FAILED" | "MISSING_TARGET" | "ABORTED";
4
4
  declare class CfInspectorError extends Error {
5
5
  readonly code: CfInspectorErrorCode;
6
6
  readonly detail?: string;
@@ -55,6 +55,7 @@ interface PauseEvent {
55
55
  readonly hitBreakpoints: readonly string[];
56
56
  readonly callFrames: readonly CallFrameInfo[];
57
57
  readonly receivedAtMs?: number;
58
+ readonly data?: unknown;
58
59
  }
59
60
  interface VariableSnapshot {
60
61
  readonly name: string;
@@ -72,6 +73,7 @@ interface FrameSnapshot {
72
73
  readonly line: number;
73
74
  readonly column: number;
74
75
  readonly scopes?: readonly ScopeSnapshot[];
76
+ readonly captures?: readonly CapturedExpression[];
75
77
  }
76
78
  interface CapturedExpression {
77
79
  readonly expression: string;
@@ -79,16 +81,35 @@ interface CapturedExpression {
79
81
  readonly type?: string;
80
82
  readonly error?: string;
81
83
  }
84
+ interface ExceptionSnapshot {
85
+ readonly value?: string;
86
+ readonly type?: string;
87
+ readonly description?: string;
88
+ readonly error?: string;
89
+ }
82
90
  interface SnapshotCaptureResult {
83
91
  readonly reason: string;
84
92
  readonly hitBreakpoints: readonly string[];
85
93
  readonly capturedAt: string;
86
94
  readonly topFrame?: FrameSnapshot;
87
95
  readonly captures: readonly CapturedExpression[];
96
+ readonly stack?: readonly FrameSnapshot[];
97
+ readonly exception?: ExceptionSnapshot;
88
98
  }
89
99
  interface SnapshotResult extends SnapshotCaptureResult {
90
100
  readonly pausedDurationMs: number | null;
91
101
  }
102
+ interface WatchEvent {
103
+ readonly ts: string;
104
+ readonly at: string;
105
+ readonly hit: number;
106
+ readonly reason: string;
107
+ readonly hitBreakpoints: readonly string[];
108
+ readonly topFrame?: FrameSnapshot;
109
+ readonly captures: readonly CapturedExpression[];
110
+ readonly stack?: readonly FrameSnapshot[];
111
+ readonly exception?: ExceptionSnapshot;
112
+ }
92
113
  interface ScriptInfo {
93
114
  readonly scriptId: string;
94
115
  readonly url: string;
@@ -218,20 +239,25 @@ interface CdpProperty {
218
239
  interface SetBreakpointInput extends BreakpointLocation {
219
240
  readonly remoteRoot?: RemoteRootSetting;
220
241
  readonly condition?: string;
242
+ readonly hitCount?: number;
221
243
  }
222
244
  interface WaitForPauseOptions {
223
245
  readonly timeoutMs: number;
224
246
  readonly breakpointIds?: readonly string[];
247
+ readonly pauseReasons?: readonly string[];
225
248
  readonly unmatchedPausePolicy?: "wait-for-resume" | "fail";
226
249
  readonly onUnmatchedPause?: (pause: PauseEvent) => void;
227
250
  }
228
251
 
252
+ declare function buildHitCountedCondition(hitCount: number, counterKey: string, userCondition: string | undefined): string;
229
253
  declare function setBreakpoint(session: InspectorSession, input: SetBreakpointInput): Promise<BreakpointHandle>;
230
254
  declare function removeBreakpoint(session: InspectorSession, breakpointId: string): Promise<void>;
231
255
 
232
256
  declare function waitForPause(session: InspectorSession, options: WaitForPauseOptions): Promise<PauseEvent>;
233
257
 
258
+ type PauseOnExceptionsState = "none" | "uncaught" | "caught" | "all";
234
259
  declare function resume(session: InspectorSession): Promise<void>;
260
+ declare function setPauseOnExceptions(session: InspectorSession, state: PauseOnExceptionsState): Promise<void>;
235
261
  declare function evaluateOnFrame(session: InspectorSession, callFrameId: string, expression: string): Promise<CdpEvalResult>;
236
262
  declare function evaluateGlobal(session: InspectorSession, expression: string): Promise<CdpEvalResult>;
237
263
  declare function listScripts(session: InspectorSession): readonly ScriptInfo[];
@@ -244,10 +270,26 @@ interface CaptureSnapshotOptions {
244
270
  readonly captures?: readonly string[];
245
271
  readonly includeScopes?: boolean;
246
272
  readonly maxValueLength?: number;
273
+ readonly stackDepth?: number;
274
+ readonly stackCaptures?: readonly string[];
247
275
  }
248
276
  declare function captureSnapshot(session: InspectorSession, pause: PauseEvent, options?: CaptureSnapshotOptions): Promise<SnapshotCaptureResult>;
249
277
 
250
- declare function buildLogpointCondition(sentinel: string, expression: string): string;
278
+ declare function captureException(session: InspectorSession, pause: PauseEvent, maxValueLength: number): Promise<ExceptionSnapshot | undefined>;
279
+
280
+ interface WalkStackOptions {
281
+ readonly stackDepth: number;
282
+ readonly stackCaptures: readonly string[];
283
+ readonly maxValueLength: number;
284
+ }
285
+ declare function walkStack(session: InspectorSession, callFrames: readonly CallFrameInfo[], options: WalkStackOptions): Promise<readonly FrameSnapshot[]>;
286
+
287
+ interface LogpointConditionOptions {
288
+ readonly predicate?: string;
289
+ readonly hitCount?: number;
290
+ readonly counterKey?: string;
291
+ }
292
+ declare function buildLogpointCondition(sentinel: string, expression: string, options?: LogpointConditionOptions): string;
251
293
 
252
294
  interface LogpointEvent {
253
295
  readonly ts: string;
@@ -257,11 +299,15 @@ interface LogpointEvent {
257
299
  readonly raw?: string;
258
300
  }
259
301
 
302
+ type LogpointStopReason = "duration" | "signal" | "transport-closed" | "max-events";
260
303
  interface LogpointStreamOptions {
261
304
  readonly location: BreakpointLocation;
262
305
  readonly expression: string;
263
306
  readonly remoteRoot?: RemoteRootSetting;
264
307
  readonly durationMs?: number;
308
+ readonly maxEvents?: number;
309
+ readonly hitCount?: number;
310
+ readonly condition?: string;
265
311
  readonly signal?: AbortSignal;
266
312
  readonly onEvent: (event: LogpointEvent) => void;
267
313
  readonly onBreakpointSet?: (handle: BreakpointHandle) => void;
@@ -270,7 +316,7 @@ interface LogpointStreamResult {
270
316
  readonly handle: BreakpointHandle;
271
317
  readonly sentinel: string;
272
318
  readonly emitted: number;
273
- readonly stoppedReason: "duration" | "signal" | "transport-closed";
319
+ readonly stoppedReason: LogpointStopReason;
274
320
  }
275
321
  declare function streamLogpoint(session: InspectorSession, options: LogpointStreamOptions): Promise<LogpointStreamResult>;
276
322
 
@@ -291,4 +337,4 @@ interface OpenedTunnel {
291
337
  }
292
338
  declare function openCfTunnel(target: TunnelTarget): Promise<OpenedTunnel>;
293
339
 
294
- export { type BreakpointHandle, type BreakpointLocation, type CallFrameInfo, type CaptureSnapshotOptions, type CapturedExpression, CfInspectorError, type CfInspectorErrorCode, type DebuggerState, type FrameSnapshot, type InspectorConnectOptions, type InspectorSession, type InspectorTarget, type LogpointEvent, type LogpointStreamOptions, type LogpointStreamResult, type OpenedTunnel, type PauseEvent, type RemoteRootSetting, type ResolvedLocation, type ScopeInfo, type ScopeSnapshot, type ScriptInfo, type SetBreakpointInput, type SnapshotCaptureResult, type SnapshotResult, type TunnelTarget, type VariableSnapshot, type WaitForPauseOptions, buildBreakpointUrlRegex, buildLogpointCondition, captureSnapshot, connectInspector, discoverInspectorTargets, evaluateGlobal, evaluateOnFrame, fetchInspectorVersion, getProperties, listScripts, openCfTunnel, parseBreakpointSpec, parseRemoteRoot, removeBreakpoint, resume, setBreakpoint, streamLogpoint, validateExpression, waitForPause };
340
+ export { type BreakpointHandle, type BreakpointLocation, type CallFrameInfo, type CaptureSnapshotOptions, type CapturedExpression, CfInspectorError, type CfInspectorErrorCode, type DebuggerState, type ExceptionSnapshot, type FrameSnapshot, type InspectorConnectOptions, type InspectorSession, type InspectorTarget, type LogpointConditionOptions, type LogpointEvent, type LogpointStopReason, type LogpointStreamOptions, type LogpointStreamResult, type OpenedTunnel, type PauseEvent, type PauseOnExceptionsState, type RemoteRootSetting, type ResolvedLocation, type ScopeInfo, type ScopeSnapshot, type ScriptInfo, type SetBreakpointInput, type SnapshotCaptureResult, type SnapshotResult, type TunnelTarget, type VariableSnapshot, type WaitForPauseOptions, type WalkStackOptions, type WatchEvent, buildBreakpointUrlRegex, buildHitCountedCondition, buildLogpointCondition, captureException, captureSnapshot, connectInspector, discoverInspectorTargets, evaluateGlobal, evaluateOnFrame, fetchInspectorVersion, getProperties, listScripts, openCfTunnel, parseBreakpointSpec, parseRemoteRoot, removeBreakpoint, resume, setBreakpoint, setPauseOnExceptions, streamLogpoint, validateExpression, waitForPause, walkStack };
package/dist/index.js CHANGED
@@ -338,12 +338,13 @@ function toCallFrames(value, scripts) {
338
338
  });
339
339
  }
340
340
  function toPauseEvent(params, receivedAtMs, scripts) {
341
- return {
341
+ const base = {
342
342
  reason: asString(params.reason),
343
343
  hitBreakpoints: Array.isArray(params.hitBreakpoints) ? params.hitBreakpoints.filter((id) => typeof id === "string") : [],
344
344
  callFrames: toCallFrames(params.callFrames, scripts),
345
345
  receivedAtMs
346
346
  };
347
+ return params.data === void 0 ? base : { ...base, data: params.data };
347
348
  }
348
349
  function topFrameLocation(pause) {
349
350
  const top = pause.callFrames[0];
@@ -362,6 +363,46 @@ function pauseDetail(pause) {
362
363
  }
363
364
 
364
365
  // src/inspector/breakpoints.ts
366
+ var HITS_GLOBAL = "globalThis.__CFI_HITS";
367
+ var counterKeyCounter = 0;
368
+ function nextCounterKey(file, line) {
369
+ counterKeyCounter += 1;
370
+ return `${file}:${line.toString()}:${counterKeyCounter.toString()}`;
371
+ }
372
+ function validateHitCount(hitCount) {
373
+ if (hitCount === void 0) {
374
+ return void 0;
375
+ }
376
+ if (!Number.isInteger(hitCount) || hitCount <= 0) {
377
+ throw new CfInspectorError(
378
+ "INVALID_HIT_COUNT",
379
+ `hitCount must be a positive integer, received: ${hitCount.toString()}`
380
+ );
381
+ }
382
+ return hitCount;
383
+ }
384
+ function buildHitCountedCondition(hitCount, counterKey, userCondition) {
385
+ const keyLiteral = JSON.stringify(counterKey);
386
+ const baseCondition = userCondition !== void 0 && userCondition.trim().length > 0 ? `(${userCondition})` : "true";
387
+ return [
388
+ "(function(){",
389
+ `var m=(${HITS_GLOBAL}=${HITS_GLOBAL}||{});`,
390
+ `var k=${keyLiteral};`,
391
+ "m[k]=(m[k]||0)+1;",
392
+ `if(m[k]<${hitCount.toString()})return false;`,
393
+ `return ${baseCondition};`,
394
+ "})()"
395
+ ].join("");
396
+ }
397
+ function resolveCondition(input) {
398
+ const condition = input.condition;
399
+ const hitCount = validateHitCount(input.hitCount);
400
+ if (hitCount === void 0) {
401
+ return condition !== void 0 && condition.length > 0 ? condition : void 0;
402
+ }
403
+ const counterKey = nextCounterKey(input.file, input.line);
404
+ return buildHitCountedCondition(hitCount, counterKey, condition);
405
+ }
365
406
  async function setBreakpoint(session, input) {
366
407
  const remoteRoot = input.remoteRoot ?? { kind: "none" };
367
408
  const urlRegex = buildBreakpointUrlRegex({ file: input.file, remoteRoot });
@@ -369,8 +410,9 @@ async function setBreakpoint(session, input) {
369
410
  lineNumber: input.line - 1,
370
411
  urlRegex
371
412
  };
372
- if (input.condition !== void 0 && input.condition.length > 0) {
373
- params["condition"] = input.condition;
413
+ const condition = resolveCondition(input);
414
+ if (condition !== void 0) {
415
+ params["condition"] = condition;
374
416
  }
375
417
  const result = await session.client.send(
376
418
  "Debugger.setBreakpointByUrl",
@@ -516,7 +558,10 @@ async function fetchInspectorVersion(host, port, timeoutMs) {
516
558
  // src/inspector/pause.ts
517
559
  init_types();
518
560
  import { performance } from "perf_hooks";
519
- function pauseMatches(pause, breakpointIds) {
561
+ function pauseMatches(pause, breakpointIds, pauseReasons) {
562
+ if (pauseReasons !== void 0 && pauseReasons.length > 0) {
563
+ return pauseReasons.includes(pause.reason);
564
+ }
520
565
  if (breakpointIds === void 0 || breakpointIds.length === 0) {
521
566
  return true;
522
567
  }
@@ -584,13 +629,13 @@ async function waitForPause(session, options) {
584
629
  if (buffered === void 0) {
585
630
  continue;
586
631
  }
587
- if (pauseMatches(buffered, options.breakpointIds)) {
632
+ if (pauseMatches(buffered, options.breakpointIds, options.pauseReasons)) {
588
633
  return buffered;
589
634
  }
590
635
  await handleUnmatchedPause(session, buffered, options, deadlineMs);
591
636
  }
592
637
  const pause = await waitForLivePause(session, options, deadlineMs);
593
- if (pauseMatches(pause, options.breakpointIds)) {
638
+ if (pauseMatches(pause, options.breakpointIds, options.pauseReasons)) {
594
639
  return pause;
595
640
  }
596
641
  await handleUnmatchedPause(session, pause, options, deadlineMs);
@@ -624,6 +669,9 @@ init_types();
624
669
  async function resume(session) {
625
670
  await session.client.send("Debugger.resume");
626
671
  }
672
+ async function setPauseOnExceptions(session, state) {
673
+ await session.client.send("Debugger.setPauseOnExceptions", { state });
674
+ }
627
675
  async function evaluateOnFrame(session, callFrameId, expression) {
628
676
  return await session.client.send("Debugger.evaluateOnCallFrame", {
629
677
  callFrameId,
@@ -1174,6 +1222,93 @@ async function capturePropertyChildren(session, described, depth, maxValueLength
1174
1222
  }
1175
1223
  }
1176
1224
 
1225
+ // src/snapshot/exception.ts
1226
+ function asString2(value) {
1227
+ return typeof value === "string" && value.length > 0 ? value : void 0;
1228
+ }
1229
+ async function materializeObject(session, objectId, maxValueLength) {
1230
+ try {
1231
+ const properties = await captureProperties(
1232
+ session,
1233
+ objectId,
1234
+ MAX_SCOPE_VARIABLES,
1235
+ MAX_VARIABLE_DEPTH,
1236
+ maxValueLength
1237
+ );
1238
+ if (properties.length === 0) {
1239
+ return void 0;
1240
+ }
1241
+ const structured = {};
1242
+ for (const variable of properties) {
1243
+ structured[variable.name] = toStructuredValue(variable);
1244
+ }
1245
+ return JSON.stringify(structured);
1246
+ } catch {
1247
+ return void 0;
1248
+ }
1249
+ }
1250
+ async function readPropertyDescription(session, objectId, name) {
1251
+ try {
1252
+ const properties = await getProperties(session, objectId);
1253
+ for (const prop of properties) {
1254
+ if (prop.name !== name) {
1255
+ continue;
1256
+ }
1257
+ const value = prop.value;
1258
+ if (value === void 0) {
1259
+ continue;
1260
+ }
1261
+ if (typeof value.value === "string") {
1262
+ return value.value;
1263
+ }
1264
+ if (typeof value.description === "string") {
1265
+ return value.description;
1266
+ }
1267
+ }
1268
+ return void 0;
1269
+ } catch {
1270
+ return void 0;
1271
+ }
1272
+ }
1273
+ async function captureException(session, pause, maxValueLength) {
1274
+ if (pause.reason !== "exception" && pause.reason !== "promiseRejection") {
1275
+ return void 0;
1276
+ }
1277
+ const data = pause.data;
1278
+ if (typeof data !== "object" || data === null) {
1279
+ return { error: "no exception data attached" };
1280
+ }
1281
+ const candidate = data;
1282
+ const type = asString2(candidate.type);
1283
+ const description = asString2(candidate.description);
1284
+ if (typeof candidate.value === "string") {
1285
+ return buildResult(type, description, JSON.stringify(candidate.value), maxValueLength);
1286
+ }
1287
+ if (typeof candidate.value === "number" || typeof candidate.value === "boolean") {
1288
+ return buildResult(type, description, String(candidate.value), maxValueLength);
1289
+ }
1290
+ const objectId = asString2(candidate.objectId);
1291
+ if (objectId === void 0) {
1292
+ if (description !== void 0) {
1293
+ return buildResult(type, description, description, maxValueLength);
1294
+ }
1295
+ return { error: "exception data has no objectId or value" };
1296
+ }
1297
+ const message = await readPropertyDescription(session, objectId, "message");
1298
+ const rendered = await materializeObject(session, objectId, maxValueLength);
1299
+ if (rendered !== void 0) {
1300
+ const result = buildResult(type, description, rendered, maxValueLength);
1301
+ return message === void 0 ? result : { ...result, description: limitValueLength(message, maxValueLength) };
1302
+ }
1303
+ return buildResult(type, description, description ?? "[exception]", maxValueLength);
1304
+ }
1305
+ function buildResult(type, description, value, maxValueLength) {
1306
+ const safeValue = limitValueLength(value, maxValueLength);
1307
+ const base = { value: safeValue };
1308
+ const withType = type === void 0 ? base : { ...base, type };
1309
+ return description === void 0 ? withType : { ...withType, description: limitValueLength(description, maxValueLength) };
1310
+ }
1311
+
1177
1312
  // src/snapshot/objects.ts
1178
1313
  function objectIdFromEvalResult(result) {
1179
1314
  const inner = result.result;
@@ -1276,12 +1411,73 @@ async function captureScopes(session, frame, maxValueLength) {
1276
1411
  );
1277
1412
  }
1278
1413
 
1414
+ // src/snapshot/stack.ts
1415
+ var DEFAULT_STACK_DEPTH = 1;
1416
+ var MAX_STACK_DEPTH = 64;
1417
+ function clampDepth(depth, frameCount) {
1418
+ if (depth <= 0) {
1419
+ return 0;
1420
+ }
1421
+ return Math.min(depth, frameCount, MAX_STACK_DEPTH);
1422
+ }
1423
+ function buildBaseFrame(frame) {
1424
+ const base = {
1425
+ functionName: frame.functionName,
1426
+ line: frame.lineNumber + 1,
1427
+ column: frame.columnNumber + 1
1428
+ };
1429
+ return frame.url === void 0 ? base : { ...base, url: frame.url };
1430
+ }
1431
+ async function captureFrameExpression(session, callFrameId, expression, maxValueLength) {
1432
+ try {
1433
+ const result = await evaluateOnFrame(session, callFrameId, expression);
1434
+ const captured = evalResultToCaptured(expression, result, maxValueLength);
1435
+ return await withSerializedObjectCapture(session, expression, result, captured, maxValueLength);
1436
+ } catch (err) {
1437
+ const message = err instanceof Error ? err.message : String(err);
1438
+ return { expression, error: limitValueLength(message, maxValueLength) };
1439
+ }
1440
+ }
1441
+ async function captureFrameExpressions(session, frame, expressions, maxValueLength) {
1442
+ if (expressions.length === 0) {
1443
+ return [];
1444
+ }
1445
+ return await Promise.all(
1446
+ expressions.map(
1447
+ (expression) => captureFrameExpression(session, frame.callFrameId, expression, maxValueLength)
1448
+ )
1449
+ );
1450
+ }
1451
+ async function walkStack(session, callFrames, options) {
1452
+ const depth = clampDepth(options.stackDepth, callFrames.length);
1453
+ if (depth <= 1) {
1454
+ return [];
1455
+ }
1456
+ const slice = callFrames.slice(0, depth);
1457
+ return await Promise.all(
1458
+ slice.map(async (frame) => {
1459
+ const base = buildBaseFrame(frame);
1460
+ if (options.stackCaptures.length === 0) {
1461
+ return base;
1462
+ }
1463
+ const captures = await captureFrameExpressions(
1464
+ session,
1465
+ frame,
1466
+ options.stackCaptures,
1467
+ options.maxValueLength
1468
+ );
1469
+ return { ...base, captures };
1470
+ })
1471
+ );
1472
+ }
1473
+
1279
1474
  // src/snapshot/capture.ts
1280
1475
  async function captureSnapshot(session, pause, options = {}) {
1281
1476
  const maxValueLength = resolveMaxValueLength(options.maxValueLength);
1282
1477
  const top = pause.callFrames[0];
1283
1478
  let topFrame;
1284
1479
  let captures = [];
1480
+ let stack = [];
1285
1481
  if (top) {
1286
1482
  topFrame = {
1287
1483
  functionName: top.functionName,
@@ -1294,14 +1490,31 @@ async function captureSnapshot(session, pause, options = {}) {
1294
1490
  topFrame = { ...topFrame, scopes };
1295
1491
  }
1296
1492
  captures = await captureExpressions(session, top.callFrameId, options.captures, maxValueLength);
1493
+ stack = await walkStack(session, pause.callFrames, {
1494
+ stackDepth: options.stackDepth ?? DEFAULT_STACK_DEPTH,
1495
+ stackCaptures: options.stackCaptures ?? [],
1496
+ maxValueLength
1497
+ });
1297
1498
  }
1298
- return {
1299
- reason: pause.reason,
1300
- hitBreakpoints: pause.hitBreakpoints,
1499
+ const exception = await captureException(session, pause, maxValueLength);
1500
+ return buildResult2({
1501
+ pause,
1502
+ topFrame,
1503
+ captures,
1504
+ stack,
1505
+ exception
1506
+ });
1507
+ }
1508
+ function buildResult2(input) {
1509
+ const base = {
1510
+ reason: input.pause.reason,
1511
+ hitBreakpoints: input.pause.hitBreakpoints,
1301
1512
  capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
1302
- ...topFrame === void 0 ? {} : { topFrame },
1303
- captures
1513
+ captures: input.captures
1304
1514
  };
1515
+ const withFrame = input.topFrame === void 0 ? base : { ...base, topFrame: input.topFrame };
1516
+ const withStack = input.stack.length > 0 ? { ...withFrame, stack: input.stack } : withFrame;
1517
+ return input.exception === void 0 ? withStack : { ...withStack, exception: input.exception };
1305
1518
  }
1306
1519
  async function captureExpressions(session, callFrameId, captures, maxValueLength) {
1307
1520
  if (captures === void 0 || captures.length === 0) {
@@ -1328,7 +1541,8 @@ async function captureExpression(session, callFrameId, expression, maxValueLengt
1328
1541
  import { randomBytes } from "crypto";
1329
1542
  var SENTINEL_PREFIX = "__CFI_LOG_";
1330
1543
  var SENTINEL_SUFFIX = "__";
1331
- function buildLogpointCondition(sentinel, expression) {
1544
+ var HITS_GLOBAL2 = "globalThis.__CFI_LOG_HITS";
1545
+ function buildLoggingIife(sentinel, expression) {
1332
1546
  return [
1333
1547
  "(function(){",
1334
1548
  `var s=${JSON.stringify(sentinel)};`,
@@ -1343,12 +1557,53 @@ function buildLogpointCondition(sentinel, expression) {
1343
1557
  "})()"
1344
1558
  ].join("");
1345
1559
  }
1560
+ function buildHitGate(hitCount, counterKey) {
1561
+ const keyLiteral = JSON.stringify(counterKey);
1562
+ return [
1563
+ "(function(){",
1564
+ `var m=(${HITS_GLOBAL2}=${HITS_GLOBAL2}||{});`,
1565
+ `var k=${keyLiteral};`,
1566
+ "m[k]=(m[k]||0)+1;",
1567
+ `return m[k]>=${hitCount.toString()};`,
1568
+ "})()"
1569
+ ].join("");
1570
+ }
1571
+ function combineGuards(guards) {
1572
+ const filtered = guards.filter((guard) => guard.length > 0);
1573
+ if (filtered.length === 0) {
1574
+ return void 0;
1575
+ }
1576
+ if (filtered.length === 1) {
1577
+ return filtered[0];
1578
+ }
1579
+ return filtered.map((guard) => `(${guard})`).join("&&");
1580
+ }
1581
+ function buildLogpointCondition(sentinel, expression, options = {}) {
1582
+ const guards = [];
1583
+ if (options.hitCount !== void 0) {
1584
+ const counterKey = options.counterKey ?? sentinel;
1585
+ guards.push(buildHitGate(options.hitCount, counterKey));
1586
+ }
1587
+ const userPredicate = options.predicate?.trim();
1588
+ if (userPredicate !== void 0 && userPredicate.length > 0) {
1589
+ guards.push(`(${userPredicate})`);
1590
+ }
1591
+ const iife = buildLoggingIife(sentinel, expression);
1592
+ const guard = combineGuards(guards);
1593
+ if (guard === void 0) {
1594
+ return iife;
1595
+ }
1596
+ return `(${guard})?${iife}:false`;
1597
+ }
1346
1598
  function generateSentinel() {
1347
1599
  return `${SENTINEL_PREFIX}${randomBytes(8).toString("hex")}${SENTINEL_SUFFIX}`;
1348
1600
  }
1349
1601
 
1602
+ // src/logpoint/stream.ts
1603
+ init_types();
1604
+
1350
1605
  // src/logpoint/events.ts
1351
- function asString2(value) {
1606
+ function asString3(value) {
1352
1607
  return typeof value === "string" ? value : void 0;
1353
1608
  }
1354
1609
  function readArg(arg, index) {
@@ -1395,17 +1650,55 @@ function parsePayload(ts, at, payload) {
1395
1650
  }
1396
1651
 
1397
1652
  // src/logpoint/stream.ts
1653
+ function validateMaxEvents(maxEvents) {
1654
+ if (maxEvents === void 0) {
1655
+ return void 0;
1656
+ }
1657
+ if (!Number.isInteger(maxEvents) || maxEvents <= 0) {
1658
+ throw new CfInspectorError(
1659
+ "INVALID_ARGUMENT",
1660
+ `maxEvents must be a positive integer, received: ${maxEvents.toString()}`
1661
+ );
1662
+ }
1663
+ return maxEvents;
1664
+ }
1665
+ function validateHitCount2(hitCount) {
1666
+ if (hitCount === void 0) {
1667
+ return void 0;
1668
+ }
1669
+ if (!Number.isInteger(hitCount) || hitCount <= 0) {
1670
+ throw new CfInspectorError(
1671
+ "INVALID_HIT_COUNT",
1672
+ `hitCount must be a positive integer, received: ${hitCount.toString()}`
1673
+ );
1674
+ }
1675
+ return hitCount;
1676
+ }
1398
1677
  async function streamLogpoint(session, options) {
1678
+ const maxEvents = validateMaxEvents(options.maxEvents);
1679
+ const hitCount = validateHitCount2(options.hitCount);
1399
1680
  const sentinel = generateSentinel();
1400
- const condition = buildLogpointCondition(sentinel, options.expression);
1681
+ const condition = buildLogpointCondition(sentinel, options.expression, {
1682
+ ...options.condition === void 0 ? {} : { predicate: options.condition },
1683
+ ...hitCount === void 0 ? {} : { hitCount }
1684
+ });
1401
1685
  let emitted = 0;
1686
+ let maxEventsReached = false;
1687
+ let stopMaxEvents;
1402
1688
  const offEvent = session.client.on("Runtime.consoleAPICalled", (raw) => {
1689
+ if (maxEventsReached) {
1690
+ return;
1691
+ }
1403
1692
  const event = toLogpointEvent(raw, sentinel, options.location);
1404
1693
  if (event === void 0) {
1405
1694
  return;
1406
1695
  }
1407
1696
  emitted += 1;
1408
1697
  options.onEvent(event);
1698
+ if (maxEvents !== void 0 && emitted >= maxEvents) {
1699
+ maxEventsReached = true;
1700
+ stopMaxEvents?.();
1701
+ }
1409
1702
  });
1410
1703
  let handle;
1411
1704
  try {
@@ -1421,7 +1714,12 @@ async function streamLogpoint(session, options) {
1421
1714
  }
1422
1715
  options.onBreakpointSet?.(handle);
1423
1716
  try {
1424
- const reason = await waitForStop(session, options);
1717
+ const reason = await waitForStop(session, options, (signal) => {
1718
+ stopMaxEvents = signal;
1719
+ if (maxEventsReached) {
1720
+ signal();
1721
+ }
1722
+ });
1425
1723
  return { handle, sentinel, emitted, stoppedReason: reason };
1426
1724
  } finally {
1427
1725
  offEvent();
@@ -1430,7 +1728,7 @@ async function streamLogpoint(session, options) {
1430
1728
  }
1431
1729
  function toLogpointEvent(raw, sentinel, location) {
1432
1730
  const params = raw;
1433
- if (asString2(params.type) !== "log") {
1731
+ if (asString3(params.type) !== "log") {
1434
1732
  return void 0;
1435
1733
  }
1436
1734
  const ts = typeof params.timestamp === "number" ? params.timestamp : void 0;
@@ -1442,7 +1740,7 @@ async function removeBreakpointBestEffort(session, breakpointId) {
1442
1740
  } catch {
1443
1741
  }
1444
1742
  }
1445
- async function waitForStop(session, options) {
1743
+ async function waitForStop(session, options, registerMaxEventsSignal) {
1446
1744
  return await new Promise((resolve) => {
1447
1745
  let settled = false;
1448
1746
  const finish = (reason) => {
@@ -1466,6 +1764,9 @@ async function waitForStop(session, options) {
1466
1764
  if (options.signal?.aborted === true) {
1467
1765
  finish("signal");
1468
1766
  }
1767
+ registerMaxEventsSignal(() => {
1768
+ finish("max-events");
1769
+ });
1469
1770
  function cleanup() {
1470
1771
  if (timer !== void 0) {
1471
1772
  clearTimeout(timer);
@@ -1501,7 +1802,9 @@ async function openCfTunnel(target) {
1501
1802
  export {
1502
1803
  CfInspectorError,
1503
1804
  buildBreakpointUrlRegex,
1805
+ buildHitCountedCondition,
1504
1806
  buildLogpointCondition,
1807
+ captureException,
1505
1808
  captureSnapshot,
1506
1809
  connectInspector,
1507
1810
  discoverInspectorTargets,
@@ -1516,8 +1819,10 @@ export {
1516
1819
  removeBreakpoint,
1517
1820
  resume,
1518
1821
  setBreakpoint,
1822
+ setPauseOnExceptions,
1519
1823
  streamLogpoint,
1520
1824
  validateExpression,
1521
- waitForPause
1825
+ waitForPause,
1826
+ walkStack
1522
1827
  };
1523
1828
  //# sourceMappingURL=index.js.map