trickle-observe 0.2.129 → 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.
- package/observe-esm-hooks.mjs +148 -5
- package/package.json +1 -1
package/observe-esm-hooks.mjs
CHANGED
|
@@ -648,24 +648,123 @@ function transformSource(source, url, originalSource) {
|
|
|
648
648
|
}
|
|
649
649
|
}
|
|
650
650
|
|
|
651
|
-
//
|
|
652
|
-
|
|
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; }
|
|
657
|
+
const frameworkInjects = [];
|
|
658
|
+
const expressImportMatch = source.match(/import\s+(\w+)\s+from\s+['"]express['"]/);
|
|
659
|
+
const fastifyImportMatch = source.match(/import\s+(\w+)\s+from\s+['"]fastify['"]/);
|
|
660
|
+
const koaImportMatch = source.match(/import\s+(\w+)\s+from\s+['"]koa['"]/);
|
|
661
|
+
const honoImportMatch = source.match(/import\s+\{\s*Hono\s*(?:,\s*\w+)*\s*\}\s+from\s+['"]hono['"]/);
|
|
662
|
+
|
|
663
|
+
if (expressImportMatch) {
|
|
664
|
+
const factoryName = expressImportMatch[1];
|
|
665
|
+
frameworkInjects.push({ framework: 'express', factoryName, type: 'factory', importPath: config.wrapperPath.replace('/wrap.js', '/express.js') });
|
|
666
|
+
}
|
|
667
|
+
if (fastifyImportMatch) {
|
|
668
|
+
const factoryName = fastifyImportMatch[1];
|
|
669
|
+
frameworkInjects.push({ framework: 'fastify', factoryName, type: 'factory', importPath: config.wrapperPath.replace('/wrap.js', '/fastify.js') });
|
|
670
|
+
}
|
|
671
|
+
if (koaImportMatch) {
|
|
672
|
+
const className = koaImportMatch[1];
|
|
673
|
+
frameworkInjects.push({ framework: 'koa', factoryName: className, type: 'class', importPath: config.wrapperPath.replace('/wrap.js', '/koa.js') });
|
|
674
|
+
}
|
|
675
|
+
if (honoImportMatch) {
|
|
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
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// Framework imports are now rewritten above (constructor/factory wrapping).
|
|
740
|
+
// Early return if nothing else to transform.
|
|
741
|
+
if (exportedFunctions.length === 0 && exportedDefaults.length === 0 && namedExports.length === 0 && !hasVarTracing && frameworkInjects.length === 0) {
|
|
653
742
|
return source;
|
|
654
743
|
}
|
|
744
|
+
if (exportedFunctions.length === 0 && exportedDefaults.length === 0 && namedExports.length === 0 && !hasVarTracing && frameworkInjects.length > 0) {
|
|
745
|
+
if (config.debug) {
|
|
746
|
+
console.log(`[trickle/esm] Wrapped ${frameworkInjects.map(f => f.framework).join(', ')} constructor(s) in ${moduleName}`);
|
|
747
|
+
}
|
|
748
|
+
return result.join('\n');
|
|
749
|
+
}
|
|
655
750
|
|
|
656
751
|
// Add wrapper import and wrapping code
|
|
657
752
|
const wrapperPathEscaped = config.wrapperPath.replace(/\\/g, '\\\\');
|
|
658
753
|
|
|
659
754
|
// Insert wrapper setup at the top (after imports, before user code)
|
|
660
755
|
// Using import + createRequire to load CJS wrapper from ESM context
|
|
756
|
+
// Always create __require if we have exports OR framework injections
|
|
757
|
+
const needsWrapper = exportedFunctions.length > 0 || exportedDefaults.length > 0 || namedExports.length > 0 || frameworkInjects.length > 0;
|
|
661
758
|
const wrapSetup = [
|
|
662
759
|
'',
|
|
663
760
|
'// [trickle] Auto-observation wrappers',
|
|
664
761
|
`import { createRequire as __cr } from 'node:module';`,
|
|
665
762
|
`const __require = __cr(import.meta.url);`,
|
|
666
|
-
`const { wrapFunction: __tw } = __require('${wrapperPathEscaped}');`,
|
|
667
|
-
`const __twOpts = (name, paramNames) => { const o = { functionName: name, module: '${moduleName}', trackArgs: true, trackReturn: true, sampleRate: 1, maxDepth: 5, environment: process.env.TRICKLE_ENV || 'development', enabled: true }; if (paramNames && paramNames.length) o.paramNames = paramNames; return o; };`,
|
|
668
763
|
];
|
|
764
|
+
if (exportedFunctions.length > 0 || exportedDefaults.length > 0 || namedExports.length > 0) {
|
|
765
|
+
wrapSetup.push(`const { wrapFunction: __tw } = __require('${wrapperPathEscaped}');`);
|
|
766
|
+
wrapSetup.push(`const __twOpts = (name, paramNames) => { const o = { functionName: name, module: '${moduleName}', trackArgs: true, trackReturn: true, sampleRate: 1, maxDepth: 5, environment: process.env.TRICKLE_ENV || 'development', enabled: true }; if (paramNames && paramNames.length) o.paramNames = paramNames; return o; };`);
|
|
767
|
+
}
|
|
669
768
|
|
|
670
769
|
// For in-place wrapping: insert wrapper calls right after each function declaration
|
|
671
770
|
// This ensures functions are wrapped BEFORE any top-level code calls them
|
|
@@ -726,17 +825,61 @@ function transformSource(source, url, originalSource) {
|
|
|
726
825
|
result.push(`export default __trickle_default_wrapped;`);
|
|
727
826
|
}
|
|
728
827
|
|
|
828
|
+
// Inject framework instrumentation if detected
|
|
829
|
+
if (frameworkInjects.length > 0) {
|
|
830
|
+
for (const fi of frameworkInjects) {
|
|
831
|
+
const instrumentFn = fi.framework === 'express' ? 'instrumentExpress'
|
|
832
|
+
: fi.framework === 'fastify' ? 'instrumentFastify'
|
|
833
|
+
: fi.framework === 'koa' ? 'instrumentKoa'
|
|
834
|
+
: 'instrumentHono';
|
|
835
|
+
const fiPath = fi.importPath.replace(/\\/g, '\\\\');
|
|
836
|
+
// Find where the app is created and inject after
|
|
837
|
+
const appPattern = new RegExp(`(?:const|let|var)\\s+${fi.appVar}\\s*=`);
|
|
838
|
+
for (let ri = 0; ri < result.length; ri++) {
|
|
839
|
+
if (appPattern.test(result[ri])) {
|
|
840
|
+
let endLine = ri;
|
|
841
|
+
let depth = 0;
|
|
842
|
+
for (let j = ri; j < result.length; j++) {
|
|
843
|
+
for (const ch of result[j]) {
|
|
844
|
+
if (ch === '(' || ch === '{' || ch === '[') depth++;
|
|
845
|
+
if (ch === ')' || ch === '}' || ch === ']') depth--;
|
|
846
|
+
}
|
|
847
|
+
if (result[j].includes(';') && depth <= 0) { endLine = j; break; }
|
|
848
|
+
if (j > ri && depth <= 0) { endLine = j - 1; break; }
|
|
849
|
+
}
|
|
850
|
+
// We need to inject BEFORE routes are defined. Since __require from wrapSetup
|
|
851
|
+
// may not be initialized yet, we add a separate import + require pair.
|
|
852
|
+
// ESM import declarations are hoisted, so this import is available immediately.
|
|
853
|
+
// We use __cr_fw (unique name) to avoid conflicts with other createRequire imports.
|
|
854
|
+
const crImportLine = `import { createRequire as __cr_fw_${fi.framework} } from 'node:module';`;
|
|
855
|
+
// Insert the import at the very beginning of result
|
|
856
|
+
result.unshift(crImportLine);
|
|
857
|
+
// The endLine index shifted by 1 due to unshift
|
|
858
|
+
result.splice(endLine + 2, 0,
|
|
859
|
+
`try { const __rq_fw = __cr_fw_${fi.framework}(import.meta.url); const __fw = __rq_fw('${fiPath}'); __fw.${instrumentFn}(${fi.appVar}, { environment: process.env.TRICKLE_ENV || 'development' }); } catch(__e) { if (${config.debug}) console.error('[trickle/esm] Framework injection error:', __e.message); }`
|
|
860
|
+
);
|
|
861
|
+
break;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
729
867
|
const transformed = result.join('\n');
|
|
730
868
|
|
|
731
869
|
if (config.debug) {
|
|
732
870
|
const fnCount = exportedFunctions.length + exportedDefaults.length + namedExports.length;
|
|
733
871
|
const varCount = varDecls.length + destructDecls.length;
|
|
734
|
-
|
|
872
|
+
const fwCount = frameworkInjects.length;
|
|
873
|
+
console.log(`[trickle/esm] Transformed ${fnCount} exports, ${varCount} vars, ${fwCount} frameworks from ${moduleName}`);
|
|
735
874
|
}
|
|
736
875
|
|
|
737
876
|
return transformed;
|
|
738
877
|
}
|
|
739
878
|
|
|
879
|
+
// ── Framework auto-instrumentation for ESM ──
|
|
880
|
+
// ESM imports bypass Module._load, so CJS hooks don't fire.
|
|
881
|
+
// We detect framework imports in user source code and inject instrumentation calls.
|
|
882
|
+
|
|
740
883
|
/**
|
|
741
884
|
* ESM load hook — intercepts module loading to transform user modules.
|
|
742
885
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trickle-observe",
|
|
3
|
-
"version": "0.2.
|
|
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",
|