trickle-observe 0.2.71 → 0.2.72

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,7 +1314,7 @@ 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;
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;
1294
1318
  const importLines = [
1295
1319
  `import { wrapFunction as __trickle_wrapFn, configure as __trickle_configure } from 'trickle-observe';`,
1296
1320
  ];
@@ -1328,7 +1352,7 @@ function transformEsmSource(source, filename, moduleName, backendUrl, debug, tra
1328
1352
  }
1329
1353
  }
1330
1354
  // 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) {
1355
+ if (varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || catchInsertions.length > 0 || funcParamInsertions.length > 0) {
1332
1356
  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
1357
  }
1334
1358
  // Add React component render tracker if needed
@@ -1373,6 +1397,14 @@ function transformEsmSource(source, filename, moduleName, backendUrl, debug, tra
1373
1397
  code: `\n;try{__trickle_tv(${varName},${JSON.stringify(varName)},${lineNo})}catch(__e){}\n`,
1374
1398
  });
1375
1399
  }
1400
+ // Catch clause insertions: insert trace at start of catch body
1401
+ for (const { bodyStart, varNames, lineNo } of catchInsertions) {
1402
+ const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
1403
+ allInsertions.push({
1404
+ position: bodyStart,
1405
+ code: `\ntry{${calls}}catch(__e2){}\n`,
1406
+ });
1407
+ }
1376
1408
  // For-loop variable insertions: insert trace at start of loop body
1377
1409
  for (const { bodyStart, varNames, lineNo } of forLoopInsertions) {
1378
1410
  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,7 +1307,7 @@ 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;
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;
1287
1311
  const importLines = [
1288
1312
  `import { wrapFunction as __trickle_wrapFn, configure as __trickle_configure } from 'trickle-observe';`,
1289
1313
  ];
@@ -1321,7 +1345,7 @@ export function transformEsmSource(source, filename, moduleName, backendUrl, deb
1321
1345
  }
1322
1346
  }
1323
1347
  // 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) {
1348
+ if (varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || catchInsertions.length > 0 || funcParamInsertions.length > 0) {
1325
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) {} }`);
1326
1350
  }
1327
1351
  // Add React component render tracker if needed
@@ -1366,6 +1390,14 @@ export function transformEsmSource(source, filename, moduleName, backendUrl, deb
1366
1390
  code: `\n;try{__trickle_tv(${varName},${JSON.stringify(varName)},${lineNo})}catch(__e){}\n`,
1367
1391
  });
1368
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
+ }
1369
1401
  // For-loop variable insertions: insert trace at start of loop body
1370
1402
  for (const { bodyStart, varNames, lineNo } of forLoopInsertions) {
1371
1403
  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.72",
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,7 +1308,7 @@ 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;
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;
1284
1312
  const importLines: string[] = [
1285
1313
  `import { wrapFunction as __trickle_wrapFn, configure as __trickle_configure } from 'trickle-observe';`,
1286
1314
  ];
@@ -1350,7 +1378,7 @@ export function transformEsmSource(
1350
1378
  }
1351
1379
 
1352
1380
  // 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) {
1381
+ if (varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || catchInsertions.length > 0 || funcParamInsertions.length > 0) {
1354
1382
  prefixLines.push(
1355
1383
  `if (!globalThis.__trickle_var_tracer) {`,
1356
1384
  ` const _cache = new Set();`,
@@ -1529,6 +1557,15 @@ export function transformEsmSource(
1529
1557
  });
1530
1558
  }
1531
1559
 
1560
+ // Catch clause insertions: insert trace at start of catch body
1561
+ for (const { bodyStart, varNames, lineNo } of catchInsertions) {
1562
+ const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
1563
+ allInsertions.push({
1564
+ position: bodyStart,
1565
+ code: `\ntry{${calls}}catch(__e2){}\n`,
1566
+ });
1567
+ }
1568
+
1532
1569
  // For-loop variable insertions: insert trace at start of loop body
1533
1570
  for (const { bodyStart, varNames, lineNo } of forLoopInsertions) {
1534
1571
  const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');