@rasputin-ai/node 0.4.1 → 0.5.0-alpha.10
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/README.md +188 -162
- package/dist/auto-instrumentation/collect-instrumentation-delta.d.ts +2 -37
- package/dist/auto-instrumentation/collect-instrumentation-delta.d.ts.map +1 -1
- package/dist/auto-instrumentation/instrument-compiled-esm.d.ts +19 -0
- package/dist/auto-instrumentation/instrument-compiled-esm.d.ts.map +1 -0
- package/dist/auto-instrumentation/instrumentation-manifest-delta.d.ts +41 -0
- package/dist/auto-instrumentation/instrumentation-manifest-delta.d.ts.map +1 -0
- package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts +5 -2
- package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts.map +1 -1
- package/dist/auto-instrumentation/transform-source.d.ts +7 -1
- package/dist/auto-instrumentation/transform-source.d.ts.map +1 -1
- package/dist/auto-instrumentation/warn-production-runtime-transform.d.ts +8 -0
- package/dist/auto-instrumentation/warn-production-runtime-transform.d.ts.map +1 -0
- package/dist/derivation/analyze-function.d.ts +42 -0
- package/dist/derivation/analyze-function.d.ts.map +1 -0
- package/dist/derivation/build-dependency-graph.d.ts +21 -0
- package/dist/derivation/build-dependency-graph.d.ts.map +1 -0
- package/dist/derivation/derivation-ir.d.ts +33 -0
- package/dist/derivation/derivation-ir.d.ts.map +1 -0
- package/dist/derivation/derivation-proof.d.ts +14 -0
- package/dist/derivation/derivation-proof.d.ts.map +1 -0
- package/dist/derivation/emit-capture-plan.d.ts +8 -0
- package/dist/derivation/emit-capture-plan.d.ts.map +1 -0
- package/dist/execution-recorder/automatic-runtime.d.ts +21 -10
- package/dist/execution-recorder/automatic-runtime.d.ts.map +1 -1
- package/dist/execution-recorder/execution-journal.d.ts +84 -0
- package/dist/execution-recorder/execution-journal.d.ts.map +1 -0
- package/dist/execution-recorder/execution-recorder-types.d.ts +36 -0
- package/dist/execution-recorder/execution-recorder-types.d.ts.map +1 -1
- package/dist/execution-recorder/execution-recorder.d.ts.map +1 -1
- package/dist/execution-recorder/function-plan.d.ts +26 -0
- package/dist/execution-recorder/function-plan.d.ts.map +1 -0
- package/dist/execution-recorder/recorder-memory-budget.d.ts +8 -1
- package/dist/execution-recorder/recorder-memory-budget.d.ts.map +1 -1
- package/dist/execution-recorder/safe-serialize.d.ts +8 -0
- package/dist/execution-recorder/safe-serialize.d.ts.map +1 -1
- package/dist/index.js +1432 -418
- package/dist/instrument/build.d.ts +2 -0
- package/dist/instrument/build.d.ts.map +1 -0
- package/dist/instrument/build.js +1430 -0
- package/dist/instrument/bun.js +825 -82
- package/dist/instrument/node.js +825 -82
- package/dist/rasputin-init.d.ts +5 -5
- package/dist/rasputin-init.d.ts.map +1 -1
- package/dist/sdk-meta.d.ts +1 -1
- package/dist/sdk-meta.d.ts.map +1 -1
- package/package.json +10 -2
- package/dist/execution-recorder/call-aware-event-buffer.d.ts +0 -23
- package/dist/execution-recorder/call-aware-event-buffer.d.ts.map +0 -1
package/dist/instrument/bun.js
CHANGED
|
@@ -12,25 +12,29 @@ import {
|
|
|
12
12
|
sourceLocatorFromFile as sourceLocatorFromFile2
|
|
13
13
|
} from "@rasputin-ai/core";
|
|
14
14
|
|
|
15
|
-
// src/execution-recorder/
|
|
15
|
+
// src/execution-recorder/function-plan.ts
|
|
16
16
|
import {
|
|
17
17
|
runtimeFunctionId,
|
|
18
18
|
sourceLocatorFromFile
|
|
19
19
|
} from "@rasputin-ai/core";
|
|
20
|
-
var
|
|
21
|
-
var AUTOMATIC_SOURCE_PREFIX = "rasputin-source-v2:";
|
|
22
|
-
var runtimeSymbol = Symbol.for(AUTOMATIC_RUNTIME_SYMBOL);
|
|
23
|
-
var encodeAutomaticFunctionSource = (source) => {
|
|
20
|
+
var encodeFunctionPlan = (source) => {
|
|
24
21
|
const locator = sourceLocatorFromFile(source.filePath);
|
|
25
|
-
const
|
|
22
|
+
const definition = {
|
|
26
23
|
name: source.name,
|
|
27
24
|
...locator ? { source: { ...locator, line: source.line, column: source.column } } : {}
|
|
28
25
|
};
|
|
29
|
-
return
|
|
26
|
+
return [
|
|
27
|
+
runtimeFunctionId(definition),
|
|
28
|
+
source.name,
|
|
29
|
+
locator?.packageName ?? null,
|
|
30
|
+
locator?.packageRelativePath ?? null,
|
|
31
|
+
source.line,
|
|
32
|
+
source.column
|
|
33
|
+
];
|
|
30
34
|
};
|
|
31
35
|
|
|
32
36
|
// src/auto-instrumentation/instrumentation-manifest-registry.ts
|
|
33
|
-
var INSTRUMENTATION_MANIFEST_SYMBOL = "rasputin.instrumentation.manifest.
|
|
37
|
+
var INSTRUMENTATION_MANIFEST_SYMBOL = "rasputin.instrumentation.manifest.v3";
|
|
34
38
|
var registrySymbol = Symbol.for(INSTRUMENTATION_MANIFEST_SYMBOL);
|
|
35
39
|
var getRegistry = () => {
|
|
36
40
|
const existing = Reflect.get(globalThis, registrySymbol);
|
|
@@ -84,16 +88,663 @@ var isApplicationSource = (filePath, options = {}) => {
|
|
|
84
88
|
|
|
85
89
|
// src/auto-instrumentation/transform-source.ts
|
|
86
90
|
import { resolve as resolve3 } from "node:path";
|
|
87
|
-
import
|
|
91
|
+
import ts3 from "typescript";
|
|
88
92
|
|
|
89
|
-
// src/
|
|
93
|
+
// src/derivation/analyze-function.ts
|
|
90
94
|
import ts from "typescript";
|
|
95
|
+
|
|
96
|
+
// src/derivation/build-dependency-graph.ts
|
|
97
|
+
var buildDependencyGraph = (functions, edges) => {
|
|
98
|
+
const names = new Set(functions.map((fn) => fn.name));
|
|
99
|
+
const localEdges = edges.filter((edge) => names.has(edge.calleeName));
|
|
100
|
+
const calleesByCaller = /* @__PURE__ */ new Map();
|
|
101
|
+
for (const edge of localEdges) {
|
|
102
|
+
const existing = calleesByCaller.get(edge.callerFunctionId) ?? [];
|
|
103
|
+
existing.push(edge.calleeName);
|
|
104
|
+
calleesByCaller.set(edge.callerFunctionId, existing);
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
functions: functions.map((fn) => fn.functionId),
|
|
108
|
+
edges: localEdges,
|
|
109
|
+
calleesByCaller
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
var topologicalFunctionOrder = (functions, graph) => {
|
|
113
|
+
const idByName = new Map(functions.map((fn) => [fn.name, fn.functionId]));
|
|
114
|
+
const remainingCallees = new Map(
|
|
115
|
+
functions.map((fn) => {
|
|
116
|
+
const unique = new Set(
|
|
117
|
+
(graph.calleesByCaller.get(fn.functionId) ?? []).map((name) => idByName.get(name)).filter((id) => Boolean(id) && id !== fn.functionId)
|
|
118
|
+
);
|
|
119
|
+
return [fn.functionId, unique.size];
|
|
120
|
+
})
|
|
121
|
+
);
|
|
122
|
+
const callersOf = /* @__PURE__ */ new Map();
|
|
123
|
+
for (const fn of functions) {
|
|
124
|
+
for (const calleeName of graph.calleesByCaller.get(fn.functionId) ?? []) {
|
|
125
|
+
const calleeId = idByName.get(calleeName);
|
|
126
|
+
if (!calleeId || calleeId === fn.functionId) continue;
|
|
127
|
+
const callers = callersOf.get(calleeId) ?? [];
|
|
128
|
+
callers.push(fn.functionId);
|
|
129
|
+
callersOf.set(calleeId, callers);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const queue = functions.filter((fn) => (remainingCallees.get(fn.functionId) ?? 0) === 0).map((fn) => fn.functionId);
|
|
133
|
+
const ordered = [];
|
|
134
|
+
const queued = new Set(queue);
|
|
135
|
+
while (queue.length > 0) {
|
|
136
|
+
const current = queue.shift();
|
|
137
|
+
if (current === void 0) break;
|
|
138
|
+
ordered.push(current);
|
|
139
|
+
for (const caller of callersOf.get(current) ?? []) {
|
|
140
|
+
const next = (remainingCallees.get(caller) ?? 0) - 1;
|
|
141
|
+
remainingCallees.set(caller, next);
|
|
142
|
+
if (next === 0 && !queued.has(caller)) {
|
|
143
|
+
queued.add(caller);
|
|
144
|
+
queue.push(caller);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
for (const fn of functions) {
|
|
149
|
+
if (!queued.has(fn.functionId)) ordered.push(fn.functionId);
|
|
150
|
+
}
|
|
151
|
+
return ordered;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// src/derivation/derivation-ir.ts
|
|
155
|
+
var DerivationIrBuilder = class {
|
|
156
|
+
expressions = [];
|
|
157
|
+
nextId = 0;
|
|
158
|
+
constValue(value) {
|
|
159
|
+
return this.add({ id: this.nextId++, kind: "const", value });
|
|
160
|
+
}
|
|
161
|
+
param(index) {
|
|
162
|
+
return this.add({ id: this.nextId++, kind: "param", index });
|
|
163
|
+
}
|
|
164
|
+
root(rootId) {
|
|
165
|
+
return this.add({ id: this.nextId++, kind: "root", rootId });
|
|
166
|
+
}
|
|
167
|
+
alias(from) {
|
|
168
|
+
return this.add({ id: this.nextId++, kind: "alias", from });
|
|
169
|
+
}
|
|
170
|
+
binary(op, left, right, guard) {
|
|
171
|
+
return this.add({ id: this.nextId++, kind: "binary", op, left, right, guard });
|
|
172
|
+
}
|
|
173
|
+
unary(op, operand) {
|
|
174
|
+
return this.add({ id: this.nextId++, kind: "unary", op, operand });
|
|
175
|
+
}
|
|
176
|
+
logical(op, left, right) {
|
|
177
|
+
return this.add({ id: this.nextId++, kind: "logical", op, left, right });
|
|
178
|
+
}
|
|
179
|
+
cond(test, consequent, alternate) {
|
|
180
|
+
return this.add({ id: this.nextId++, kind: "cond", test, consequent, alternate });
|
|
181
|
+
}
|
|
182
|
+
object(properties) {
|
|
183
|
+
return this.add({ id: this.nextId++, kind: "object", properties });
|
|
184
|
+
}
|
|
185
|
+
array(elements) {
|
|
186
|
+
return this.add({ id: this.nextId++, kind: "array", elements });
|
|
187
|
+
}
|
|
188
|
+
prop(object, key) {
|
|
189
|
+
return this.add({ id: this.nextId++, kind: "prop", object, key });
|
|
190
|
+
}
|
|
191
|
+
index(array, index) {
|
|
192
|
+
return this.add({ id: this.nextId++, kind: "index", array, index });
|
|
193
|
+
}
|
|
194
|
+
pureCall(calleeFunctionId, args) {
|
|
195
|
+
return this.add({ id: this.nextId++, kind: "pure_call", calleeFunctionId, args });
|
|
196
|
+
}
|
|
197
|
+
template(parts) {
|
|
198
|
+
return this.add({ id: this.nextId++, kind: "template", parts });
|
|
199
|
+
}
|
|
200
|
+
add(expression) {
|
|
201
|
+
this.expressions.push(expression);
|
|
202
|
+
return expression.id;
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
var emptyPlan = (functionId) => ({
|
|
206
|
+
planId: `dp_${functionId}`,
|
|
207
|
+
functionId,
|
|
208
|
+
expressions: [],
|
|
209
|
+
variables: [],
|
|
210
|
+
captureBoundaries: [],
|
|
211
|
+
controlFacts: []
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// src/derivation/analyze-function.ts
|
|
215
|
+
var loc = (node, sourceFile) => {
|
|
216
|
+
const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
217
|
+
return { line: position.line + 1, column: position.character + 1 };
|
|
218
|
+
};
|
|
219
|
+
var lookup = (ctx, name) => {
|
|
220
|
+
for (let index = ctx.scopes.length - 1; index >= 0; index--) {
|
|
221
|
+
const binding = ctx.scopes[index]?.get(name);
|
|
222
|
+
if (binding) return binding;
|
|
223
|
+
}
|
|
224
|
+
return void 0;
|
|
225
|
+
};
|
|
226
|
+
var fallback = (ctx, reason) => {
|
|
227
|
+
ctx.fallback ??= reason;
|
|
228
|
+
return { expressionId: ctx.ir.constValue({ type: "undefined" }), shape: "unknown" };
|
|
229
|
+
};
|
|
230
|
+
var capture = (ctx, node, reason) => {
|
|
231
|
+
const rootId = ctx.nextRootId.value++;
|
|
232
|
+
const position = loc(node, ctx.sourceFile);
|
|
233
|
+
const expressionId = ctx.ir.root(rootId);
|
|
234
|
+
ctx.captureWraps.push({
|
|
235
|
+
rootId,
|
|
236
|
+
reason,
|
|
237
|
+
start: node.getStart(ctx.sourceFile),
|
|
238
|
+
end: node.end,
|
|
239
|
+
line: position.line,
|
|
240
|
+
column: position.column,
|
|
241
|
+
expressionId
|
|
242
|
+
});
|
|
243
|
+
return { expressionId, shape: "unknown", captured: true };
|
|
244
|
+
};
|
|
245
|
+
var binaryOp = (kind) => {
|
|
246
|
+
switch (kind) {
|
|
247
|
+
case ts.SyntaxKind.PlusToken:
|
|
248
|
+
return "add";
|
|
249
|
+
case ts.SyntaxKind.MinusToken:
|
|
250
|
+
return "sub";
|
|
251
|
+
case ts.SyntaxKind.AsteriskToken:
|
|
252
|
+
return "mul";
|
|
253
|
+
case ts.SyntaxKind.SlashToken:
|
|
254
|
+
return "div";
|
|
255
|
+
case ts.SyntaxKind.PercentToken:
|
|
256
|
+
return "mod";
|
|
257
|
+
case ts.SyntaxKind.EqualsEqualsToken:
|
|
258
|
+
return "eq";
|
|
259
|
+
case ts.SyntaxKind.ExclamationEqualsToken:
|
|
260
|
+
return "neq";
|
|
261
|
+
case ts.SyntaxKind.EqualsEqualsEqualsToken:
|
|
262
|
+
return "seq";
|
|
263
|
+
case ts.SyntaxKind.ExclamationEqualsEqualsToken:
|
|
264
|
+
return "sneq";
|
|
265
|
+
case ts.SyntaxKind.LessThanToken:
|
|
266
|
+
return "lt";
|
|
267
|
+
case ts.SyntaxKind.LessThanEqualsToken:
|
|
268
|
+
return "lte";
|
|
269
|
+
case ts.SyntaxKind.GreaterThanToken:
|
|
270
|
+
return "gt";
|
|
271
|
+
case ts.SyntaxKind.GreaterThanEqualsToken:
|
|
272
|
+
return "gte";
|
|
273
|
+
default:
|
|
274
|
+
return void 0;
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
var isNumericBinaryOp = (op) => op === "add" || op === "sub" || op === "mul" || op === "div" || op === "mod";
|
|
278
|
+
var unwrapParens = (node) => {
|
|
279
|
+
while (ts.isParenthesizedExpression(node)) node = node.expression;
|
|
280
|
+
return node;
|
|
281
|
+
};
|
|
282
|
+
var numericLiteralValue = (node) => {
|
|
283
|
+
const value = Number(node.text);
|
|
284
|
+
return { type: "number", value };
|
|
285
|
+
};
|
|
286
|
+
var analyzeExpression = (ctx, node) => {
|
|
287
|
+
if (ctx.fallback) return fallback(ctx, ctx.fallback);
|
|
288
|
+
if (ts.isParenthesizedExpression(node)) return analyzeExpression(ctx, node.expression);
|
|
289
|
+
if (ts.isAsExpression(node) || ts.isNonNullExpression(node)) {
|
|
290
|
+
return analyzeExpression(ctx, node.expression);
|
|
291
|
+
}
|
|
292
|
+
if (node.kind === ts.SyntaxKind.TypeAssertionExpression) {
|
|
293
|
+
return analyzeExpression(ctx, node.expression);
|
|
294
|
+
}
|
|
295
|
+
if (ts.isSatisfiesExpression(node)) return analyzeExpression(ctx, node.expression);
|
|
296
|
+
if (node.kind === ts.SyntaxKind.NullKeyword) {
|
|
297
|
+
return { expressionId: ctx.ir.constValue({ type: "null" }), shape: "primitive" };
|
|
298
|
+
}
|
|
299
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword) {
|
|
300
|
+
return {
|
|
301
|
+
expressionId: ctx.ir.constValue({ type: "boolean", value: true }),
|
|
302
|
+
shape: "primitive"
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword) {
|
|
306
|
+
return {
|
|
307
|
+
expressionId: ctx.ir.constValue({ type: "boolean", value: false }),
|
|
308
|
+
shape: "primitive"
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
if (ts.isNumericLiteral(node)) {
|
|
312
|
+
return { expressionId: ctx.ir.constValue(numericLiteralValue(node)), shape: "primitive" };
|
|
313
|
+
}
|
|
314
|
+
if (ts.isPrefixUnaryExpression(node) && ts.isNumericLiteral(node.operand) && node.operator === ts.SyntaxKind.MinusToken) {
|
|
315
|
+
const value = -Number(node.operand.text);
|
|
316
|
+
return {
|
|
317
|
+
expressionId: ctx.ir.constValue(
|
|
318
|
+
Object.is(value, -0) ? { type: "number", value: 0, negativeZero: true } : { type: "number", value }
|
|
319
|
+
),
|
|
320
|
+
shape: "primitive"
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
324
|
+
return {
|
|
325
|
+
expressionId: ctx.ir.constValue({ type: "string", value: node.text }),
|
|
326
|
+
shape: "primitive"
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
if (ts.isBigIntLiteral(node)) {
|
|
330
|
+
return {
|
|
331
|
+
expressionId: ctx.ir.constValue({ type: "bigint", value: node.text.replace(/n$/, "") }),
|
|
332
|
+
shape: "primitive"
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
if (ts.isIdentifier(node)) {
|
|
336
|
+
if (node.text === "undefined") {
|
|
337
|
+
return { expressionId: ctx.ir.constValue({ type: "undefined" }), shape: "primitive" };
|
|
338
|
+
}
|
|
339
|
+
const binding = lookup(ctx, node.text);
|
|
340
|
+
if (!binding) return capture(ctx, node, "external_state");
|
|
341
|
+
if (binding.mutable) return capture(ctx, node, "mutable_object");
|
|
342
|
+
return {
|
|
343
|
+
expressionId: ctx.ir.alias(binding.expressionId),
|
|
344
|
+
shape: binding.shape
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
if (ts.isTemplateExpression(node)) {
|
|
348
|
+
const parts = [
|
|
349
|
+
{ kind: "lit", value: node.head.text }
|
|
350
|
+
];
|
|
351
|
+
for (const span of node.templateSpans) {
|
|
352
|
+
const nested = analyzeExpression(ctx, span.expression);
|
|
353
|
+
if (nested.shape !== "primitive" && !nested.captured) {
|
|
354
|
+
return capture(ctx, node, "unsupported_expression");
|
|
355
|
+
}
|
|
356
|
+
parts.push({ kind: "expr", expressionId: nested.expressionId });
|
|
357
|
+
parts.push({ kind: "lit", value: span.literal.text });
|
|
358
|
+
}
|
|
359
|
+
return { expressionId: ctx.ir.template(parts), shape: "primitive" };
|
|
360
|
+
}
|
|
361
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
362
|
+
if (node.elements.some((element) => ts.isSpreadElement(element))) {
|
|
363
|
+
return capture(ctx, node, "unsupported_expression");
|
|
364
|
+
}
|
|
365
|
+
const elements = node.elements.map((element) => {
|
|
366
|
+
if (ts.isOmittedExpression(element)) return { kind: "hole" };
|
|
367
|
+
return analyzeExpression(ctx, element).expressionId;
|
|
368
|
+
});
|
|
369
|
+
return { expressionId: ctx.ir.array(elements), shape: "array" };
|
|
370
|
+
}
|
|
371
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
372
|
+
const properties = [];
|
|
373
|
+
for (const property of node.properties) {
|
|
374
|
+
if (ts.isShorthandPropertyAssignment(property)) {
|
|
375
|
+
const nested = analyzeExpression(ctx, property.name);
|
|
376
|
+
if (nested.captured) {
|
|
377
|
+
const wrap = ctx.captureWraps.find(
|
|
378
|
+
(candidate) => candidate.expressionId === nested.expressionId
|
|
379
|
+
);
|
|
380
|
+
if (wrap) wrap.shorthandPropertyName = property.name.text;
|
|
381
|
+
}
|
|
382
|
+
properties.push({ key: property.name.text, value: nested.expressionId });
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (!ts.isPropertyAssignment(property)) {
|
|
386
|
+
return capture(ctx, node, "unsupported_expression");
|
|
387
|
+
}
|
|
388
|
+
if (ts.isPrivateIdentifier(property.name) || ts.isComputedPropertyName(property.name)) {
|
|
389
|
+
return capture(ctx, node, "unsupported_expression");
|
|
390
|
+
}
|
|
391
|
+
const key = ts.isIdentifier(property.name) ? property.name.text : ts.isStringLiteral(property.name) || ts.isNumericLiteral(property.name) ? property.name.text : void 0;
|
|
392
|
+
if (key === void 0) return capture(ctx, node, "unsupported_expression");
|
|
393
|
+
properties.push({ key, value: analyzeExpression(ctx, property.initializer).expressionId });
|
|
394
|
+
}
|
|
395
|
+
return { expressionId: ctx.ir.object(properties), shape: "object" };
|
|
396
|
+
}
|
|
397
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
398
|
+
if (node.questionDotToken) return capture(ctx, node, "unknown_property");
|
|
399
|
+
const object = analyzeExpression(ctx, node.expression);
|
|
400
|
+
if (object.shape === "object" && ts.isIdentifier(node.name)) {
|
|
401
|
+
return { expressionId: ctx.ir.prop(object.expressionId, node.name.text), shape: "unknown" };
|
|
402
|
+
}
|
|
403
|
+
return capture(ctx, node, "unknown_property");
|
|
404
|
+
}
|
|
405
|
+
if (ts.isElementAccessExpression(node)) {
|
|
406
|
+
if (node.questionDotToken) return capture(ctx, node, "unknown_property");
|
|
407
|
+
const array = analyzeExpression(ctx, node.expression);
|
|
408
|
+
if (array.shape === "array" && ts.isNumericLiteral(node.argumentExpression)) {
|
|
409
|
+
return {
|
|
410
|
+
expressionId: ctx.ir.index(array.expressionId, Number(node.argumentExpression.text)),
|
|
411
|
+
shape: "unknown"
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
return capture(ctx, node, "unknown_property");
|
|
415
|
+
}
|
|
416
|
+
if (ts.isCallExpression(node)) {
|
|
417
|
+
if (node.questionDotToken || node.typeArguments?.length) {
|
|
418
|
+
return capture(ctx, node, "unsupported_expression");
|
|
419
|
+
}
|
|
420
|
+
if (!ts.isIdentifier(node.expression)) return capture(ctx, node, "dynamic_callee");
|
|
421
|
+
const calleeName = node.expression.text;
|
|
422
|
+
if (lookup(ctx, calleeName)) return capture(ctx, node, "opaque_call");
|
|
423
|
+
const provenId = ctx.moduleCallees.get(calleeName);
|
|
424
|
+
ctx.callEdges.push({
|
|
425
|
+
callerFunctionId: ctx.functionId,
|
|
426
|
+
calleeName,
|
|
427
|
+
...provenId ? { calleeFunctionId: provenId } : {}
|
|
428
|
+
});
|
|
429
|
+
if (!provenId) return capture(ctx, node, "opaque_call");
|
|
430
|
+
const args = node.arguments.map((argument) => {
|
|
431
|
+
if (ts.isSpreadElement(argument)) return void 0;
|
|
432
|
+
return analyzeExpression(ctx, argument).expressionId;
|
|
433
|
+
});
|
|
434
|
+
if (args.some((argument) => argument === void 0))
|
|
435
|
+
return capture(ctx, node, "unsupported_expression");
|
|
436
|
+
return {
|
|
437
|
+
expressionId: ctx.ir.pureCall(
|
|
438
|
+
provenId,
|
|
439
|
+
args.filter((id) => id !== void 0)
|
|
440
|
+
),
|
|
441
|
+
shape: "unknown"
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
if (ts.isNewExpression(node) || ts.isAwaitExpression(node) || ts.isYieldExpression(node)) {
|
|
445
|
+
return capture(
|
|
446
|
+
ctx,
|
|
447
|
+
node,
|
|
448
|
+
node.kind === ts.SyntaxKind.AwaitExpression ? "nondeterministic" : "unsupported_expression"
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
if (ts.isConditionalExpression(node)) {
|
|
452
|
+
const test = analyzeExpression(ctx, node.condition);
|
|
453
|
+
const consequent = analyzeExpression(ctx, node.whenTrue);
|
|
454
|
+
const alternate = analyzeExpression(ctx, node.whenFalse);
|
|
455
|
+
return {
|
|
456
|
+
expressionId: ctx.ir.cond(test.expressionId, consequent.expressionId, alternate.expressionId),
|
|
457
|
+
shape: consequent.shape === alternate.shape ? consequent.shape : "unknown"
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
if (ts.isBinaryExpression(node)) {
|
|
461
|
+
if (node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
|
|
462
|
+
ctx.fallback ??= "mutable_state";
|
|
463
|
+
return capture(ctx, node, "mutable_object");
|
|
464
|
+
}
|
|
465
|
+
if (node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === ts.SyntaxKind.BarBarToken || node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
|
|
466
|
+
const left2 = analyzeExpression(ctx, node.left);
|
|
467
|
+
const right2 = analyzeExpression(ctx, node.right);
|
|
468
|
+
const op2 = node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ? "and" : node.operatorToken.kind === ts.SyntaxKind.BarBarToken ? "or" : "nullish";
|
|
469
|
+
return {
|
|
470
|
+
expressionId: ctx.ir.logical(op2, left2.expressionId, right2.expressionId),
|
|
471
|
+
shape: "unknown"
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
const op = binaryOp(node.operatorToken.kind);
|
|
475
|
+
if (!op) return capture(ctx, node, "unsupported_expression");
|
|
476
|
+
if (!ts.isExpression(node.left)) return capture(ctx, node, "unsupported_expression");
|
|
477
|
+
const left = analyzeExpression(ctx, node.left);
|
|
478
|
+
const right = analyzeExpression(ctx, node.right);
|
|
479
|
+
const numeric = isNumericBinaryOp(op);
|
|
480
|
+
const expressionId = ctx.ir.binary(
|
|
481
|
+
op,
|
|
482
|
+
left.expressionId,
|
|
483
|
+
right.expressionId,
|
|
484
|
+
numeric ? "numeric" : "primitive"
|
|
485
|
+
);
|
|
486
|
+
if (numeric && (left.captured || right.captured || left.shape !== "primitive" || right.shape !== "primitive")) {
|
|
487
|
+
ctx.hasCoercionGuard = true;
|
|
488
|
+
const fallbackRootId = ctx.nextRootId.value++;
|
|
489
|
+
ctx.guardWraps.push({
|
|
490
|
+
fallbackRootId,
|
|
491
|
+
start: node.getStart(ctx.sourceFile),
|
|
492
|
+
end: node.end,
|
|
493
|
+
op
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
return { expressionId, shape: "primitive" };
|
|
497
|
+
}
|
|
498
|
+
if (ts.isPrefixUnaryExpression(node)) {
|
|
499
|
+
const operand = analyzeExpression(ctx, node.operand);
|
|
500
|
+
if (node.operator === ts.SyntaxKind.ExclamationToken) {
|
|
501
|
+
return { expressionId: ctx.ir.unary("not", operand.expressionId), shape: "primitive" };
|
|
502
|
+
}
|
|
503
|
+
if (node.operator === ts.SyntaxKind.PlusToken || node.operator === ts.SyntaxKind.MinusToken) {
|
|
504
|
+
if (operand.captured || operand.shape !== "primitive") ctx.hasCoercionGuard = true;
|
|
505
|
+
return {
|
|
506
|
+
expressionId: ctx.ir.unary(
|
|
507
|
+
node.operator === ts.SyntaxKind.MinusToken ? "minus" : "plus",
|
|
508
|
+
operand.expressionId
|
|
509
|
+
),
|
|
510
|
+
shape: "primitive"
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
return capture(ctx, node, "unsupported_expression");
|
|
514
|
+
}
|
|
515
|
+
if (ts.isTypeOfExpression(node)) {
|
|
516
|
+
const operand = unwrapParens(node.expression);
|
|
517
|
+
if (ts.isIdentifier(operand) && operand.text !== "undefined" && !lookup(ctx, operand.text)) {
|
|
518
|
+
return capture(ctx, node, "external_state");
|
|
519
|
+
}
|
|
520
|
+
const inner = analyzeExpression(ctx, node.expression);
|
|
521
|
+
return { expressionId: ctx.ir.unary("typeof", inner.expressionId), shape: "primitive" };
|
|
522
|
+
}
|
|
523
|
+
return capture(ctx, node, "unsupported_expression");
|
|
524
|
+
};
|
|
525
|
+
var bindIdentifier = (ctx, name, result, declaration) => {
|
|
526
|
+
const current = ctx.scopes[ctx.scopes.length - 1];
|
|
527
|
+
current?.set(name.text, {
|
|
528
|
+
expressionId: result.expressionId,
|
|
529
|
+
shape: result.shape,
|
|
530
|
+
mutable: false
|
|
531
|
+
});
|
|
532
|
+
const position = loc(name, ctx.sourceFile);
|
|
533
|
+
const available = loc(declaration, ctx.sourceFile);
|
|
534
|
+
ctx.variables.push({
|
|
535
|
+
id: ctx.nextVariableId.value++,
|
|
536
|
+
name: name.text,
|
|
537
|
+
functionId: ctx.functionId,
|
|
538
|
+
scopeId: `${ctx.functionId}:${ctx.scopes.length}`,
|
|
539
|
+
line: position.line,
|
|
540
|
+
column: position.column,
|
|
541
|
+
assignmentId: ctx.nextAssignmentId.value++,
|
|
542
|
+
expressionId: result.expressionId,
|
|
543
|
+
availableAtLine: available.line,
|
|
544
|
+
availableAtColumn: available.column
|
|
545
|
+
});
|
|
546
|
+
};
|
|
547
|
+
var analyzeStatement = (ctx, node) => {
|
|
548
|
+
if (ctx.fallback) return;
|
|
549
|
+
if (ts.isEmptyStatement(node) || ts.isDebuggerStatement(node)) return;
|
|
550
|
+
if (ts.isBlock(node)) {
|
|
551
|
+
ctx.scopes.push(/* @__PURE__ */ new Map());
|
|
552
|
+
for (const statement of node.statements) analyzeStatement(ctx, statement);
|
|
553
|
+
ctx.scopes.pop();
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
if (ts.isReturnStatement(node)) {
|
|
557
|
+
if (!node.expression) {
|
|
558
|
+
ctx.returnExpressionId = ctx.ir.constValue({ type: "undefined" });
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
ctx.returnExpressionId = analyzeExpression(ctx, node.expression).expressionId;
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
if (ts.isVariableStatement(node)) {
|
|
565
|
+
if (node.declarationList.flags & ts.NodeFlags.Using) {
|
|
566
|
+
fallback(ctx, "side_effect");
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
for (const declaration of node.declarationList.declarations) {
|
|
570
|
+
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) {
|
|
571
|
+
fallback(ctx, "unsupported_expression");
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
const result = analyzeExpression(ctx, declaration.initializer);
|
|
575
|
+
bindIdentifier(ctx, declaration.name, result, declaration);
|
|
576
|
+
}
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (ts.isExpressionStatement(node)) {
|
|
580
|
+
analyzeExpression(ctx, node.expression);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
if (ts.isThrowStatement(node)) {
|
|
584
|
+
ctx.hasThrow = true;
|
|
585
|
+
analyzeExpression(ctx, node.expression);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (ts.isIfStatement(node) || ts.isSwitchStatement(node) || ts.isTryStatement(node)) {
|
|
589
|
+
fallback(ctx, "unsupported_expression");
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if (ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node) || ts.isWhileStatement(node) || ts.isDoStatement(node)) {
|
|
593
|
+
fallback(ctx, "has_loop");
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
fallback(ctx, "unsupported_expression");
|
|
597
|
+
};
|
|
598
|
+
var bindParams = (ctx, node) => {
|
|
599
|
+
let index = 0;
|
|
600
|
+
for (const parameter of node.parameters) {
|
|
601
|
+
if (ts.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
|
|
602
|
+
if (!ts.isIdentifier(parameter.name) || parameter.dotDotDotToken) {
|
|
603
|
+
fallback(ctx, "unsupported_expression");
|
|
604
|
+
index++;
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
const expressionId = ctx.ir.param(index);
|
|
608
|
+
ctx.identifierParams.push(parameter.name.text);
|
|
609
|
+
bindIdentifier(ctx, parameter.name, { expressionId, shape: "unknown" }, parameter);
|
|
610
|
+
index++;
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
var analyzeOne = (node, functionId, name, sourceFile, moduleCallees) => {
|
|
614
|
+
const ctx = {
|
|
615
|
+
sourceFile,
|
|
616
|
+
functionId,
|
|
617
|
+
name,
|
|
618
|
+
ir: new DerivationIrBuilder(),
|
|
619
|
+
variables: [],
|
|
620
|
+
captureWraps: [],
|
|
621
|
+
guardWraps: [],
|
|
622
|
+
callEdges: [],
|
|
623
|
+
scopes: [/* @__PURE__ */ new Map()],
|
|
624
|
+
nextRootId: { value: 0 },
|
|
625
|
+
nextVariableId: { value: 0 },
|
|
626
|
+
nextAssignmentId: { value: 0 },
|
|
627
|
+
hasThrow: false,
|
|
628
|
+
hasCoercionGuard: false,
|
|
629
|
+
moduleCallees,
|
|
630
|
+
identifierParams: []
|
|
631
|
+
};
|
|
632
|
+
if (node.asteriskToken || node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)) {
|
|
633
|
+
ctx.fallback = "async";
|
|
634
|
+
} else {
|
|
635
|
+
bindParams(ctx, node);
|
|
636
|
+
if (ts.isBlock(node.body)) {
|
|
637
|
+
for (const statement of node.body.statements) analyzeStatement(ctx, statement);
|
|
638
|
+
} else {
|
|
639
|
+
ctx.returnExpressionId = analyzeExpression(ctx, node.body).expressionId;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
const plan = {
|
|
643
|
+
...emptyPlan(functionId),
|
|
644
|
+
expressions: ctx.ir.expressions,
|
|
645
|
+
variables: ctx.variables,
|
|
646
|
+
captureBoundaries: ctx.captureWraps.map((wrap) => ({
|
|
647
|
+
id: wrap.rootId,
|
|
648
|
+
reason: wrap.reason,
|
|
649
|
+
expressionId: wrap.expressionId,
|
|
650
|
+
line: wrap.line,
|
|
651
|
+
column: wrap.column
|
|
652
|
+
})),
|
|
653
|
+
controlFacts: [],
|
|
654
|
+
...ctx.returnExpressionId === void 0 ? {} : { returnExpressionId: ctx.returnExpressionId },
|
|
655
|
+
...ctx.fallback ? { fallbackReason: ctx.fallback } : {}
|
|
656
|
+
};
|
|
657
|
+
return {
|
|
658
|
+
functionId,
|
|
659
|
+
name,
|
|
660
|
+
node,
|
|
661
|
+
plan,
|
|
662
|
+
captureWraps: ctx.fallback ? [] : ctx.captureWraps,
|
|
663
|
+
guardWraps: ctx.fallback ? [] : ctx.guardWraps,
|
|
664
|
+
callEdges: ctx.callEdges,
|
|
665
|
+
hasThrow: ctx.hasThrow,
|
|
666
|
+
hasCoercionGuard: ctx.hasCoercionGuard
|
|
667
|
+
};
|
|
668
|
+
};
|
|
669
|
+
var analyzeModuleFunctions = (sourceFile, targets) => {
|
|
670
|
+
const nameCounts = /* @__PURE__ */ new Map();
|
|
671
|
+
for (const target of targets) {
|
|
672
|
+
nameCounts.set(target.name, (nameCounts.get(target.name) ?? 0) + 1);
|
|
673
|
+
}
|
|
674
|
+
const firstPass = targets.map(
|
|
675
|
+
(target) => analyzeOne(target.node, target.functionId, target.name, sourceFile, /* @__PURE__ */ new Map())
|
|
676
|
+
);
|
|
677
|
+
const graph = buildDependencyGraph(
|
|
678
|
+
firstPass.map((fn) => ({ functionId: fn.functionId, name: fn.name })),
|
|
679
|
+
firstPass.flatMap((fn) => fn.callEdges)
|
|
680
|
+
);
|
|
681
|
+
const order = topologicalFunctionOrder(
|
|
682
|
+
firstPass.map((fn) => ({ functionId: fn.functionId, name: fn.name })),
|
|
683
|
+
graph
|
|
684
|
+
);
|
|
685
|
+
const byId = new Map(targets.map((target) => [target.functionId, target]));
|
|
686
|
+
const moduleCallees = /* @__PURE__ */ new Map();
|
|
687
|
+
const analyzed = [];
|
|
688
|
+
for (const functionId of order) {
|
|
689
|
+
const target = byId.get(functionId);
|
|
690
|
+
if (!target) continue;
|
|
691
|
+
const result = analyzeOne(
|
|
692
|
+
target.node,
|
|
693
|
+
target.functionId,
|
|
694
|
+
target.name,
|
|
695
|
+
sourceFile,
|
|
696
|
+
moduleCallees
|
|
697
|
+
);
|
|
698
|
+
if (nameCounts.get(result.name) === 1) {
|
|
699
|
+
moduleCallees.set(result.name, result.functionId);
|
|
700
|
+
}
|
|
701
|
+
analyzed.push(result);
|
|
702
|
+
}
|
|
703
|
+
const remaining = targets.filter(
|
|
704
|
+
(target) => !analyzed.some((fn) => fn.functionId === target.functionId)
|
|
705
|
+
);
|
|
706
|
+
for (const target of remaining) {
|
|
707
|
+
analyzed.push(
|
|
708
|
+
analyzeOne(target.node, target.functionId, target.name, sourceFile, moduleCallees)
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
return analyzed;
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
// src/derivation/derivation-proof.ts
|
|
715
|
+
var functionIsExact = (plan) => plan.fallbackReason === void 0 && plan.captureBoundaries.length === 0 && plan.controlFacts.length === 0;
|
|
716
|
+
|
|
717
|
+
// src/derivation/emit-capture-plan.ts
|
|
718
|
+
var captureWrapEdits = (wraps, capName) => {
|
|
719
|
+
const edits = [];
|
|
720
|
+
let order = 0;
|
|
721
|
+
for (const wrap of wraps) {
|
|
722
|
+
edits.push({
|
|
723
|
+
position: wrap.start,
|
|
724
|
+
text: `${wrap.shorthandPropertyName ? `${wrap.shorthandPropertyName}:` : ""}${capName}(${wrap.rootId},`,
|
|
725
|
+
order: order++
|
|
726
|
+
});
|
|
727
|
+
edits.push({
|
|
728
|
+
position: wrap.end,
|
|
729
|
+
text: ")",
|
|
730
|
+
order: order++
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
return edits;
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
// src/execution-recorder/automatic-runtime.ts
|
|
737
|
+
var AUTOMATIC_RUNTIME_SYMBOL = "rasputin.execution.runtime.v2";
|
|
738
|
+
var runtimeSymbol = Symbol.for(AUTOMATIC_RUNTIME_SYMBOL);
|
|
739
|
+
|
|
740
|
+
// src/auto-instrumentation/collect-instrumentation-delta.ts
|
|
741
|
+
import ts2 from "typescript";
|
|
91
742
|
var paramBindingFromName = (name, sourceFile) => {
|
|
92
|
-
if (
|
|
93
|
-
if (
|
|
743
|
+
if (ts2.isIdentifier(name)) return { kind: "identifier", name: name.text };
|
|
744
|
+
if (ts2.isArrayBindingPattern(name)) {
|
|
94
745
|
const elements = name.elements.map(
|
|
95
746
|
(element) => {
|
|
96
|
-
if (
|
|
747
|
+
if (ts2.isOmittedExpression(element)) return { kind: "omitted" };
|
|
97
748
|
const binding = paramBindingFromName(element.name, sourceFile);
|
|
98
749
|
if (element.dotDotDotToken) return { kind: "rest", binding };
|
|
99
750
|
return binding;
|
|
@@ -103,7 +754,7 @@ var paramBindingFromName = (name, sourceFile) => {
|
|
|
103
754
|
}
|
|
104
755
|
const properties = name.elements.map((element) => {
|
|
105
756
|
const binding = paramBindingFromName(element.name, sourceFile);
|
|
106
|
-
const key = element.propertyName ? element.propertyName.getText(sourceFile).replace(/^['"]|['"]$/g, "") :
|
|
757
|
+
const key = element.propertyName ? element.propertyName.getText(sourceFile).replace(/^['"]|['"]$/g, "") : ts2.isIdentifier(element.name) ? element.name.text : element.name.getText(sourceFile);
|
|
107
758
|
if (element.dotDotDotToken) {
|
|
108
759
|
const rest = { kind: "rest", binding };
|
|
109
760
|
return { key, binding: rest };
|
|
@@ -115,7 +766,7 @@ var paramBindingFromName = (name, sourceFile) => {
|
|
|
115
766
|
var collectFunctionParams = (node, sourceFile) => {
|
|
116
767
|
const params = [];
|
|
117
768
|
for (const parameter of node.parameters) {
|
|
118
|
-
if (
|
|
769
|
+
if (ts2.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
|
|
119
770
|
const binding = paramBindingFromName(parameter.name, sourceFile);
|
|
120
771
|
params.push(parameter.dotDotDotToken ? { kind: "rest", binding } : binding);
|
|
121
772
|
}
|
|
@@ -156,18 +807,18 @@ var mappedLocation = (sourceFile, position, absolutePath, mapLocation) => {
|
|
|
156
807
|
};
|
|
157
808
|
var unwrapExpression = (expression) => {
|
|
158
809
|
let current = expression;
|
|
159
|
-
while (
|
|
810
|
+
while (ts2.isParenthesizedExpression(current) || ts2.isNonNullExpression(current)) {
|
|
160
811
|
current = current.expression;
|
|
161
812
|
}
|
|
162
|
-
if (
|
|
813
|
+
if (ts2.isAwaitExpression(current)) return unwrapExpression(current.expression);
|
|
163
814
|
return current;
|
|
164
815
|
};
|
|
165
816
|
var calleeFromExpression = (expression, sourceFile) => {
|
|
166
817
|
const current = unwrapExpression(expression);
|
|
167
|
-
if (
|
|
818
|
+
if (ts2.isIdentifier(current)) {
|
|
168
819
|
return { calleeName: current.text, calleeText: current.text };
|
|
169
820
|
}
|
|
170
|
-
if (
|
|
821
|
+
if (ts2.isPropertyAccessExpression(current) && ts2.isIdentifier(current.name)) {
|
|
171
822
|
return {
|
|
172
823
|
calleeName: current.name.text,
|
|
173
824
|
calleeText: current.getText(sourceFile).replace(/\s+/g, "")
|
|
@@ -177,9 +828,9 @@ var calleeFromExpression = (expression, sourceFile) => {
|
|
|
177
828
|
};
|
|
178
829
|
var resultBindingForCall = (call) => {
|
|
179
830
|
let node = call.parent;
|
|
180
|
-
if (
|
|
181
|
-
if (!
|
|
182
|
-
if (!
|
|
831
|
+
if (ts2.isAwaitExpression(node)) node = node.parent;
|
|
832
|
+
if (!ts2.isVariableDeclaration(node)) return null;
|
|
833
|
+
if (!ts2.isIdentifier(node.name)) return null;
|
|
183
834
|
const initializer = node.initializer;
|
|
184
835
|
if (!initializer) return null;
|
|
185
836
|
const inner = unwrapExpression(initializer);
|
|
@@ -189,18 +840,18 @@ var resultBindingForCall = (call) => {
|
|
|
189
840
|
var argIdentifiersForCall = (call) => {
|
|
190
841
|
const identifiers = [];
|
|
191
842
|
call.arguments?.forEach((argument, index) => {
|
|
192
|
-
if (
|
|
843
|
+
if (ts2.isIdentifier(argument)) {
|
|
193
844
|
identifiers.push({ index, kind: "identifier", name: argument.text });
|
|
194
845
|
return;
|
|
195
846
|
}
|
|
196
|
-
if (!
|
|
847
|
+
if (!ts2.isObjectLiteralExpression(argument)) return;
|
|
197
848
|
const properties = [];
|
|
198
849
|
for (const property of argument.properties) {
|
|
199
|
-
if (
|
|
850
|
+
if (ts2.isShorthandPropertyAssignment(property)) {
|
|
200
851
|
properties.push({ key: property.name.text, name: property.name.text });
|
|
201
852
|
continue;
|
|
202
853
|
}
|
|
203
|
-
if (
|
|
854
|
+
if (ts2.isPropertyAssignment(property) && ts2.isIdentifier(property.name) && ts2.isIdentifier(property.initializer)) {
|
|
204
855
|
properties.push({ key: property.name.text, name: property.initializer.text });
|
|
205
856
|
}
|
|
206
857
|
}
|
|
@@ -227,7 +878,7 @@ var collectCallSite = (call, caller, context) => {
|
|
|
227
878
|
);
|
|
228
879
|
if (!start || !end) return null;
|
|
229
880
|
return {
|
|
230
|
-
|
|
881
|
+
callerFunctionId: caller.functionId,
|
|
231
882
|
calleeName: callee.calleeName,
|
|
232
883
|
calleeText: callee.calleeText,
|
|
233
884
|
filePath: start.filePath,
|
|
@@ -240,8 +891,8 @@ var collectCallSite = (call, caller, context) => {
|
|
|
240
891
|
};
|
|
241
892
|
};
|
|
242
893
|
var collectParamDestructure = (declaration, caller, context) => {
|
|
243
|
-
if (!declaration.initializer || !
|
|
244
|
-
if (!
|
|
894
|
+
if (!declaration.initializer || !ts2.isIdentifier(declaration.initializer)) return null;
|
|
895
|
+
if (!ts2.isObjectBindingPattern(declaration.name) && !ts2.isArrayBindingPattern(declaration.name)) {
|
|
245
896
|
return null;
|
|
246
897
|
}
|
|
247
898
|
const sourceParam = declaration.initializer.text;
|
|
@@ -254,7 +905,7 @@ var collectParamDestructure = (declaration, caller, context) => {
|
|
|
254
905
|
);
|
|
255
906
|
if (!start) return null;
|
|
256
907
|
return {
|
|
257
|
-
|
|
908
|
+
callerFunctionId: caller.functionId,
|
|
258
909
|
sourceParam,
|
|
259
910
|
filePath: start.filePath,
|
|
260
911
|
line: start.line,
|
|
@@ -265,33 +916,33 @@ var collectParamDestructure = (declaration, caller, context) => {
|
|
|
265
916
|
|
|
266
917
|
// src/auto-instrumentation/transform-source.ts
|
|
267
918
|
var scriptKindFor = (filePath) => {
|
|
268
|
-
if (/\.tsx$/i.test(filePath)) return
|
|
269
|
-
if (/\.jsx$/i.test(filePath)) return
|
|
270
|
-
if (/\.[cm]?ts$/i.test(filePath)) return
|
|
271
|
-
return
|
|
919
|
+
if (/\.tsx$/i.test(filePath)) return ts3.ScriptKind.TSX;
|
|
920
|
+
if (/\.jsx$/i.test(filePath)) return ts3.ScriptKind.JSX;
|
|
921
|
+
if (/\.[cm]?ts$/i.test(filePath)) return ts3.ScriptKind.TS;
|
|
922
|
+
return ts3.ScriptKind.JS;
|
|
272
923
|
};
|
|
273
924
|
var functionName = (node, sourceFile) => {
|
|
274
925
|
if (node.name) return node.name.getText(sourceFile).replace(/^['"]|['"]$/g, "");
|
|
275
926
|
const parent = node.parent;
|
|
276
|
-
if (
|
|
277
|
-
if (
|
|
278
|
-
if (
|
|
927
|
+
if (ts3.isVariableDeclaration(parent) && ts3.isIdentifier(parent.name)) return parent.name.text;
|
|
928
|
+
if (ts3.isPropertyAssignment(parent)) return parent.name.getText(sourceFile);
|
|
929
|
+
if (ts3.isPropertyDeclaration(parent) && parent.name) return parent.name.getText(sourceFile);
|
|
279
930
|
const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
280
931
|
return `anonymous@${location.line + 1}:${location.character + 1}`;
|
|
281
932
|
};
|
|
282
933
|
var functionLocationPosition = (node, sourceFile) => {
|
|
283
934
|
if (node.name) return node.name.getStart(sourceFile);
|
|
284
935
|
const parent = node.parent;
|
|
285
|
-
if ((
|
|
936
|
+
if ((ts3.isVariableDeclaration(parent) || ts3.isPropertyAssignment(parent)) && parent.name) {
|
|
286
937
|
return parent.name.getStart(sourceFile);
|
|
287
938
|
}
|
|
288
939
|
return node.getStart(sourceFile);
|
|
289
940
|
};
|
|
290
941
|
var bindingValue = (name, sourceFile) => {
|
|
291
|
-
if (
|
|
292
|
-
if (
|
|
942
|
+
if (ts3.isIdentifier(name)) return name.text;
|
|
943
|
+
if (ts3.isArrayBindingPattern(name)) {
|
|
293
944
|
const values = name.elements.map((element) => {
|
|
294
|
-
if (
|
|
945
|
+
if (ts3.isOmittedExpression(element)) return "";
|
|
295
946
|
const value = bindingValue(element.name, sourceFile);
|
|
296
947
|
return element.dotDotDotToken ? `...${value}` : value;
|
|
297
948
|
});
|
|
@@ -307,20 +958,40 @@ var bindingValue = (name, sourceFile) => {
|
|
|
307
958
|
};
|
|
308
959
|
var arrowArguments = (node) => {
|
|
309
960
|
const values = node.parameters.flatMap((parameter) => {
|
|
310
|
-
if (
|
|
961
|
+
if (ts3.isIdentifier(parameter.name) && parameter.name.text === "this") return [];
|
|
311
962
|
const value = bindingValue(parameter.name, node.getSourceFile());
|
|
312
963
|
return [parameter.dotDotDotToken ? `...${value}` : value];
|
|
313
964
|
});
|
|
314
965
|
return `[${values.join(",")}]`;
|
|
315
966
|
};
|
|
316
967
|
var isSupportedFunction = (node) => {
|
|
317
|
-
if (!
|
|
968
|
+
if (!ts3.isFunctionDeclaration(node) && !ts3.isFunctionExpression(node) && !ts3.isArrowFunction(node) && !ts3.isMethodDeclaration(node) && !ts3.isGetAccessorDeclaration(node) && !ts3.isSetAccessorDeclaration(node)) {
|
|
318
969
|
return false;
|
|
319
970
|
}
|
|
320
971
|
if (!node.body || "asteriskToken" in node && node.asteriskToken) return false;
|
|
321
972
|
return true;
|
|
322
973
|
};
|
|
323
|
-
var
|
|
974
|
+
var uniqueName = (source, base) => {
|
|
975
|
+
let name = base;
|
|
976
|
+
while (source.includes(name)) name += "_";
|
|
977
|
+
return name;
|
|
978
|
+
};
|
|
979
|
+
var isDirectivePrologue = (statement) => ts3.isExpressionStatement(statement) && ts3.isStringLiteral(statement.expression);
|
|
980
|
+
var insertPositionAfterPrologue = (source, sourceFile) => {
|
|
981
|
+
const shebangEnd = source.startsWith("#!") ? source.indexOf("\n") + 1 : 0;
|
|
982
|
+
let end = shebangEnd;
|
|
983
|
+
for (const statement of sourceFile.statements) {
|
|
984
|
+
if (!isDirectivePrologue(statement)) break;
|
|
985
|
+
end = statement.end;
|
|
986
|
+
}
|
|
987
|
+
return end;
|
|
988
|
+
};
|
|
989
|
+
var preambleInsert = (source, position, preamble) => {
|
|
990
|
+
if (position <= 0) return preamble;
|
|
991
|
+
const previous = source[position - 1];
|
|
992
|
+
return previous === ";" || previous === "\n" || previous === "\r" ? preamble : `;${preamble}`;
|
|
993
|
+
};
|
|
994
|
+
var modulePreamble = (runName, capName, plansName, plansJson) => `const ${plansName}=${plansJson};const ${runName}=((g,k)=>{const r=g[k]??(g[k]={runtimes:[],modules:[],captureRoot(i,v){return v},register(p){const d={run:(i,a,c)=>c()};const m={plans:p,cell:d};this.modules.push(m);const n=this.runtimes.at(-1);if(n)n.bind(m);return(i,a,c,l)=>d.run(i,a,c,l)}});return r.register(${plansName})})(globalThis,Symbol.for(${JSON.stringify(AUTOMATIC_RUNTIME_SYMBOL)}));const ${capName}=((g,k)=>{const r=g[k];return(i,v)=>typeof r?.captureRoot==='function'?r.captureRoot(i,v):v})(globalThis,Symbol.for(${JSON.stringify(AUTOMATIC_RUNTIME_SYMBOL)}));`;
|
|
324
995
|
var collectWrappedFunction = (node, sourceFile, absolutePath, mapLocation) => {
|
|
325
996
|
const start = mappedLocation(
|
|
326
997
|
sourceFile,
|
|
@@ -332,13 +1003,15 @@ var collectWrappedFunction = (node, sourceFile, absolutePath, mapLocation) => {
|
|
|
332
1003
|
const end = mappedLocation(sourceFile, node.end, absolutePath, mapLocation) ?? start;
|
|
333
1004
|
const name = functionName(node, sourceFile).replace(/@\d+:\d+$/, "");
|
|
334
1005
|
const { params, paramNames } = collectFunctionParams(node, sourceFile);
|
|
1006
|
+
const plan = encodeFunctionPlan({
|
|
1007
|
+
filePath: start.filePath,
|
|
1008
|
+
name,
|
|
1009
|
+
line: start.line,
|
|
1010
|
+
column: start.column
|
|
1011
|
+
});
|
|
335
1012
|
return {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
name,
|
|
339
|
-
line: start.line,
|
|
340
|
-
column: start.column
|
|
341
|
-
}),
|
|
1013
|
+
plan,
|
|
1014
|
+
functionId: plan[0],
|
|
342
1015
|
filePath: start.filePath,
|
|
343
1016
|
name,
|
|
344
1017
|
startLine: start.line,
|
|
@@ -349,74 +1022,130 @@ var collectWrappedFunction = (node, sourceFile, absolutePath, mapLocation) => {
|
|
|
349
1022
|
paramNames
|
|
350
1023
|
};
|
|
351
1024
|
};
|
|
1025
|
+
var canOmitCallValues = (analysis, analyzed) => {
|
|
1026
|
+
if (!functionIsExact(analysis.plan) || analysis.hasThrow || analysis.hasCoercionGuard) {
|
|
1027
|
+
return false;
|
|
1028
|
+
}
|
|
1029
|
+
const callers = analyzed.filter(
|
|
1030
|
+
(caller) => caller.callEdges.some((edge) => edge.calleeFunctionId === analysis.functionId)
|
|
1031
|
+
);
|
|
1032
|
+
if (callers.length === 0) return false;
|
|
1033
|
+
return callers.every((caller) => caller.plan.fallbackReason === void 0 && !caller.hasThrow);
|
|
1034
|
+
};
|
|
352
1035
|
var transformSource = (source, options) => {
|
|
353
1036
|
if (source.includes("@rasputin-ignore-file")) return void 0;
|
|
354
|
-
const sourceFile =
|
|
1037
|
+
const sourceFile = ts3.createSourceFile(
|
|
355
1038
|
options.filePath,
|
|
356
1039
|
source,
|
|
357
|
-
|
|
1040
|
+
ts3.ScriptTarget.Latest,
|
|
358
1041
|
true,
|
|
359
1042
|
scriptKindFor(options.filePath)
|
|
360
1043
|
);
|
|
361
|
-
|
|
362
|
-
|
|
1044
|
+
const runName = uniqueName(source, "__rasputinRun__");
|
|
1045
|
+
const capName = uniqueName(source, "__rasputinCap__");
|
|
1046
|
+
const plansName = uniqueName(source, "__rasputinPlans__");
|
|
363
1047
|
const absolutePath = resolve3(options.filePath);
|
|
1048
|
+
const derivationMode = options.derivationMode ?? "analysis";
|
|
364
1049
|
const edits = [];
|
|
365
1050
|
let order = 0;
|
|
366
1051
|
const delta = {
|
|
367
1052
|
functions: [],
|
|
368
1053
|
callSites: [],
|
|
369
|
-
destructures: []
|
|
1054
|
+
destructures: [],
|
|
1055
|
+
derivationPlans: []
|
|
370
1056
|
};
|
|
371
1057
|
const collectContext = {
|
|
372
1058
|
sourceFile,
|
|
373
1059
|
mapLocation: options.mapLocation,
|
|
374
1060
|
absolutePath
|
|
375
1061
|
};
|
|
1062
|
+
const pending = [];
|
|
376
1063
|
const visit = (node, caller) => {
|
|
377
1064
|
if (isSupportedFunction(node)) {
|
|
378
1065
|
const collected = collectWrappedFunction(node, sourceFile, absolutePath, options.mapLocation);
|
|
379
1066
|
if (!collected) {
|
|
380
|
-
|
|
1067
|
+
ts3.forEachChild(node, (child) => visit(child, caller));
|
|
381
1068
|
return;
|
|
382
1069
|
}
|
|
1070
|
+
pending.push({ node, collected });
|
|
383
1071
|
delta.functions.push(collected);
|
|
384
|
-
|
|
385
|
-
const asyncCallback = node.modifiers?.some(
|
|
386
|
-
(modifier) => modifier.kind === ts2.SyntaxKind.AsyncKeyword
|
|
387
|
-
) ? "async " : "";
|
|
388
|
-
if (ts2.isBlock(node.body)) {
|
|
389
|
-
edits.push({
|
|
390
|
-
position: node.body.getStart(sourceFile) + 1,
|
|
391
|
-
text: `return ${helperName}(${JSON.stringify(collected.sourceToken)},${args},${asyncCallback}()=>{`,
|
|
392
|
-
order: order++
|
|
393
|
-
});
|
|
394
|
-
edits.push({ position: node.body.end - 1, text: "});", order: order++ });
|
|
395
|
-
} else {
|
|
396
|
-
edits.push({
|
|
397
|
-
position: node.body.getStart(sourceFile),
|
|
398
|
-
text: `${helperName}(${JSON.stringify(collected.sourceToken)},${args},${asyncCallback}()=>(`,
|
|
399
|
-
order: order++
|
|
400
|
-
});
|
|
401
|
-
edits.push({ position: node.body.end, text: "))", order: order++ });
|
|
402
|
-
}
|
|
403
|
-
ts2.forEachChild(node, (child) => visit(child, collected));
|
|
1072
|
+
ts3.forEachChild(node, (child) => visit(child, collected));
|
|
404
1073
|
return;
|
|
405
1074
|
}
|
|
406
|
-
if (caller && (
|
|
1075
|
+
if (caller && (ts3.isCallExpression(node) || ts3.isNewExpression(node))) {
|
|
407
1076
|
const site = collectCallSite(node, caller, collectContext);
|
|
408
1077
|
if (site) delta.callSites.push(site);
|
|
409
1078
|
}
|
|
410
|
-
if (caller &&
|
|
1079
|
+
if (caller && ts3.isVariableDeclaration(node)) {
|
|
411
1080
|
const destructure = collectParamDestructure(node, caller, collectContext);
|
|
412
1081
|
if (destructure) delta.destructures.push(destructure);
|
|
413
1082
|
}
|
|
414
|
-
|
|
1083
|
+
ts3.forEachChild(node, (child) => visit(child, caller));
|
|
415
1084
|
};
|
|
416
1085
|
visit(sourceFile);
|
|
417
|
-
if (
|
|
418
|
-
const
|
|
419
|
-
|
|
1086
|
+
if (pending.length === 0) return void 0;
|
|
1087
|
+
const analyzed = analyzeModuleFunctions(
|
|
1088
|
+
sourceFile,
|
|
1089
|
+
pending.map((entry) => ({
|
|
1090
|
+
functionId: entry.collected.functionId,
|
|
1091
|
+
name: entry.collected.name,
|
|
1092
|
+
node: entry.node
|
|
1093
|
+
}))
|
|
1094
|
+
);
|
|
1095
|
+
delta.derivationPlans = analyzed.map((fn) => fn.plan);
|
|
1096
|
+
const analyzedById = new Map(analyzed.map((fn) => [fn.functionId, fn]));
|
|
1097
|
+
for (const [planIndex, entry] of pending.entries()) {
|
|
1098
|
+
const analysis = analyzedById.get(entry.collected.functionId);
|
|
1099
|
+
const omitValues = derivationMode === "selective" && analysis && canOmitCallValues(analysis, analyzed);
|
|
1100
|
+
const valueFlag = omitValues ? ",1" : "";
|
|
1101
|
+
const args = ts3.isArrowFunction(entry.node) ? arrowArguments(entry.node) : "arguments";
|
|
1102
|
+
const asyncCallback = entry.node.modifiers?.some(
|
|
1103
|
+
(modifier) => modifier.kind === ts3.SyntaxKind.AsyncKeyword
|
|
1104
|
+
) ? "async " : "";
|
|
1105
|
+
if (ts3.isBlock(entry.node.body)) {
|
|
1106
|
+
edits.push({
|
|
1107
|
+
position: entry.node.body.getStart(sourceFile) + 1,
|
|
1108
|
+
text: `return ${runName}(${planIndex},${args},${asyncCallback}()=>{`,
|
|
1109
|
+
order: order++
|
|
1110
|
+
});
|
|
1111
|
+
edits.push({
|
|
1112
|
+
position: entry.node.body.end - 1,
|
|
1113
|
+
text: `}${valueFlag});`,
|
|
1114
|
+
order: order++
|
|
1115
|
+
});
|
|
1116
|
+
} else {
|
|
1117
|
+
edits.push({
|
|
1118
|
+
position: entry.node.body.getStart(sourceFile),
|
|
1119
|
+
text: `${runName}(${planIndex},${args},${asyncCallback}()=>(`,
|
|
1120
|
+
order: order++
|
|
1121
|
+
});
|
|
1122
|
+
edits.push({
|
|
1123
|
+
position: entry.node.body.end,
|
|
1124
|
+
text: `)${valueFlag})`,
|
|
1125
|
+
order: order++
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
if (analysis) {
|
|
1129
|
+
for (const edit of captureWrapEdits(analysis.captureWraps, capName)) {
|
|
1130
|
+
edits.push({ ...edit, order: order++ });
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
const insertAt = insertPositionAfterPrologue(source, sourceFile);
|
|
1135
|
+
edits.push({
|
|
1136
|
+
position: insertAt,
|
|
1137
|
+
text: preambleInsert(
|
|
1138
|
+
source,
|
|
1139
|
+
insertAt,
|
|
1140
|
+
modulePreamble(
|
|
1141
|
+
runName,
|
|
1142
|
+
capName,
|
|
1143
|
+
plansName,
|
|
1144
|
+
JSON.stringify(delta.functions.map((fn) => fn.plan))
|
|
1145
|
+
)
|
|
1146
|
+
),
|
|
1147
|
+
order: -1
|
|
1148
|
+
});
|
|
420
1149
|
edits.sort((left, right) => right.position - left.position || right.order - left.order);
|
|
421
1150
|
let transformed = source;
|
|
422
1151
|
for (const edit of edits) {
|
|
@@ -481,7 +1210,21 @@ import { dirname, resolve as resolve5 } from "node:path";
|
|
|
481
1210
|
import { fileURLToPath } from "node:url";
|
|
482
1211
|
var sdkPackageScopeRootFrom = (instrumentEntryUrl) => resolve5(dirname(fileURLToPath(instrumentEntryUrl)), "../../..");
|
|
483
1212
|
|
|
1213
|
+
// src/auto-instrumentation/warn-production-runtime-transform.ts
|
|
1214
|
+
var RUNTIME_TRANSFORM_WARNING_SUPPRESS_ENV = "RASPUTIN_SUPPRESS_RUNTIME_TRANSFORM_WARNING";
|
|
1215
|
+
var warnedSymbol = Symbol.for("rasputin.runtimeTransformWarning");
|
|
1216
|
+
var warnIfProductionRuntimeTransform = () => {
|
|
1217
|
+
if (process.env.NODE_ENV !== "production") return;
|
|
1218
|
+
if (process.env[RUNTIME_TRANSFORM_WARNING_SUPPRESS_ENV] === "1") return;
|
|
1219
|
+
if (Reflect.get(globalThis, warnedSymbol)) return;
|
|
1220
|
+
Reflect.set(globalThis, warnedSymbol, true);
|
|
1221
|
+
console.warn(
|
|
1222
|
+
"[rasputin] Production is using load-time source transformation (`--import` / `--preload`). Prefer build-time `rasputin-instrument` so the process does not load the TypeScript compiler. Set RASPUTIN_SUPPRESS_RUNTIME_TRANSFORM_WARNING=1 to hide this warning."
|
|
1223
|
+
);
|
|
1224
|
+
};
|
|
1225
|
+
|
|
484
1226
|
// src/instrument/bun.ts
|
|
1227
|
+
warnIfProductionRuntimeTransform();
|
|
485
1228
|
plugin(
|
|
486
1229
|
createRasputinBunPlugin({
|
|
487
1230
|
excludedRoots: [sdkPackageScopeRootFrom(import.meta.url)]
|