trickle-observe 0.2.128 → 0.2.129

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.
@@ -9,8 +9,8 @@
9
9
  *
10
10
  * TRICKLE_BACKEND_URL=http://localhost:4888 node -r trickle/register app.js
11
11
  *
12
- * This module patches Node's module loader to intercept `require('express')`
13
- * and automatically instrument any Express app created — no code changes needed.
12
+ * This module patches Node's module loader to intercept framework requires
13
+ * (Express, Fastify, Koa, Hono) and automatically instrument any app created.
14
14
  *
15
15
  * Supported environment variables:
16
16
  * TRICKLE_BACKEND_URL — Backend URL (default: http://localhost:4888)
package/dist/register.js CHANGED
@@ -10,8 +10,8 @@
10
10
  *
11
11
  * TRICKLE_BACKEND_URL=http://localhost:4888 node -r trickle/register app.js
12
12
  *
13
- * This module patches Node's module loader to intercept `require('express')`
14
- * and automatically instrument any Express app created — no code changes needed.
13
+ * This module patches Node's module loader to intercept framework requires
14
+ * (Express, Fastify, Koa, Hono) and automatically instrument any app created.
15
15
  *
16
16
  * Supported environment variables:
17
17
  * TRICKLE_BACKEND_URL — Backend URL (default: http://localhost:4888)
@@ -26,6 +26,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
26
26
  const module_1 = __importDefault(require("module"));
27
27
  const transport_1 = require("./transport");
28
28
  const express_1 = require("./express");
29
+ const fastify_1 = require("./fastify");
30
+ const koa_1 = require("./koa");
31
+ const hono_1 = require("./hono");
29
32
  const env_detect_1 = require("./env-detect");
30
33
  const M = module_1.default;
31
34
  const originalLoad = M._load;
@@ -35,6 +38,7 @@ const backendUrl = process.env.TRICKLE_BACKEND_URL || 'http://localhost:4888';
35
38
  const enabled = process.env.TRICKLE_ENABLED !== '0' && process.env.TRICKLE_ENABLED !== 'false';
36
39
  const debug = process.env.TRICKLE_DEBUG === '1' || process.env.TRICKLE_DEBUG === 'true';
37
40
  const envOverride = process.env.TRICKLE_ENV || undefined;
41
+ const environment = envOverride || (0, env_detect_1.detectEnvironment)();
38
42
  if (enabled) {
39
43
  // Configure the transport
40
44
  (0, transport_1.configure)({
@@ -42,7 +46,7 @@ if (enabled) {
42
46
  batchIntervalMs: 2000,
43
47
  debug,
44
48
  enabled: true,
45
- environment: envOverride || (0, env_detect_1.detectEnvironment)(),
49
+ environment,
46
50
  });
47
51
  if (debug) {
48
52
  console.log(`[trickle] Auto-instrumentation enabled (backend: ${backendUrl})`);
@@ -55,6 +59,21 @@ if (enabled) {
55
59
  patched.add('express');
56
60
  return patchExpress(exports, request, parent);
57
61
  }
62
+ // Intercept require('fastify')
63
+ if (request === 'fastify' && !patched.has('fastify')) {
64
+ patched.add('fastify');
65
+ return patchFastify(exports, request, parent);
66
+ }
67
+ // Intercept require('koa')
68
+ if (request === 'koa' && !patched.has('koa')) {
69
+ patched.add('koa');
70
+ return patchKoa(exports, request, parent);
71
+ }
72
+ // Intercept require('hono')
73
+ if (request === 'hono' && !patched.has('hono')) {
74
+ patched.add('hono');
75
+ return patchHono(exports, request, parent);
76
+ }
58
77
  return exports;
59
78
  };
60
79
  }
@@ -63,15 +82,12 @@ else if (debug) {
63
82
  }
64
83
  /**
65
84
  * Wrap the Express factory function so every app created is auto-instrumented.
66
- * Preserves all static properties (express.json, express.static, etc.).
67
85
  */
68
86
  function patchExpress(originalExpress, request, parent) {
69
87
  function wrappedExpress(...args) {
70
88
  const app = originalExpress.apply(this, args);
71
89
  try {
72
- (0, express_1.instrumentExpress)(app, {
73
- environment: envOverride || (0, env_detect_1.detectEnvironment)(),
74
- });
90
+ (0, express_1.instrumentExpress)(app, { environment });
75
91
  if (debug) {
76
92
  console.log('[trickle] Auto-instrumented Express app');
77
93
  }
@@ -84,22 +100,126 @@ function patchExpress(originalExpress, request, parent) {
84
100
  }
85
101
  return app;
86
102
  }
87
- // Copy all static properties (express.json, express.static, express.Router, etc.)
88
103
  for (const key of Object.keys(originalExpress)) {
89
104
  wrappedExpress[key] = originalExpress[key];
90
105
  }
91
- // Preserve prototype chain
92
106
  Object.setPrototypeOf(wrappedExpress, Object.getPrototypeOf(originalExpress));
93
- // Update require cache so subsequent require('express') returns the patched version
94
107
  try {
95
108
  const resolvedPath = M._resolveFilename(request, parent);
96
109
  if (require.cache[resolvedPath]) {
97
110
  require.cache[resolvedPath].exports = wrappedExpress;
98
111
  }
99
112
  }
100
- catch {
101
- // Cache update failed — first require still returns patched, but subsequent
102
- // requires from other modules may get the original. This is rare.
103
- }
113
+ catch { }
104
114
  return wrappedExpress;
105
115
  }
116
+ /**
117
+ * Wrap the Fastify factory function.
118
+ */
119
+ function patchFastify(origExports, request, parent) {
120
+ const factory = typeof origExports === 'function' ? origExports : origExports.default || origExports.fastify;
121
+ if (typeof factory !== 'function')
122
+ return origExports;
123
+ const wrappedFactory = function (...args) {
124
+ const app = factory.apply(this, args);
125
+ try {
126
+ (0, fastify_1.instrumentFastify)(app, { environment });
127
+ if (debug)
128
+ console.log('[trickle] Auto-instrumented Fastify app');
129
+ }
130
+ catch (err) {
131
+ if (debug)
132
+ console.warn(`[trickle] Failed to auto-instrument Fastify: ${err.message}`);
133
+ }
134
+ return app;
135
+ };
136
+ for (const key of Object.keys(factory)) {
137
+ wrappedFactory[key] = factory[key];
138
+ }
139
+ if (typeof origExports === 'function') {
140
+ try {
141
+ const resolvedPath = M._resolveFilename(request, parent);
142
+ if (require.cache[resolvedPath])
143
+ require.cache[resolvedPath].exports = wrappedFactory;
144
+ }
145
+ catch { }
146
+ return wrappedFactory;
147
+ }
148
+ const wrapped = { ...origExports };
149
+ if (origExports.default)
150
+ wrapped.default = wrappedFactory;
151
+ if (origExports.fastify)
152
+ wrapped.fastify = wrappedFactory;
153
+ return wrapped;
154
+ }
155
+ /**
156
+ * Wrap the Koa constructor.
157
+ */
158
+ function patchKoa(origExports, request, parent) {
159
+ const KoaClass = typeof origExports === 'function' ? origExports : origExports.default;
160
+ if (typeof KoaClass !== 'function')
161
+ return origExports;
162
+ const WrappedKoa = function (...args) {
163
+ const app = new KoaClass(...args);
164
+ try {
165
+ (0, koa_1.instrumentKoa)(app, { environment });
166
+ if (debug)
167
+ console.log('[trickle] Auto-instrumented Koa app');
168
+ }
169
+ catch (err) {
170
+ if (debug)
171
+ console.warn(`[trickle] Failed to auto-instrument Koa: ${err.message}`);
172
+ }
173
+ return app;
174
+ };
175
+ WrappedKoa.prototype = KoaClass.prototype;
176
+ for (const key of Object.keys(KoaClass)) {
177
+ WrappedKoa[key] = KoaClass[key];
178
+ }
179
+ if (typeof origExports === 'function') {
180
+ try {
181
+ const resolvedPath = M._resolveFilename(request, parent);
182
+ if (require.cache[resolvedPath])
183
+ require.cache[resolvedPath].exports = WrappedKoa;
184
+ }
185
+ catch { }
186
+ return WrappedKoa;
187
+ }
188
+ return { ...origExports, default: WrappedKoa };
189
+ }
190
+ /**
191
+ * Wrap the Hono constructor.
192
+ */
193
+ function patchHono(origExports, request, parent) {
194
+ const HonoClass = origExports.Hono || (origExports.default && origExports.default.Hono);
195
+ if (typeof HonoClass !== 'function')
196
+ return origExports;
197
+ const WrappedHono = function (...args) {
198
+ const app = new HonoClass(...args);
199
+ try {
200
+ (0, hono_1.instrumentHono)(app, { environment });
201
+ if (debug)
202
+ console.log('[trickle] Auto-instrumented Hono app');
203
+ }
204
+ catch (err) {
205
+ if (debug)
206
+ console.warn(`[trickle] Failed to auto-instrument Hono: ${err.message}`);
207
+ }
208
+ return app;
209
+ };
210
+ WrappedHono.prototype = HonoClass.prototype;
211
+ for (const key of Object.keys(HonoClass)) {
212
+ WrappedHono[key] = HonoClass[key];
213
+ }
214
+ const result = { ...origExports, Hono: WrappedHono };
215
+ try {
216
+ const resolvedPath = M._resolveFilename(request, parent);
217
+ if (require.cache[resolvedPath]) {
218
+ const cached = require.cache[resolvedPath].exports;
219
+ if (cached.Hono)
220
+ cached.Hono = WrappedHono;
221
+ }
222
+ }
223
+ catch { }
224
+ return result;
225
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trickle-observe",
3
- "version": "0.2.128",
3
+ "version": "0.2.129",
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",
package/src/register.ts CHANGED
@@ -9,8 +9,8 @@
9
9
  *
10
10
  * TRICKLE_BACKEND_URL=http://localhost:4888 node -r trickle/register app.js
11
11
  *
12
- * This module patches Node's module loader to intercept `require('express')`
13
- * and automatically instrument any Express app created — no code changes needed.
12
+ * This module patches Node's module loader to intercept framework requires
13
+ * (Express, Fastify, Koa, Hono) and automatically instrument any app created.
14
14
  *
15
15
  * Supported environment variables:
16
16
  * TRICKLE_BACKEND_URL — Backend URL (default: http://localhost:4888)
@@ -22,6 +22,9 @@
22
22
  import Module from 'module';
23
23
  import { configure } from './transport';
24
24
  import { instrumentExpress } from './express';
25
+ import { instrumentFastify } from './fastify';
26
+ import { instrumentKoa } from './koa';
27
+ import { instrumentHono } from './hono';
25
28
  import { detectEnvironment } from './env-detect';
26
29
 
27
30
  const M = Module as any;
@@ -33,6 +36,7 @@ const backendUrl = process.env.TRICKLE_BACKEND_URL || 'http://localhost:4888';
33
36
  const enabled = process.env.TRICKLE_ENABLED !== '0' && process.env.TRICKLE_ENABLED !== 'false';
34
37
  const debug = process.env.TRICKLE_DEBUG === '1' || process.env.TRICKLE_DEBUG === 'true';
35
38
  const envOverride = process.env.TRICKLE_ENV || undefined;
39
+ const environment = envOverride || detectEnvironment();
36
40
 
37
41
  if (enabled) {
38
42
  // Configure the transport
@@ -41,7 +45,7 @@ if (enabled) {
41
45
  batchIntervalMs: 2000,
42
46
  debug,
43
47
  enabled: true,
44
- environment: envOverride || detectEnvironment(),
48
+ environment,
45
49
  });
46
50
 
47
51
  if (debug) {
@@ -58,6 +62,24 @@ if (enabled) {
58
62
  return patchExpress(exports, request, parent);
59
63
  }
60
64
 
65
+ // Intercept require('fastify')
66
+ if (request === 'fastify' && !patched.has('fastify')) {
67
+ patched.add('fastify');
68
+ return patchFastify(exports, request, parent);
69
+ }
70
+
71
+ // Intercept require('koa')
72
+ if (request === 'koa' && !patched.has('koa')) {
73
+ patched.add('koa');
74
+ return patchKoa(exports, request, parent);
75
+ }
76
+
77
+ // Intercept require('hono')
78
+ if (request === 'hono' && !patched.has('hono')) {
79
+ patched.add('hono');
80
+ return patchHono(exports, request, parent);
81
+ }
82
+
61
83
  return exports;
62
84
  };
63
85
  } else if (debug) {
@@ -66,15 +88,12 @@ if (enabled) {
66
88
 
67
89
  /**
68
90
  * Wrap the Express factory function so every app created is auto-instrumented.
69
- * Preserves all static properties (express.json, express.static, etc.).
70
91
  */
71
92
  function patchExpress(originalExpress: any, request: string, parent: any): any {
72
93
  function wrappedExpress(this: any, ...args: any[]): any {
73
94
  const app = originalExpress.apply(this, args);
74
95
  try {
75
- instrumentExpress(app, {
76
- environment: envOverride || detectEnvironment(),
77
- });
96
+ instrumentExpress(app, { environment });
78
97
  if (debug) {
79
98
  console.log('[trickle] Auto-instrumented Express app');
80
99
  }
@@ -87,24 +106,120 @@ function patchExpress(originalExpress: any, request: string, parent: any): any {
87
106
  return app;
88
107
  }
89
108
 
90
- // Copy all static properties (express.json, express.static, express.Router, etc.)
91
109
  for (const key of Object.keys(originalExpress)) {
92
110
  (wrappedExpress as any)[key] = originalExpress[key];
93
111
  }
94
-
95
- // Preserve prototype chain
96
112
  Object.setPrototypeOf(wrappedExpress, Object.getPrototypeOf(originalExpress));
97
113
 
98
- // Update require cache so subsequent require('express') returns the patched version
99
114
  try {
100
115
  const resolvedPath = M._resolveFilename(request, parent);
101
116
  if (require.cache[resolvedPath]) {
102
117
  require.cache[resolvedPath]!.exports = wrappedExpress;
103
118
  }
104
- } catch {
105
- // Cache update failed — first require still returns patched, but subsequent
106
- // requires from other modules may get the original. This is rare.
107
- }
119
+ } catch {}
108
120
 
109
121
  return wrappedExpress;
110
122
  }
123
+
124
+ /**
125
+ * Wrap the Fastify factory function.
126
+ */
127
+ function patchFastify(origExports: any, request: string, parent: any): any {
128
+ const factory = typeof origExports === 'function' ? origExports : origExports.default || origExports.fastify;
129
+ if (typeof factory !== 'function') return origExports;
130
+
131
+ const wrappedFactory = function (this: any, ...args: any[]): any {
132
+ const app = factory.apply(this, args);
133
+ try {
134
+ instrumentFastify(app, { environment });
135
+ if (debug) console.log('[trickle] Auto-instrumented Fastify app');
136
+ } catch (err: unknown) {
137
+ if (debug) console.warn(`[trickle] Failed to auto-instrument Fastify: ${(err as Error).message}`);
138
+ }
139
+ return app;
140
+ };
141
+
142
+ for (const key of Object.keys(factory)) {
143
+ (wrappedFactory as any)[key] = (factory as any)[key];
144
+ }
145
+
146
+ if (typeof origExports === 'function') {
147
+ try {
148
+ const resolvedPath = M._resolveFilename(request, parent);
149
+ if (require.cache[resolvedPath]) require.cache[resolvedPath]!.exports = wrappedFactory;
150
+ } catch {}
151
+ return wrappedFactory;
152
+ }
153
+
154
+ const wrapped = { ...origExports };
155
+ if (origExports.default) wrapped.default = wrappedFactory;
156
+ if (origExports.fastify) wrapped.fastify = wrappedFactory;
157
+ return wrapped;
158
+ }
159
+
160
+ /**
161
+ * Wrap the Koa constructor.
162
+ */
163
+ function patchKoa(origExports: any, request: string, parent: any): any {
164
+ const KoaClass = typeof origExports === 'function' ? origExports : origExports.default;
165
+ if (typeof KoaClass !== 'function') return origExports;
166
+
167
+ const WrappedKoa = function (this: any, ...args: any[]): any {
168
+ const app = new KoaClass(...args);
169
+ try {
170
+ instrumentKoa(app, { environment });
171
+ if (debug) console.log('[trickle] Auto-instrumented Koa app');
172
+ } catch (err: unknown) {
173
+ if (debug) console.warn(`[trickle] Failed to auto-instrument Koa: ${(err as Error).message}`);
174
+ }
175
+ return app;
176
+ };
177
+ WrappedKoa.prototype = KoaClass.prototype;
178
+ for (const key of Object.keys(KoaClass)) {
179
+ (WrappedKoa as any)[key] = (KoaClass as any)[key];
180
+ }
181
+
182
+ if (typeof origExports === 'function') {
183
+ try {
184
+ const resolvedPath = M._resolveFilename(request, parent);
185
+ if (require.cache[resolvedPath]) require.cache[resolvedPath]!.exports = WrappedKoa;
186
+ } catch {}
187
+ return WrappedKoa;
188
+ }
189
+ return { ...origExports, default: WrappedKoa };
190
+ }
191
+
192
+ /**
193
+ * Wrap the Hono constructor.
194
+ */
195
+ function patchHono(origExports: any, request: string, parent: any): any {
196
+ const HonoClass = origExports.Hono || (origExports.default && origExports.default.Hono);
197
+ if (typeof HonoClass !== 'function') return origExports;
198
+
199
+ const WrappedHono = function (this: any, ...args: any[]): any {
200
+ const app = new HonoClass(...args);
201
+ try {
202
+ instrumentHono(app, { environment });
203
+ if (debug) console.log('[trickle] Auto-instrumented Hono app');
204
+ } catch (err: unknown) {
205
+ if (debug) console.warn(`[trickle] Failed to auto-instrument Hono: ${(err as Error).message}`);
206
+ }
207
+ return app;
208
+ };
209
+ WrappedHono.prototype = HonoClass.prototype;
210
+ for (const key of Object.keys(HonoClass)) {
211
+ (WrappedHono as any)[key] = (HonoClass as any)[key];
212
+ }
213
+
214
+ const result = { ...origExports, Hono: WrappedHono };
215
+
216
+ try {
217
+ const resolvedPath = M._resolveFilename(request, parent);
218
+ if (require.cache[resolvedPath]) {
219
+ const cached = require.cache[resolvedPath]!.exports;
220
+ if (cached.Hono) cached.Hono = WrappedHono;
221
+ }
222
+ } catch {}
223
+
224
+ return result;
225
+ }