trickle-observe 0.2.78 → 0.2.79

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.
@@ -39,6 +39,7 @@ const wrap_1 = require("./wrap");
39
39
  const fetch_observer_1 = require("./fetch-observer");
40
40
  const express_1 = require("./express");
41
41
  const trace_var_1 = require("./trace-var");
42
+ const vite_plugin_1 = require("./vite-plugin");
42
43
  const M = module_1.default;
43
44
  const originalLoad = M._load;
44
45
  const originalCompile = M.prototype._compile;
@@ -538,7 +539,12 @@ function transformCjsSource(source, filename, moduleName, env) {
538
539
  destructInsertions = findDestructuredDeclarations(source);
539
540
  }
540
541
  }
541
- if (insertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0 && classInsertions.length === 0)
542
+ // Additional variable patterns: reassignments, for-loops, catch clauses, function params
543
+ const reassignInsertions = (0, vite_plugin_1.findReassignments)(source);
544
+ const forLoopInsertions = (0, vite_plugin_1.findForLoopVars)(source);
545
+ const catchInsertions = (0, vite_plugin_1.findCatchVars)(source);
546
+ const funcParamInsertions = (0, vite_plugin_1.findFunctionParams)(source, false);
547
+ if (insertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0 && reassignInsertions.length === 0 && forLoopInsertions.length === 0 && catchInsertions.length === 0 && funcParamInsertions.length === 0 && classInsertions.length === 0)
542
548
  return source;
543
549
  // Resolve the path to the wrap helper (compiled JS)
544
550
  const wrapHelperPath = path_1.default.join(__dirname, 'wrap.js');
@@ -561,7 +567,7 @@ function transformCjsSource(source, filename, moduleName, env) {
561
567
  `};`,
562
568
  ];
563
569
  // Add variable tracing helper if we have var insertions
564
- if (varInsertions.length > 0 || destructInsertions.length > 0) {
570
+ if (varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || catchInsertions.length > 0 || funcParamInsertions.length > 0) {
565
571
  const traceVarPath = path_1.default.join(__dirname, 'trace-var.js');
566
572
  prefixLines.push(`var __trickle_tv_mod = require(${JSON.stringify(traceVarPath)});`, `var __trickle_tv = function(v, n, l) { try { __trickle_tv_mod.traceVar(v, n, l, ${JSON.stringify(moduleName)}, ${JSON.stringify(filename)}); } catch(e){} };`);
567
573
  }
@@ -588,6 +594,37 @@ function transformCjsSource(source, filename, moduleName, env) {
588
594
  code: `\n;try{${calls}}catch(__e){}\n`,
589
595
  });
590
596
  }
597
+ // Reassignment insertions
598
+ for (const { lineEnd, varName, lineNo } of reassignInsertions) {
599
+ allInsertions.push({
600
+ position: lineEnd,
601
+ code: `\n;try{__trickle_tv(${varName},${JSON.stringify(varName)},${lineNo})}catch(__e){}\n`,
602
+ });
603
+ }
604
+ // For-loop variable insertions
605
+ for (const { bodyStart, varNames, lineNo } of forLoopInsertions) {
606
+ const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
607
+ allInsertions.push({
608
+ position: bodyStart,
609
+ code: `\ntry{${calls}}catch(__e){}\n`,
610
+ });
611
+ }
612
+ // Catch clause insertions
613
+ for (const { bodyStart, varNames, lineNo } of catchInsertions) {
614
+ const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
615
+ allInsertions.push({
616
+ position: bodyStart,
617
+ code: `\ntry{${calls}}catch(__e2){}\n`,
618
+ });
619
+ }
620
+ // Function parameter insertions
621
+ for (const { bodyStart, paramNames, lineNo } of funcParamInsertions) {
622
+ const calls = paramNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
623
+ allInsertions.push({
624
+ position: bodyStart,
625
+ code: `\ntry{${calls}}catch(__e){}\n`,
626
+ });
627
+ }
591
628
  // Add class method wrappings
592
629
  for (const ci of classInsertions) {
593
630
  allInsertions.push(ci);
package/dist/trace-var.js CHANGED
@@ -57,8 +57,8 @@ const type_hash_1 = require("./type-hash");
57
57
  /** Where to write variable observations */
58
58
  let varsFilePath = '';
59
59
  let debugMode = false;
60
- /** Cache: "file:line:varName:typeHash" → already written */
61
- const varCache = new Set();
60
+ /** Cache: "file:line:varName" → { fingerprint, timestamp } for value-aware dedup */
61
+ const varCache = new Map();
62
62
  /** Batch buffer for writing — avoids one fs.appendFileSync per variable */
63
63
  let varBuffer = [];
64
64
  let flushTimer = null;
@@ -105,10 +105,17 @@ function traceVar(value, varName, line, moduleName, filePath) {
105
105
  // Create a stable hash for dedup
106
106
  const dummyArgs = { kind: 'tuple', elements: [] };
107
107
  const typeHash = (0, type_hash_1.hashType)(dummyArgs, type);
108
- const cacheKey = `${filePath}:${line}:${varName}:${typeHash}`;
109
- if (varCache.has(cacheKey))
108
+ // Value-aware dedup: re-send if value changed or 10s elapsed
109
+ const cacheKey = `${filePath}:${line}:${varName}`;
110
+ const t = typeof value;
111
+ const fp = (t === 'string' || t === 'number' || t === 'boolean' || value === null || value === undefined)
112
+ ? String(value).substring(0, 60)
113
+ : typeHash;
114
+ const now = Date.now();
115
+ const prev = varCache.get(cacheKey);
116
+ if (prev && prev.fp === fp && (now - prev.ts) < 10000)
110
117
  return;
111
- varCache.add(cacheKey);
118
+ varCache.set(cacheKey, { fp, ts: now });
112
119
  const sample = sanitizeVarSample(value);
113
120
  const observation = {
114
121
  kind: 'variable',
@@ -40,6 +40,53 @@ export declare function tricklePlugin(options?: TricklePluginOptions): {
40
40
  map: null;
41
41
  } | null;
42
42
  };
43
+ /**
44
+ * Find variable reassignments (not declarations) and return insertions for tracing.
45
+ * Handles: x = newValue; x += 1; x ||= fallback; etc.
46
+ * Only matches standalone reassignment statements at the start of a line.
47
+ * Skips: property assignments (obj.x = ...), indexed (arr[i] = ...),
48
+ * comparisons (===, !==), arrow functions (=>), declarations (const/let/var).
49
+ */
50
+ export declare function findReassignments(source: string): Array<{
51
+ lineEnd: number;
52
+ varName: string;
53
+ lineNo: number;
54
+ }>;
55
+ /**
56
+ * Find for-loop variable declarations and return insertions for tracing.
57
+ * Handles:
58
+ * for (const item of items) { ... } → trace item
59
+ * for (const [key, val] of entries) { ... } → trace key, val
60
+ * for (const { a, b } of items) { ... } → trace a, b
61
+ * for (const key in obj) { ... } → trace key
62
+ * for (let i = 0; i < n; i++) { ... } → trace i
63
+ * Inserts trace calls at the start of the loop body.
64
+ */
65
+ /**
66
+ * Find catch clause variables and return insertions for tracing.
67
+ * Handles: catch (err) { ... } → trace err at start of catch body.
68
+ */
69
+ export declare function findCatchVars(source: string): Array<{
70
+ bodyStart: number;
71
+ varNames: string[];
72
+ lineNo: number;
73
+ }>;
74
+ export declare function findForLoopVars(source: string): Array<{
75
+ bodyStart: number;
76
+ varNames: string[];
77
+ lineNo: number;
78
+ }>;
79
+ /**
80
+ * Find function parameter names and return insertions for tracing at the start
81
+ * of function bodies. Traces the runtime values of all parameters.
82
+ * Handles: function declarations, arrow functions, method definitions.
83
+ * Skips: React components (already tracked via __trickle_rc with props).
84
+ */
85
+ export declare function findFunctionParams(source: string, isReactFile: boolean): Array<{
86
+ bodyStart: number;
87
+ paramNames: string[];
88
+ lineNo: number;
89
+ }>;
43
90
  export declare function transformEsmSource(source: string, filename: string, moduleName: string, backendUrl: string, debug: boolean, traceVars: boolean, originalSource?: string | null, isSSR?: boolean,
44
91
  /** URL for fetch-based browser transport (Next.js client). When set and isSSR=false, uses fetch() instead of import.meta.hot */
45
92
  ingestUrl?: string | null): string;
@@ -23,6 +23,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
23
23
  };
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
25
  exports.tricklePlugin = tricklePlugin;
26
+ exports.findReassignments = findReassignments;
27
+ exports.findCatchVars = findCatchVars;
28
+ exports.findForLoopVars = findForLoopVars;
29
+ exports.findFunctionParams = findFunctionParams;
26
30
  exports.transformEsmSource = transformEsmSource;
27
31
  const path_1 = __importDefault(require("path"));
28
32
  const fs_1 = __importDefault(require("fs"));
@@ -488,7 +488,7 @@ function extractDestructuredNames(pattern) {
488
488
  * Skips: property assignments (obj.x = ...), indexed (arr[i] = ...),
489
489
  * comparisons (===, !==), arrow functions (=>), declarations (const/let/var).
490
490
  */
491
- function findReassignments(source) {
491
+ export function findReassignments(source) {
492
492
  const results = [];
493
493
  // Match: <identifier> <assignOp>= <value> at the start of a line
494
494
  // Compound operators: +=, -=, *=, /=, %=, **=, &&=, ||=, ??=, <<=, >>=, >>>=, &=, |=, ^=
@@ -604,7 +604,7 @@ function findReassignments(source) {
604
604
  * Find catch clause variables and return insertions for tracing.
605
605
  * Handles: catch (err) { ... } → trace err at start of catch body.
606
606
  */
607
- function findCatchVars(source) {
607
+ export function findCatchVars(source) {
608
608
  const results = [];
609
609
  const catchRegex = /\bcatch\s*\(\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?::\s*[^)]+?)?\s*\)\s*\{/g;
610
610
  let match;
@@ -766,7 +766,7 @@ function findJsxExpressions(source) {
766
766
  }
767
767
  return results;
768
768
  }
769
- function findForLoopVars(source) {
769
+ export function findForLoopVars(source) {
770
770
  const results = [];
771
771
  // Match: for (const/let/var ...
772
772
  const forRegex = /\bfor\s*\(/g;
@@ -858,7 +858,7 @@ function findForLoopVars(source) {
858
858
  * Handles: function declarations, arrow functions, method definitions.
859
859
  * Skips: React components (already tracked via __trickle_rc with props).
860
860
  */
861
- function findFunctionParams(source, isReactFile) {
861
+ export function findFunctionParams(source, isReactFile) {
862
862
  const results = [];
863
863
  // Match function declarations: function name(params) {
864
864
  const funcDeclRegex = /\b(?:async\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trickle-observe",
3
- "version": "0.2.78",
3
+ "version": "0.2.79",
4
4
  "description": "Runtime type observability for JavaScript applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -36,6 +36,12 @@ import { WrapOptions } from './types';
36
36
  import { patchFetch } from './fetch-observer';
37
37
  import { instrumentExpress, trickleMiddleware } from './express';
38
38
  import { initVarTracer, traceVar } from './trace-var';
39
+ import {
40
+ findReassignments,
41
+ findForLoopVars,
42
+ findCatchVars,
43
+ findFunctionParams,
44
+ } from './vite-plugin';
39
45
 
40
46
  const M = Module as any;
41
47
  const originalLoad = M._load;
@@ -508,7 +514,13 @@ function transformCjsSource(source: string, filename: string, moduleName: string
508
514
  }
509
515
  }
510
516
 
511
- if (insertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0 && classInsertions.length === 0) return source;
517
+ // Additional variable patterns: reassignments, for-loops, catch clauses, function params
518
+ const reassignInsertions = findReassignments(source);
519
+ const forLoopInsertions = findForLoopVars(source);
520
+ const catchInsertions = findCatchVars(source);
521
+ const funcParamInsertions = findFunctionParams(source, false);
522
+
523
+ if (insertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0 && reassignInsertions.length === 0 && forLoopInsertions.length === 0 && catchInsertions.length === 0 && funcParamInsertions.length === 0 && classInsertions.length === 0) return source;
512
524
 
513
525
  // Resolve the path to the wrap helper (compiled JS)
514
526
  const wrapHelperPath = path.join(__dirname, 'wrap.js');
@@ -533,7 +545,7 @@ function transformCjsSource(source: string, filename: string, moduleName: string
533
545
  ];
534
546
 
535
547
  // Add variable tracing helper if we have var insertions
536
- if (varInsertions.length > 0 || destructInsertions.length > 0) {
548
+ if (varInsertions.length > 0 || destructInsertions.length > 0 || reassignInsertions.length > 0 || forLoopInsertions.length > 0 || catchInsertions.length > 0 || funcParamInsertions.length > 0) {
537
549
  const traceVarPath = path.join(__dirname, 'trace-var.js');
538
550
  prefixLines.push(
539
551
  `var __trickle_tv_mod = require(${JSON.stringify(traceVarPath)});`,
@@ -571,6 +583,41 @@ function transformCjsSource(source: string, filename: string, moduleName: string
571
583
  });
572
584
  }
573
585
 
586
+ // Reassignment insertions
587
+ for (const { lineEnd, varName, lineNo } of reassignInsertions) {
588
+ allInsertions.push({
589
+ position: lineEnd,
590
+ code: `\n;try{__trickle_tv(${varName},${JSON.stringify(varName)},${lineNo})}catch(__e){}\n`,
591
+ });
592
+ }
593
+
594
+ // For-loop variable insertions
595
+ for (const { bodyStart, varNames, lineNo } of forLoopInsertions) {
596
+ const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
597
+ allInsertions.push({
598
+ position: bodyStart,
599
+ code: `\ntry{${calls}}catch(__e){}\n`,
600
+ });
601
+ }
602
+
603
+ // Catch clause insertions
604
+ for (const { bodyStart, varNames, lineNo } of catchInsertions) {
605
+ const calls = varNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
606
+ allInsertions.push({
607
+ position: bodyStart,
608
+ code: `\ntry{${calls}}catch(__e2){}\n`,
609
+ });
610
+ }
611
+
612
+ // Function parameter insertions
613
+ for (const { bodyStart, paramNames, lineNo } of funcParamInsertions) {
614
+ const calls = paramNames.map(n => `__trickle_tv(${n},${JSON.stringify(n)},${lineNo})`).join(';');
615
+ allInsertions.push({
616
+ position: bodyStart,
617
+ code: `\ntry{${calls}}catch(__e){}\n`,
618
+ });
619
+ }
620
+
574
621
  // Add class method wrappings
575
622
  for (const ci of classInsertions) {
576
623
  allInsertions.push(ci);
package/src/trace-var.ts CHANGED
@@ -24,8 +24,8 @@ import { hashType } from './type-hash';
24
24
  let varsFilePath = '';
25
25
  let debugMode = false;
26
26
 
27
- /** Cache: "file:line:varName:typeHash" → already written */
28
- const varCache = new Set<string>();
27
+ /** Cache: "file:line:varName" → { fingerprint, timestamp } for value-aware dedup */
28
+ const varCache = new Map<string, { fp: string; ts: number }>();
29
29
 
30
30
  /** Batch buffer for writing — avoids one fs.appendFileSync per variable */
31
31
  let varBuffer: string[] = [];
@@ -92,9 +92,16 @@ export function traceVar(
92
92
  const dummyArgs: TypeNode = { kind: 'tuple', elements: [] };
93
93
  const typeHash = hashType(dummyArgs, type);
94
94
 
95
- const cacheKey = `${filePath}:${line}:${varName}:${typeHash}`;
96
- if (varCache.has(cacheKey)) return;
97
- varCache.add(cacheKey);
95
+ // Value-aware dedup: re-send if value changed or 10s elapsed
96
+ const cacheKey = `${filePath}:${line}:${varName}`;
97
+ const t = typeof value;
98
+ const fp = (t === 'string' || t === 'number' || t === 'boolean' || value === null || value === undefined)
99
+ ? String(value).substring(0, 60)
100
+ : typeHash;
101
+ const now = Date.now();
102
+ const prev = varCache.get(cacheKey);
103
+ if (prev && prev.fp === fp && (now - prev.ts) < 10000) return;
104
+ varCache.set(cacheKey, { fp, ts: now });
98
105
 
99
106
  const sample = sanitizeVarSample(value);
100
107
 
@@ -473,7 +473,7 @@ function extractDestructuredNames(pattern: string): string[] {
473
473
  * Skips: property assignments (obj.x = ...), indexed (arr[i] = ...),
474
474
  * comparisons (===, !==), arrow functions (=>), declarations (const/let/var).
475
475
  */
476
- function findReassignments(source: string): Array<{ lineEnd: number; varName: string; lineNo: number }> {
476
+ export function findReassignments(source: string): Array<{ lineEnd: number; varName: string; lineNo: number }> {
477
477
  const results: Array<{ lineEnd: number; varName: string; lineNo: number }> = [];
478
478
 
479
479
  // Match: <identifier> <assignOp>= <value> at the start of a line
@@ -578,7 +578,7 @@ function findReassignments(source: string): Array<{ lineEnd: number; varName: st
578
578
  * Find catch clause variables and return insertions for tracing.
579
579
  * Handles: catch (err) { ... } → trace err at start of catch body.
580
580
  */
581
- function findCatchVars(source: string): Array<{ bodyStart: number; varNames: string[]; lineNo: number }> {
581
+ export function findCatchVars(source: string): Array<{ bodyStart: number; varNames: string[]; lineNo: number }> {
582
582
  const results: Array<{ bodyStart: number; varNames: string[]; lineNo: number }> = [];
583
583
  const catchRegex = /\bcatch\s*\(\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?::\s*[^)]+?)?\s*\)\s*\{/g;
584
584
  let match;
@@ -731,7 +731,7 @@ function findJsxExpressions(source: string): Array<{ exprStart: number; exprEnd:
731
731
  return results;
732
732
  }
733
733
 
734
- function findForLoopVars(source: string): Array<{ bodyStart: number; varNames: string[]; lineNo: number }> {
734
+ export function findForLoopVars(source: string): Array<{ bodyStart: number; varNames: string[]; lineNo: number }> {
735
735
  const results: Array<{ bodyStart: number; varNames: string[]; lineNo: number }> = [];
736
736
 
737
737
  // Match: for (const/let/var ...
@@ -823,7 +823,7 @@ function findForLoopVars(source: string): Array<{ bodyStart: number; varNames: s
823
823
  * Handles: function declarations, arrow functions, method definitions.
824
824
  * Skips: React components (already tracked via __trickle_rc with props).
825
825
  */
826
- function findFunctionParams(source: string, isReactFile: boolean): Array<{ bodyStart: number; paramNames: string[]; lineNo: number }> {
826
+ export function findFunctionParams(source: string, isReactFile: boolean): Array<{ bodyStart: number; paramNames: string[]; lineNo: number }> {
827
827
  const results: Array<{ bodyStart: number; paramNames: string[]; lineNo: number }> = [];
828
828
 
829
829
  // Match function declarations: function name(params) {