trickle-observe 0.2.96 → 0.2.97

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.
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Call trace recorder — captures function call/return events with timing
3
+ * and parent-child relationships for building call graphs.
4
+ *
5
+ * Written to .trickle/calltrace.jsonl as:
6
+ * { "kind": "call", "function": "createUser", "module": "api",
7
+ * "parentId": 0, "callId": 1, "timestamp": 1710516000,
8
+ * "durationMs": 2.5, "args": ["Alice"], "result": {...} }
9
+ */
10
+ /**
11
+ * Record a function call event. Returns the callId for pairing with traceReturn.
12
+ */
13
+ export declare function traceCall(functionName: string, moduleName: string): number;
14
+ /**
15
+ * Record a function return event with timing.
16
+ */
17
+ export declare function traceReturn(callId: number, functionName: string, moduleName: string, durationMs: number, error?: string): void;
18
+ export declare function initCallTrace(): void;
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ /**
3
+ * Call trace recorder — captures function call/return events with timing
4
+ * and parent-child relationships for building call graphs.
5
+ *
6
+ * Written to .trickle/calltrace.jsonl as:
7
+ * { "kind": "call", "function": "createUser", "module": "api",
8
+ * "parentId": 0, "callId": 1, "timestamp": 1710516000,
9
+ * "durationMs": 2.5, "args": ["Alice"], "result": {...} }
10
+ */
11
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ var desc = Object.getOwnPropertyDescriptor(m, k);
14
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
15
+ desc = { enumerable: true, get: function() { return m[k]; } };
16
+ }
17
+ Object.defineProperty(o, k2, desc);
18
+ }) : (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ o[k2] = m[k];
21
+ }));
22
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
23
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
24
+ }) : function(o, v) {
25
+ o["default"] = v;
26
+ });
27
+ var __importStar = (this && this.__importStar) || (function () {
28
+ var ownKeys = function(o) {
29
+ ownKeys = Object.getOwnPropertyNames || function (o) {
30
+ var ar = [];
31
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
32
+ return ar;
33
+ };
34
+ return ownKeys(o);
35
+ };
36
+ return function (mod) {
37
+ if (mod && mod.__esModule) return mod;
38
+ var result = {};
39
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
40
+ __setModuleDefault(result, mod);
41
+ return result;
42
+ };
43
+ })();
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.traceCall = traceCall;
46
+ exports.traceReturn = traceReturn;
47
+ exports.initCallTrace = initCallTrace;
48
+ const fs = __importStar(require("fs"));
49
+ const path = __importStar(require("path"));
50
+ let traceFile = null;
51
+ let callCounter = 0;
52
+ let currentCallId = 0; // 0 = top level
53
+ const callStack = [0];
54
+ const MAX_TRACE_EVENTS = 500;
55
+ let eventCount = 0;
56
+ function getTraceFile() {
57
+ if (traceFile)
58
+ return traceFile;
59
+ const dir = process.env.TRICKLE_LOCAL_DIR || path.join(process.cwd(), '.trickle');
60
+ try {
61
+ fs.mkdirSync(dir, { recursive: true });
62
+ }
63
+ catch { }
64
+ traceFile = path.join(dir, 'calltrace.jsonl');
65
+ try {
66
+ fs.writeFileSync(traceFile, '');
67
+ }
68
+ catch { }
69
+ return traceFile;
70
+ }
71
+ function writeEvent(event) {
72
+ if (eventCount >= MAX_TRACE_EVENTS)
73
+ return;
74
+ eventCount++;
75
+ try {
76
+ fs.appendFileSync(getTraceFile(), JSON.stringify(event) + '\n');
77
+ }
78
+ catch { }
79
+ }
80
+ /**
81
+ * Record a function call event. Returns the callId for pairing with traceReturn.
82
+ */
83
+ function traceCall(functionName, moduleName) {
84
+ const id = ++callCounter;
85
+ const parentId = callStack[callStack.length - 1] || 0;
86
+ callStack.push(id);
87
+ // We record a placeholder — duration is filled in by traceReturn
88
+ return id;
89
+ }
90
+ /**
91
+ * Record a function return event with timing.
92
+ */
93
+ function traceReturn(callId, functionName, moduleName, durationMs, error) {
94
+ const parentId = callStack.length >= 2 ? callStack[callStack.length - 2] : 0;
95
+ const depth = callStack.length - 1;
96
+ writeEvent({
97
+ kind: 'call',
98
+ function: functionName,
99
+ module: moduleName,
100
+ callId,
101
+ parentId,
102
+ depth,
103
+ timestamp: Date.now(),
104
+ durationMs: Math.round(durationMs * 100) / 100,
105
+ ...(error ? { error } : {}),
106
+ });
107
+ // Pop from stack
108
+ if (callStack[callStack.length - 1] === callId) {
109
+ callStack.pop();
110
+ }
111
+ }
112
+ function initCallTrace() {
113
+ // Ensure trace file is initialized
114
+ getTraceFile();
115
+ }
package/dist/wrap.js CHANGED
@@ -6,6 +6,7 @@ const type_inference_1 = require("./type-inference");
6
6
  const type_hash_1 = require("./type-hash");
7
7
  const cache_1 = require("./cache");
8
8
  const transport_1 = require("./transport");
9
+ const call_trace_1 = require("./call-trace");
9
10
  const typeCache = new cache_1.TypeCache();
10
11
  exports.typeCache = typeCache;
11
12
  /** Symbol to mark already-wrapped functions, preventing double-wrap. */
@@ -33,6 +34,7 @@ function wrapFunction(fn, opts) {
33
34
  let caughtError;
34
35
  const trackers = [];
35
36
  const startTime = performance.now();
37
+ const callId = (0, call_trace_1.traceCall)(opts.functionName, opts.module);
36
38
  try {
37
39
  // Always pass ORIGINAL args to the function — never proxied ones.
38
40
  // Proxied args can break framework internals (Express Router, DI containers, etc.)
@@ -45,6 +47,7 @@ function wrapFunction(fn, opts) {
45
47
  try {
46
48
  const durationMs = performance.now() - startTime;
47
49
  captureErrorPayload(functionKey, opts, args, trackers, err, durationMs);
50
+ (0, call_trace_1.traceReturn)(callId, opts.functionName, opts.module, durationMs, err?.message);
48
51
  }
49
52
  catch {
50
53
  // Never let our instrumentation interfere
@@ -58,6 +61,7 @@ function wrapFunction(fn, opts) {
58
61
  try {
59
62
  const durationMs = performance.now() - startTime;
60
63
  capturePayload(functionKey, opts, args, trackers, resolved, true, durationMs);
64
+ (0, call_trace_1.traceReturn)(callId, opts.functionName, opts.module, durationMs);
61
65
  }
62
66
  catch {
63
67
  // Never let our instrumentation interfere
@@ -67,6 +71,7 @@ function wrapFunction(fn, opts) {
67
71
  try {
68
72
  const durationMs = performance.now() - startTime;
69
73
  captureErrorPayload(functionKey, opts, args, trackers, err, durationMs);
74
+ (0, call_trace_1.traceReturn)(callId, opts.functionName, opts.module, durationMs, err?.message);
70
75
  }
71
76
  catch {
72
77
  // Never let our instrumentation interfere
@@ -79,6 +84,7 @@ function wrapFunction(fn, opts) {
79
84
  try {
80
85
  const durationMs = performance.now() - startTime;
81
86
  capturePayload(functionKey, opts, args, trackers, result, false, durationMs);
87
+ (0, call_trace_1.traceReturn)(callId, opts.functionName, opts.module, durationMs);
82
88
  }
83
89
  catch {
84
90
  // Never let our instrumentation interfere
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trickle-observe",
3
- "version": "0.2.96",
3
+ "version": "0.2.97",
4
4
  "description": "Runtime type observability for JavaScript applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Call trace recorder — captures function call/return events with timing
3
+ * and parent-child relationships for building call graphs.
4
+ *
5
+ * Written to .trickle/calltrace.jsonl as:
6
+ * { "kind": "call", "function": "createUser", "module": "api",
7
+ * "parentId": 0, "callId": 1, "timestamp": 1710516000,
8
+ * "durationMs": 2.5, "args": ["Alice"], "result": {...} }
9
+ */
10
+
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+
14
+ interface CallEvent {
15
+ kind: 'call';
16
+ function: string;
17
+ module: string;
18
+ callId: number;
19
+ parentId: number;
20
+ depth: number;
21
+ timestamp: number;
22
+ durationMs: number;
23
+ error?: string;
24
+ }
25
+
26
+ let traceFile: string | null = null;
27
+ let callCounter = 0;
28
+ let currentCallId = 0; // 0 = top level
29
+ const callStack: number[] = [0];
30
+ const MAX_TRACE_EVENTS = 500;
31
+ let eventCount = 0;
32
+
33
+ function getTraceFile(): string {
34
+ if (traceFile) return traceFile;
35
+ const dir = process.env.TRICKLE_LOCAL_DIR || path.join(process.cwd(), '.trickle');
36
+ try { fs.mkdirSync(dir, { recursive: true }); } catch {}
37
+ traceFile = path.join(dir, 'calltrace.jsonl');
38
+ try { fs.writeFileSync(traceFile, ''); } catch {}
39
+ return traceFile;
40
+ }
41
+
42
+ function writeEvent(event: CallEvent): void {
43
+ if (eventCount >= MAX_TRACE_EVENTS) return;
44
+ eventCount++;
45
+ try {
46
+ fs.appendFileSync(getTraceFile(), JSON.stringify(event) + '\n');
47
+ } catch {}
48
+ }
49
+
50
+ /**
51
+ * Record a function call event. Returns the callId for pairing with traceReturn.
52
+ */
53
+ export function traceCall(functionName: string, moduleName: string): number {
54
+ const id = ++callCounter;
55
+ const parentId = callStack[callStack.length - 1] || 0;
56
+ callStack.push(id);
57
+ // We record a placeholder — duration is filled in by traceReturn
58
+ return id;
59
+ }
60
+
61
+ /**
62
+ * Record a function return event with timing.
63
+ */
64
+ export function traceReturn(
65
+ callId: number,
66
+ functionName: string,
67
+ moduleName: string,
68
+ durationMs: number,
69
+ error?: string,
70
+ ): void {
71
+ const parentId = callStack.length >= 2 ? callStack[callStack.length - 2] : 0;
72
+ const depth = callStack.length - 1;
73
+
74
+ writeEvent({
75
+ kind: 'call',
76
+ function: functionName,
77
+ module: moduleName,
78
+ callId,
79
+ parentId,
80
+ depth,
81
+ timestamp: Date.now(),
82
+ durationMs: Math.round(durationMs * 100) / 100,
83
+ ...(error ? { error } : {}),
84
+ });
85
+
86
+ // Pop from stack
87
+ if (callStack[callStack.length - 1] === callId) {
88
+ callStack.pop();
89
+ }
90
+ }
91
+
92
+ export function initCallTrace(): void {
93
+ // Ensure trace file is initialized
94
+ getTraceFile();
95
+ }
package/src/wrap.ts CHANGED
@@ -4,6 +4,7 @@ import { hashType } from './type-hash';
4
4
  import { createTracker } from './proxy-tracker';
5
5
  import { TypeCache } from './cache';
6
6
  import { enqueue } from './transport';
7
+ import { traceCall, traceReturn } from './call-trace';
7
8
 
8
9
  const typeCache = new TypeCache();
9
10
 
@@ -35,6 +36,7 @@ export function wrapFunction<T extends (...args: any[]) => any>(fn: T, opts: Wra
35
36
  let caughtError: unknown;
36
37
  const trackers: Array<{ proxy: unknown; getAccessedPaths: () => Map<string, TypeNode> }> = [];
37
38
  const startTime = performance.now();
39
+ const callId = traceCall(opts.functionName, opts.module);
38
40
 
39
41
  try {
40
42
  // Always pass ORIGINAL args to the function — never proxied ones.
@@ -48,6 +50,7 @@ export function wrapFunction<T extends (...args: any[]) => any>(fn: T, opts: Wra
48
50
  try {
49
51
  const durationMs = performance.now() - startTime;
50
52
  captureErrorPayload(functionKey, opts, args, trackers, err, durationMs);
53
+ traceReturn(callId, opts.functionName, opts.module, durationMs, (err as Error)?.message);
51
54
  } catch {
52
55
  // Never let our instrumentation interfere
53
56
  }
@@ -63,6 +66,7 @@ export function wrapFunction<T extends (...args: any[]) => any>(fn: T, opts: Wra
63
66
  try {
64
67
  const durationMs = performance.now() - startTime;
65
68
  capturePayload(functionKey, opts, args, trackers, resolved, true, durationMs);
69
+ traceReturn(callId, opts.functionName, opts.module, durationMs);
66
70
  } catch {
67
71
  // Never let our instrumentation interfere
68
72
  }
@@ -72,6 +76,7 @@ export function wrapFunction<T extends (...args: any[]) => any>(fn: T, opts: Wra
72
76
  try {
73
77
  const durationMs = performance.now() - startTime;
74
78
  captureErrorPayload(functionKey, opts, args, trackers, err, durationMs);
79
+ traceReturn(callId, opts.functionName, opts.module, durationMs, (err as Error)?.message);
75
80
  } catch {
76
81
  // Never let our instrumentation interfere
77
82
  }
@@ -85,6 +90,7 @@ export function wrapFunction<T extends (...args: any[]) => any>(fn: T, opts: Wra
85
90
  try {
86
91
  const durationMs = performance.now() - startTime;
87
92
  capturePayload(functionKey, opts, args, trackers, result, false, durationMs);
93
+ traceReturn(callId, opts.functionName, opts.module, durationMs);
88
94
  } catch {
89
95
  // Never let our instrumentation interfere
90
96
  }