trickle-observe 0.2.71 → 0.2.73

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.
@@ -594,6 +594,28 @@ function findReassignments(source) {
594
594
  * for (let i = 0; i < n; i++) { ... } → trace i
595
595
  * Inserts trace calls at the start of the loop body.
596
596
  */
597
+ /**
598
+ * Find catch clause variables and return insertions for tracing.
599
+ * Handles: catch (err) { ... } → trace err at start of catch body.
600
+ */
601
+ function findCatchVars(source) {
602
+ const results = [];
603
+ const catchRegex = /\bcatch\s*\(\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?::\s*[^)]+?)?\s*\)\s*\{/g;
604
+ let match;
605
+ while ((match = catchRegex.exec(source)) !== null) {
606
+ const varName = match[1];
607
+ if (varName.startsWith('__trickle'))
608
+ continue;
609
+ const bodyBrace = match.index + match[0].length - 1;
610
+ let lineNo = 1;
611
+ for (let i = 0; i < match.index; i++) {
612
+ if (source[i] === '\n')
613
+ lineNo++;
614
+ }
615
+ results.push({ bodyStart: bodyBrace + 1, varNames: [varName], lineNo });
616
+ }
617
+ return results;
618
+ }
597
619
  function findForLoopVars(source) {
598
620
  const results = [];
599
621
  // Match: for (const/let/var ...
@@ -1219,9 +1241,11 @@ function transformEsmSource(source, filename, moduleName, backendUrl, debug, tra
1219
1241
  const reassignInsertions = traceVars ? findReassignments(source) : [];
1220
1242
  // Find for-loop variable declarations for tracing
1221
1243
  const forLoopInsertions = traceVars ? findForLoopVars(source) : [];
1244
+ // Find catch clause variables for tracing
1245
+ const catchInsertions = traceVars ? findCatchVars(source) : [];
1222
1246
  // Find function parameter names for tracing
1223
1247
  const funcParamInsertions = traceVars ? findFunctionParams(source, isReactFile) : [];
1224
- if (funcInsertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0 && reassignInsertions.length === 0 && forLoopInsertions.length === 0 && funcParamInsertions.length === 0 && bodyInsertions.length === 0 && hookInsertions.length === 0 && stateInsertions.length === 0 && conciseBodyInsertions.length === 0)
1248
+ if (funcInsertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0 && reassignInsertions.length === 0 && forLoopInsertions.length === 0 && catchInsertions.length === 0 && funcParamInsertions.length === 0 && bodyInsertions.length === 0 && hookInsertions.length === 0 && stateInsertions.length === 0 && conciseBodyInsertions.length === 0)
1225
1249
  return source;
1226
1250
  // Fix line numbers: Vite transforms (TypeScript stripping) may change line numbers.
1227
1251
  // Map transformed line numbers to original source line numbers.
@@ -1290,32 +1314,25 @@ function transformEsmSource(source, filename, moduleName, backendUrl, debug, tra
1290
1314
  }
1291
1315
  }
1292
1316
  // Build prefix — ALL imports first (ESM requires imports before any statements)
1293
- const needsTracing = varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || funcParamInsertions.length > 0 || bodyInsertions.length > 0 || hookInsertions.length > 0 || stateInsertions.length > 0 || conciseBodyInsertions.length > 0;
1294
- const importLines = [
1295
- `import { wrapFunction as __trickle_wrapFn, configure as __trickle_configure } from 'trickle-observe';`,
1296
- ];
1297
- if (needsTracing && isSSR) {
1298
- // SSR/Node.js — use file system for writing
1299
- importLines.push(`import { mkdirSync as __trickle_mkdirSync, appendFileSync as __trickle_appendFileSync } from 'node:fs';`, `import { join as __trickle_join } from 'node:path';`);
1317
+ const needsTracing = varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || catchInsertions.length > 0 || funcParamInsertions.length > 0 || bodyInsertions.length > 0 || hookInsertions.length > 0 || stateInsertions.length > 0 || conciseBodyInsertions.length > 0;
1318
+ const importLines = [];
1319
+ if (isSSR) {
1320
+ // SSR/Node.js — import trickle-observe for function wrapping + file system for writing
1321
+ importLines.push(`import { wrapFunction as __trickle_wrapFn, configure as __trickle_configure } from 'trickle-observe';`);
1322
+ if (needsTracing) {
1323
+ importLines.push(`import { mkdirSync as __trickle_mkdirSync, appendFileSync as __trickle_appendFileSync } from 'node:fs';`, `import { join as __trickle_join } from 'node:path';`);
1324
+ }
1325
+ }
1326
+ // Browser mode: no imports needed — variable tracers are self-contained,
1327
+ // function wrapping is a no-op, and transport uses import.meta.hot
1328
+ const prefixLines = [...importLines];
1329
+ if (isSSR) {
1330
+ prefixLines.push(`__trickle_configure({ backendUrl: ${JSON.stringify(backendUrl)}, batchIntervalMs: 2000, debug: ${debug}, enabled: true, environment: 'node' });`, `function __trickle_wrap(fn, name, paramNames) {`, ` const opts = {`, ` functionName: name,`, ` module: ${JSON.stringify(moduleName)},`, ` trackArgs: true,`, ` trackReturn: true,`, ` sampleRate: 1,`, ` maxDepth: 5,`, ` environment: 'node',`, ` enabled: true,`, ` };`, ` if (paramNames && paramNames.length) opts.paramNames = paramNames;`, ` return __trickle_wrapFn(fn, opts);`, `}`);
1331
+ }
1332
+ else {
1333
+ // Browser mode: __trickle_wrap is a no-op (function wrapping uses Node.js APIs)
1334
+ prefixLines.push(`function __trickle_wrap(fn) { return fn; }`);
1300
1335
  }
1301
- const prefixLines = [
1302
- ...importLines,
1303
- `__trickle_configure({ backendUrl: ${JSON.stringify(backendUrl)}, batchIntervalMs: 2000, debug: ${debug}, enabled: true, environment: 'node' });`,
1304
- `function __trickle_wrap(fn, name, paramNames) {`,
1305
- ` const opts = {`,
1306
- ` functionName: name,`,
1307
- ` module: ${JSON.stringify(moduleName)},`,
1308
- ` trackArgs: true,`,
1309
- ` trackReturn: true,`,
1310
- ` sampleRate: 1,`,
1311
- ` maxDepth: 5,`,
1312
- ` environment: 'node',`,
1313
- ` enabled: true,`,
1314
- ` };`,
1315
- ` if (paramNames && paramNames.length) opts.paramNames = paramNames;`,
1316
- ` return __trickle_wrapFn(fn, opts);`,
1317
- `}`,
1318
- ];
1319
1336
  // Add unified __trickle_send() transport — browser uses HMR WebSocket, SSR uses fs
1320
1337
  if (needsTracing) {
1321
1338
  if (isSSR) {
@@ -1328,7 +1345,7 @@ function transformEsmSource(source, filename, moduleName, backendUrl, debug, tra
1328
1345
  }
1329
1346
  }
1330
1347
  // Add variable tracing if needed — inlined to avoid import resolution issues in Vite SSR.
1331
- if (varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || funcParamInsertions.length > 0) {
1348
+ if (varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || catchInsertions.length > 0 || funcParamInsertions.length > 0) {
1332
1349
  prefixLines.push(`if (!globalThis.__trickle_var_tracer) {`, ` const _cache = new Set();`, ` function _inferType(v, d) {`, ` if (d <= 0) return { kind: 'primitive', name: 'unknown' };`, ` if (v === null) return { kind: 'primitive', name: 'null' };`, ` if (v === undefined) return { kind: 'primitive', name: 'undefined' };`, ` const t = typeof v;`, ` if (t === 'string' || t === 'number' || t === 'boolean' || t === 'bigint' || t === 'symbol') return { kind: 'primitive', name: t };`, ` if (t === 'function') return { kind: 'function' };`, ` if (Array.isArray(v)) { return v.length === 0 ? { kind: 'array', element: { kind: 'primitive', name: 'unknown' } } : { kind: 'array', element: _inferType(v[0], d-1) }; }`, ` if (t === 'object') {`, ` if (v instanceof Date) return { kind: 'object', properties: { __date: { kind: 'primitive', name: 'string' } } };`, ` if (v instanceof RegExp) return { kind: 'object', properties: { __regexp: { kind: 'primitive', name: 'string' } } };`, ` if (v instanceof Error) return { kind: 'object', properties: { __error: { kind: 'primitive', name: 'string' } } };`, ` if (v instanceof Promise) return { kind: 'promise', resolved: { kind: 'primitive', name: 'unknown' } };`, ` const props = {}; const keys = Object.keys(v).slice(0, 20);`, ` for (const k of keys) { try { props[k] = _inferType(v[k], d-1); } catch(e) { props[k] = { kind: 'primitive', name: 'unknown' }; } }`, ` return { kind: 'object', properties: props };`, ` }`, ` return { kind: 'primitive', name: 'unknown' };`, ` }`, ` function _sanitize(v, d) {`, ` if (d <= 0) return '[truncated]'; if (v === null || v === undefined) return v; const t = typeof v;`, ` if (t === 'string') return v.length > 100 ? v.substring(0, 100) + '...' : v;`, ` if (t === 'number' || t === 'boolean') return v; if (t === 'bigint') return String(v);`, ` if (t === 'function') return '[Function: ' + (v.name || 'anonymous') + ']';`, ` if (Array.isArray(v)) return v.slice(0, 3).map(i => _sanitize(i, d-1));`, ` if (t === 'object') { if (v instanceof Date) return v.toISOString(); if (v instanceof RegExp) return String(v); if (v instanceof Error) return { error: v.message }; if (v instanceof Promise) return '[Promise]';`, ` const r = {}; const keys = Object.keys(v).slice(0, 10); for (const k of keys) { try { r[k] = _sanitize(v[k], d-1); } catch(e) { r[k] = '[unreadable]'; } } return r; }`, ` return String(v);`, ` }`, ` globalThis.__trickle_var_tracer = function(v, n, l, mod, file) {`, ` try {`, ` const type = _inferType(v, 3);`, ` const th = JSON.stringify(type).substring(0, 32);`, ` const ck = file + ':' + l + ':' + n + ':' + th;`, ` if (_cache.has(ck)) return;`, ` _cache.add(ck);`, ` __trickle_send(JSON.stringify({ kind: 'variable', varName: n, line: l, module: mod, file: file, type: type, typeHash: th, sample: _sanitize(v, 2) }));`, ` } catch(e) {}`, ` };`, `}`, `function __trickle_tv(v, n, l) { try { globalThis.__trickle_var_tracer(v, n, l, ${JSON.stringify(moduleName)}, ${JSON.stringify(filename)}); } catch(e) {} }`);
1333
1350
  }
1334
1351
  // Add React component render tracker if needed
@@ -1373,6 +1390,14 @@ function transformEsmSource(source, filename, moduleName, backendUrl, debug, tra
1373
1390
  code: `\n;try{__trickle_tv(${varName},${JSON.stringify(varName)},${lineNo})}catch(__e){}\n`,
1374
1391
  });
1375
1392
  }
1393
+ // Catch clause insertions: insert trace at start of catch body
1394
+ for (const { bodyStart, varNames, lineNo } of catchInsertions) {
1395
+ const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
1396
+ allInsertions.push({
1397
+ position: bodyStart,
1398
+ code: `\ntry{${calls}}catch(__e2){}\n`,
1399
+ });
1400
+ }
1376
1401
  // For-loop variable insertions: insert trace at start of loop body
1377
1402
  for (const { bodyStart, varNames, lineNo } of forLoopInsertions) {
1378
1403
  const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
@@ -587,6 +587,28 @@ function findReassignments(source) {
587
587
  * for (let i = 0; i < n; i++) { ... } → trace i
588
588
  * Inserts trace calls at the start of the loop body.
589
589
  */
590
+ /**
591
+ * Find catch clause variables and return insertions for tracing.
592
+ * Handles: catch (err) { ... } → trace err at start of catch body.
593
+ */
594
+ function findCatchVars(source) {
595
+ const results = [];
596
+ const catchRegex = /\bcatch\s*\(\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?::\s*[^)]+?)?\s*\)\s*\{/g;
597
+ let match;
598
+ while ((match = catchRegex.exec(source)) !== null) {
599
+ const varName = match[1];
600
+ if (varName.startsWith('__trickle'))
601
+ continue;
602
+ const bodyBrace = match.index + match[0].length - 1;
603
+ let lineNo = 1;
604
+ for (let i = 0; i < match.index; i++) {
605
+ if (source[i] === '\n')
606
+ lineNo++;
607
+ }
608
+ results.push({ bodyStart: bodyBrace + 1, varNames: [varName], lineNo });
609
+ }
610
+ return results;
611
+ }
590
612
  function findForLoopVars(source) {
591
613
  const results = [];
592
614
  // Match: for (const/let/var ...
@@ -1212,9 +1234,11 @@ export function transformEsmSource(source, filename, moduleName, backendUrl, deb
1212
1234
  const reassignInsertions = traceVars ? findReassignments(source) : [];
1213
1235
  // Find for-loop variable declarations for tracing
1214
1236
  const forLoopInsertions = traceVars ? findForLoopVars(source) : [];
1237
+ // Find catch clause variables for tracing
1238
+ const catchInsertions = traceVars ? findCatchVars(source) : [];
1215
1239
  // Find function parameter names for tracing
1216
1240
  const funcParamInsertions = traceVars ? findFunctionParams(source, isReactFile) : [];
1217
- if (funcInsertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0 && reassignInsertions.length === 0 && forLoopInsertions.length === 0 && funcParamInsertions.length === 0 && bodyInsertions.length === 0 && hookInsertions.length === 0 && stateInsertions.length === 0 && conciseBodyInsertions.length === 0)
1241
+ if (funcInsertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0 && reassignInsertions.length === 0 && forLoopInsertions.length === 0 && catchInsertions.length === 0 && funcParamInsertions.length === 0 && bodyInsertions.length === 0 && hookInsertions.length === 0 && stateInsertions.length === 0 && conciseBodyInsertions.length === 0)
1218
1242
  return source;
1219
1243
  // Fix line numbers: Vite transforms (TypeScript stripping) may change line numbers.
1220
1244
  // Map transformed line numbers to original source line numbers.
@@ -1283,32 +1307,25 @@ export function transformEsmSource(source, filename, moduleName, backendUrl, deb
1283
1307
  }
1284
1308
  }
1285
1309
  // Build prefix — ALL imports first (ESM requires imports before any statements)
1286
- const needsTracing = varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || funcParamInsertions.length > 0 || bodyInsertions.length > 0 || hookInsertions.length > 0 || stateInsertions.length > 0 || conciseBodyInsertions.length > 0;
1287
- const importLines = [
1288
- `import { wrapFunction as __trickle_wrapFn, configure as __trickle_configure } from 'trickle-observe';`,
1289
- ];
1290
- if (needsTracing && isSSR) {
1291
- // SSR/Node.js — use file system for writing
1292
- importLines.push(`import { mkdirSync as __trickle_mkdirSync, appendFileSync as __trickle_appendFileSync } from 'node:fs';`, `import { join as __trickle_join } from 'node:path';`);
1310
+ const needsTracing = varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || catchInsertions.length > 0 || funcParamInsertions.length > 0 || bodyInsertions.length > 0 || hookInsertions.length > 0 || stateInsertions.length > 0 || conciseBodyInsertions.length > 0;
1311
+ const importLines = [];
1312
+ if (isSSR) {
1313
+ // SSR/Node.js — import trickle-observe for function wrapping + file system for writing
1314
+ importLines.push(`import { wrapFunction as __trickle_wrapFn, configure as __trickle_configure } from 'trickle-observe';`);
1315
+ if (needsTracing) {
1316
+ importLines.push(`import { mkdirSync as __trickle_mkdirSync, appendFileSync as __trickle_appendFileSync } from 'node:fs';`, `import { join as __trickle_join } from 'node:path';`);
1317
+ }
1318
+ }
1319
+ // Browser mode: no imports needed — variable tracers are self-contained,
1320
+ // function wrapping is a no-op, and transport uses import.meta.hot
1321
+ const prefixLines = [...importLines];
1322
+ if (isSSR) {
1323
+ prefixLines.push(`__trickle_configure({ backendUrl: ${JSON.stringify(backendUrl)}, batchIntervalMs: 2000, debug: ${debug}, enabled: true, environment: 'node' });`, `function __trickle_wrap(fn, name, paramNames) {`, ` const opts = {`, ` functionName: name,`, ` module: ${JSON.stringify(moduleName)},`, ` trackArgs: true,`, ` trackReturn: true,`, ` sampleRate: 1,`, ` maxDepth: 5,`, ` environment: 'node',`, ` enabled: true,`, ` };`, ` if (paramNames && paramNames.length) opts.paramNames = paramNames;`, ` return __trickle_wrapFn(fn, opts);`, `}`);
1324
+ }
1325
+ else {
1326
+ // Browser mode: __trickle_wrap is a no-op (function wrapping uses Node.js APIs)
1327
+ prefixLines.push(`function __trickle_wrap(fn) { return fn; }`);
1293
1328
  }
1294
- const prefixLines = [
1295
- ...importLines,
1296
- `__trickle_configure({ backendUrl: ${JSON.stringify(backendUrl)}, batchIntervalMs: 2000, debug: ${debug}, enabled: true, environment: 'node' });`,
1297
- `function __trickle_wrap(fn, name, paramNames) {`,
1298
- ` const opts = {`,
1299
- ` functionName: name,`,
1300
- ` module: ${JSON.stringify(moduleName)},`,
1301
- ` trackArgs: true,`,
1302
- ` trackReturn: true,`,
1303
- ` sampleRate: 1,`,
1304
- ` maxDepth: 5,`,
1305
- ` environment: 'node',`,
1306
- ` enabled: true,`,
1307
- ` };`,
1308
- ` if (paramNames && paramNames.length) opts.paramNames = paramNames;`,
1309
- ` return __trickle_wrapFn(fn, opts);`,
1310
- `}`,
1311
- ];
1312
1329
  // Add unified __trickle_send() transport — browser uses HMR WebSocket, SSR uses fs
1313
1330
  if (needsTracing) {
1314
1331
  if (isSSR) {
@@ -1321,7 +1338,7 @@ export function transformEsmSource(source, filename, moduleName, backendUrl, deb
1321
1338
  }
1322
1339
  }
1323
1340
  // Add variable tracing if needed — inlined to avoid import resolution issues in Vite SSR.
1324
- if (varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || funcParamInsertions.length > 0) {
1341
+ if (varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || catchInsertions.length > 0 || funcParamInsertions.length > 0) {
1325
1342
  prefixLines.push(`if (!globalThis.__trickle_var_tracer) {`, ` const _cache = new Set();`, ` function _inferType(v, d) {`, ` if (d <= 0) return { kind: 'primitive', name: 'unknown' };`, ` if (v === null) return { kind: 'primitive', name: 'null' };`, ` if (v === undefined) return { kind: 'primitive', name: 'undefined' };`, ` const t = typeof v;`, ` if (t === 'string' || t === 'number' || t === 'boolean' || t === 'bigint' || t === 'symbol') return { kind: 'primitive', name: t };`, ` if (t === 'function') return { kind: 'function' };`, ` if (Array.isArray(v)) { return v.length === 0 ? { kind: 'array', element: { kind: 'primitive', name: 'unknown' } } : { kind: 'array', element: _inferType(v[0], d-1) }; }`, ` if (t === 'object') {`, ` if (v instanceof Date) return { kind: 'object', properties: { __date: { kind: 'primitive', name: 'string' } } };`, ` if (v instanceof RegExp) return { kind: 'object', properties: { __regexp: { kind: 'primitive', name: 'string' } } };`, ` if (v instanceof Error) return { kind: 'object', properties: { __error: { kind: 'primitive', name: 'string' } } };`, ` if (v instanceof Promise) return { kind: 'promise', resolved: { kind: 'primitive', name: 'unknown' } };`, ` const props = {}; const keys = Object.keys(v).slice(0, 20);`, ` for (const k of keys) { try { props[k] = _inferType(v[k], d-1); } catch(e) { props[k] = { kind: 'primitive', name: 'unknown' }; } }`, ` return { kind: 'object', properties: props };`, ` }`, ` return { kind: 'primitive', name: 'unknown' };`, ` }`, ` function _sanitize(v, d) {`, ` if (d <= 0) return '[truncated]'; if (v === null || v === undefined) return v; const t = typeof v;`, ` if (t === 'string') return v.length > 100 ? v.substring(0, 100) + '...' : v;`, ` if (t === 'number' || t === 'boolean') return v; if (t === 'bigint') return String(v);`, ` if (t === 'function') return '[Function: ' + (v.name || 'anonymous') + ']';`, ` if (Array.isArray(v)) return v.slice(0, 3).map(i => _sanitize(i, d-1));`, ` if (t === 'object') { if (v instanceof Date) return v.toISOString(); if (v instanceof RegExp) return String(v); if (v instanceof Error) return { error: v.message }; if (v instanceof Promise) return '[Promise]';`, ` const r = {}; const keys = Object.keys(v).slice(0, 10); for (const k of keys) { try { r[k] = _sanitize(v[k], d-1); } catch(e) { r[k] = '[unreadable]'; } } return r; }`, ` return String(v);`, ` }`, ` globalThis.__trickle_var_tracer = function(v, n, l, mod, file) {`, ` try {`, ` const type = _inferType(v, 3);`, ` const th = JSON.stringify(type).substring(0, 32);`, ` const ck = file + ':' + l + ':' + n + ':' + th;`, ` if (_cache.has(ck)) return;`, ` _cache.add(ck);`, ` __trickle_send(JSON.stringify({ kind: 'variable', varName: n, line: l, module: mod, file: file, type: type, typeHash: th, sample: _sanitize(v, 2) }));`, ` } catch(e) {}`, ` };`, `}`, `function __trickle_tv(v, n, l) { try { globalThis.__trickle_var_tracer(v, n, l, ${JSON.stringify(moduleName)}, ${JSON.stringify(filename)}); } catch(e) {} }`);
1326
1343
  }
1327
1344
  // Add React component render tracker if needed
@@ -1366,6 +1383,14 @@ export function transformEsmSource(source, filename, moduleName, backendUrl, deb
1366
1383
  code: `\n;try{__trickle_tv(${varName},${JSON.stringify(varName)},${lineNo})}catch(__e){}\n`,
1367
1384
  });
1368
1385
  }
1386
+ // Catch clause insertions: insert trace at start of catch body
1387
+ for (const { bodyStart, varNames, lineNo } of catchInsertions) {
1388
+ const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
1389
+ allInsertions.push({
1390
+ position: bodyStart,
1391
+ code: `\ntry{${calls}}catch(__e2){}\n`,
1392
+ });
1393
+ }
1369
1394
  // For-loop variable insertions: insert trace at start of loop body
1370
1395
  for (const { bodyStart, varNames, lineNo } of forLoopInsertions) {
1371
1396
  const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trickle-observe",
3
- "version": "0.2.71",
3
+ "version": "0.2.73",
4
4
  "description": "Runtime type observability for JavaScript applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -566,6 +566,31 @@ function findReassignments(source: string): Array<{ lineEnd: number; varName: st
566
566
  * for (let i = 0; i < n; i++) { ... } → trace i
567
567
  * Inserts trace calls at the start of the loop body.
568
568
  */
569
+ /**
570
+ * Find catch clause variables and return insertions for tracing.
571
+ * Handles: catch (err) { ... } → trace err at start of catch body.
572
+ */
573
+ function findCatchVars(source: string): Array<{ bodyStart: number; varNames: string[]; lineNo: number }> {
574
+ const results: Array<{ bodyStart: number; varNames: string[]; lineNo: number }> = [];
575
+ const catchRegex = /\bcatch\s*\(\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?::\s*[^)]+?)?\s*\)\s*\{/g;
576
+ let match;
577
+
578
+ while ((match = catchRegex.exec(source)) !== null) {
579
+ const varName = match[1];
580
+ if (varName.startsWith('__trickle')) continue;
581
+
582
+ const bodyBrace = match.index + match[0].length - 1;
583
+ let lineNo = 1;
584
+ for (let i = 0; i < match.index; i++) {
585
+ if (source[i] === '\n') lineNo++;
586
+ }
587
+
588
+ results.push({ bodyStart: bodyBrace + 1, varNames: [varName], lineNo });
589
+ }
590
+
591
+ return results;
592
+ }
593
+
569
594
  function findForLoopVars(source: string): Array<{ bodyStart: number; varNames: string[]; lineNo: number }> {
570
595
  const results: Array<{ bodyStart: number; varNames: string[]; lineNo: number }> = [];
571
596
 
@@ -1209,10 +1234,13 @@ export function transformEsmSource(
1209
1234
  // Find for-loop variable declarations for tracing
1210
1235
  const forLoopInsertions = traceVars ? findForLoopVars(source) : [];
1211
1236
 
1237
+ // Find catch clause variables for tracing
1238
+ const catchInsertions = traceVars ? findCatchVars(source) : [];
1239
+
1212
1240
  // Find function parameter names for tracing
1213
1241
  const funcParamInsertions = traceVars ? findFunctionParams(source, isReactFile) : [];
1214
1242
 
1215
- if (funcInsertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0 && reassignInsertions.length === 0 && forLoopInsertions.length === 0 && funcParamInsertions.length === 0 && bodyInsertions.length === 0 && hookInsertions.length === 0 && stateInsertions.length === 0 && conciseBodyInsertions.length === 0) return source;
1243
+ if (funcInsertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0 && reassignInsertions.length === 0 && forLoopInsertions.length === 0 && catchInsertions.length === 0 && funcParamInsertions.length === 0 && bodyInsertions.length === 0 && hookInsertions.length === 0 && stateInsertions.length === 0 && conciseBodyInsertions.length === 0) return source;
1216
1244
 
1217
1245
  // Fix line numbers: Vite transforms (TypeScript stripping) may change line numbers.
1218
1246
  // Map transformed line numbers to original source line numbers.
@@ -1280,36 +1308,50 @@ export function transformEsmSource(
1280
1308
  }
1281
1309
 
1282
1310
  // Build prefix — ALL imports first (ESM requires imports before any statements)
1283
- const needsTracing = varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || funcParamInsertions.length > 0 || bodyInsertions.length > 0 || hookInsertions.length > 0 || stateInsertions.length > 0 || conciseBodyInsertions.length > 0;
1284
- const importLines: string[] = [
1285
- `import { wrapFunction as __trickle_wrapFn, configure as __trickle_configure } from 'trickle-observe';`,
1286
- ];
1287
- if (needsTracing && isSSR) {
1288
- // SSR/Node.js — use file system for writing
1311
+ const needsTracing = varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || catchInsertions.length > 0 || funcParamInsertions.length > 0 || bodyInsertions.length > 0 || hookInsertions.length > 0 || stateInsertions.length > 0 || conciseBodyInsertions.length > 0;
1312
+ const importLines: string[] = [];
1313
+
1314
+ if (isSSR) {
1315
+ // SSR/Node.js import trickle-observe for function wrapping + file system for writing
1289
1316
  importLines.push(
1290
- `import { mkdirSync as __trickle_mkdirSync, appendFileSync as __trickle_appendFileSync } from 'node:fs';`,
1291
- `import { join as __trickle_join } from 'node:path';`,
1317
+ `import { wrapFunction as __trickle_wrapFn, configure as __trickle_configure } from 'trickle-observe';`,
1292
1318
  );
1319
+ if (needsTracing) {
1320
+ importLines.push(
1321
+ `import { mkdirSync as __trickle_mkdirSync, appendFileSync as __trickle_appendFileSync } from 'node:fs';`,
1322
+ `import { join as __trickle_join } from 'node:path';`,
1323
+ );
1324
+ }
1293
1325
  }
1326
+ // Browser mode: no imports needed — variable tracers are self-contained,
1327
+ // function wrapping is a no-op, and transport uses import.meta.hot
1294
1328
 
1295
- const prefixLines = [
1296
- ...importLines,
1297
- `__trickle_configure({ backendUrl: ${JSON.stringify(backendUrl)}, batchIntervalMs: 2000, debug: ${debug}, enabled: true, environment: 'node' });`,
1298
- `function __trickle_wrap(fn, name, paramNames) {`,
1299
- ` const opts = {`,
1300
- ` functionName: name,`,
1301
- ` module: ${JSON.stringify(moduleName)},`,
1302
- ` trackArgs: true,`,
1303
- ` trackReturn: true,`,
1304
- ` sampleRate: 1,`,
1305
- ` maxDepth: 5,`,
1306
- ` environment: 'node',`,
1307
- ` enabled: true,`,
1308
- ` };`,
1309
- ` if (paramNames && paramNames.length) opts.paramNames = paramNames;`,
1310
- ` return __trickle_wrapFn(fn, opts);`,
1311
- `}`,
1312
- ];
1329
+ const prefixLines = [...importLines];
1330
+
1331
+ if (isSSR) {
1332
+ prefixLines.push(
1333
+ `__trickle_configure({ backendUrl: ${JSON.stringify(backendUrl)}, batchIntervalMs: 2000, debug: ${debug}, enabled: true, environment: 'node' });`,
1334
+ `function __trickle_wrap(fn, name, paramNames) {`,
1335
+ ` const opts = {`,
1336
+ ` functionName: name,`,
1337
+ ` module: ${JSON.stringify(moduleName)},`,
1338
+ ` trackArgs: true,`,
1339
+ ` trackReturn: true,`,
1340
+ ` sampleRate: 1,`,
1341
+ ` maxDepth: 5,`,
1342
+ ` environment: 'node',`,
1343
+ ` enabled: true,`,
1344
+ ` };`,
1345
+ ` if (paramNames && paramNames.length) opts.paramNames = paramNames;`,
1346
+ ` return __trickle_wrapFn(fn, opts);`,
1347
+ `}`,
1348
+ );
1349
+ } else {
1350
+ // Browser mode: __trickle_wrap is a no-op (function wrapping uses Node.js APIs)
1351
+ prefixLines.push(
1352
+ `function __trickle_wrap(fn) { return fn; }`,
1353
+ );
1354
+ }
1313
1355
 
1314
1356
  // Add unified __trickle_send() transport — browser uses HMR WebSocket, SSR uses fs
1315
1357
  if (needsTracing) {
@@ -1350,7 +1392,7 @@ export function transformEsmSource(
1350
1392
  }
1351
1393
 
1352
1394
  // Add variable tracing if needed — inlined to avoid import resolution issues in Vite SSR.
1353
- if (varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || funcParamInsertions.length > 0) {
1395
+ if (varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || catchInsertions.length > 0 || funcParamInsertions.length > 0) {
1354
1396
  prefixLines.push(
1355
1397
  `if (!globalThis.__trickle_var_tracer) {`,
1356
1398
  ` const _cache = new Set();`,
@@ -1529,6 +1571,15 @@ export function transformEsmSource(
1529
1571
  });
1530
1572
  }
1531
1573
 
1574
+ // Catch clause insertions: insert trace at start of catch body
1575
+ for (const { bodyStart, varNames, lineNo } of catchInsertions) {
1576
+ const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
1577
+ allInsertions.push({
1578
+ position: bodyStart,
1579
+ code: `\ntry{${calls}}catch(__e2){}\n`,
1580
+ });
1581
+ }
1582
+
1532
1583
  // For-loop variable insertions: insert trace at start of loop body
1533
1584
  for (const { bodyStart, varNames, lineNo } of forLoopInsertions) {
1534
1585
  const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');