trickle-observe 0.2.130 → 0.2.131

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.
@@ -648,87 +648,104 @@ 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
+ );
712
+ break;
713
+ }
714
+
715
+ if (fi.framework === 'fastify' && new RegExp(`import\\s+${fi.factoryName}\\s+from\\s+['"]fastify['"]`).test(line)) {
716
+ result[ri] = line.replace(fi.factoryName, `__OrigFastify`);
717
+ result.splice(ri + 1, 0,
718
+ `import { createRequire as __cr_fastify } from 'node:module';`,
719
+ `const __rq_fastify = __cr_fastify(import.meta.url);`,
720
+ `const __fwFastify = __rq_fastify('${fiPath}');`,
721
+ `const ${fi.factoryName} = function(...args) { const a = __OrigFastify(...args); try { __fwFastify.${instrumentFn}(a, { environment: process.env.TRICKLE_ENV || 'development' }); } catch(e) {} return a; };`,
722
+ );
723
+ break;
724
+ }
725
+
726
+ if (fi.framework === 'koa' && new RegExp(`import\\s+${fi.factoryName}\\s+from\\s+['"]koa['"]`).test(line)) {
727
+ result[ri] = line.replace(fi.factoryName, `__OrigKoa`);
728
+ result.splice(ri + 1, 0,
729
+ `import { createRequire as __cr_koa } from 'node:module';`,
730
+ `const __rq_koa = __cr_koa(import.meta.url);`,
731
+ `const __fwKoa = __rq_koa('${fiPath}');`,
732
+ `const ${fi.factoryName} = function(...args) { const a = new __OrigKoa(...args); try { __fwKoa.${instrumentFn}(a, { environment: process.env.TRICKLE_ENV || 'development' }); } catch(e) {} return a; };`,
733
+ );
734
+ break;
735
+ }
685
736
  }
686
737
  }
687
738
 
688
- // If nothing to wrap or trace, but we have framework injections, continue
739
+ // Framework imports are now rewritten above (constructor/factory wrapping).
740
+ // Early return if nothing else to transform.
689
741
  if (exportedFunctions.length === 0 && exportedDefaults.length === 0 && namedExports.length === 0 && !hasVarTracing && frameworkInjects.length === 0) {
690
742
  return source;
691
743
  }
692
-
693
- // If ONLY framework injections (no function wrapping needed), inject directly
694
744
  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
745
  if (config.debug) {
729
- console.log(`[trickle/esm] Injected ${frameworkInjects.map(f => f.framework).join(', ')} instrumentation into ${moduleName}`);
746
+ console.log(`[trickle/esm] Wrapped ${frameworkInjects.map(f => f.framework).join(', ')} constructor(s) in ${moduleName}`);
730
747
  }
731
- return lines.join('\n');
748
+ return result.join('\n');
732
749
  }
733
750
 
734
751
  // 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.131",
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",