trickle-observe 0.2.66 → 0.2.68
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/observe-register.js +47 -1
- package/observe-esm-hooks.mjs +66 -16
- package/package.json +1 -1
- package/src/observe-register.ts +46 -1
package/dist/observe-register.js
CHANGED
|
@@ -418,6 +418,48 @@ function transformCjsSource(source, filename, moduleName, env) {
|
|
|
418
418
|
continue;
|
|
419
419
|
insertions.push({ position: closeBrace + 1, name, paramNames });
|
|
420
420
|
}
|
|
421
|
+
// Find class declarations and wrap their methods
|
|
422
|
+
const classRegex = /^[ \t]*class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?:extends\s+[a-zA-Z_$.]+\s*)?\{/gm;
|
|
423
|
+
const classInsertions = [];
|
|
424
|
+
let classMatch;
|
|
425
|
+
while ((classMatch = classRegex.exec(source)) !== null) {
|
|
426
|
+
const className = classMatch[1];
|
|
427
|
+
const classOpenBrace = source.indexOf('{', classMatch.index + classMatch[0].length - 1);
|
|
428
|
+
if (classOpenBrace === -1)
|
|
429
|
+
continue;
|
|
430
|
+
const classCloseBrace = findClosingBrace(source, classOpenBrace);
|
|
431
|
+
if (classCloseBrace === -1)
|
|
432
|
+
continue;
|
|
433
|
+
// Extract methods from the class body
|
|
434
|
+
const classBody = source.slice(classOpenBrace + 1, classCloseBrace);
|
|
435
|
+
// Match: methodName(params) { or async methodName(params) { or static methodName(params) {
|
|
436
|
+
const methodRegex = /^[ \t]*(static\s+)?(?:async\s+)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(([^)]*)\)\s*\{/gm;
|
|
437
|
+
let methodMatch;
|
|
438
|
+
while ((methodMatch = methodRegex.exec(classBody)) !== null) {
|
|
439
|
+
const isStatic = !!methodMatch[1];
|
|
440
|
+
const methodName = methodMatch[2];
|
|
441
|
+
// Skip constructor and private methods
|
|
442
|
+
if (methodName === 'constructor' || methodName.startsWith('_'))
|
|
443
|
+
continue;
|
|
444
|
+
// Extract param names
|
|
445
|
+
const mParamStr = methodMatch[3].trim();
|
|
446
|
+
const mParamNames = mParamStr
|
|
447
|
+
? mParamStr.split(',').map((p) => {
|
|
448
|
+
const trimmed = p.trim().split('=')[0].trim().split(':')[0].trim();
|
|
449
|
+
if (trimmed.startsWith('{') || trimmed.startsWith('[') || trimmed.startsWith('...'))
|
|
450
|
+
return '';
|
|
451
|
+
return trimmed;
|
|
452
|
+
}).filter(Boolean)
|
|
453
|
+
: [];
|
|
454
|
+
const target = isStatic ? className : `${className}.prototype`;
|
|
455
|
+
const obsName = `${className}.${methodName}`;
|
|
456
|
+
const paramNamesArg = mParamNames.length > 0 ? JSON.stringify(mParamNames) : 'null';
|
|
457
|
+
classInsertions.push({
|
|
458
|
+
position: classCloseBrace + 1,
|
|
459
|
+
code: `\ntry{${target}.${methodName}=__trickle_wrap(${target}.${methodName},'${obsName}',${paramNamesArg})}catch(__e){}\n`,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
421
463
|
// Also find variable declarations for tracing
|
|
422
464
|
const varTraceEnabled = process.env.TRICKLE_TRACE_VARS !== '0';
|
|
423
465
|
// For TypeScript files (compiled by ts-node/tsc), type declarations (interfaces, type aliases)
|
|
@@ -496,7 +538,7 @@ function transformCjsSource(source, filename, moduleName, env) {
|
|
|
496
538
|
destructInsertions = findDestructuredDeclarations(source);
|
|
497
539
|
}
|
|
498
540
|
}
|
|
499
|
-
if (insertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0)
|
|
541
|
+
if (insertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0 && classInsertions.length === 0)
|
|
500
542
|
return source;
|
|
501
543
|
// Resolve the path to the wrap helper (compiled JS)
|
|
502
544
|
const wrapHelperPath = path_1.default.join(__dirname, 'wrap.js');
|
|
@@ -546,6 +588,10 @@ function transformCjsSource(source, filename, moduleName, env) {
|
|
|
546
588
|
code: `\n;try{${calls}}catch(__e){}\n`,
|
|
547
589
|
});
|
|
548
590
|
}
|
|
591
|
+
// Add class method wrappings
|
|
592
|
+
for (const ci of classInsertions) {
|
|
593
|
+
allInsertions.push(ci);
|
|
594
|
+
}
|
|
549
595
|
// Sort by position descending (insert from end to preserve earlier positions)
|
|
550
596
|
allInsertions.sort((a, b) => b.position - a.position);
|
|
551
597
|
let result = source;
|
package/observe-esm-hooks.mjs
CHANGED
|
@@ -523,7 +523,21 @@ function transformSource(source, url, originalSource) {
|
|
|
523
523
|
const paramNames = parenPos >= 0 ? extractParamNamesFromSource(source, parenPos) : [];
|
|
524
524
|
// Remove 'export ' prefix, keep the function
|
|
525
525
|
result.push(line.replace(/^(\s*)export\s+/, '$1'));
|
|
526
|
-
exportedFunctions.push({ name, paramNames });
|
|
526
|
+
exportedFunctions.push({ name, paramNames, wrapInPlace: true });
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Non-exported function declarations — wrap them too for entry module coverage
|
|
531
|
+
const plainFuncMatch = trimmed.match(/^(async\s+)?function\s+(\w+)\s*(?:<[^>]*>)?\s*\(/);
|
|
532
|
+
if (plainFuncMatch && !trimmed.startsWith('export')) {
|
|
533
|
+
const name = plainFuncMatch[2];
|
|
534
|
+
if (name !== 'require' && name !== 'exports' && name !== 'module' && !name.startsWith('__trickle')) {
|
|
535
|
+
const srcOffset = lineOffset(source, i) + (line.length - trimmed.length);
|
|
536
|
+
const parenPos = source.indexOf('(', srcOffset + plainFuncMatch[0].indexOf('('));
|
|
537
|
+
const paramNames = parenPos >= 0 ? extractParamNamesFromSource(source, parenPos) : [];
|
|
538
|
+
exportedFunctions.push({ name, paramNames, wrapInPlace: true, noExport: true });
|
|
539
|
+
}
|
|
540
|
+
result.push(line);
|
|
527
541
|
continue;
|
|
528
542
|
}
|
|
529
543
|
|
|
@@ -607,23 +621,58 @@ function transformSource(source, url, originalSource) {
|
|
|
607
621
|
return source;
|
|
608
622
|
}
|
|
609
623
|
|
|
610
|
-
// Add wrapper import and wrapping code
|
|
624
|
+
// Add wrapper import and wrapping code
|
|
611
625
|
const wrapperPathEscaped = config.wrapperPath.replace(/\\/g, '\\\\');
|
|
612
626
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
627
|
+
// Insert wrapper setup at the top (after imports, before user code)
|
|
628
|
+
// Using import + createRequire to load CJS wrapper from ESM context
|
|
629
|
+
const wrapSetup = [
|
|
630
|
+
'',
|
|
631
|
+
'// [trickle] Auto-observation wrappers',
|
|
632
|
+
`import { createRequire as __cr } from 'node:module';`,
|
|
633
|
+
`const __require = __cr(import.meta.url);`,
|
|
634
|
+
`const { wrapFunction: __tw } = __require('${wrapperPathEscaped}');`,
|
|
635
|
+
`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; };`,
|
|
636
|
+
];
|
|
637
|
+
|
|
638
|
+
// For in-place wrapping: insert wrapper calls right after each function declaration
|
|
639
|
+
// This ensures functions are wrapped BEFORE any top-level code calls them
|
|
640
|
+
const inPlaceWraps = [];
|
|
641
|
+
for (const fn of exportedFunctions) {
|
|
642
|
+
if (fn.wrapInPlace) {
|
|
643
|
+
const pn = fn.paramNames.length > 0 ? JSON.stringify(fn.paramNames) : 'null';
|
|
644
|
+
inPlaceWraps.push(`try{${fn.name}=__tw(${fn.name},__twOpts('${fn.name}',${pn}))}catch(__e){}`);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
619
647
|
|
|
620
|
-
//
|
|
648
|
+
// Find the right position to insert the wrap setup + in-place wraps
|
|
649
|
+
// Insert after the last import/function-declaration block, before top-level code
|
|
650
|
+
let insertIdx = 0;
|
|
651
|
+
for (let j = 0; j < result.length; j++) {
|
|
652
|
+
const t = result[j].trimStart();
|
|
653
|
+
if (t.startsWith('import ') || t.startsWith('// [trickle]') || t === '' ||
|
|
654
|
+
t.startsWith('function ') || t.startsWith('async function ') ||
|
|
655
|
+
t.startsWith('const __trickle_tv') || t.startsWith('const __require_tv') ||
|
|
656
|
+
t.startsWith('const __tv_mod') || t.startsWith('if (typeof __tv_mod')) {
|
|
657
|
+
insertIdx = j + 1;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
// But make sure we're after any function body closing braces too
|
|
661
|
+
// Simple heuristic: find the last line that's just '}' before any assignment/call
|
|
662
|
+
for (let j = insertIdx; j < result.length; j++) {
|
|
663
|
+
const t = result[j].trim();
|
|
664
|
+
if (t === '}') insertIdx = j + 1;
|
|
665
|
+
else if (t && !t.startsWith('//') && !t.startsWith('function') && !t.startsWith('async function')) break;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
result.splice(insertIdx, 0, ...wrapSetup, ...inPlaceWraps, '');
|
|
669
|
+
|
|
670
|
+
// Re-export wrapped functions (for importers of this module)
|
|
621
671
|
const reExports = [];
|
|
622
|
-
for (const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
reExports.push(`${wrappedName} as ${name}`);
|
|
672
|
+
for (const fn of exportedFunctions) {
|
|
673
|
+
if (!fn.noExport) {
|
|
674
|
+
reExports.push(`${fn.name}`);
|
|
675
|
+
}
|
|
627
676
|
}
|
|
628
677
|
|
|
629
678
|
// Handle named exports from export { } statements
|
|
@@ -703,10 +752,11 @@ export async function load(url, context, nextLoad) {
|
|
|
703
752
|
? source
|
|
704
753
|
: Buffer.from(source).toString('utf-8');
|
|
705
754
|
|
|
706
|
-
// Only transform if the module has exports or variable declarations to trace
|
|
755
|
+
// Only transform if the module has exports, function declarations, or variable declarations to trace
|
|
707
756
|
const varTraceEnabled = process.env.TRICKLE_TRACE_VARS !== '0' && config.traceVarPath;
|
|
708
757
|
const hasVarDecls = varTraceEnabled && /^[ \t]*(?:export\s+)?(?:const|let|var)\s+[a-zA-Z_$]/m.test(sourceStr);
|
|
709
|
-
|
|
758
|
+
const hasFuncDecls = /^[ \t]*(?:export\s+)?(?:async\s+)?function\s+\w/m.test(sourceStr);
|
|
759
|
+
if (!sourceStr.includes('export ') && !hasVarDecls && !hasFuncDecls) return result;
|
|
710
760
|
|
|
711
761
|
try {
|
|
712
762
|
const transformed = transformSource(sourceStr, url);
|
package/package.json
CHANGED
package/src/observe-register.ts
CHANGED
|
@@ -389,6 +389,46 @@ function transformCjsSource(source: string, filename: string, moduleName: string
|
|
|
389
389
|
insertions.push({ position: closeBrace + 1, name, paramNames });
|
|
390
390
|
}
|
|
391
391
|
|
|
392
|
+
// Find class declarations and wrap their methods
|
|
393
|
+
const classRegex = /^[ \t]*class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?:extends\s+[a-zA-Z_$.]+\s*)?\{/gm;
|
|
394
|
+
const classInsertions: Array<{ position: number; code: string }> = [];
|
|
395
|
+
let classMatch;
|
|
396
|
+
while ((classMatch = classRegex.exec(source)) !== null) {
|
|
397
|
+
const className = classMatch[1];
|
|
398
|
+
const classOpenBrace = source.indexOf('{', classMatch.index + classMatch[0].length - 1);
|
|
399
|
+
if (classOpenBrace === -1) continue;
|
|
400
|
+
const classCloseBrace = findClosingBrace(source, classOpenBrace);
|
|
401
|
+
if (classCloseBrace === -1) continue;
|
|
402
|
+
|
|
403
|
+
// Extract methods from the class body
|
|
404
|
+
const classBody = source.slice(classOpenBrace + 1, classCloseBrace);
|
|
405
|
+
// Match: methodName(params) { or async methodName(params) { or static methodName(params) {
|
|
406
|
+
const methodRegex = /^[ \t]*(static\s+)?(?:async\s+)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(([^)]*)\)\s*\{/gm;
|
|
407
|
+
let methodMatch;
|
|
408
|
+
while ((methodMatch = methodRegex.exec(classBody)) !== null) {
|
|
409
|
+
const isStatic = !!methodMatch[1];
|
|
410
|
+
const methodName = methodMatch[2];
|
|
411
|
+
// Skip constructor and private methods
|
|
412
|
+
if (methodName === 'constructor' || methodName.startsWith('_')) continue;
|
|
413
|
+
// Extract param names
|
|
414
|
+
const mParamStr = methodMatch[3].trim();
|
|
415
|
+
const mParamNames = mParamStr
|
|
416
|
+
? mParamStr.split(',').map((p: string) => {
|
|
417
|
+
const trimmed = p.trim().split('=')[0].trim().split(':')[0].trim();
|
|
418
|
+
if (trimmed.startsWith('{') || trimmed.startsWith('[') || trimmed.startsWith('...')) return '';
|
|
419
|
+
return trimmed;
|
|
420
|
+
}).filter(Boolean)
|
|
421
|
+
: [];
|
|
422
|
+
const target = isStatic ? className : `${className}.prototype`;
|
|
423
|
+
const obsName = `${className}.${methodName}`;
|
|
424
|
+
const paramNamesArg = mParamNames.length > 0 ? JSON.stringify(mParamNames) : 'null';
|
|
425
|
+
classInsertions.push({
|
|
426
|
+
position: classCloseBrace + 1,
|
|
427
|
+
code: `\ntry{${target}.${methodName}=__trickle_wrap(${target}.${methodName},'${obsName}',${paramNamesArg})}catch(__e){}\n`,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
392
432
|
// Also find variable declarations for tracing
|
|
393
433
|
const varTraceEnabled = process.env.TRICKLE_TRACE_VARS !== '0';
|
|
394
434
|
|
|
@@ -468,7 +508,7 @@ function transformCjsSource(source: string, filename: string, moduleName: string
|
|
|
468
508
|
}
|
|
469
509
|
}
|
|
470
510
|
|
|
471
|
-
if (insertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0) return source;
|
|
511
|
+
if (insertions.length === 0 && varInsertions.length === 0 && destructInsertions.length === 0 && classInsertions.length === 0) return source;
|
|
472
512
|
|
|
473
513
|
// Resolve the path to the wrap helper (compiled JS)
|
|
474
514
|
const wrapHelperPath = path.join(__dirname, 'wrap.js');
|
|
@@ -531,6 +571,11 @@ function transformCjsSource(source: string, filename: string, moduleName: string
|
|
|
531
571
|
});
|
|
532
572
|
}
|
|
533
573
|
|
|
574
|
+
// Add class method wrappings
|
|
575
|
+
for (const ci of classInsertions) {
|
|
576
|
+
allInsertions.push(ci);
|
|
577
|
+
}
|
|
578
|
+
|
|
534
579
|
// Sort by position descending (insert from end to preserve earlier positions)
|
|
535
580
|
allInsertions.sort((a, b) => b.position - a.position);
|
|
536
581
|
|