trickle-observe 0.2.90 → 0.2.92

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.
@@ -61,8 +61,11 @@ function patchFetch(environment, debugMode) {
61
61
  if (url.includes('/api/ingest') || url.includes('/api/functions') || url.includes('/api/health')) {
62
62
  return originalFetch.call(globalThis, input, init);
63
63
  }
64
- // Make the actual request
64
+ // Make the actual request with timing
65
+ const startTime = performance.now();
65
66
  const response = await originalFetch.call(globalThis, input, init);
67
+ const durationMs = Math.round((performance.now() - startTime) * 100) / 100;
68
+ const statusCode = response.status;
66
69
  // Only observe JSON responses
67
70
  const contentType = response.headers.get('content-type') || '';
68
71
  if (!contentType.includes('json')) {
@@ -73,7 +76,7 @@ function patchFetch(environment, debugMode) {
73
76
  const cloned = response.clone();
74
77
  cloned.json().then((data) => {
75
78
  try {
76
- captureHttpResponse(method, url, init?.body, data, environment, debugMode);
79
+ captureHttpResponse(method, url, init?.body, data, environment, debugMode, statusCode, durationMs);
77
80
  }
78
81
  catch {
79
82
  // Never interfere
@@ -113,7 +116,7 @@ function parseUrl(method, rawUrl) {
113
116
  /**
114
117
  * Capture the HTTP response type and enqueue it to the backend.
115
118
  */
116
- function captureHttpResponse(method, url, requestBody, responseData, environment, debugMode) {
119
+ function captureHttpResponse(method, url, requestBody, responseData, environment, debugMode, statusCode, durationMs) {
117
120
  const { functionName, module: moduleName } = parseUrl(method, url);
118
121
  // Infer types
119
122
  const returnType = (0, type_inference_1.inferType)(responseData, 5);
@@ -148,7 +151,7 @@ function captureHttpResponse(method, url, requestBody, responseData, environment
148
151
  }
149
152
  }
150
153
  const payload = {
151
- functionName,
154
+ functionName: statusCode ? `${functionName} [${statusCode}]` : functionName,
152
155
  module: moduleName,
153
156
  language: 'js',
154
157
  environment,
@@ -157,6 +160,7 @@ function captureHttpResponse(method, url, requestBody, responseData, environment
157
160
  returnType,
158
161
  sampleInput: sampleInput ? [sampleInput] : undefined,
159
162
  sampleOutput: sanitizeSample(responseData),
163
+ durationMs,
160
164
  };
161
165
  (0, transport_1.enqueue)(payload);
162
166
  if (debugMode) {
package/dist/types.d.ts CHANGED
@@ -42,6 +42,7 @@ export interface IngestPayload {
42
42
  paramNames?: string[];
43
43
  sampleInput?: unknown;
44
44
  sampleOutput?: unknown;
45
+ durationMs?: number;
45
46
  error?: {
46
47
  type: string;
47
48
  message: string;
package/dist/wrap.js CHANGED
@@ -32,6 +32,7 @@ function wrapFunction(fn, opts) {
32
32
  let threwError = false;
33
33
  let caughtError;
34
34
  const trackers = [];
35
+ const startTime = performance.now();
35
36
  try {
36
37
  // Always pass ORIGINAL args to the function — never proxied ones.
37
38
  // Proxied args can break framework internals (Express Router, DI containers, etc.)
@@ -40,9 +41,10 @@ function wrapFunction(fn, opts) {
40
41
  catch (err) {
41
42
  threwError = true;
42
43
  caughtError = err;
43
- // Capture error context
44
+ // Capture error context with timing
44
45
  try {
45
- captureErrorPayload(functionKey, opts, args, trackers, err);
46
+ const durationMs = performance.now() - startTime;
47
+ captureErrorPayload(functionKey, opts, args, trackers, err, durationMs);
46
48
  }
47
49
  catch {
48
50
  // Never let our instrumentation interfere
@@ -54,7 +56,8 @@ function wrapFunction(fn, opts) {
54
56
  if (result !== null && result !== undefined && typeof result === 'object' && typeof result.then === 'function') {
55
57
  return result.then((resolved) => {
56
58
  try {
57
- capturePayload(functionKey, opts, args, trackers, resolved, true);
59
+ const durationMs = performance.now() - startTime;
60
+ capturePayload(functionKey, opts, args, trackers, resolved, true, durationMs);
58
61
  }
59
62
  catch {
60
63
  // Never let our instrumentation interfere
@@ -62,7 +65,8 @@ function wrapFunction(fn, opts) {
62
65
  return resolved;
63
66
  }, (err) => {
64
67
  try {
65
- captureErrorPayload(functionKey, opts, args, trackers, err);
68
+ const durationMs = performance.now() - startTime;
69
+ captureErrorPayload(functionKey, opts, args, trackers, err, durationMs);
66
70
  }
67
71
  catch {
68
72
  // Never let our instrumentation interfere
@@ -73,7 +77,8 @@ function wrapFunction(fn, opts) {
73
77
  }
74
78
  // Synchronous return
75
79
  try {
76
- capturePayload(functionKey, opts, args, trackers, result);
80
+ const durationMs = performance.now() - startTime;
81
+ capturePayload(functionKey, opts, args, trackers, result, false, durationMs);
77
82
  }
78
83
  catch {
79
84
  // Never let our instrumentation interfere
@@ -90,7 +95,7 @@ function wrapFunction(fn, opts) {
90
95
  /**
91
96
  * Capture and enqueue a successful invocation's type data.
92
97
  */
93
- function capturePayload(functionKey, opts, originalArgs, trackers, returnValue, isAsync = false) {
98
+ function capturePayload(functionKey, opts, originalArgs, trackers, returnValue, isAsync = false, durationMs) {
94
99
  // Build args type as a tuple
95
100
  const argsType = buildArgsType(originalArgs, trackers, opts.maxDepth);
96
101
  const returnType = (0, type_inference_1.inferType)(returnValue, opts.maxDepth);
@@ -117,12 +122,15 @@ function capturePayload(functionKey, opts, originalArgs, trackers, returnValue,
117
122
  if (opts.paramNames && opts.paramNames.length > 0) {
118
123
  payload.paramNames = opts.paramNames;
119
124
  }
125
+ if (durationMs !== undefined) {
126
+ payload.durationMs = Math.round(durationMs * 100) / 100;
127
+ }
120
128
  (0, transport_1.enqueue)(payload);
121
129
  }
122
130
  /**
123
131
  * Capture type context for a failed invocation.
124
132
  */
125
- function captureErrorPayload(functionKey, opts, originalArgs, trackers, error) {
133
+ function captureErrorPayload(functionKey, opts, originalArgs, trackers, error, durationMs) {
126
134
  const argsType = buildArgsType(originalArgs, trackers, opts.maxDepth);
127
135
  const returnType = { kind: 'unknown' };
128
136
  const hash = (0, type_hash_1.hashType)(argsType, returnType);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trickle-observe",
3
- "version": "0.2.90",
3
+ "version": "0.2.92",
4
4
  "description": "Runtime type observability for JavaScript applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -66,8 +66,11 @@ export function patchFetch(environment: string, debugMode: boolean): void {
66
66
  return originalFetch.call(globalThis, input, init);
67
67
  }
68
68
 
69
- // Make the actual request
69
+ // Make the actual request with timing
70
+ const startTime = performance.now();
70
71
  const response = await originalFetch.call(globalThis, input, init);
72
+ const durationMs = Math.round((performance.now() - startTime) * 100) / 100;
73
+ const statusCode = response.status;
71
74
 
72
75
  // Only observe JSON responses
73
76
  const contentType = response.headers.get('content-type') || '';
@@ -80,7 +83,7 @@ export function patchFetch(environment: string, debugMode: boolean): void {
80
83
  const cloned = response.clone();
81
84
  cloned.json().then((data: any) => {
82
85
  try {
83
- captureHttpResponse(method, url, init?.body, data, environment, debugMode);
86
+ captureHttpResponse(method, url, init?.body, data, environment, debugMode, statusCode, durationMs);
84
87
  } catch {
85
88
  // Never interfere
86
89
  }
@@ -128,6 +131,8 @@ function captureHttpResponse(
128
131
  responseData: unknown,
129
132
  environment: string,
130
133
  debugMode: boolean,
134
+ statusCode?: number,
135
+ durationMs?: number,
131
136
  ): void {
132
137
  const { functionName, module: moduleName } = parseUrl(method, url);
133
138
 
@@ -164,8 +169,8 @@ function captureHttpResponse(
164
169
  }
165
170
  }
166
171
 
167
- const payload: IngestPayload = {
168
- functionName,
172
+ const payload: IngestPayload & { statusCode?: number } = {
173
+ functionName: statusCode ? `${functionName} [${statusCode}]` : functionName,
169
174
  module: moduleName,
170
175
  language: 'js',
171
176
  environment,
@@ -174,9 +179,10 @@ function captureHttpResponse(
174
179
  returnType,
175
180
  sampleInput: sampleInput ? [sampleInput] : undefined,
176
181
  sampleOutput: sanitizeSample(responseData),
182
+ durationMs,
177
183
  };
178
184
 
179
- enqueue(payload);
185
+ enqueue(payload as IngestPayload);
180
186
 
181
187
  if (debugMode) {
182
188
  console.log(`[trickle/fetch] Captured ${functionName} → ${describeType(returnType)}`);
package/src/types.ts CHANGED
@@ -22,6 +22,7 @@ export interface IngestPayload {
22
22
  paramNames?: string[];
23
23
  sampleInput?: unknown;
24
24
  sampleOutput?: unknown;
25
+ durationMs?: number;
25
26
  error?: {
26
27
  type: string;
27
28
  message: string;
package/src/wrap.ts CHANGED
@@ -34,6 +34,7 @@ export function wrapFunction<T extends (...args: any[]) => any>(fn: T, opts: Wra
34
34
  let threwError = false;
35
35
  let caughtError: unknown;
36
36
  const trackers: Array<{ proxy: unknown; getAccessedPaths: () => Map<string, TypeNode> }> = [];
37
+ const startTime = performance.now();
37
38
 
38
39
  try {
39
40
  // Always pass ORIGINAL args to the function — never proxied ones.
@@ -43,9 +44,10 @@ export function wrapFunction<T extends (...args: any[]) => any>(fn: T, opts: Wra
43
44
  threwError = true;
44
45
  caughtError = err;
45
46
 
46
- // Capture error context
47
+ // Capture error context with timing
47
48
  try {
48
- captureErrorPayload(functionKey, opts, args, trackers, err);
49
+ const durationMs = performance.now() - startTime;
50
+ captureErrorPayload(functionKey, opts, args, trackers, err, durationMs);
49
51
  } catch {
50
52
  // Never let our instrumentation interfere
51
53
  }
@@ -59,7 +61,8 @@ export function wrapFunction<T extends (...args: any[]) => any>(fn: T, opts: Wra
59
61
  return result.then(
60
62
  (resolved: unknown) => {
61
63
  try {
62
- capturePayload(functionKey, opts, args, trackers, resolved, true);
64
+ const durationMs = performance.now() - startTime;
65
+ capturePayload(functionKey, opts, args, trackers, resolved, true, durationMs);
63
66
  } catch {
64
67
  // Never let our instrumentation interfere
65
68
  }
@@ -67,7 +70,8 @@ export function wrapFunction<T extends (...args: any[]) => any>(fn: T, opts: Wra
67
70
  },
68
71
  (err: unknown) => {
69
72
  try {
70
- captureErrorPayload(functionKey, opts, args, trackers, err);
73
+ const durationMs = performance.now() - startTime;
74
+ captureErrorPayload(functionKey, opts, args, trackers, err, durationMs);
71
75
  } catch {
72
76
  // Never let our instrumentation interfere
73
77
  }
@@ -79,7 +83,8 @@ export function wrapFunction<T extends (...args: any[]) => any>(fn: T, opts: Wra
79
83
 
80
84
  // Synchronous return
81
85
  try {
82
- capturePayload(functionKey, opts, args, trackers, result);
86
+ const durationMs = performance.now() - startTime;
87
+ capturePayload(functionKey, opts, args, trackers, result, false, durationMs);
83
88
  } catch {
84
89
  // Never let our instrumentation interfere
85
90
  }
@@ -107,6 +112,7 @@ function capturePayload(
107
112
  trackers: Array<{ proxy: unknown; getAccessedPaths: () => Map<string, TypeNode> }>,
108
113
  returnValue: unknown,
109
114
  isAsync: boolean = false,
115
+ durationMs?: number,
110
116
  ): void {
111
117
  // Build args type as a tuple
112
118
  const argsType = buildArgsType(originalArgs, trackers, opts.maxDepth);
@@ -141,6 +147,10 @@ function capturePayload(
141
147
  payload.paramNames = opts.paramNames;
142
148
  }
143
149
 
150
+ if (durationMs !== undefined) {
151
+ payload.durationMs = Math.round(durationMs * 100) / 100;
152
+ }
153
+
144
154
  enqueue(payload);
145
155
  }
146
156
 
@@ -153,6 +163,7 @@ function captureErrorPayload(
153
163
  originalArgs: unknown[],
154
164
  trackers: Array<{ proxy: unknown; getAccessedPaths: () => Map<string, TypeNode> }>,
155
165
  error: unknown,
166
+ durationMs?: number,
156
167
  ): void {
157
168
  const argsType = buildArgsType(originalArgs, trackers, opts.maxDepth);
158
169
  const returnType: TypeNode = { kind: 'unknown' };