trickle-observe 0.2.130 → 0.2.132

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/express.js CHANGED
@@ -100,7 +100,8 @@ function sanitizeSample(value, depth = 3) {
100
100
  if (t === 'function')
101
101
  return `[Function: ${value.name || 'anonymous'}]`;
102
102
  if (Array.isArray(value)) {
103
- return value.slice(0, 5).map(item => sanitizeSample(item, depth - 1));
103
+ // Preserve full depth for the first element (used for type assertions in test-gen)
104
+ return value.slice(0, 5).map((item, i) => sanitizeSample(item, i === 0 ? depth : depth - 1));
104
105
  }
105
106
  if (t === 'object') {
106
107
  if (value instanceof Date)
package/dist/fastify.js CHANGED
@@ -87,7 +87,7 @@ function sanitizeSample(value, depth = 3) {
87
87
  if (t === 'function')
88
88
  return `[Function: ${value.name || 'anonymous'}]`;
89
89
  if (Array.isArray(value)) {
90
- return value.slice(0, 5).map(item => sanitizeSample(item, depth - 1));
90
+ return value.slice(0, 5).map((item, i) => sanitizeSample(item, i === 0 ? depth : depth - 1));
91
91
  }
92
92
  if (t === 'object') {
93
93
  if (value instanceof Date)
package/dist/hono.js CHANGED
@@ -97,7 +97,7 @@ function sanitizeSample(value, depth = 3) {
97
97
  if (t === 'function')
98
98
  return `[Function: ${value.name || 'anonymous'}]`;
99
99
  if (Array.isArray(value)) {
100
- return value.slice(0, 5).map(item => sanitizeSample(item, depth - 1));
100
+ return value.slice(0, 5).map((item, i) => sanitizeSample(item, i === 0 ? depth : depth - 1));
101
101
  }
102
102
  if (t === 'object') {
103
103
  if (value instanceof Date)
package/dist/koa.js CHANGED
@@ -90,7 +90,7 @@ function sanitizeSample(value, depth = 3) {
90
90
  if (t === 'function')
91
91
  return `[Function: ${value.name || 'anonymous'}]`;
92
92
  if (Array.isArray(value)) {
93
- return value.slice(0, 5).map(item => sanitizeSample(item, depth - 1));
93
+ return value.slice(0, 5).map((item, i) => sanitizeSample(item, i === 0 ? depth : depth - 1));
94
94
  }
95
95
  if (t === 'object') {
96
96
  if (value instanceof Date)
@@ -648,87 +648,106 @@ function transformSource(source, url, originalSource) {
648
648
  }
649
649
  }
650
650
 
651
- // ── Detect framework imports and inject instrumentation ──
652
- // Scan for: import express from 'express' / import { Hono } from 'hono' etc.
651
+ // ── Detect framework imports and rewrite to auto-instrument all instances ──
652
+ // Strategy: rewrite the import to wrap the factory/constructor so EVERY
653
+ // instance created from it is automatically instrumented.
654
+ // E.g., `import { Hono } from 'hono'` becomes:
655
+ // import { Hono as __OrigHono } from 'hono'
656
+ // const Hono = (...args) => { const a = new __OrigHono(...args); instrumentHono(a); return a; }
653
657
  const frameworkInjects = [];
654
658
  const expressImportMatch = source.match(/import\s+(\w+)\s+from\s+['"]express['"]/);
655
659
  const fastifyImportMatch = source.match(/import\s+(\w+)\s+from\s+['"]fastify['"]/);
656
660
  const koaImportMatch = source.match(/import\s+(\w+)\s+from\s+['"]koa['"]/);
657
- const honoImportMatch = source.match(/import\s+\{\s*Hono\s*\}\s+from\s+['"]hono['"]/);
661
+ const honoImportMatch = source.match(/import\s+\{\s*Hono\s*(?:,\s*\w+)*\s*\}\s+from\s+['"]hono['"]/);
658
662
 
659
663
  if (expressImportMatch) {
660
- // Express: imported as factory. Detect `const app = express()` and inject instrument(app)
661
664
  const factoryName = expressImportMatch[1];
662
- const appMatch = source.match(new RegExp(`(?:const|let|var)\\s+(\\w+)\\s*=\\s*${factoryName}\\s*\\(`));
663
- if (appMatch) {
664
- frameworkInjects.push({ framework: 'express', appVar: appMatch[1], importPath: config.wrapperPath.replace('/wrap.js', '/express.js') });
665
- }
665
+ frameworkInjects.push({ framework: 'express', factoryName, type: 'factory', importPath: config.wrapperPath.replace('/wrap.js', '/express.js') });
666
666
  }
667
667
  if (fastifyImportMatch) {
668
668
  const factoryName = fastifyImportMatch[1];
669
- const appMatch = source.match(new RegExp(`(?:const|let|var)\\s+(\\w+)\\s*=\\s*${factoryName}\\s*\\(`));
670
- if (appMatch) {
671
- frameworkInjects.push({ framework: 'fastify', appVar: appMatch[1], importPath: config.wrapperPath.replace('/wrap.js', '/fastify.js') });
672
- }
669
+ frameworkInjects.push({ framework: 'fastify', factoryName, type: 'factory', importPath: config.wrapperPath.replace('/wrap.js', '/fastify.js') });
673
670
  }
674
671
  if (koaImportMatch) {
675
672
  const className = koaImportMatch[1];
676
- const appMatch = source.match(new RegExp(`(?:const|let|var)\\s+(\\w+)\\s*=\\s*new\\s+${className}\\s*\\(`));
677
- if (appMatch) {
678
- frameworkInjects.push({ framework: 'koa', appVar: appMatch[1], importPath: config.wrapperPath.replace('/wrap.js', '/koa.js') });
679
- }
673
+ frameworkInjects.push({ framework: 'koa', factoryName: className, type: 'class', importPath: config.wrapperPath.replace('/wrap.js', '/koa.js') });
680
674
  }
681
675
  if (honoImportMatch) {
682
- const appMatch = source.match(/(?:const|let|var)\s+(\w+)\s*=\s*new\s+Hono\s*\(/);
683
- if (appMatch) {
684
- frameworkInjects.push({ framework: 'hono', appVar: appMatch[1], importPath: config.wrapperPath.replace('/wrap.js', '/hono.js') });
676
+ frameworkInjects.push({ framework: 'hono', factoryName: 'Hono', type: 'class', importPath: config.wrapperPath.replace('/wrap.js', '/hono.js') });
677
+ }
678
+
679
+ // Rewrite framework imports in the result array
680
+ for (const fi of frameworkInjects) {
681
+ const instrumentFn = fi.framework === 'express' ? 'instrumentExpress'
682
+ : fi.framework === 'fastify' ? 'instrumentFastify'
683
+ : fi.framework === 'koa' ? 'instrumentKoa'
684
+ : 'instrumentHono';
685
+ const fiPath = fi.importPath.replace(/\\/g, '\\\\');
686
+
687
+ for (let ri = 0; ri < result.length; ri++) {
688
+ const line = result[ri];
689
+
690
+ if (fi.framework === 'hono' && /import\s+\{[^}]*Hono[^}]*\}\s+from\s+['"]hono['"]/.test(line)) {
691
+ // Rewrite: import { Hono } from 'hono' → import { Hono as __OrigHono } from 'hono'
692
+ result[ri] = line.replace(/\bHono\b/, 'Hono as __OrigHono');
693
+ // Insert wrapper after imports are hoisted (add at this position, it'll run after all imports)
694
+ result.splice(ri + 1, 0,
695
+ `import { createRequire as __cr_hono } from 'node:module';`,
696
+ `const __rq_hono = __cr_hono(import.meta.url);`,
697
+ `const __fwHono = __rq_hono('${fiPath}');`,
698
+ `const Hono = function(...args) { const a = new __OrigHono(...args); try { __fwHono.${instrumentFn}(a, { environment: process.env.TRICKLE_ENV || 'development' }); } catch(e) {} return a; };`,
699
+ `Hono.prototype = __OrigHono.prototype;`,
700
+ );
701
+ break;
702
+ }
703
+
704
+ if (fi.framework === 'express' && new RegExp(`import\\s+${fi.factoryName}\\s+from\\s+['"]express['"]`).test(line)) {
705
+ result[ri] = line.replace(fi.factoryName, `__OrigExpress`);
706
+ result.splice(ri + 1, 0,
707
+ `import { createRequire as __cr_express } from 'node:module';`,
708
+ `const __rq_express = __cr_express(import.meta.url);`,
709
+ `const __fwExpress = __rq_express('${fiPath}');`,
710
+ `const ${fi.factoryName} = function(...args) { const a = __OrigExpress(...args); try { __fwExpress.${instrumentFn}(a, { environment: process.env.TRICKLE_ENV || 'development' }); } catch(e) {} return a; };`,
711
+ `Object.keys(__OrigExpress).forEach(k => { ${fi.factoryName}[k] = __OrigExpress[k]; });`,
712
+ `Object.setPrototypeOf(${fi.factoryName}, Object.getPrototypeOf(__OrigExpress));`,
713
+ );
714
+ break;
715
+ }
716
+
717
+ if (fi.framework === 'fastify' && new RegExp(`import\\s+${fi.factoryName}\\s+from\\s+['"]fastify['"]`).test(line)) {
718
+ result[ri] = line.replace(fi.factoryName, `__OrigFastify`);
719
+ result.splice(ri + 1, 0,
720
+ `import { createRequire as __cr_fastify } from 'node:module';`,
721
+ `const __rq_fastify = __cr_fastify(import.meta.url);`,
722
+ `const __fwFastify = __rq_fastify('${fiPath}');`,
723
+ `const ${fi.factoryName} = function(...args) { const a = __OrigFastify(...args); try { __fwFastify.${instrumentFn}(a, { environment: process.env.TRICKLE_ENV || 'development' }); } catch(e) {} return a; };`,
724
+ );
725
+ break;
726
+ }
727
+
728
+ if (fi.framework === 'koa' && new RegExp(`import\\s+${fi.factoryName}\\s+from\\s+['"]koa['"]`).test(line)) {
729
+ result[ri] = line.replace(fi.factoryName, `__OrigKoa`);
730
+ result.splice(ri + 1, 0,
731
+ `import { createRequire as __cr_koa } from 'node:module';`,
732
+ `const __rq_koa = __cr_koa(import.meta.url);`,
733
+ `const __fwKoa = __rq_koa('${fiPath}');`,
734
+ `const ${fi.factoryName} = function(...args) { const a = new __OrigKoa(...args); try { __fwKoa.${instrumentFn}(a, { environment: process.env.TRICKLE_ENV || 'development' }); } catch(e) {} return a; };`,
735
+ );
736
+ break;
737
+ }
685
738
  }
686
739
  }
687
740
 
688
- // If nothing to wrap or trace, but we have framework injections, continue
741
+ // Framework imports are now rewritten above (constructor/factory wrapping).
742
+ // Early return if nothing else to transform.
689
743
  if (exportedFunctions.length === 0 && exportedDefaults.length === 0 && namedExports.length === 0 && !hasVarTracing && frameworkInjects.length === 0) {
690
744
  return source;
691
745
  }
692
-
693
- // If ONLY framework injections (no function wrapping needed), inject directly
694
746
  if (exportedFunctions.length === 0 && exportedDefaults.length === 0 && namedExports.length === 0 && !hasVarTracing && frameworkInjects.length > 0) {
695
- const lines = source.split('\n');
696
- const injections = [];
697
- for (const fi of frameworkInjects) {
698
- const instrumentFn = fi.framework === 'express' ? 'instrumentExpress'
699
- : fi.framework === 'fastify' ? 'instrumentFastify'
700
- : fi.framework === 'koa' ? 'instrumentKoa'
701
- : 'instrumentHono';
702
- const fiPath = fi.importPath.replace(/\\/g, '\\\\');
703
- injections.push(`import { createRequire as __cr_fw } from 'node:module';`);
704
- injections.push(`const __req_fw = __cr_fw(import.meta.url);`);
705
- injections.push(`try { const __fwMod = __req_fw('${fiPath}'); __fwMod.${instrumentFn}(${fi.appVar}, { environment: process.env.TRICKLE_ENV || 'development' }); } catch(__e) { if (${config.debug}) console.error('[trickle/esm] Framework instrumentation failed:', __e.message); }`);
706
- }
707
- // Find the line after the app creation and insert
708
- for (const fi of frameworkInjects) {
709
- const appPattern = new RegExp(`(?:const|let|var)\\s+${fi.appVar}\\s*=`);
710
- for (let i = 0; i < lines.length; i++) {
711
- if (appPattern.test(lines[i])) {
712
- // Find end of statement
713
- let endLine = i;
714
- let depth = 0;
715
- for (let j = i; j < lines.length; j++) {
716
- for (const ch of lines[j]) {
717
- if (ch === '(' || ch === '{' || ch === '[') depth++;
718
- if (ch === ')' || ch === '}' || ch === ']') depth--;
719
- }
720
- if (lines[j].includes(';') && depth <= 0) { endLine = j; break; }
721
- if (j > i && depth <= 0) { endLine = j - 1; break; }
722
- }
723
- lines.splice(endLine + 1, 0, ...injections);
724
- break;
725
- }
726
- }
727
- }
728
747
  if (config.debug) {
729
- console.log(`[trickle/esm] Injected ${frameworkInjects.map(f => f.framework).join(', ')} instrumentation into ${moduleName}`);
748
+ console.log(`[trickle/esm] Wrapped ${frameworkInjects.map(f => f.framework).join(', ')} constructor(s) in ${moduleName}`);
730
749
  }
731
- return lines.join('\n');
750
+ return result.join('\n');
732
751
  }
733
752
 
734
753
  // Add wrapper import and wrapping code
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trickle-observe",
3
- "version": "0.2.130",
3
+ "version": "0.2.132",
4
4
  "description": "Zero-code runtime observability for JavaScript/TypeScript. Auto-instruments Express, Fastify, Koa, Hono, OpenAI, Anthropic, Gemini, MCP. Captures functions, variables, LLM calls, agent workflows.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/express.ts CHANGED
@@ -72,7 +72,8 @@ function sanitizeSample(value: unknown, depth: number = 3): unknown {
72
72
  if (t === 'function') return `[Function: ${(value as Function).name || 'anonymous'}]`;
73
73
 
74
74
  if (Array.isArray(value)) {
75
- return value.slice(0, 5).map(item => sanitizeSample(item, depth - 1));
75
+ // Preserve full depth for the first element (used for type assertions in test-gen)
76
+ return value.slice(0, 5).map((item, i) => sanitizeSample(item, i === 0 ? depth : depth - 1));
76
77
  }
77
78
 
78
79
  if (t === 'object') {
package/src/fastify.ts CHANGED
@@ -61,7 +61,7 @@ function sanitizeSample(value: unknown, depth: number = 3): unknown {
61
61
  if (t === 'function') return `[Function: ${(value as Function).name || 'anonymous'}]`;
62
62
 
63
63
  if (Array.isArray(value)) {
64
- return value.slice(0, 5).map(item => sanitizeSample(item, depth - 1));
64
+ return value.slice(0, 5).map((item, i) => sanitizeSample(item, i === 0 ? depth : depth - 1));
65
65
  }
66
66
 
67
67
  if (t === 'object') {
package/src/hono.ts CHANGED
@@ -71,7 +71,7 @@ function sanitizeSample(value: unknown, depth: number = 3): unknown {
71
71
  if (t === 'function') return `[Function: ${(value as Function).name || 'anonymous'}]`;
72
72
 
73
73
  if (Array.isArray(value)) {
74
- return value.slice(0, 5).map(item => sanitizeSample(item, depth - 1));
74
+ return value.slice(0, 5).map((item, i) => sanitizeSample(item, i === 0 ? depth : depth - 1));
75
75
  }
76
76
 
77
77
  if (t === 'object') {
package/src/koa.ts CHANGED
@@ -64,7 +64,7 @@ function sanitizeSample(value: unknown, depth: number = 3): unknown {
64
64
  if (t === 'function') return `[Function: ${(value as Function).name || 'anonymous'}]`;
65
65
 
66
66
  if (Array.isArray(value)) {
67
- return value.slice(0, 5).map(item => sanitizeSample(item, depth - 1));
67
+ return value.slice(0, 5).map((item, i) => sanitizeSample(item, i === 0 ? depth : depth - 1));
68
68
  }
69
69
 
70
70
  if (t === 'object') {