@saptools/cf-inspector 0.3.18 → 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
@@ -261,6 +261,9 @@ function asString(value, fallback = "") {
261
261
  function asNumber(value, fallback = 0) {
262
262
  return typeof value === "number" && Number.isFinite(value) ? value : fallback;
263
263
  }
264
+ function nonEmptyString(value) {
265
+ return typeof value === "string" && value.length > 0 ? value : void 0;
266
+ }
264
267
  function toResolvedLocations(value) {
265
268
  if (!Array.isArray(value)) {
266
269
  return [];
@@ -299,7 +302,18 @@ function toScopeChain(value) {
299
302
  return [objectId === void 0 ? base : { ...base, objectId }];
300
303
  });
301
304
  }
302
- function toCallFrames(value) {
305
+ function resolveCallFrameUrl(frame, scripts) {
306
+ const direct = nonEmptyString(frame.url);
307
+ if (direct !== void 0) {
308
+ return direct;
309
+ }
310
+ const scriptId = nonEmptyString(frame.location?.scriptId);
311
+ if (scriptId === void 0) {
312
+ return void 0;
313
+ }
314
+ return nonEmptyString(scripts?.get(scriptId)?.url);
315
+ }
316
+ function toCallFrames(value, scripts) {
303
317
  if (!Array.isArray(value)) {
304
318
  return [];
305
319
  }
@@ -312,7 +326,7 @@ function toCallFrames(value) {
312
326
  if (callFrameId.length === 0) {
313
327
  return [];
314
328
  }
315
- const url = typeof candidate.url === "string" ? candidate.url : void 0;
329
+ const url = resolveCallFrameUrl(candidate, scripts);
316
330
  const base = {
317
331
  callFrameId,
318
332
  functionName: asString(candidate.functionName),
@@ -323,13 +337,14 @@ function toCallFrames(value) {
323
337
  return [url === void 0 ? base : { ...base, url }];
324
338
  });
325
339
  }
326
- function toPauseEvent(params, receivedAtMs) {
327
- return {
340
+ function toPauseEvent(params, receivedAtMs, scripts) {
341
+ const base = {
328
342
  reason: asString(params.reason),
329
343
  hitBreakpoints: Array.isArray(params.hitBreakpoints) ? params.hitBreakpoints.filter((id) => typeof id === "string") : [],
330
- callFrames: toCallFrames(params.callFrames),
344
+ callFrames: toCallFrames(params.callFrames, scripts),
331
345
  receivedAtMs
332
346
  };
347
+ return params.data === void 0 ? base : { ...base, data: params.data };
333
348
  }
334
349
  function topFrameLocation(pause) {
335
350
  const top = pause.callFrames[0];
@@ -348,6 +363,46 @@ function pauseDetail(pause) {
348
363
  }
349
364
 
350
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
+ }
351
406
  async function setBreakpoint(session, input) {
352
407
  const remoteRoot = input.remoteRoot ?? { kind: "none" };
353
408
  const urlRegex = buildBreakpointUrlRegex({ file: input.file, remoteRoot });
@@ -355,8 +410,9 @@ async function setBreakpoint(session, input) {
355
410
  lineNumber: input.line - 1,
356
411
  urlRegex
357
412
  };
358
- if (input.condition !== void 0 && input.condition.length > 0) {
359
- params["condition"] = input.condition;
413
+ const condition = resolveCondition(input);
414
+ if (condition !== void 0) {
415
+ params["condition"] = condition;
360
416
  }
361
417
  const result = await session.client.send(
362
418
  "Debugger.setBreakpointByUrl",
@@ -502,7 +558,10 @@ async function fetchInspectorVersion(host, port, timeoutMs) {
502
558
  // src/inspector/pause.ts
503
559
  init_types();
504
560
  import { performance } from "perf_hooks";
505
- 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
+ }
506
565
  if (breakpointIds === void 0 || breakpointIds.length === 0) {
507
566
  return true;
508
567
  }
@@ -570,13 +629,13 @@ async function waitForPause(session, options) {
570
629
  if (buffered === void 0) {
571
630
  continue;
572
631
  }
573
- if (pauseMatches(buffered, options.breakpointIds)) {
632
+ if (pauseMatches(buffered, options.breakpointIds, options.pauseReasons)) {
574
633
  return buffered;
575
634
  }
576
635
  await handleUnmatchedPause(session, buffered, options, deadlineMs);
577
636
  }
578
637
  const pause = await waitForLivePause(session, options, deadlineMs);
579
- if (pauseMatches(pause, options.breakpointIds)) {
638
+ if (pauseMatches(pause, options.breakpointIds, options.pauseReasons)) {
580
639
  return pause;
581
640
  }
582
641
  await handleUnmatchedPause(session, pause, options, deadlineMs);
@@ -602,7 +661,7 @@ async function waitForLivePause(session, options, deadlineMs) {
602
661
  } finally {
603
662
  session.pauseWaitGate.active = false;
604
663
  }
605
- return toPauseEvent(params, receivedAtMs ?? performance.now());
664
+ return toPauseEvent(params, receivedAtMs ?? performance.now(), session.scripts);
606
665
  }
607
666
 
608
667
  // src/inspector/runtime.ts
@@ -610,6 +669,9 @@ init_types();
610
669
  async function resume(session) {
611
670
  await session.client.send("Debugger.resume");
612
671
  }
672
+ async function setPauseOnExceptions(session, state) {
673
+ await session.client.send("Debugger.setPauseOnExceptions", { state });
674
+ }
613
675
  async function evaluateOnFrame(session, callFrameId, expression) {
614
676
  return await session.client.send("Debugger.evaluateOnCallFrame", {
615
677
  callFrameId,
@@ -916,7 +978,7 @@ async function connectInspector(options) {
916
978
  return;
917
979
  }
918
980
  const params = raw;
919
- const event = toPauseEvent(params, performance2.now());
981
+ const event = toPauseEvent(params, performance2.now(), scripts);
920
982
  if (pauseBuffer.length >= PAUSE_BUFFER_LIMIT) {
921
983
  pauseBuffer.shift();
922
984
  }
@@ -1160,6 +1222,93 @@ async function capturePropertyChildren(session, described, depth, maxValueLength
1160
1222
  }
1161
1223
  }
1162
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
+
1163
1312
  // src/snapshot/objects.ts
1164
1313
  function objectIdFromEvalResult(result) {
1165
1314
  const inner = result.result;
@@ -1262,12 +1411,73 @@ async function captureScopes(session, frame, maxValueLength) {
1262
1411
  );
1263
1412
  }
1264
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
+
1265
1474
  // src/snapshot/capture.ts
1266
1475
  async function captureSnapshot(session, pause, options = {}) {
1267
1476
  const maxValueLength = resolveMaxValueLength(options.maxValueLength);
1268
1477
  const top = pause.callFrames[0];
1269
1478
  let topFrame;
1270
1479
  let captures = [];
1480
+ let stack = [];
1271
1481
  if (top) {
1272
1482
  topFrame = {
1273
1483
  functionName: top.functionName,
@@ -1280,14 +1490,31 @@ async function captureSnapshot(session, pause, options = {}) {
1280
1490
  topFrame = { ...topFrame, scopes };
1281
1491
  }
1282
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
+ });
1283
1498
  }
1284
- return {
1285
- reason: pause.reason,
1286
- 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,
1287
1512
  capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
1288
- ...topFrame === void 0 ? {} : { topFrame },
1289
- captures
1513
+ captures: input.captures
1290
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 };
1291
1518
  }
1292
1519
  async function captureExpressions(session, callFrameId, captures, maxValueLength) {
1293
1520
  if (captures === void 0 || captures.length === 0) {
@@ -1314,7 +1541,8 @@ async function captureExpression(session, callFrameId, expression, maxValueLengt
1314
1541
  import { randomBytes } from "crypto";
1315
1542
  var SENTINEL_PREFIX = "__CFI_LOG_";
1316
1543
  var SENTINEL_SUFFIX = "__";
1317
- function buildLogpointCondition(sentinel, expression) {
1544
+ var HITS_GLOBAL2 = "globalThis.__CFI_LOG_HITS";
1545
+ function buildLoggingIife(sentinel, expression) {
1318
1546
  return [
1319
1547
  "(function(){",
1320
1548
  `var s=${JSON.stringify(sentinel)};`,
@@ -1329,12 +1557,53 @@ function buildLogpointCondition(sentinel, expression) {
1329
1557
  "})()"
1330
1558
  ].join("");
1331
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
+ }
1332
1598
  function generateSentinel() {
1333
1599
  return `${SENTINEL_PREFIX}${randomBytes(8).toString("hex")}${SENTINEL_SUFFIX}`;
1334
1600
  }
1335
1601
 
1602
+ // src/logpoint/stream.ts
1603
+ init_types();
1604
+
1336
1605
  // src/logpoint/events.ts
1337
- function asString2(value) {
1606
+ function asString3(value) {
1338
1607
  return typeof value === "string" ? value : void 0;
1339
1608
  }
1340
1609
  function readArg(arg, index) {
@@ -1381,17 +1650,55 @@ function parsePayload(ts, at, payload) {
1381
1650
  }
1382
1651
 
1383
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
+ }
1384
1677
  async function streamLogpoint(session, options) {
1678
+ const maxEvents = validateMaxEvents(options.maxEvents);
1679
+ const hitCount = validateHitCount2(options.hitCount);
1385
1680
  const sentinel = generateSentinel();
1386
- 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
+ });
1387
1685
  let emitted = 0;
1686
+ let maxEventsReached = false;
1687
+ let stopMaxEvents;
1388
1688
  const offEvent = session.client.on("Runtime.consoleAPICalled", (raw) => {
1689
+ if (maxEventsReached) {
1690
+ return;
1691
+ }
1389
1692
  const event = toLogpointEvent(raw, sentinel, options.location);
1390
1693
  if (event === void 0) {
1391
1694
  return;
1392
1695
  }
1393
1696
  emitted += 1;
1394
1697
  options.onEvent(event);
1698
+ if (maxEvents !== void 0 && emitted >= maxEvents) {
1699
+ maxEventsReached = true;
1700
+ stopMaxEvents?.();
1701
+ }
1395
1702
  });
1396
1703
  let handle;
1397
1704
  try {
@@ -1407,7 +1714,12 @@ async function streamLogpoint(session, options) {
1407
1714
  }
1408
1715
  options.onBreakpointSet?.(handle);
1409
1716
  try {
1410
- const reason = await waitForStop(session, options);
1717
+ const reason = await waitForStop(session, options, (signal) => {
1718
+ stopMaxEvents = signal;
1719
+ if (maxEventsReached) {
1720
+ signal();
1721
+ }
1722
+ });
1411
1723
  return { handle, sentinel, emitted, stoppedReason: reason };
1412
1724
  } finally {
1413
1725
  offEvent();
@@ -1416,7 +1728,7 @@ async function streamLogpoint(session, options) {
1416
1728
  }
1417
1729
  function toLogpointEvent(raw, sentinel, location) {
1418
1730
  const params = raw;
1419
- if (asString2(params.type) !== "log") {
1731
+ if (asString3(params.type) !== "log") {
1420
1732
  return void 0;
1421
1733
  }
1422
1734
  const ts = typeof params.timestamp === "number" ? params.timestamp : void 0;
@@ -1428,7 +1740,7 @@ async function removeBreakpointBestEffort(session, breakpointId) {
1428
1740
  } catch {
1429
1741
  }
1430
1742
  }
1431
- async function waitForStop(session, options) {
1743
+ async function waitForStop(session, options, registerMaxEventsSignal) {
1432
1744
  return await new Promise((resolve) => {
1433
1745
  let settled = false;
1434
1746
  const finish = (reason) => {
@@ -1452,6 +1764,9 @@ async function waitForStop(session, options) {
1452
1764
  if (options.signal?.aborted === true) {
1453
1765
  finish("signal");
1454
1766
  }
1767
+ registerMaxEventsSignal(() => {
1768
+ finish("max-events");
1769
+ });
1455
1770
  function cleanup() {
1456
1771
  if (timer !== void 0) {
1457
1772
  clearTimeout(timer);
@@ -1487,7 +1802,9 @@ async function openCfTunnel(target) {
1487
1802
  export {
1488
1803
  CfInspectorError,
1489
1804
  buildBreakpointUrlRegex,
1805
+ buildHitCountedCondition,
1490
1806
  buildLogpointCondition,
1807
+ captureException,
1491
1808
  captureSnapshot,
1492
1809
  connectInspector,
1493
1810
  discoverInspectorTargets,
@@ -1502,8 +1819,10 @@ export {
1502
1819
  removeBreakpoint,
1503
1820
  resume,
1504
1821
  setBreakpoint,
1822
+ setPauseOnExceptions,
1505
1823
  streamLogpoint,
1506
1824
  validateExpression,
1507
- waitForPause
1825
+ waitForPause,
1826
+ walkStack
1508
1827
  };
1509
1828
  //# sourceMappingURL=index.js.map