trickle-observe 0.2.1 → 0.2.3
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/auto-codegen.d.ts +1 -1
- package/dist/auto-codegen.js +234 -17
- package/dist/auto-register.js +1 -1
- package/dist/express.js +13 -0
- package/dist/observe-register.js +450 -41
- package/dist/trace-var.d.ts +44 -0
- package/dist/trace-var.js +219 -0
- package/dist/transport.js +5 -1
- package/dist/type-inference.js +9 -1
- package/dist/vite-plugin.d.ts +5 -2
- package/dist/vite-plugin.js +385 -25
- package/dist/wrap.js +4 -12
- package/package.json +10 -3
- package/src/auto-codegen.ts +226 -18
- package/src/auto-register.ts +1 -1
- package/src/express.d.ts +387 -0
- package/src/express.ts +14 -0
- package/src/observe-register.ts +420 -41
- package/src/trace-var.ts +202 -0
- package/src/transport.ts +4 -1
- package/src/type-inference.ts +11 -1
- package/src/vite-plugin.ts +444 -24
- package/src/wrap.ts +4 -12
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Variable-level tracing — captures the runtime type and sample value
|
|
4
|
+
* of variable assignments within function bodies.
|
|
5
|
+
*
|
|
6
|
+
* This is injected by the Module._compile source transform. After each
|
|
7
|
+
* `const/let/var x = expr;` statement, the transform inserts:
|
|
8
|
+
*
|
|
9
|
+
* __trickle_tv(x, 'x', 42, 'my-module', '/path/to/file.ts');
|
|
10
|
+
*
|
|
11
|
+
* The traceVar function:
|
|
12
|
+
* 1. Infers the TypeNode from the runtime value
|
|
13
|
+
* 2. Captures a sanitized sample value
|
|
14
|
+
* 3. Appends to .trickle/variables.jsonl
|
|
15
|
+
* 4. Caches by (file:line:varName + typeHash) to avoid duplicates
|
|
16
|
+
*/
|
|
17
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
20
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
21
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
22
|
+
}
|
|
23
|
+
Object.defineProperty(o, k2, desc);
|
|
24
|
+
}) : (function(o, m, k, k2) {
|
|
25
|
+
if (k2 === undefined) k2 = k;
|
|
26
|
+
o[k2] = m[k];
|
|
27
|
+
}));
|
|
28
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
29
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
30
|
+
}) : function(o, v) {
|
|
31
|
+
o["default"] = v;
|
|
32
|
+
});
|
|
33
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
34
|
+
var ownKeys = function(o) {
|
|
35
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
36
|
+
var ar = [];
|
|
37
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
38
|
+
return ar;
|
|
39
|
+
};
|
|
40
|
+
return ownKeys(o);
|
|
41
|
+
};
|
|
42
|
+
return function (mod) {
|
|
43
|
+
if (mod && mod.__esModule) return mod;
|
|
44
|
+
var result = {};
|
|
45
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
46
|
+
__setModuleDefault(result, mod);
|
|
47
|
+
return result;
|
|
48
|
+
};
|
|
49
|
+
})();
|
|
50
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
|
+
exports.initVarTracer = initVarTracer;
|
|
52
|
+
exports.traceVar = traceVar;
|
|
53
|
+
const fs = __importStar(require("fs"));
|
|
54
|
+
const path = __importStar(require("path"));
|
|
55
|
+
const type_inference_1 = require("./type-inference");
|
|
56
|
+
const type_hash_1 = require("./type-hash");
|
|
57
|
+
/** Where to write variable observations */
|
|
58
|
+
let varsFilePath = '';
|
|
59
|
+
let debugMode = false;
|
|
60
|
+
/** Cache: "file:line:varName:typeHash" → already written */
|
|
61
|
+
const varCache = new Set();
|
|
62
|
+
/** Batch buffer for writing — avoids one fs.appendFileSync per variable */
|
|
63
|
+
let varBuffer = [];
|
|
64
|
+
let flushTimer = null;
|
|
65
|
+
const FLUSH_INTERVAL_MS = 1000;
|
|
66
|
+
const MAX_BUFFER_SIZE = 100;
|
|
67
|
+
/**
|
|
68
|
+
* Initialize the variable tracer.
|
|
69
|
+
* Called once during observe-register setup.
|
|
70
|
+
*/
|
|
71
|
+
function initVarTracer(opts = {}) {
|
|
72
|
+
debugMode = opts.debug === true;
|
|
73
|
+
const dir = process.env.TRICKLE_LOCAL_DIR || path.join(process.cwd(), '.trickle');
|
|
74
|
+
try {
|
|
75
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
catch { }
|
|
78
|
+
varsFilePath = path.join(dir, 'variables.jsonl');
|
|
79
|
+
if (debugMode) {
|
|
80
|
+
console.log(`[trickle/vars] Variable tracing enabled → ${varsFilePath}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Trace a variable's runtime value.
|
|
85
|
+
* Called by injected code after each variable declaration.
|
|
86
|
+
*
|
|
87
|
+
* @param value - The variable's current value (already computed)
|
|
88
|
+
* @param varName - The variable name in source
|
|
89
|
+
* @param line - Line number in source file
|
|
90
|
+
* @param moduleName - Module name (derived from filename)
|
|
91
|
+
* @param filePath - Absolute path to source file
|
|
92
|
+
*/
|
|
93
|
+
function traceVar(value, varName, line, moduleName, filePath) {
|
|
94
|
+
// Auto-initialize if not yet done (needed for Vite/Vitest worker processes)
|
|
95
|
+
if (!varsFilePath) {
|
|
96
|
+
initVarTracer();
|
|
97
|
+
if (!varsFilePath)
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const type = (0, type_inference_1.inferType)(value, 3);
|
|
102
|
+
// Create a stable hash for dedup
|
|
103
|
+
const dummyArgs = { kind: 'tuple', elements: [] };
|
|
104
|
+
const typeHash = (0, type_hash_1.hashType)(dummyArgs, type);
|
|
105
|
+
const cacheKey = `${filePath}:${line}:${varName}:${typeHash}`;
|
|
106
|
+
if (varCache.has(cacheKey))
|
|
107
|
+
return;
|
|
108
|
+
varCache.add(cacheKey);
|
|
109
|
+
const sample = sanitizeVarSample(value);
|
|
110
|
+
const observation = {
|
|
111
|
+
kind: 'variable',
|
|
112
|
+
varName,
|
|
113
|
+
line,
|
|
114
|
+
module: moduleName,
|
|
115
|
+
file: filePath,
|
|
116
|
+
type,
|
|
117
|
+
typeHash,
|
|
118
|
+
sample,
|
|
119
|
+
};
|
|
120
|
+
// Buffer the write
|
|
121
|
+
varBuffer.push(JSON.stringify(observation));
|
|
122
|
+
if (varBuffer.length >= MAX_BUFFER_SIZE) {
|
|
123
|
+
flushVarBuffer();
|
|
124
|
+
}
|
|
125
|
+
else if (!flushTimer) {
|
|
126
|
+
flushTimer = setTimeout(() => {
|
|
127
|
+
flushVarBuffer();
|
|
128
|
+
}, FLUSH_INTERVAL_MS);
|
|
129
|
+
if (flushTimer && typeof flushTimer === 'object' && 'unref' in flushTimer) {
|
|
130
|
+
flushTimer.unref();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Never crash user's app
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Flush buffered variable observations to disk.
|
|
140
|
+
*/
|
|
141
|
+
function flushVarBuffer() {
|
|
142
|
+
if (flushTimer) {
|
|
143
|
+
clearTimeout(flushTimer);
|
|
144
|
+
flushTimer = null;
|
|
145
|
+
}
|
|
146
|
+
if (varBuffer.length === 0)
|
|
147
|
+
return;
|
|
148
|
+
const lines = varBuffer.join('\n') + '\n';
|
|
149
|
+
varBuffer = [];
|
|
150
|
+
try {
|
|
151
|
+
fs.appendFileSync(varsFilePath, lines);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// Never crash user's app
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Sanitize a variable value for safe serialization.
|
|
159
|
+
* More aggressive truncation than function samples since there are many more variables.
|
|
160
|
+
*/
|
|
161
|
+
function sanitizeVarSample(value, depth = 2) {
|
|
162
|
+
if (depth <= 0)
|
|
163
|
+
return '[truncated]';
|
|
164
|
+
if (value === null || value === undefined)
|
|
165
|
+
return value;
|
|
166
|
+
const t = typeof value;
|
|
167
|
+
if (t === 'string') {
|
|
168
|
+
const s = value;
|
|
169
|
+
return s.length > 100 ? s.substring(0, 100) + '...' : s;
|
|
170
|
+
}
|
|
171
|
+
if (t === 'number' || t === 'boolean')
|
|
172
|
+
return value;
|
|
173
|
+
if (t === 'bigint')
|
|
174
|
+
return String(value);
|
|
175
|
+
if (t === 'symbol')
|
|
176
|
+
return String(value);
|
|
177
|
+
if (t === 'function')
|
|
178
|
+
return `[Function: ${value.name || 'anonymous'}]`;
|
|
179
|
+
if (Array.isArray(value)) {
|
|
180
|
+
return value.slice(0, 3).map(item => sanitizeVarSample(item, depth - 1));
|
|
181
|
+
}
|
|
182
|
+
if (t === 'object') {
|
|
183
|
+
if (value instanceof Date)
|
|
184
|
+
return value.toISOString();
|
|
185
|
+
if (value instanceof RegExp)
|
|
186
|
+
return String(value);
|
|
187
|
+
if (value instanceof Error)
|
|
188
|
+
return { error: value.message };
|
|
189
|
+
if (value instanceof Map)
|
|
190
|
+
return `[Map: ${value.size} entries]`;
|
|
191
|
+
if (value instanceof Set)
|
|
192
|
+
return `[Set: ${value.size} items]`;
|
|
193
|
+
if (value instanceof Promise)
|
|
194
|
+
return '[Promise]';
|
|
195
|
+
const obj = value;
|
|
196
|
+
const result = {};
|
|
197
|
+
const keys = Object.keys(obj).slice(0, 10);
|
|
198
|
+
for (const key of keys) {
|
|
199
|
+
try {
|
|
200
|
+
result[key] = sanitizeVarSample(obj[key], depth - 1);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
result[key] = '[unreadable]';
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
return String(value);
|
|
209
|
+
}
|
|
210
|
+
// Flush on process exit — use 'exit' event (synchronous, fires even on process.exit())
|
|
211
|
+
// because Vitest workers and forked processes may exit without 'beforeExit'.
|
|
212
|
+
// flushVarBuffer uses fs.appendFileSync so it's safe in the 'exit' handler.
|
|
213
|
+
if (typeof process !== 'undefined' && process.on) {
|
|
214
|
+
const exitFlush = () => { flushVarBuffer(); };
|
|
215
|
+
process.on('exit', exitFlush);
|
|
216
|
+
process.on('beforeExit', exitFlush);
|
|
217
|
+
process.on('SIGTERM', exitFlush);
|
|
218
|
+
process.on('SIGINT', exitFlush);
|
|
219
|
+
}
|
package/dist/transport.js
CHANGED
|
@@ -202,8 +202,12 @@ function silentError() {
|
|
|
202
202
|
}
|
|
203
203
|
// Register process exit handler to flush remaining events
|
|
204
204
|
if (typeof process !== 'undefined' && process.on) {
|
|
205
|
+
let flushing = false;
|
|
205
206
|
process.on('beforeExit', () => {
|
|
206
|
-
|
|
207
|
+
if (flushing || queue.length === 0)
|
|
208
|
+
return;
|
|
209
|
+
flushing = true;
|
|
210
|
+
flush().catch(silentError).finally(() => { flushing = false; stopTimer(); });
|
|
207
211
|
});
|
|
208
212
|
// SIGTERM / SIGINT: attempt a sync-ish flush
|
|
209
213
|
const exitHandler = () => {
|
package/dist/type-inference.js
CHANGED
|
@@ -167,10 +167,18 @@ function inferArray(arr, depth, seen) {
|
|
|
167
167
|
}
|
|
168
168
|
return { kind: 'array', element: unifyTypes(elementTypes) };
|
|
169
169
|
}
|
|
170
|
+
const MAX_PROPERTIES = 20;
|
|
170
171
|
function inferPlainObject(obj, depth, seen) {
|
|
171
172
|
const properties = {};
|
|
172
173
|
const keys = Object.keys(obj);
|
|
173
|
-
|
|
174
|
+
// Skip internal/private properties (common in Node.js built-in objects like
|
|
175
|
+
// http.Server, streams, etc.) — they make generated types unusably verbose.
|
|
176
|
+
const publicKeys = keys.filter(k => !k.startsWith('_'));
|
|
177
|
+
// If filtering removed everything, use all keys (it's a plain data object with _ keys)
|
|
178
|
+
const effectiveKeys = publicKeys.length > 0 ? publicKeys : keys;
|
|
179
|
+
// Cap the number of properties to keep types manageable
|
|
180
|
+
const cappedKeys = effectiveKeys.slice(0, MAX_PROPERTIES);
|
|
181
|
+
for (const key of cappedKeys) {
|
|
174
182
|
try {
|
|
175
183
|
properties[key] = infer(obj[key], depth - 1, seen);
|
|
176
184
|
}
|
package/dist/vite-plugin.d.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Vite plugin for trickle observation.
|
|
3
3
|
*
|
|
4
4
|
* Integrates into Vite's (and Vitest's) transform pipeline to wrap
|
|
5
|
-
* user functions with trickle observation
|
|
6
|
-
* does for Node's Module._compile,
|
|
5
|
+
* user functions with trickle observation AND trace variable assignments —
|
|
6
|
+
* the same thing observe-register.ts does for Node's Module._compile,
|
|
7
|
+
* but for Vite/Vitest.
|
|
7
8
|
*
|
|
8
9
|
* Usage in vitest.config.ts:
|
|
9
10
|
*
|
|
@@ -25,6 +26,8 @@ export interface TricklePluginOptions {
|
|
|
25
26
|
backendUrl?: string;
|
|
26
27
|
/** Enable debug logging */
|
|
27
28
|
debug?: boolean;
|
|
29
|
+
/** Disable variable tracing (default: enabled) */
|
|
30
|
+
traceVars?: boolean;
|
|
28
31
|
}
|
|
29
32
|
export declare function tricklePlugin(options?: TricklePluginOptions): {
|
|
30
33
|
name: string;
|