trickle-observe 0.1.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/auto-env.js +13 -0
- package/auto-esm.mjs +128 -0
- package/auto.js +3 -0
- package/dist/auto-codegen.d.ts +29 -0
- package/dist/auto-codegen.js +999 -0
- package/dist/auto-register.d.ts +16 -0
- package/dist/auto-register.js +99 -0
- package/dist/cache.d.ts +27 -0
- package/dist/cache.js +52 -0
- package/dist/env-detect.d.ts +5 -0
- package/dist/env-detect.js +35 -0
- package/dist/express.d.ts +44 -0
- package/dist/express.js +342 -0
- package/dist/fetch-observer.d.ts +24 -0
- package/dist/fetch-observer.js +217 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.js +172 -0
- package/dist/observe-register.d.ts +29 -0
- package/dist/observe-register.js +455 -0
- package/dist/observe.d.ts +44 -0
- package/dist/observe.js +109 -0
- package/dist/proxy-tracker.d.ts +15 -0
- package/dist/proxy-tracker.js +172 -0
- package/dist/register.d.ts +21 -0
- package/dist/register.js +105 -0
- package/dist/transport.d.ts +22 -0
- package/dist/transport.js +228 -0
- package/dist/type-hash.d.ts +5 -0
- package/dist/type-hash.js +60 -0
- package/dist/type-inference.d.ts +14 -0
- package/dist/type-inference.js +259 -0
- package/dist/types.d.ts +78 -0
- package/dist/types.js +2 -0
- package/dist/wrap.d.ts +10 -0
- package/dist/wrap.js +247 -0
- package/observe-esm-hooks.mjs +367 -0
- package/observe-esm.mjs +40 -0
- package/observe.js +2 -0
- package/package.json +26 -0
- package/register.js +2 -0
- package/src/auto-codegen.ts +1058 -0
- package/src/auto-register.ts +102 -0
- package/src/cache.ts +53 -0
- package/src/env-detect.ts +22 -0
- package/src/express.ts +386 -0
- package/src/fetch-observer.ts +226 -0
- package/src/index.ts +199 -0
- package/src/observe-register.ts +453 -0
- package/src/observe.ts +127 -0
- package/src/proxy-tracker.ts +208 -0
- package/src/register.ts +110 -0
- package/src/transport.ts +207 -0
- package/src/type-hash.ts +71 -0
- package/src/type-inference.ts +285 -0
- package/src/types.ts +61 -0
- package/src/wrap.ts +289 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Fetch observer — patches global.fetch to automatically capture
|
|
4
|
+
* request/response types from HTTP calls made by user code.
|
|
5
|
+
*
|
|
6
|
+
* When your app does:
|
|
7
|
+
* const data = await fetch('https://api.example.com/users').then(r => r.json());
|
|
8
|
+
*
|
|
9
|
+
* Trickle captures:
|
|
10
|
+
* - Function name: "GET /users" (method + path)
|
|
11
|
+
* - Module: "api.example.com" (hostname)
|
|
12
|
+
* - Input type: request body (for POST/PUT/PATCH)
|
|
13
|
+
* - Return type: inferred from JSON response
|
|
14
|
+
* - Sample data: actual response payload
|
|
15
|
+
*
|
|
16
|
+
* The observer only intercepts when .json() is called, so:
|
|
17
|
+
* - Non-JSON responses (HTML, binary) are ignored
|
|
18
|
+
* - The original response is never modified
|
|
19
|
+
* - No extra network requests are made
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.patchFetch = patchFetch;
|
|
23
|
+
const type_inference_1 = require("./type-inference");
|
|
24
|
+
const type_hash_1 = require("./type-hash");
|
|
25
|
+
const transport_1 = require("./transport");
|
|
26
|
+
// Track which type hashes we've already sent to avoid duplicates
|
|
27
|
+
const sentHashes = new Set();
|
|
28
|
+
/**
|
|
29
|
+
* Patch global.fetch to observe JSON responses.
|
|
30
|
+
* Safe to call multiple times (idempotent).
|
|
31
|
+
*/
|
|
32
|
+
function patchFetch(environment, debugMode) {
|
|
33
|
+
if (typeof globalThis.fetch !== 'function')
|
|
34
|
+
return;
|
|
35
|
+
const originalFetch = globalThis.fetch;
|
|
36
|
+
// Guard against double-patching
|
|
37
|
+
if (originalFetch.__trickle_patched)
|
|
38
|
+
return;
|
|
39
|
+
globalThis.fetch = async function trickleObservedFetch(input, init) {
|
|
40
|
+
// Extract URL and method before the request
|
|
41
|
+
let url;
|
|
42
|
+
let method;
|
|
43
|
+
if (typeof input === 'string') {
|
|
44
|
+
url = input;
|
|
45
|
+
method = init?.method?.toUpperCase() || 'GET';
|
|
46
|
+
}
|
|
47
|
+
else if (input instanceof URL) {
|
|
48
|
+
url = input.href;
|
|
49
|
+
method = init?.method?.toUpperCase() || 'GET';
|
|
50
|
+
}
|
|
51
|
+
else if (typeof input === 'object' && input !== null && 'url' in input) {
|
|
52
|
+
// Request object
|
|
53
|
+
url = input.url;
|
|
54
|
+
method = (init?.method || input.method || 'GET').toUpperCase();
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
url = String(input);
|
|
58
|
+
method = init?.method?.toUpperCase() || 'GET';
|
|
59
|
+
}
|
|
60
|
+
// Skip trickle's own backend calls
|
|
61
|
+
if (url.includes('/api/ingest') || url.includes('/api/functions') || url.includes('/api/health')) {
|
|
62
|
+
return originalFetch.call(globalThis, input, init);
|
|
63
|
+
}
|
|
64
|
+
// Make the actual request
|
|
65
|
+
const response = await originalFetch.call(globalThis, input, init);
|
|
66
|
+
// Only observe JSON responses
|
|
67
|
+
const contentType = response.headers.get('content-type') || '';
|
|
68
|
+
if (!contentType.includes('json')) {
|
|
69
|
+
return response;
|
|
70
|
+
}
|
|
71
|
+
// Clone and intercept: read the clone's JSON in background
|
|
72
|
+
try {
|
|
73
|
+
const cloned = response.clone();
|
|
74
|
+
cloned.json().then((data) => {
|
|
75
|
+
try {
|
|
76
|
+
captureHttpResponse(method, url, init?.body, data, environment, debugMode);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Never interfere
|
|
80
|
+
}
|
|
81
|
+
}).catch(() => { });
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Clone/read failed — ignore
|
|
85
|
+
}
|
|
86
|
+
return response;
|
|
87
|
+
};
|
|
88
|
+
// Mark as patched
|
|
89
|
+
globalThis.fetch.__trickle_patched = true;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Parse a URL into a clean function name and module name.
|
|
93
|
+
* "https://api.example.com/v1/users?limit=10"
|
|
94
|
+
* → functionName: "GET /v1/users", module: "api.example.com"
|
|
95
|
+
*/
|
|
96
|
+
function parseUrl(method, rawUrl) {
|
|
97
|
+
try {
|
|
98
|
+
const parsed = new URL(rawUrl);
|
|
99
|
+
const pathname = parsed.pathname || '/';
|
|
100
|
+
return {
|
|
101
|
+
functionName: `${method} ${pathname}`,
|
|
102
|
+
module: parsed.hostname || 'http',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Relative URL or invalid — use as-is
|
|
107
|
+
return {
|
|
108
|
+
functionName: `${method} ${rawUrl}`,
|
|
109
|
+
module: 'http',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Capture the HTTP response type and enqueue it to the backend.
|
|
115
|
+
*/
|
|
116
|
+
function captureHttpResponse(method, url, requestBody, responseData, environment, debugMode) {
|
|
117
|
+
const { functionName, module: moduleName } = parseUrl(method, url);
|
|
118
|
+
// Infer types
|
|
119
|
+
const returnType = (0, type_inference_1.inferType)(responseData, 5);
|
|
120
|
+
// Infer request body type (for POST/PUT/PATCH)
|
|
121
|
+
let argsType;
|
|
122
|
+
if (requestBody && typeof requestBody === 'string') {
|
|
123
|
+
try {
|
|
124
|
+
const parsed = JSON.parse(requestBody);
|
|
125
|
+
argsType = { kind: 'tuple', elements: [(0, type_inference_1.inferType)(parsed, 5)] };
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
argsType = { kind: 'tuple', elements: [] };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
argsType = { kind: 'tuple', elements: [] };
|
|
133
|
+
}
|
|
134
|
+
const hash = (0, type_hash_1.hashType)(argsType, returnType);
|
|
135
|
+
// Dedup — only send each unique type shape once
|
|
136
|
+
const key = `${functionName}::${hash}`;
|
|
137
|
+
if (sentHashes.has(key))
|
|
138
|
+
return;
|
|
139
|
+
sentHashes.add(key);
|
|
140
|
+
// Build sample input from request body
|
|
141
|
+
let sampleInput = undefined;
|
|
142
|
+
if (requestBody && typeof requestBody === 'string') {
|
|
143
|
+
try {
|
|
144
|
+
sampleInput = JSON.parse(requestBody);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
sampleInput = undefined;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const payload = {
|
|
151
|
+
functionName,
|
|
152
|
+
module: moduleName,
|
|
153
|
+
language: 'js',
|
|
154
|
+
environment,
|
|
155
|
+
typeHash: hash,
|
|
156
|
+
argsType,
|
|
157
|
+
returnType,
|
|
158
|
+
sampleInput: sampleInput ? [sampleInput] : undefined,
|
|
159
|
+
sampleOutput: sanitizeSample(responseData),
|
|
160
|
+
};
|
|
161
|
+
(0, transport_1.enqueue)(payload);
|
|
162
|
+
if (debugMode) {
|
|
163
|
+
console.log(`[trickle/fetch] Captured ${functionName} → ${describeType(returnType)}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Brief description of a type for debug logging.
|
|
168
|
+
*/
|
|
169
|
+
function describeType(type) {
|
|
170
|
+
if (type.kind === 'object') {
|
|
171
|
+
const props = Object.keys(type.properties || {});
|
|
172
|
+
if (props.length <= 4)
|
|
173
|
+
return `{ ${props.join(', ')} }`;
|
|
174
|
+
return `{ ${props.slice(0, 3).join(', ')}, ... } (${props.length} props)`;
|
|
175
|
+
}
|
|
176
|
+
if (type.kind === 'array')
|
|
177
|
+
return `${describeType(type.element)}[]`;
|
|
178
|
+
if (type.kind === 'primitive')
|
|
179
|
+
return type.name;
|
|
180
|
+
return type.kind;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Sanitize sample data for storage (truncate large values).
|
|
184
|
+
*/
|
|
185
|
+
function sanitizeSample(value, depth = 3) {
|
|
186
|
+
if (depth <= 0)
|
|
187
|
+
return '[truncated]';
|
|
188
|
+
if (value === null || value === undefined)
|
|
189
|
+
return value;
|
|
190
|
+
const t = typeof value;
|
|
191
|
+
if (t === 'string') {
|
|
192
|
+
const s = value;
|
|
193
|
+
return s.length > 200 ? s.substring(0, 200) + '...' : s;
|
|
194
|
+
}
|
|
195
|
+
if (t === 'number' || t === 'boolean')
|
|
196
|
+
return value;
|
|
197
|
+
if (t === 'function')
|
|
198
|
+
return '[Function]';
|
|
199
|
+
if (Array.isArray(value)) {
|
|
200
|
+
return value.slice(0, 5).map(item => sanitizeSample(item, depth - 1));
|
|
201
|
+
}
|
|
202
|
+
if (t === 'object') {
|
|
203
|
+
const obj = value;
|
|
204
|
+
const result = {};
|
|
205
|
+
const keys = Object.keys(obj).slice(0, 20);
|
|
206
|
+
for (const key of keys) {
|
|
207
|
+
try {
|
|
208
|
+
result[key] = sanitizeSample(obj[key], depth - 1);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
result[key] = '[unreadable]';
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
return String(value);
|
|
217
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { GlobalOpts, TrickleOpts } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Configure trickle global options.
|
|
4
|
+
* Call this before wrapping any functions if you need non-default settings.
|
|
5
|
+
*/
|
|
6
|
+
export declare function configure(opts: Partial<GlobalOpts>): void;
|
|
7
|
+
/**
|
|
8
|
+
* Wrap a function to capture runtime type information.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* const wrapped = trickle(myFunction);
|
|
12
|
+
* const wrapped = trickle(myFunction, { name: 'myFn', module: 'api' });
|
|
13
|
+
* const wrapped = trickle('myFunction', myFunction);
|
|
14
|
+
* const wrapped = trickle('myFunction', myFunction, { module: 'api' });
|
|
15
|
+
*/
|
|
16
|
+
export declare function trickle<T extends (...args: any[]) => any>(fn: T, opts?: TrickleOpts): T;
|
|
17
|
+
export declare function trickle<T extends (...args: any[]) => any>(name: string, fn: T, opts?: TrickleOpts): T;
|
|
18
|
+
/**
|
|
19
|
+
* Wrap a Lambda handler function.
|
|
20
|
+
* Same as trickle() but automatically flushes the transport after each invocation,
|
|
21
|
+
* since Lambda may freeze the process between invocations.
|
|
22
|
+
*/
|
|
23
|
+
export declare function trickleHandler<T extends (...args: any[]) => any>(handler: T, opts?: TrickleOpts): T;
|
|
24
|
+
/**
|
|
25
|
+
* Instrument an Express app by monkey-patching route methods to capture types.
|
|
26
|
+
*
|
|
27
|
+
* Must be called BEFORE defining routes:
|
|
28
|
+
*
|
|
29
|
+
* const app = express();
|
|
30
|
+
* trickleExpress(app);
|
|
31
|
+
* app.get('/api/users', (req, res) => { ... });
|
|
32
|
+
*
|
|
33
|
+
* Each handler is wrapped to capture:
|
|
34
|
+
* - Input: `{ body, params, query }` from the request
|
|
35
|
+
* - Output: data passed to `res.json()` or `res.send()`
|
|
36
|
+
* - Errors: thrown exceptions or `next(err)` calls
|
|
37
|
+
*/
|
|
38
|
+
export declare function trickleExpress(app: any, opts?: {
|
|
39
|
+
enabled?: boolean;
|
|
40
|
+
environment?: string;
|
|
41
|
+
sampleRate?: number;
|
|
42
|
+
maxDepth?: number;
|
|
43
|
+
}): void;
|
|
44
|
+
/**
|
|
45
|
+
* Auto-instrument a framework app. Currently supports Express.
|
|
46
|
+
*
|
|
47
|
+
* Usage:
|
|
48
|
+
* import { instrument } from 'trickle';
|
|
49
|
+
* const app = express();
|
|
50
|
+
* instrument(app);
|
|
51
|
+
*
|
|
52
|
+
* Detects Express by checking for `app.listen` and `app.get` (function) on the object.
|
|
53
|
+
*/
|
|
54
|
+
export declare function instrument(app: any, opts?: {
|
|
55
|
+
enabled?: boolean;
|
|
56
|
+
environment?: string;
|
|
57
|
+
sampleRate?: number;
|
|
58
|
+
maxDepth?: number;
|
|
59
|
+
}): void;
|
|
60
|
+
export type { TypeNode, GlobalOpts, TrickleOpts, IngestPayload } from './types';
|
|
61
|
+
export { flush } from './transport';
|
|
62
|
+
export { instrumentExpress, trickleMiddleware } from './express';
|
|
63
|
+
export { observe, observeFn } from './observe';
|
|
64
|
+
export type { ObserveOpts } from './observe';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.observeFn = exports.observe = exports.trickleMiddleware = exports.instrumentExpress = exports.flush = void 0;
|
|
4
|
+
exports.configure = configure;
|
|
5
|
+
exports.trickle = trickle;
|
|
6
|
+
exports.trickleHandler = trickleHandler;
|
|
7
|
+
exports.trickleExpress = trickleExpress;
|
|
8
|
+
exports.instrument = instrument;
|
|
9
|
+
const transport_1 = require("./transport");
|
|
10
|
+
const wrap_1 = require("./wrap");
|
|
11
|
+
const env_detect_1 = require("./env-detect");
|
|
12
|
+
const express_1 = require("./express");
|
|
13
|
+
let globalOpts = {
|
|
14
|
+
backendUrl: 'http://localhost:4888',
|
|
15
|
+
batchIntervalMs: 2000,
|
|
16
|
+
enabled: true,
|
|
17
|
+
environment: undefined,
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Configure trickle global options.
|
|
21
|
+
* Call this before wrapping any functions if you need non-default settings.
|
|
22
|
+
*/
|
|
23
|
+
function configure(opts) {
|
|
24
|
+
Object.assign(globalOpts, opts);
|
|
25
|
+
(0, transport_1.configure)(globalOpts);
|
|
26
|
+
}
|
|
27
|
+
function trickle(...args) {
|
|
28
|
+
let fn;
|
|
29
|
+
let opts = {};
|
|
30
|
+
let explicitName;
|
|
31
|
+
if (typeof args[0] === 'string') {
|
|
32
|
+
explicitName = args[0];
|
|
33
|
+
fn = args[1];
|
|
34
|
+
opts = args[2] || {};
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
fn = args[0];
|
|
38
|
+
opts = args[1] || {};
|
|
39
|
+
}
|
|
40
|
+
if (typeof fn !== 'function') {
|
|
41
|
+
throw new TypeError('trickle: expected a function argument');
|
|
42
|
+
}
|
|
43
|
+
const functionName = explicitName || opts.name || fn.name || 'anonymous';
|
|
44
|
+
const module = opts.module || inferModule();
|
|
45
|
+
const environment = globalOpts.environment || (0, env_detect_1.detectEnvironment)();
|
|
46
|
+
const wrapOpts = {
|
|
47
|
+
functionName,
|
|
48
|
+
module,
|
|
49
|
+
trackArgs: opts.trackArgs !== false,
|
|
50
|
+
trackReturn: opts.trackReturn !== false,
|
|
51
|
+
sampleRate: opts.sampleRate ?? 1,
|
|
52
|
+
maxDepth: opts.maxDepth ?? 5,
|
|
53
|
+
environment,
|
|
54
|
+
enabled: globalOpts.enabled,
|
|
55
|
+
};
|
|
56
|
+
return (0, wrap_1.wrapFunction)(fn, wrapOpts);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Wrap a Lambda handler function.
|
|
60
|
+
* Same as trickle() but automatically flushes the transport after each invocation,
|
|
61
|
+
* since Lambda may freeze the process between invocations.
|
|
62
|
+
*/
|
|
63
|
+
function trickleHandler(handler, opts) {
|
|
64
|
+
const wrapped = trickle(handler, {
|
|
65
|
+
...opts,
|
|
66
|
+
name: opts?.name || handler.name || 'handler',
|
|
67
|
+
});
|
|
68
|
+
const flushing = function (...args) {
|
|
69
|
+
const result = wrapped.apply(this, args);
|
|
70
|
+
// If the handler returns a promise, flush after it resolves
|
|
71
|
+
if (result !== null && result !== undefined && typeof result === 'object' && typeof result.then === 'function') {
|
|
72
|
+
return result.then(async (resolved) => {
|
|
73
|
+
await (0, transport_1.flush)().catch(() => { });
|
|
74
|
+
return resolved;
|
|
75
|
+
}, async (err) => {
|
|
76
|
+
await (0, transport_1.flush)().catch(() => { });
|
|
77
|
+
throw err;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
// Synchronous handler — flush and return
|
|
81
|
+
(0, transport_1.flush)().catch(() => { });
|
|
82
|
+
return result;
|
|
83
|
+
};
|
|
84
|
+
Object.defineProperty(flushing, 'name', { value: handler.name || 'handler', configurable: true });
|
|
85
|
+
Object.defineProperty(flushing, 'length', { value: handler.length, configurable: true });
|
|
86
|
+
return flushing;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Instrument an Express app by monkey-patching route methods to capture types.
|
|
90
|
+
*
|
|
91
|
+
* Must be called BEFORE defining routes:
|
|
92
|
+
*
|
|
93
|
+
* const app = express();
|
|
94
|
+
* trickleExpress(app);
|
|
95
|
+
* app.get('/api/users', (req, res) => { ... });
|
|
96
|
+
*
|
|
97
|
+
* Each handler is wrapped to capture:
|
|
98
|
+
* - Input: `{ body, params, query }` from the request
|
|
99
|
+
* - Output: data passed to `res.json()` or `res.send()`
|
|
100
|
+
* - Errors: thrown exceptions or `next(err)` calls
|
|
101
|
+
*/
|
|
102
|
+
function trickleExpress(app, opts) {
|
|
103
|
+
(0, express_1.instrumentExpress)(app, {
|
|
104
|
+
enabled: opts?.enabled ?? globalOpts.enabled,
|
|
105
|
+
environment: opts?.environment ?? globalOpts.environment ?? (0, env_detect_1.detectEnvironment)(),
|
|
106
|
+
sampleRate: opts?.sampleRate ?? 1,
|
|
107
|
+
maxDepth: opts?.maxDepth ?? 5,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Auto-instrument a framework app. Currently supports Express.
|
|
112
|
+
*
|
|
113
|
+
* Usage:
|
|
114
|
+
* import { instrument } from 'trickle';
|
|
115
|
+
* const app = express();
|
|
116
|
+
* instrument(app);
|
|
117
|
+
*
|
|
118
|
+
* Detects Express by checking for `app.listen` and `app.get` (function) on the object.
|
|
119
|
+
*/
|
|
120
|
+
function instrument(app, opts) {
|
|
121
|
+
// Detect Express-like app
|
|
122
|
+
if (app && typeof app.listen === 'function' && typeof app.get === 'function' && typeof app.use === 'function') {
|
|
123
|
+
trickleExpress(app, opts);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// Future: detect other frameworks here (Koa, Fastify, etc.)
|
|
127
|
+
if (typeof console !== 'undefined' && console.warn) {
|
|
128
|
+
console.warn('[trickle] instrument(): could not detect a supported framework on the provided object');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Attempt to infer the module name from the call stack.
|
|
133
|
+
* Falls back to 'unknown' if we can't determine it.
|
|
134
|
+
*/
|
|
135
|
+
function inferModule() {
|
|
136
|
+
try {
|
|
137
|
+
const stack = new Error().stack;
|
|
138
|
+
if (!stack)
|
|
139
|
+
return 'unknown';
|
|
140
|
+
const lines = stack.split('\n');
|
|
141
|
+
// Skip first 3 lines: "Error", trickle internals
|
|
142
|
+
for (let i = 3; i < lines.length; i++) {
|
|
143
|
+
const line = lines[i].trim();
|
|
144
|
+
// Look for a file path
|
|
145
|
+
const match = line.match(/(?:at\s+)?(?:.*?\s+\()?(.+?)(?::\d+:\d+)?\)?$/);
|
|
146
|
+
if (match) {
|
|
147
|
+
let filePath = match[1];
|
|
148
|
+
// Strip node_modules paths
|
|
149
|
+
if (filePath.includes('node_modules'))
|
|
150
|
+
continue;
|
|
151
|
+
// Extract just the filename or relative path
|
|
152
|
+
const parts = filePath.split('/');
|
|
153
|
+
const filename = parts[parts.length - 1];
|
|
154
|
+
if (filename && !filename.startsWith('<')) {
|
|
155
|
+
return filename.replace(/\.[jt]sx?$/, '');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// Don't crash on stack inspection failure
|
|
162
|
+
}
|
|
163
|
+
return 'unknown';
|
|
164
|
+
}
|
|
165
|
+
var transport_2 = require("./transport");
|
|
166
|
+
Object.defineProperty(exports, "flush", { enumerable: true, get: function () { return transport_2.flush; } });
|
|
167
|
+
var express_2 = require("./express");
|
|
168
|
+
Object.defineProperty(exports, "instrumentExpress", { enumerable: true, get: function () { return express_2.instrumentExpress; } });
|
|
169
|
+
Object.defineProperty(exports, "trickleMiddleware", { enumerable: true, get: function () { return express_2.trickleMiddleware; } });
|
|
170
|
+
var observe_1 = require("./observe");
|
|
171
|
+
Object.defineProperty(exports, "observe", { enumerable: true, get: function () { return observe_1.observe; } });
|
|
172
|
+
Object.defineProperty(exports, "observeFn", { enumerable: true, get: function () { return observe_1.observeFn; } });
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-observation register — patches Node's module loader to automatically
|
|
3
|
+
* wrap ALL functions in user code (not just exports).
|
|
4
|
+
*
|
|
5
|
+
* Uses two complementary hooks:
|
|
6
|
+
* 1. Module._compile: Transforms source code to wrap function declarations
|
|
7
|
+
* IMMEDIATELY after each function body. This catches:
|
|
8
|
+
* - Entry file functions (previously invisible)
|
|
9
|
+
* - Non-exported helper functions inside modules
|
|
10
|
+
* - All function declarations in user code
|
|
11
|
+
* 2. Module._load: Wraps exported functions on the exports object
|
|
12
|
+
* (covers module.exports patterns like { foo, bar })
|
|
13
|
+
*
|
|
14
|
+
* A double-wrap guard in wrapFunction prevents the same function being
|
|
15
|
+
* wrapped by both hooks.
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
*
|
|
19
|
+
* node -r trickle/observe app.js
|
|
20
|
+
*
|
|
21
|
+
* Environment variables:
|
|
22
|
+
* TRICKLE_BACKEND_URL — Backend URL (default: http://localhost:4888)
|
|
23
|
+
* TRICKLE_ENABLED — Set to "0" or "false" to disable
|
|
24
|
+
* TRICKLE_DEBUG — Set to "1" for debug logging
|
|
25
|
+
* TRICKLE_ENV — Override detected environment
|
|
26
|
+
* TRICKLE_OBSERVE_INCLUDE — Comma-separated substrings to include (default: all user code)
|
|
27
|
+
* TRICKLE_OBSERVE_EXCLUDE — Comma-separated substrings to exclude (default: none)
|
|
28
|
+
*/
|
|
29
|
+
export {};
|