trickle-observe 0.1.0 → 0.2.0
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/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/vite-plugin.d.ts +37 -0
- package/dist/vite-plugin.js +229 -0
- package/package.json +3 -2
- package/src/index.ts +1 -0
- package/src/vite-plugin.ts +243 -0
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.observeFn = exports.observe = exports.trickleMiddleware = exports.instrumentExpress = exports.flush = void 0;
|
|
3
|
+
exports.wrapFunction = exports.observeFn = exports.observe = exports.trickleMiddleware = exports.instrumentExpress = exports.flush = void 0;
|
|
4
4
|
exports.configure = configure;
|
|
5
5
|
exports.trickle = trickle;
|
|
6
6
|
exports.trickleHandler = trickleHandler;
|
|
@@ -170,3 +170,5 @@ Object.defineProperty(exports, "trickleMiddleware", { enumerable: true, get: fun
|
|
|
170
170
|
var observe_1 = require("./observe");
|
|
171
171
|
Object.defineProperty(exports, "observe", { enumerable: true, get: function () { return observe_1.observe; } });
|
|
172
172
|
Object.defineProperty(exports, "observeFn", { enumerable: true, get: function () { return observe_1.observeFn; } });
|
|
173
|
+
var wrap_2 = require("./wrap");
|
|
174
|
+
Object.defineProperty(exports, "wrapFunction", { enumerable: true, get: function () { return wrap_2.wrapFunction; } });
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite plugin for trickle observation.
|
|
3
|
+
*
|
|
4
|
+
* Integrates into Vite's (and Vitest's) transform pipeline to wrap
|
|
5
|
+
* user functions with trickle observation — the same thing observe-register.ts
|
|
6
|
+
* does for Node's Module._compile, but for Vite/Vitest.
|
|
7
|
+
*
|
|
8
|
+
* Usage in vitest.config.ts:
|
|
9
|
+
*
|
|
10
|
+
* import { tricklePlugin } from 'trickle-observe/vite-plugin';
|
|
11
|
+
* export default defineConfig({
|
|
12
|
+
* plugins: [tricklePlugin()],
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* Or via CLI:
|
|
16
|
+
*
|
|
17
|
+
* trickle run vitest run tests/
|
|
18
|
+
*/
|
|
19
|
+
export interface TricklePluginOptions {
|
|
20
|
+
/** Substrings — only observe files whose paths contain one of these */
|
|
21
|
+
include?: string[];
|
|
22
|
+
/** Substrings — skip files whose paths contain one of these */
|
|
23
|
+
exclude?: string[];
|
|
24
|
+
/** Backend URL (default: http://localhost:4888) */
|
|
25
|
+
backendUrl?: string;
|
|
26
|
+
/** Enable debug logging */
|
|
27
|
+
debug?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare function tricklePlugin(options?: TricklePluginOptions): {
|
|
30
|
+
name: string;
|
|
31
|
+
enforce: "pre";
|
|
32
|
+
transform(code: string, id: string): {
|
|
33
|
+
code: string;
|
|
34
|
+
map: null;
|
|
35
|
+
} | null;
|
|
36
|
+
};
|
|
37
|
+
export default tricklePlugin;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Vite plugin for trickle observation.
|
|
4
|
+
*
|
|
5
|
+
* Integrates into Vite's (and Vitest's) transform pipeline to wrap
|
|
6
|
+
* user functions with trickle observation — the same thing observe-register.ts
|
|
7
|
+
* does for Node's Module._compile, but for Vite/Vitest.
|
|
8
|
+
*
|
|
9
|
+
* Usage in vitest.config.ts:
|
|
10
|
+
*
|
|
11
|
+
* import { tricklePlugin } from 'trickle-observe/vite-plugin';
|
|
12
|
+
* export default defineConfig({
|
|
13
|
+
* plugins: [tricklePlugin()],
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* Or via CLI:
|
|
17
|
+
*
|
|
18
|
+
* trickle run vitest run tests/
|
|
19
|
+
*/
|
|
20
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
21
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
22
|
+
};
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.tricklePlugin = tricklePlugin;
|
|
25
|
+
const path_1 = __importDefault(require("path"));
|
|
26
|
+
function tricklePlugin(options = {}) {
|
|
27
|
+
const include = options.include
|
|
28
|
+
?? (process.env.TRICKLE_OBSERVE_INCLUDE
|
|
29
|
+
? process.env.TRICKLE_OBSERVE_INCLUDE.split(',').map(s => s.trim())
|
|
30
|
+
: []);
|
|
31
|
+
const exclude = options.exclude
|
|
32
|
+
?? (process.env.TRICKLE_OBSERVE_EXCLUDE
|
|
33
|
+
? process.env.TRICKLE_OBSERVE_EXCLUDE.split(',').map(s => s.trim())
|
|
34
|
+
: []);
|
|
35
|
+
const backendUrl = options.backendUrl
|
|
36
|
+
?? process.env.TRICKLE_BACKEND_URL
|
|
37
|
+
?? 'http://localhost:4888';
|
|
38
|
+
const debug = options.debug
|
|
39
|
+
?? (process.env.TRICKLE_DEBUG === '1' || process.env.TRICKLE_DEBUG === 'true');
|
|
40
|
+
function shouldTransform(id) {
|
|
41
|
+
// Only JS/TS files
|
|
42
|
+
const ext = path_1.default.extname(id).toLowerCase();
|
|
43
|
+
if (!['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.mts'].includes(ext))
|
|
44
|
+
return false;
|
|
45
|
+
// Skip node_modules
|
|
46
|
+
if (id.includes('node_modules'))
|
|
47
|
+
return false;
|
|
48
|
+
// Skip trickle internals
|
|
49
|
+
if (id.includes('trickle-observe') || id.includes('client-js/'))
|
|
50
|
+
return false;
|
|
51
|
+
// Include filter
|
|
52
|
+
if (include.length > 0) {
|
|
53
|
+
if (!include.some(p => id.includes(p)))
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
// Exclude filter
|
|
57
|
+
if (exclude.length > 0) {
|
|
58
|
+
if (exclude.some(p => id.includes(p)))
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
name: 'trickle-observe',
|
|
65
|
+
enforce: 'pre',
|
|
66
|
+
transform(code, id) {
|
|
67
|
+
if (!shouldTransform(id))
|
|
68
|
+
return null;
|
|
69
|
+
const moduleName = path_1.default.basename(id).replace(/\.[jt]sx?$/, '');
|
|
70
|
+
const transformed = transformEsmSource(code, id, moduleName, backendUrl, debug);
|
|
71
|
+
if (transformed === code)
|
|
72
|
+
return null;
|
|
73
|
+
if (debug) {
|
|
74
|
+
console.log(`[trickle/vite] Transformed ${moduleName} (${id})`);
|
|
75
|
+
}
|
|
76
|
+
return { code: transformed, map: null };
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Find the closing brace position for a function body starting at `openBrace`.
|
|
82
|
+
*/
|
|
83
|
+
function findClosingBrace(source, openBrace) {
|
|
84
|
+
let depth = 1;
|
|
85
|
+
let pos = openBrace + 1;
|
|
86
|
+
while (pos < source.length && depth > 0) {
|
|
87
|
+
const ch = source[pos];
|
|
88
|
+
if (ch === '{') {
|
|
89
|
+
depth++;
|
|
90
|
+
}
|
|
91
|
+
else if (ch === '}') {
|
|
92
|
+
depth--;
|
|
93
|
+
if (depth === 0)
|
|
94
|
+
return pos;
|
|
95
|
+
}
|
|
96
|
+
else if (ch === '"' || ch === "'" || ch === '`') {
|
|
97
|
+
const quote = ch;
|
|
98
|
+
pos++;
|
|
99
|
+
while (pos < source.length) {
|
|
100
|
+
if (source[pos] === '\\') {
|
|
101
|
+
pos++;
|
|
102
|
+
}
|
|
103
|
+
else if (source[pos] === quote)
|
|
104
|
+
break;
|
|
105
|
+
else if (quote === '`' && source[pos] === '$' && pos + 1 < source.length && source[pos + 1] === '{') {
|
|
106
|
+
pos += 2;
|
|
107
|
+
let tDepth = 1;
|
|
108
|
+
while (pos < source.length && tDepth > 0) {
|
|
109
|
+
if (source[pos] === '{')
|
|
110
|
+
tDepth++;
|
|
111
|
+
else if (source[pos] === '}')
|
|
112
|
+
tDepth--;
|
|
113
|
+
pos++;
|
|
114
|
+
}
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
pos++;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else if (ch === '/' && pos + 1 < source.length && source[pos + 1] === '/') {
|
|
121
|
+
while (pos < source.length && source[pos] !== '\n')
|
|
122
|
+
pos++;
|
|
123
|
+
}
|
|
124
|
+
else if (ch === '/' && pos + 1 < source.length && source[pos + 1] === '*') {
|
|
125
|
+
pos += 2;
|
|
126
|
+
while (pos < source.length - 1 && !(source[pos] === '*' && source[pos + 1] === '/'))
|
|
127
|
+
pos++;
|
|
128
|
+
pos++;
|
|
129
|
+
}
|
|
130
|
+
pos++;
|
|
131
|
+
}
|
|
132
|
+
return -1;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Transform ESM source code to wrap function declarations with trickle observation.
|
|
136
|
+
*
|
|
137
|
+
* Prepends an import of the wrap helper, then inserts wrapper calls after
|
|
138
|
+
* each function declaration body — same approach as observe-register's
|
|
139
|
+
* transformCjsSource but using ESM imports.
|
|
140
|
+
*/
|
|
141
|
+
function transformEsmSource(source, filename, moduleName, backendUrl, debug) {
|
|
142
|
+
// Match top-level and nested function declarations (including async, export)
|
|
143
|
+
const funcRegex = /^[ \t]*(?:export\s+)?(?:async\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/gm;
|
|
144
|
+
const insertions = [];
|
|
145
|
+
let match;
|
|
146
|
+
while ((match = funcRegex.exec(source)) !== null) {
|
|
147
|
+
const name = match[1];
|
|
148
|
+
if (name === 'require' || name === 'exports' || name === 'module')
|
|
149
|
+
continue;
|
|
150
|
+
const afterMatch = match.index + match[0].length;
|
|
151
|
+
const openBrace = source.indexOf('{', afterMatch);
|
|
152
|
+
if (openBrace === -1)
|
|
153
|
+
continue;
|
|
154
|
+
// Extract parameter names
|
|
155
|
+
const paramStr = source.slice(afterMatch, openBrace).replace(/[()]/g, '').trim();
|
|
156
|
+
const paramNames = paramStr
|
|
157
|
+
? paramStr.split(',').map(p => {
|
|
158
|
+
const trimmed = p.trim().split('=')[0].trim().split(':')[0].trim();
|
|
159
|
+
if (trimmed.startsWith('{') || trimmed.startsWith('[') || trimmed.startsWith('...'))
|
|
160
|
+
return '';
|
|
161
|
+
return trimmed;
|
|
162
|
+
}).filter(Boolean)
|
|
163
|
+
: [];
|
|
164
|
+
const closeBrace = findClosingBrace(source, openBrace);
|
|
165
|
+
if (closeBrace === -1)
|
|
166
|
+
continue;
|
|
167
|
+
insertions.push({ position: closeBrace + 1, name, paramNames });
|
|
168
|
+
}
|
|
169
|
+
// Also match arrow functions assigned to const/let/var
|
|
170
|
+
const arrowRegex = /^[ \t]*(?:export\s+)?(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?::\s*[^=]+?)?\s*=>\s*\{/gm;
|
|
171
|
+
while ((match = arrowRegex.exec(source)) !== null) {
|
|
172
|
+
const name = match[1];
|
|
173
|
+
const openBrace = source.indexOf('{', match.index + match[0].length - 1);
|
|
174
|
+
if (openBrace === -1)
|
|
175
|
+
continue;
|
|
176
|
+
// Extract param names from the arrow function
|
|
177
|
+
const arrowStr = match[0];
|
|
178
|
+
const arrowParamMatch = arrowStr.match(/=\s*(?:async\s+)?(?:\(([^)]*)\)|([a-zA-Z_$][a-zA-Z0-9_$]*))\s*(?::\s*[^=]+?)?\s*=>/);
|
|
179
|
+
let paramNames = [];
|
|
180
|
+
if (arrowParamMatch) {
|
|
181
|
+
const paramStr = (arrowParamMatch[1] || arrowParamMatch[2] || '').trim();
|
|
182
|
+
if (paramStr) {
|
|
183
|
+
paramNames = paramStr.split(',').map(p => {
|
|
184
|
+
const trimmed = p.trim().split('=')[0].trim().split(':')[0].trim();
|
|
185
|
+
if (trimmed.startsWith('{') || trimmed.startsWith('[') || trimmed.startsWith('...'))
|
|
186
|
+
return '';
|
|
187
|
+
return trimmed;
|
|
188
|
+
}).filter(Boolean);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const closeBrace = findClosingBrace(source, openBrace);
|
|
192
|
+
if (closeBrace === -1)
|
|
193
|
+
continue;
|
|
194
|
+
insertions.push({ position: closeBrace + 1, name, paramNames });
|
|
195
|
+
}
|
|
196
|
+
if (insertions.length === 0)
|
|
197
|
+
return source;
|
|
198
|
+
// Prepend ESM import of the wrap helper + configure transport
|
|
199
|
+
const prefix = [
|
|
200
|
+
`import { wrapFunction as __trickle_wrapFn } from 'trickle-observe';`,
|
|
201
|
+
`import { configure as __trickle_configure } from 'trickle-observe';`,
|
|
202
|
+
`__trickle_configure({ backendUrl: ${JSON.stringify(backendUrl)}, batchIntervalMs: 2000, debug: ${debug}, enabled: true, environment: 'node' });`,
|
|
203
|
+
`function __trickle_wrap(fn, name, paramNames) {`,
|
|
204
|
+
` const opts = {`,
|
|
205
|
+
` functionName: name,`,
|
|
206
|
+
` module: ${JSON.stringify(moduleName)},`,
|
|
207
|
+
` trackArgs: true,`,
|
|
208
|
+
` trackReturn: true,`,
|
|
209
|
+
` sampleRate: 1,`,
|
|
210
|
+
` maxDepth: 5,`,
|
|
211
|
+
` environment: 'node',`,
|
|
212
|
+
` enabled: true,`,
|
|
213
|
+
` };`,
|
|
214
|
+
` if (paramNames && paramNames.length) opts.paramNames = paramNames;`,
|
|
215
|
+
` return __trickle_wrapFn(fn, opts);`,
|
|
216
|
+
`}`,
|
|
217
|
+
'',
|
|
218
|
+
].join('\n');
|
|
219
|
+
// Insert wrapper calls after each function body (reverse order)
|
|
220
|
+
let result = source;
|
|
221
|
+
for (let i = insertions.length - 1; i >= 0; i--) {
|
|
222
|
+
const { position, name, paramNames } = insertions[i];
|
|
223
|
+
const paramNamesArg = paramNames.length > 0 ? JSON.stringify(paramNames) : 'null';
|
|
224
|
+
const wrapperCall = `\ntry{${name}=__trickle_wrap(${name},'${name}',${paramNamesArg})}catch(__e){}\n`;
|
|
225
|
+
result = result.slice(0, position) + wrapperCall + result.slice(position);
|
|
226
|
+
}
|
|
227
|
+
return prefix + result;
|
|
228
|
+
}
|
|
229
|
+
exports.default = tricklePlugin;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trickle-observe",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Runtime type observability for JavaScript applications",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"./observe-esm": "./observe-esm.mjs",
|
|
12
12
|
"./auto": "./auto.js",
|
|
13
13
|
"./auto-env": "./auto-env.js",
|
|
14
|
-
"./auto-esm": "./auto-esm.mjs"
|
|
14
|
+
"./auto-esm": "./auto-esm.mjs",
|
|
15
|
+
"./vite-plugin": "./dist/vite-plugin.js"
|
|
15
16
|
},
|
|
16
17
|
"scripts": {
|
|
17
18
|
"build": "tsc",
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite plugin for trickle observation.
|
|
3
|
+
*
|
|
4
|
+
* Integrates into Vite's (and Vitest's) transform pipeline to wrap
|
|
5
|
+
* user functions with trickle observation — the same thing observe-register.ts
|
|
6
|
+
* does for Node's Module._compile, but for Vite/Vitest.
|
|
7
|
+
*
|
|
8
|
+
* Usage in vitest.config.ts:
|
|
9
|
+
*
|
|
10
|
+
* import { tricklePlugin } from 'trickle-observe/vite-plugin';
|
|
11
|
+
* export default defineConfig({
|
|
12
|
+
* plugins: [tricklePlugin()],
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* Or via CLI:
|
|
16
|
+
*
|
|
17
|
+
* trickle run vitest run tests/
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import path from 'path';
|
|
21
|
+
|
|
22
|
+
export interface TricklePluginOptions {
|
|
23
|
+
/** Substrings — only observe files whose paths contain one of these */
|
|
24
|
+
include?: string[];
|
|
25
|
+
/** Substrings — skip files whose paths contain one of these */
|
|
26
|
+
exclude?: string[];
|
|
27
|
+
/** Backend URL (default: http://localhost:4888) */
|
|
28
|
+
backendUrl?: string;
|
|
29
|
+
/** Enable debug logging */
|
|
30
|
+
debug?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function tricklePlugin(options: TricklePluginOptions = {}) {
|
|
34
|
+
const include = options.include
|
|
35
|
+
?? (process.env.TRICKLE_OBSERVE_INCLUDE
|
|
36
|
+
? process.env.TRICKLE_OBSERVE_INCLUDE.split(',').map(s => s.trim())
|
|
37
|
+
: []);
|
|
38
|
+
const exclude = options.exclude
|
|
39
|
+
?? (process.env.TRICKLE_OBSERVE_EXCLUDE
|
|
40
|
+
? process.env.TRICKLE_OBSERVE_EXCLUDE.split(',').map(s => s.trim())
|
|
41
|
+
: []);
|
|
42
|
+
const backendUrl = options.backendUrl
|
|
43
|
+
?? process.env.TRICKLE_BACKEND_URL
|
|
44
|
+
?? 'http://localhost:4888';
|
|
45
|
+
const debug = options.debug
|
|
46
|
+
?? (process.env.TRICKLE_DEBUG === '1' || process.env.TRICKLE_DEBUG === 'true');
|
|
47
|
+
|
|
48
|
+
function shouldTransform(id: string): boolean {
|
|
49
|
+
// Only JS/TS files
|
|
50
|
+
const ext = path.extname(id).toLowerCase();
|
|
51
|
+
if (!['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.mts'].includes(ext)) return false;
|
|
52
|
+
|
|
53
|
+
// Skip node_modules
|
|
54
|
+
if (id.includes('node_modules')) return false;
|
|
55
|
+
|
|
56
|
+
// Skip trickle internals
|
|
57
|
+
if (id.includes('trickle-observe') || id.includes('client-js/')) return false;
|
|
58
|
+
|
|
59
|
+
// Include filter
|
|
60
|
+
if (include.length > 0) {
|
|
61
|
+
if (!include.some(p => id.includes(p))) return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Exclude filter
|
|
65
|
+
if (exclude.length > 0) {
|
|
66
|
+
if (exclude.some(p => id.includes(p))) return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
name: 'trickle-observe',
|
|
74
|
+
enforce: 'pre' as const,
|
|
75
|
+
|
|
76
|
+
transform(code: string, id: string) {
|
|
77
|
+
if (!shouldTransform(id)) return null;
|
|
78
|
+
|
|
79
|
+
const moduleName = path.basename(id).replace(/\.[jt]sx?$/, '');
|
|
80
|
+
const transformed = transformEsmSource(code, id, moduleName, backendUrl, debug);
|
|
81
|
+
if (transformed === code) return null;
|
|
82
|
+
|
|
83
|
+
if (debug) {
|
|
84
|
+
console.log(`[trickle/vite] Transformed ${moduleName} (${id})`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return { code: transformed, map: null };
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Find the closing brace position for a function body starting at `openBrace`.
|
|
94
|
+
*/
|
|
95
|
+
function findClosingBrace(source: string, openBrace: number): number {
|
|
96
|
+
let depth = 1;
|
|
97
|
+
let pos = openBrace + 1;
|
|
98
|
+
while (pos < source.length && depth > 0) {
|
|
99
|
+
const ch = source[pos];
|
|
100
|
+
if (ch === '{') {
|
|
101
|
+
depth++;
|
|
102
|
+
} else if (ch === '}') {
|
|
103
|
+
depth--;
|
|
104
|
+
if (depth === 0) return pos;
|
|
105
|
+
} else if (ch === '"' || ch === "'" || ch === '`') {
|
|
106
|
+
const quote = ch;
|
|
107
|
+
pos++;
|
|
108
|
+
while (pos < source.length) {
|
|
109
|
+
if (source[pos] === '\\') { pos++; }
|
|
110
|
+
else if (source[pos] === quote) break;
|
|
111
|
+
else if (quote === '`' && source[pos] === '$' && pos + 1 < source.length && source[pos + 1] === '{') {
|
|
112
|
+
pos += 2;
|
|
113
|
+
let tDepth = 1;
|
|
114
|
+
while (pos < source.length && tDepth > 0) {
|
|
115
|
+
if (source[pos] === '{') tDepth++;
|
|
116
|
+
else if (source[pos] === '}') tDepth--;
|
|
117
|
+
pos++;
|
|
118
|
+
}
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
pos++;
|
|
122
|
+
}
|
|
123
|
+
} else if (ch === '/' && pos + 1 < source.length && source[pos + 1] === '/') {
|
|
124
|
+
while (pos < source.length && source[pos] !== '\n') pos++;
|
|
125
|
+
} else if (ch === '/' && pos + 1 < source.length && source[pos + 1] === '*') {
|
|
126
|
+
pos += 2;
|
|
127
|
+
while (pos < source.length - 1 && !(source[pos] === '*' && source[pos + 1] === '/')) pos++;
|
|
128
|
+
pos++;
|
|
129
|
+
}
|
|
130
|
+
pos++;
|
|
131
|
+
}
|
|
132
|
+
return -1;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Transform ESM source code to wrap function declarations with trickle observation.
|
|
137
|
+
*
|
|
138
|
+
* Prepends an import of the wrap helper, then inserts wrapper calls after
|
|
139
|
+
* each function declaration body — same approach as observe-register's
|
|
140
|
+
* transformCjsSource but using ESM imports.
|
|
141
|
+
*/
|
|
142
|
+
function transformEsmSource(
|
|
143
|
+
source: string,
|
|
144
|
+
filename: string,
|
|
145
|
+
moduleName: string,
|
|
146
|
+
backendUrl: string,
|
|
147
|
+
debug: boolean,
|
|
148
|
+
): string {
|
|
149
|
+
// Match top-level and nested function declarations (including async, export)
|
|
150
|
+
const funcRegex = /^[ \t]*(?:export\s+)?(?:async\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/gm;
|
|
151
|
+
const insertions: Array<{ position: number; name: string; paramNames: string[] }> = [];
|
|
152
|
+
let match;
|
|
153
|
+
|
|
154
|
+
while ((match = funcRegex.exec(source)) !== null) {
|
|
155
|
+
const name = match[1];
|
|
156
|
+
if (name === 'require' || name === 'exports' || name === 'module') continue;
|
|
157
|
+
|
|
158
|
+
const afterMatch = match.index + match[0].length;
|
|
159
|
+
const openBrace = source.indexOf('{', afterMatch);
|
|
160
|
+
if (openBrace === -1) continue;
|
|
161
|
+
|
|
162
|
+
// Extract parameter names
|
|
163
|
+
const paramStr = source.slice(afterMatch, openBrace).replace(/[()]/g, '').trim();
|
|
164
|
+
const paramNames = paramStr
|
|
165
|
+
? paramStr.split(',').map(p => {
|
|
166
|
+
const trimmed = p.trim().split('=')[0].trim().split(':')[0].trim();
|
|
167
|
+
if (trimmed.startsWith('{') || trimmed.startsWith('[') || trimmed.startsWith('...')) return '';
|
|
168
|
+
return trimmed;
|
|
169
|
+
}).filter(Boolean)
|
|
170
|
+
: [];
|
|
171
|
+
|
|
172
|
+
const closeBrace = findClosingBrace(source, openBrace);
|
|
173
|
+
if (closeBrace === -1) continue;
|
|
174
|
+
|
|
175
|
+
insertions.push({ position: closeBrace + 1, name, paramNames });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Also match arrow functions assigned to const/let/var
|
|
179
|
+
const arrowRegex = /^[ \t]*(?:export\s+)?(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:async\s+)?(?:\([^)]*\)|[a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?::\s*[^=]+?)?\s*=>\s*\{/gm;
|
|
180
|
+
|
|
181
|
+
while ((match = arrowRegex.exec(source)) !== null) {
|
|
182
|
+
const name = match[1];
|
|
183
|
+
const openBrace = source.indexOf('{', match.index + match[0].length - 1);
|
|
184
|
+
if (openBrace === -1) continue;
|
|
185
|
+
|
|
186
|
+
// Extract param names from the arrow function
|
|
187
|
+
const arrowStr = match[0];
|
|
188
|
+
const arrowParamMatch = arrowStr.match(/=\s*(?:async\s+)?(?:\(([^)]*)\)|([a-zA-Z_$][a-zA-Z0-9_$]*))\s*(?::\s*[^=]+?)?\s*=>/);
|
|
189
|
+
let paramNames: string[] = [];
|
|
190
|
+
if (arrowParamMatch) {
|
|
191
|
+
const paramStr = (arrowParamMatch[1] || arrowParamMatch[2] || '').trim();
|
|
192
|
+
if (paramStr) {
|
|
193
|
+
paramNames = paramStr.split(',').map(p => {
|
|
194
|
+
const trimmed = p.trim().split('=')[0].trim().split(':')[0].trim();
|
|
195
|
+
if (trimmed.startsWith('{') || trimmed.startsWith('[') || trimmed.startsWith('...')) return '';
|
|
196
|
+
return trimmed;
|
|
197
|
+
}).filter(Boolean);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const closeBrace = findClosingBrace(source, openBrace);
|
|
202
|
+
if (closeBrace === -1) continue;
|
|
203
|
+
|
|
204
|
+
insertions.push({ position: closeBrace + 1, name, paramNames });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (insertions.length === 0) return source;
|
|
208
|
+
|
|
209
|
+
// Prepend ESM import of the wrap helper + configure transport
|
|
210
|
+
const prefix = [
|
|
211
|
+
`import { wrapFunction as __trickle_wrapFn } from 'trickle-observe';`,
|
|
212
|
+
`import { configure as __trickle_configure } from 'trickle-observe';`,
|
|
213
|
+
`__trickle_configure({ backendUrl: ${JSON.stringify(backendUrl)}, batchIntervalMs: 2000, debug: ${debug}, enabled: true, environment: 'node' });`,
|
|
214
|
+
`function __trickle_wrap(fn, name, paramNames) {`,
|
|
215
|
+
` const opts = {`,
|
|
216
|
+
` functionName: name,`,
|
|
217
|
+
` module: ${JSON.stringify(moduleName)},`,
|
|
218
|
+
` trackArgs: true,`,
|
|
219
|
+
` trackReturn: true,`,
|
|
220
|
+
` sampleRate: 1,`,
|
|
221
|
+
` maxDepth: 5,`,
|
|
222
|
+
` environment: 'node',`,
|
|
223
|
+
` enabled: true,`,
|
|
224
|
+
` };`,
|
|
225
|
+
` if (paramNames && paramNames.length) opts.paramNames = paramNames;`,
|
|
226
|
+
` return __trickle_wrapFn(fn, opts);`,
|
|
227
|
+
`}`,
|
|
228
|
+
'',
|
|
229
|
+
].join('\n');
|
|
230
|
+
|
|
231
|
+
// Insert wrapper calls after each function body (reverse order)
|
|
232
|
+
let result = source;
|
|
233
|
+
for (let i = insertions.length - 1; i >= 0; i--) {
|
|
234
|
+
const { position, name, paramNames } = insertions[i];
|
|
235
|
+
const paramNamesArg = paramNames.length > 0 ? JSON.stringify(paramNames) : 'null';
|
|
236
|
+
const wrapperCall = `\ntry{${name}=__trickle_wrap(${name},'${name}',${paramNamesArg})}catch(__e){}\n`;
|
|
237
|
+
result = result.slice(0, position) + wrapperCall + result.slice(position);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return prefix + result;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export default tricklePlugin;
|