@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/node.js
CHANGED
|
@@ -9,25 +9,29 @@ import {
|
|
|
9
9
|
sourceLocatorFromFile as sourceLocatorFromFile2
|
|
10
10
|
} from "@rasputin-ai/core";
|
|
11
11
|
|
|
12
|
-
// src/execution-recorder/
|
|
12
|
+
// src/execution-recorder/function-plan.ts
|
|
13
13
|
import {
|
|
14
14
|
runtimeFunctionId,
|
|
15
15
|
sourceLocatorFromFile
|
|
16
16
|
} from "@rasputin-ai/core";
|
|
17
|
-
var
|
|
18
|
-
var AUTOMATIC_SOURCE_PREFIX = "rasputin-source-v2:";
|
|
19
|
-
var runtimeSymbol = Symbol.for(AUTOMATIC_RUNTIME_SYMBOL);
|
|
20
|
-
var encodeAutomaticFunctionSource = (source) => {
|
|
17
|
+
var encodeFunctionPlan = (source) => {
|
|
21
18
|
const locator = sourceLocatorFromFile(source.filePath);
|
|
22
|
-
const
|
|
19
|
+
const definition = {
|
|
23
20
|
name: source.name,
|
|
24
21
|
...locator ? { source: { ...locator, line: source.line, column: source.column } } : {}
|
|
25
22
|
};
|
|
26
|
-
return
|
|
23
|
+
return [
|
|
24
|
+
runtimeFunctionId(definition),
|
|
25
|
+
source.name,
|
|
26
|
+
locator?.packageName ?? null,
|
|
27
|
+
locator?.packageRelativePath ?? null,
|
|
28
|
+
source.line,
|
|
29
|
+
source.column
|
|
30
|
+
];
|
|
27
31
|
};
|
|
28
32
|
|
|
29
33
|
// src/auto-instrumentation/instrumentation-manifest-registry.ts
|
|
30
|
-
var INSTRUMENTATION_MANIFEST_SYMBOL = "rasputin.instrumentation.manifest.
|
|
34
|
+
var INSTRUMENTATION_MANIFEST_SYMBOL = "rasputin.instrumentation.manifest.v3";
|
|
31
35
|
var registrySymbol = Symbol.for(INSTRUMENTATION_MANIFEST_SYMBOL);
|
|
32
36
|
var getRegistry = () => {
|
|
33
37
|
const existing = Reflect.get(globalThis, registrySymbol);
|
|
@@ -139,16 +143,663 @@ var createSourceMapLocationMapper = (generatedPath, source) => {
|
|
|
139
143
|
|
|
140
144
|
// src/auto-instrumentation/transform-source.ts
|
|
141
145
|
import { resolve as resolve4 } from "node:path";
|
|
142
|
-
import
|
|
146
|
+
import ts3 from "typescript";
|
|
143
147
|
|
|
144
|
-
// src/
|
|
148
|
+
// src/derivation/analyze-function.ts
|
|
145
149
|
import ts from "typescript";
|
|
150
|
+
|
|
151
|
+
// src/derivation/build-dependency-graph.ts
|
|
152
|
+
var buildDependencyGraph = (functions, edges) => {
|
|
153
|
+
const names = new Set(functions.map((fn) => fn.name));
|
|
154
|
+
const localEdges = edges.filter((edge) => names.has(edge.calleeName));
|
|
155
|
+
const calleesByCaller = /* @__PURE__ */ new Map();
|
|
156
|
+
for (const edge of localEdges) {
|
|
157
|
+
const existing = calleesByCaller.get(edge.callerFunctionId) ?? [];
|
|
158
|
+
existing.push(edge.calleeName);
|
|
159
|
+
calleesByCaller.set(edge.callerFunctionId, existing);
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
functions: functions.map((fn) => fn.functionId),
|
|
163
|
+
edges: localEdges,
|
|
164
|
+
calleesByCaller
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
var topologicalFunctionOrder = (functions, graph) => {
|
|
168
|
+
const idByName = new Map(functions.map((fn) => [fn.name, fn.functionId]));
|
|
169
|
+
const remainingCallees = new Map(
|
|
170
|
+
functions.map((fn) => {
|
|
171
|
+
const unique = new Set(
|
|
172
|
+
(graph.calleesByCaller.get(fn.functionId) ?? []).map((name) => idByName.get(name)).filter((id) => Boolean(id) && id !== fn.functionId)
|
|
173
|
+
);
|
|
174
|
+
return [fn.functionId, unique.size];
|
|
175
|
+
})
|
|
176
|
+
);
|
|
177
|
+
const callersOf = /* @__PURE__ */ new Map();
|
|
178
|
+
for (const fn of functions) {
|
|
179
|
+
for (const calleeName of graph.calleesByCaller.get(fn.functionId) ?? []) {
|
|
180
|
+
const calleeId = idByName.get(calleeName);
|
|
181
|
+
if (!calleeId || calleeId === fn.functionId) continue;
|
|
182
|
+
const callers = callersOf.get(calleeId) ?? [];
|
|
183
|
+
callers.push(fn.functionId);
|
|
184
|
+
callersOf.set(calleeId, callers);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const queue = functions.filter((fn) => (remainingCallees.get(fn.functionId) ?? 0) === 0).map((fn) => fn.functionId);
|
|
188
|
+
const ordered = [];
|
|
189
|
+
const queued = new Set(queue);
|
|
190
|
+
while (queue.length > 0) {
|
|
191
|
+
const current = queue.shift();
|
|
192
|
+
if (current === void 0) break;
|
|
193
|
+
ordered.push(current);
|
|
194
|
+
for (const caller of callersOf.get(current) ?? []) {
|
|
195
|
+
const next = (remainingCallees.get(caller) ?? 0) - 1;
|
|
196
|
+
remainingCallees.set(caller, next);
|
|
197
|
+
if (next === 0 && !queued.has(caller)) {
|
|
198
|
+
queued.add(caller);
|
|
199
|
+
queue.push(caller);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
for (const fn of functions) {
|
|
204
|
+
if (!queued.has(fn.functionId)) ordered.push(fn.functionId);
|
|
205
|
+
}
|
|
206
|
+
return ordered;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// src/derivation/derivation-ir.ts
|
|
210
|
+
var DerivationIrBuilder = class {
|
|
211
|
+
expressions = [];
|
|
212
|
+
nextId = 0;
|
|
213
|
+
constValue(value) {
|
|
214
|
+
return this.add({ id: this.nextId++, kind: "const", value });
|
|
215
|
+
}
|
|
216
|
+
param(index) {
|
|
217
|
+
return this.add({ id: this.nextId++, kind: "param", index });
|
|
218
|
+
}
|
|
219
|
+
root(rootId) {
|
|
220
|
+
return this.add({ id: this.nextId++, kind: "root", rootId });
|
|
221
|
+
}
|
|
222
|
+
alias(from) {
|
|
223
|
+
return this.add({ id: this.nextId++, kind: "alias", from });
|
|
224
|
+
}
|
|
225
|
+
binary(op, left, right, guard) {
|
|
226
|
+
return this.add({ id: this.nextId++, kind: "binary", op, left, right, guard });
|
|
227
|
+
}
|
|
228
|
+
unary(op, operand) {
|
|
229
|
+
return this.add({ id: this.nextId++, kind: "unary", op, operand });
|
|
230
|
+
}
|
|
231
|
+
logical(op, left, right) {
|
|
232
|
+
return this.add({ id: this.nextId++, kind: "logical", op, left, right });
|
|
233
|
+
}
|
|
234
|
+
cond(test, consequent, alternate) {
|
|
235
|
+
return this.add({ id: this.nextId++, kind: "cond", test, consequent, alternate });
|
|
236
|
+
}
|
|
237
|
+
object(properties) {
|
|
238
|
+
return this.add({ id: this.nextId++, kind: "object", properties });
|
|
239
|
+
}
|
|
240
|
+
array(elements) {
|
|
241
|
+
return this.add({ id: this.nextId++, kind: "array", elements });
|
|
242
|
+
}
|
|
243
|
+
prop(object, key) {
|
|
244
|
+
return this.add({ id: this.nextId++, kind: "prop", object, key });
|
|
245
|
+
}
|
|
246
|
+
index(array, index) {
|
|
247
|
+
return this.add({ id: this.nextId++, kind: "index", array, index });
|
|
248
|
+
}
|
|
249
|
+
pureCall(calleeFunctionId, args) {
|
|
250
|
+
return this.add({ id: this.nextId++, kind: "pure_call", calleeFunctionId, args });
|
|
251
|
+
}
|
|
252
|
+
template(parts) {
|
|
253
|
+
return this.add({ id: this.nextId++, kind: "template", parts });
|
|
254
|
+
}
|
|
255
|
+
add(expression) {
|
|
256
|
+
this.expressions.push(expression);
|
|
257
|
+
return expression.id;
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
var emptyPlan = (functionId) => ({
|
|
261
|
+
planId: `dp_${functionId}`,
|
|
262
|
+
functionId,
|
|
263
|
+
expressions: [],
|
|
264
|
+
variables: [],
|
|
265
|
+
captureBoundaries: [],
|
|
266
|
+
controlFacts: []
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// src/derivation/analyze-function.ts
|
|
270
|
+
var loc = (node, sourceFile) => {
|
|
271
|
+
const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
272
|
+
return { line: position.line + 1, column: position.character + 1 };
|
|
273
|
+
};
|
|
274
|
+
var lookup = (ctx, name) => {
|
|
275
|
+
for (let index = ctx.scopes.length - 1; index >= 0; index--) {
|
|
276
|
+
const binding = ctx.scopes[index]?.get(name);
|
|
277
|
+
if (binding) return binding;
|
|
278
|
+
}
|
|
279
|
+
return void 0;
|
|
280
|
+
};
|
|
281
|
+
var fallback = (ctx, reason) => {
|
|
282
|
+
ctx.fallback ??= reason;
|
|
283
|
+
return { expressionId: ctx.ir.constValue({ type: "undefined" }), shape: "unknown" };
|
|
284
|
+
};
|
|
285
|
+
var capture = (ctx, node, reason) => {
|
|
286
|
+
const rootId = ctx.nextRootId.value++;
|
|
287
|
+
const position = loc(node, ctx.sourceFile);
|
|
288
|
+
const expressionId = ctx.ir.root(rootId);
|
|
289
|
+
ctx.captureWraps.push({
|
|
290
|
+
rootId,
|
|
291
|
+
reason,
|
|
292
|
+
start: node.getStart(ctx.sourceFile),
|
|
293
|
+
end: node.end,
|
|
294
|
+
line: position.line,
|
|
295
|
+
column: position.column,
|
|
296
|
+
expressionId
|
|
297
|
+
});
|
|
298
|
+
return { expressionId, shape: "unknown", captured: true };
|
|
299
|
+
};
|
|
300
|
+
var binaryOp = (kind) => {
|
|
301
|
+
switch (kind) {
|
|
302
|
+
case ts.SyntaxKind.PlusToken:
|
|
303
|
+
return "add";
|
|
304
|
+
case ts.SyntaxKind.MinusToken:
|
|
305
|
+
return "sub";
|
|
306
|
+
case ts.SyntaxKind.AsteriskToken:
|
|
307
|
+
return "mul";
|
|
308
|
+
case ts.SyntaxKind.SlashToken:
|
|
309
|
+
return "div";
|
|
310
|
+
case ts.SyntaxKind.PercentToken:
|
|
311
|
+
return "mod";
|
|
312
|
+
case ts.SyntaxKind.EqualsEqualsToken:
|
|
313
|
+
return "eq";
|
|
314
|
+
case ts.SyntaxKind.ExclamationEqualsToken:
|
|
315
|
+
return "neq";
|
|
316
|
+
case ts.SyntaxKind.EqualsEqualsEqualsToken:
|
|
317
|
+
return "seq";
|
|
318
|
+
case ts.SyntaxKind.ExclamationEqualsEqualsToken:
|
|
319
|
+
return "sneq";
|
|
320
|
+
case ts.SyntaxKind.LessThanToken:
|
|
321
|
+
return "lt";
|
|
322
|
+
case ts.SyntaxKind.LessThanEqualsToken:
|
|
323
|
+
return "lte";
|
|
324
|
+
case ts.SyntaxKind.GreaterThanToken:
|
|
325
|
+
return "gt";
|
|
326
|
+
case ts.SyntaxKind.GreaterThanEqualsToken:
|
|
327
|
+
return "gte";
|
|
328
|
+
default:
|
|
329
|
+
return void 0;
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
var isNumericBinaryOp = (op) => op === "add" || op === "sub" || op === "mul" || op === "div" || op === "mod";
|
|
333
|
+
var unwrapParens = (node) => {
|
|
334
|
+
while (ts.isParenthesizedExpression(node)) node = node.expression;
|
|
335
|
+
return node;
|
|
336
|
+
};
|
|
337
|
+
var numericLiteralValue = (node) => {
|
|
338
|
+
const value = Number(node.text);
|
|
339
|
+
return { type: "number", value };
|
|
340
|
+
};
|
|
341
|
+
var analyzeExpression = (ctx, node) => {
|
|
342
|
+
if (ctx.fallback) return fallback(ctx, ctx.fallback);
|
|
343
|
+
if (ts.isParenthesizedExpression(node)) return analyzeExpression(ctx, node.expression);
|
|
344
|
+
if (ts.isAsExpression(node) || ts.isNonNullExpression(node)) {
|
|
345
|
+
return analyzeExpression(ctx, node.expression);
|
|
346
|
+
}
|
|
347
|
+
if (node.kind === ts.SyntaxKind.TypeAssertionExpression) {
|
|
348
|
+
return analyzeExpression(ctx, node.expression);
|
|
349
|
+
}
|
|
350
|
+
if (ts.isSatisfiesExpression(node)) return analyzeExpression(ctx, node.expression);
|
|
351
|
+
if (node.kind === ts.SyntaxKind.NullKeyword) {
|
|
352
|
+
return { expressionId: ctx.ir.constValue({ type: "null" }), shape: "primitive" };
|
|
353
|
+
}
|
|
354
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword) {
|
|
355
|
+
return {
|
|
356
|
+
expressionId: ctx.ir.constValue({ type: "boolean", value: true }),
|
|
357
|
+
shape: "primitive"
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword) {
|
|
361
|
+
return {
|
|
362
|
+
expressionId: ctx.ir.constValue({ type: "boolean", value: false }),
|
|
363
|
+
shape: "primitive"
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
if (ts.isNumericLiteral(node)) {
|
|
367
|
+
return { expressionId: ctx.ir.constValue(numericLiteralValue(node)), shape: "primitive" };
|
|
368
|
+
}
|
|
369
|
+
if (ts.isPrefixUnaryExpression(node) && ts.isNumericLiteral(node.operand) && node.operator === ts.SyntaxKind.MinusToken) {
|
|
370
|
+
const value = -Number(node.operand.text);
|
|
371
|
+
return {
|
|
372
|
+
expressionId: ctx.ir.constValue(
|
|
373
|
+
Object.is(value, -0) ? { type: "number", value: 0, negativeZero: true } : { type: "number", value }
|
|
374
|
+
),
|
|
375
|
+
shape: "primitive"
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
379
|
+
return {
|
|
380
|
+
expressionId: ctx.ir.constValue({ type: "string", value: node.text }),
|
|
381
|
+
shape: "primitive"
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
if (ts.isBigIntLiteral(node)) {
|
|
385
|
+
return {
|
|
386
|
+
expressionId: ctx.ir.constValue({ type: "bigint", value: node.text.replace(/n$/, "") }),
|
|
387
|
+
shape: "primitive"
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
if (ts.isIdentifier(node)) {
|
|
391
|
+
if (node.text === "undefined") {
|
|
392
|
+
return { expressionId: ctx.ir.constValue({ type: "undefined" }), shape: "primitive" };
|
|
393
|
+
}
|
|
394
|
+
const binding = lookup(ctx, node.text);
|
|
395
|
+
if (!binding) return capture(ctx, node, "external_state");
|
|
396
|
+
if (binding.mutable) return capture(ctx, node, "mutable_object");
|
|
397
|
+
return {
|
|
398
|
+
expressionId: ctx.ir.alias(binding.expressionId),
|
|
399
|
+
shape: binding.shape
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
if (ts.isTemplateExpression(node)) {
|
|
403
|
+
const parts = [
|
|
404
|
+
{ kind: "lit", value: node.head.text }
|
|
405
|
+
];
|
|
406
|
+
for (const span of node.templateSpans) {
|
|
407
|
+
const nested = analyzeExpression(ctx, span.expression);
|
|
408
|
+
if (nested.shape !== "primitive" && !nested.captured) {
|
|
409
|
+
return capture(ctx, node, "unsupported_expression");
|
|
410
|
+
}
|
|
411
|
+
parts.push({ kind: "expr", expressionId: nested.expressionId });
|
|
412
|
+
parts.push({ kind: "lit", value: span.literal.text });
|
|
413
|
+
}
|
|
414
|
+
return { expressionId: ctx.ir.template(parts), shape: "primitive" };
|
|
415
|
+
}
|
|
416
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
417
|
+
if (node.elements.some((element) => ts.isSpreadElement(element))) {
|
|
418
|
+
return capture(ctx, node, "unsupported_expression");
|
|
419
|
+
}
|
|
420
|
+
const elements = node.elements.map((element) => {
|
|
421
|
+
if (ts.isOmittedExpression(element)) return { kind: "hole" };
|
|
422
|
+
return analyzeExpression(ctx, element).expressionId;
|
|
423
|
+
});
|
|
424
|
+
return { expressionId: ctx.ir.array(elements), shape: "array" };
|
|
425
|
+
}
|
|
426
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
427
|
+
const properties = [];
|
|
428
|
+
for (const property of node.properties) {
|
|
429
|
+
if (ts.isShorthandPropertyAssignment(property)) {
|
|
430
|
+
const nested = analyzeExpression(ctx, property.name);
|
|
431
|
+
if (nested.captured) {
|
|
432
|
+
const wrap = ctx.captureWraps.find(
|
|
433
|
+
(candidate) => candidate.expressionId === nested.expressionId
|
|
434
|
+
);
|
|
435
|
+
if (wrap) wrap.shorthandPropertyName = property.name.text;
|
|
436
|
+
}
|
|
437
|
+
properties.push({ key: property.name.text, value: nested.expressionId });
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
if (!ts.isPropertyAssignment(property)) {
|
|
441
|
+
return capture(ctx, node, "unsupported_expression");
|
|
442
|
+
}
|
|
443
|
+
if (ts.isPrivateIdentifier(property.name) || ts.isComputedPropertyName(property.name)) {
|
|
444
|
+
return capture(ctx, node, "unsupported_expression");
|
|
445
|
+
}
|
|
446
|
+
const key = ts.isIdentifier(property.name) ? property.name.text : ts.isStringLiteral(property.name) || ts.isNumericLiteral(property.name) ? property.name.text : void 0;
|
|
447
|
+
if (key === void 0) return capture(ctx, node, "unsupported_expression");
|
|
448
|
+
properties.push({ key, value: analyzeExpression(ctx, property.initializer).expressionId });
|
|
449
|
+
}
|
|
450
|
+
return { expressionId: ctx.ir.object(properties), shape: "object" };
|
|
451
|
+
}
|
|
452
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
453
|
+
if (node.questionDotToken) return capture(ctx, node, "unknown_property");
|
|
454
|
+
const object = analyzeExpression(ctx, node.expression);
|
|
455
|
+
if (object.shape === "object" && ts.isIdentifier(node.name)) {
|
|
456
|
+
return { expressionId: ctx.ir.prop(object.expressionId, node.name.text), shape: "unknown" };
|
|
457
|
+
}
|
|
458
|
+
return capture(ctx, node, "unknown_property");
|
|
459
|
+
}
|
|
460
|
+
if (ts.isElementAccessExpression(node)) {
|
|
461
|
+
if (node.questionDotToken) return capture(ctx, node, "unknown_property");
|
|
462
|
+
const array = analyzeExpression(ctx, node.expression);
|
|
463
|
+
if (array.shape === "array" && ts.isNumericLiteral(node.argumentExpression)) {
|
|
464
|
+
return {
|
|
465
|
+
expressionId: ctx.ir.index(array.expressionId, Number(node.argumentExpression.text)),
|
|
466
|
+
shape: "unknown"
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
return capture(ctx, node, "unknown_property");
|
|
470
|
+
}
|
|
471
|
+
if (ts.isCallExpression(node)) {
|
|
472
|
+
if (node.questionDotToken || node.typeArguments?.length) {
|
|
473
|
+
return capture(ctx, node, "unsupported_expression");
|
|
474
|
+
}
|
|
475
|
+
if (!ts.isIdentifier(node.expression)) return capture(ctx, node, "dynamic_callee");
|
|
476
|
+
const calleeName = node.expression.text;
|
|
477
|
+
if (lookup(ctx, calleeName)) return capture(ctx, node, "opaque_call");
|
|
478
|
+
const provenId = ctx.moduleCallees.get(calleeName);
|
|
479
|
+
ctx.callEdges.push({
|
|
480
|
+
callerFunctionId: ctx.functionId,
|
|
481
|
+
calleeName,
|
|
482
|
+
...provenId ? { calleeFunctionId: provenId } : {}
|
|
483
|
+
});
|
|
484
|
+
if (!provenId) return capture(ctx, node, "opaque_call");
|
|
485
|
+
const args = node.arguments.map((argument) => {
|
|
486
|
+
if (ts.isSpreadElement(argument)) return void 0;
|
|
487
|
+
return analyzeExpression(ctx, argument).expressionId;
|
|
488
|
+
});
|
|
489
|
+
if (args.some((argument) => argument === void 0))
|
|
490
|
+
return capture(ctx, node, "unsupported_expression");
|
|
491
|
+
return {
|
|
492
|
+
expressionId: ctx.ir.pureCall(
|
|
493
|
+
provenId,
|
|
494
|
+
args.filter((id) => id !== void 0)
|
|
495
|
+
),
|
|
496
|
+
shape: "unknown"
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
if (ts.isNewExpression(node) || ts.isAwaitExpression(node) || ts.isYieldExpression(node)) {
|
|
500
|
+
return capture(
|
|
501
|
+
ctx,
|
|
502
|
+
node,
|
|
503
|
+
node.kind === ts.SyntaxKind.AwaitExpression ? "nondeterministic" : "unsupported_expression"
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
if (ts.isConditionalExpression(node)) {
|
|
507
|
+
const test = analyzeExpression(ctx, node.condition);
|
|
508
|
+
const consequent = analyzeExpression(ctx, node.whenTrue);
|
|
509
|
+
const alternate = analyzeExpression(ctx, node.whenFalse);
|
|
510
|
+
return {
|
|
511
|
+
expressionId: ctx.ir.cond(test.expressionId, consequent.expressionId, alternate.expressionId),
|
|
512
|
+
shape: consequent.shape === alternate.shape ? consequent.shape : "unknown"
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
if (ts.isBinaryExpression(node)) {
|
|
516
|
+
if (node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
|
|
517
|
+
ctx.fallback ??= "mutable_state";
|
|
518
|
+
return capture(ctx, node, "mutable_object");
|
|
519
|
+
}
|
|
520
|
+
if (node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === ts.SyntaxKind.BarBarToken || node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
|
|
521
|
+
const left2 = analyzeExpression(ctx, node.left);
|
|
522
|
+
const right2 = analyzeExpression(ctx, node.right);
|
|
523
|
+
const op2 = node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ? "and" : node.operatorToken.kind === ts.SyntaxKind.BarBarToken ? "or" : "nullish";
|
|
524
|
+
return {
|
|
525
|
+
expressionId: ctx.ir.logical(op2, left2.expressionId, right2.expressionId),
|
|
526
|
+
shape: "unknown"
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
const op = binaryOp(node.operatorToken.kind);
|
|
530
|
+
if (!op) return capture(ctx, node, "unsupported_expression");
|
|
531
|
+
if (!ts.isExpression(node.left)) return capture(ctx, node, "unsupported_expression");
|
|
532
|
+
const left = analyzeExpression(ctx, node.left);
|
|
533
|
+
const right = analyzeExpression(ctx, node.right);
|
|
534
|
+
const numeric = isNumericBinaryOp(op);
|
|
535
|
+
const expressionId = ctx.ir.binary(
|
|
536
|
+
op,
|
|
537
|
+
left.expressionId,
|
|
538
|
+
right.expressionId,
|
|
539
|
+
numeric ? "numeric" : "primitive"
|
|
540
|
+
);
|
|
541
|
+
if (numeric && (left.captured || right.captured || left.shape !== "primitive" || right.shape !== "primitive")) {
|
|
542
|
+
ctx.hasCoercionGuard = true;
|
|
543
|
+
const fallbackRootId = ctx.nextRootId.value++;
|
|
544
|
+
ctx.guardWraps.push({
|
|
545
|
+
fallbackRootId,
|
|
546
|
+
start: node.getStart(ctx.sourceFile),
|
|
547
|
+
end: node.end,
|
|
548
|
+
op
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
return { expressionId, shape: "primitive" };
|
|
552
|
+
}
|
|
553
|
+
if (ts.isPrefixUnaryExpression(node)) {
|
|
554
|
+
const operand = analyzeExpression(ctx, node.operand);
|
|
555
|
+
if (node.operator === ts.SyntaxKind.ExclamationToken) {
|
|
556
|
+
return { expressionId: ctx.ir.unary("not", operand.expressionId), shape: "primitive" };
|
|
557
|
+
}
|
|
558
|
+
if (node.operator === ts.SyntaxKind.PlusToken || node.operator === ts.SyntaxKind.MinusToken) {
|
|
559
|
+
if (operand.captured || operand.shape !== "primitive") ctx.hasCoercionGuard = true;
|
|
560
|
+
return {
|
|
561
|
+
expressionId: ctx.ir.unary(
|
|
562
|
+
node.operator === ts.SyntaxKind.MinusToken ? "minus" : "plus",
|
|
563
|
+
operand.expressionId
|
|
564
|
+
),
|
|
565
|
+
shape: "primitive"
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
return capture(ctx, node, "unsupported_expression");
|
|
569
|
+
}
|
|
570
|
+
if (ts.isTypeOfExpression(node)) {
|
|
571
|
+
const operand = unwrapParens(node.expression);
|
|
572
|
+
if (ts.isIdentifier(operand) && operand.text !== "undefined" && !lookup(ctx, operand.text)) {
|
|
573
|
+
return capture(ctx, node, "external_state");
|
|
574
|
+
}
|
|
575
|
+
const inner = analyzeExpression(ctx, node.expression);
|
|
576
|
+
return { expressionId: ctx.ir.unary("typeof", inner.expressionId), shape: "primitive" };
|
|
577
|
+
}
|
|
578
|
+
return capture(ctx, node, "unsupported_expression");
|
|
579
|
+
};
|
|
580
|
+
var bindIdentifier = (ctx, name, result, declaration) => {
|
|
581
|
+
const current = ctx.scopes[ctx.scopes.length - 1];
|
|
582
|
+
current?.set(name.text, {
|
|
583
|
+
expressionId: result.expressionId,
|
|
584
|
+
shape: result.shape,
|
|
585
|
+
mutable: false
|
|
586
|
+
});
|
|
587
|
+
const position = loc(name, ctx.sourceFile);
|
|
588
|
+
const available = loc(declaration, ctx.sourceFile);
|
|
589
|
+
ctx.variables.push({
|
|
590
|
+
id: ctx.nextVariableId.value++,
|
|
591
|
+
name: name.text,
|
|
592
|
+
functionId: ctx.functionId,
|
|
593
|
+
scopeId: `${ctx.functionId}:${ctx.scopes.length}`,
|
|
594
|
+
line: position.line,
|
|
595
|
+
column: position.column,
|
|
596
|
+
assignmentId: ctx.nextAssignmentId.value++,
|
|
597
|
+
expressionId: result.expressionId,
|
|
598
|
+
availableAtLine: available.line,
|
|
599
|
+
availableAtColumn: available.column
|
|
600
|
+
});
|
|
601
|
+
};
|
|
602
|
+
var analyzeStatement = (ctx, node) => {
|
|
603
|
+
if (ctx.fallback) return;
|
|
604
|
+
if (ts.isEmptyStatement(node) || ts.isDebuggerStatement(node)) return;
|
|
605
|
+
if (ts.isBlock(node)) {
|
|
606
|
+
ctx.scopes.push(/* @__PURE__ */ new Map());
|
|
607
|
+
for (const statement of node.statements) analyzeStatement(ctx, statement);
|
|
608
|
+
ctx.scopes.pop();
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
if (ts.isReturnStatement(node)) {
|
|
612
|
+
if (!node.expression) {
|
|
613
|
+
ctx.returnExpressionId = ctx.ir.constValue({ type: "undefined" });
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
ctx.returnExpressionId = analyzeExpression(ctx, node.expression).expressionId;
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
if (ts.isVariableStatement(node)) {
|
|
620
|
+
if (node.declarationList.flags & ts.NodeFlags.Using) {
|
|
621
|
+
fallback(ctx, "side_effect");
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
for (const declaration of node.declarationList.declarations) {
|
|
625
|
+
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) {
|
|
626
|
+
fallback(ctx, "unsupported_expression");
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
const result = analyzeExpression(ctx, declaration.initializer);
|
|
630
|
+
bindIdentifier(ctx, declaration.name, result, declaration);
|
|
631
|
+
}
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
if (ts.isExpressionStatement(node)) {
|
|
635
|
+
analyzeExpression(ctx, node.expression);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
if (ts.isThrowStatement(node)) {
|
|
639
|
+
ctx.hasThrow = true;
|
|
640
|
+
analyzeExpression(ctx, node.expression);
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
if (ts.isIfStatement(node) || ts.isSwitchStatement(node) || ts.isTryStatement(node)) {
|
|
644
|
+
fallback(ctx, "unsupported_expression");
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
if (ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node) || ts.isWhileStatement(node) || ts.isDoStatement(node)) {
|
|
648
|
+
fallback(ctx, "has_loop");
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
fallback(ctx, "unsupported_expression");
|
|
652
|
+
};
|
|
653
|
+
var bindParams = (ctx, node) => {
|
|
654
|
+
let index = 0;
|
|
655
|
+
for (const parameter of node.parameters) {
|
|
656
|
+
if (ts.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
|
|
657
|
+
if (!ts.isIdentifier(parameter.name) || parameter.dotDotDotToken) {
|
|
658
|
+
fallback(ctx, "unsupported_expression");
|
|
659
|
+
index++;
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
const expressionId = ctx.ir.param(index);
|
|
663
|
+
ctx.identifierParams.push(parameter.name.text);
|
|
664
|
+
bindIdentifier(ctx, parameter.name, { expressionId, shape: "unknown" }, parameter);
|
|
665
|
+
index++;
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
var analyzeOne = (node, functionId, name, sourceFile, moduleCallees) => {
|
|
669
|
+
const ctx = {
|
|
670
|
+
sourceFile,
|
|
671
|
+
functionId,
|
|
672
|
+
name,
|
|
673
|
+
ir: new DerivationIrBuilder(),
|
|
674
|
+
variables: [],
|
|
675
|
+
captureWraps: [],
|
|
676
|
+
guardWraps: [],
|
|
677
|
+
callEdges: [],
|
|
678
|
+
scopes: [/* @__PURE__ */ new Map()],
|
|
679
|
+
nextRootId: { value: 0 },
|
|
680
|
+
nextVariableId: { value: 0 },
|
|
681
|
+
nextAssignmentId: { value: 0 },
|
|
682
|
+
hasThrow: false,
|
|
683
|
+
hasCoercionGuard: false,
|
|
684
|
+
moduleCallees,
|
|
685
|
+
identifierParams: []
|
|
686
|
+
};
|
|
687
|
+
if (node.asteriskToken || node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)) {
|
|
688
|
+
ctx.fallback = "async";
|
|
689
|
+
} else {
|
|
690
|
+
bindParams(ctx, node);
|
|
691
|
+
if (ts.isBlock(node.body)) {
|
|
692
|
+
for (const statement of node.body.statements) analyzeStatement(ctx, statement);
|
|
693
|
+
} else {
|
|
694
|
+
ctx.returnExpressionId = analyzeExpression(ctx, node.body).expressionId;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
const plan = {
|
|
698
|
+
...emptyPlan(functionId),
|
|
699
|
+
expressions: ctx.ir.expressions,
|
|
700
|
+
variables: ctx.variables,
|
|
701
|
+
captureBoundaries: ctx.captureWraps.map((wrap) => ({
|
|
702
|
+
id: wrap.rootId,
|
|
703
|
+
reason: wrap.reason,
|
|
704
|
+
expressionId: wrap.expressionId,
|
|
705
|
+
line: wrap.line,
|
|
706
|
+
column: wrap.column
|
|
707
|
+
})),
|
|
708
|
+
controlFacts: [],
|
|
709
|
+
...ctx.returnExpressionId === void 0 ? {} : { returnExpressionId: ctx.returnExpressionId },
|
|
710
|
+
...ctx.fallback ? { fallbackReason: ctx.fallback } : {}
|
|
711
|
+
};
|
|
712
|
+
return {
|
|
713
|
+
functionId,
|
|
714
|
+
name,
|
|
715
|
+
node,
|
|
716
|
+
plan,
|
|
717
|
+
captureWraps: ctx.fallback ? [] : ctx.captureWraps,
|
|
718
|
+
guardWraps: ctx.fallback ? [] : ctx.guardWraps,
|
|
719
|
+
callEdges: ctx.callEdges,
|
|
720
|
+
hasThrow: ctx.hasThrow,
|
|
721
|
+
hasCoercionGuard: ctx.hasCoercionGuard
|
|
722
|
+
};
|
|
723
|
+
};
|
|
724
|
+
var analyzeModuleFunctions = (sourceFile, targets) => {
|
|
725
|
+
const nameCounts = /* @__PURE__ */ new Map();
|
|
726
|
+
for (const target of targets) {
|
|
727
|
+
nameCounts.set(target.name, (nameCounts.get(target.name) ?? 0) + 1);
|
|
728
|
+
}
|
|
729
|
+
const firstPass = targets.map(
|
|
730
|
+
(target) => analyzeOne(target.node, target.functionId, target.name, sourceFile, /* @__PURE__ */ new Map())
|
|
731
|
+
);
|
|
732
|
+
const graph = buildDependencyGraph(
|
|
733
|
+
firstPass.map((fn) => ({ functionId: fn.functionId, name: fn.name })),
|
|
734
|
+
firstPass.flatMap((fn) => fn.callEdges)
|
|
735
|
+
);
|
|
736
|
+
const order = topologicalFunctionOrder(
|
|
737
|
+
firstPass.map((fn) => ({ functionId: fn.functionId, name: fn.name })),
|
|
738
|
+
graph
|
|
739
|
+
);
|
|
740
|
+
const byId = new Map(targets.map((target) => [target.functionId, target]));
|
|
741
|
+
const moduleCallees = /* @__PURE__ */ new Map();
|
|
742
|
+
const analyzed = [];
|
|
743
|
+
for (const functionId of order) {
|
|
744
|
+
const target = byId.get(functionId);
|
|
745
|
+
if (!target) continue;
|
|
746
|
+
const result = analyzeOne(
|
|
747
|
+
target.node,
|
|
748
|
+
target.functionId,
|
|
749
|
+
target.name,
|
|
750
|
+
sourceFile,
|
|
751
|
+
moduleCallees
|
|
752
|
+
);
|
|
753
|
+
if (nameCounts.get(result.name) === 1) {
|
|
754
|
+
moduleCallees.set(result.name, result.functionId);
|
|
755
|
+
}
|
|
756
|
+
analyzed.push(result);
|
|
757
|
+
}
|
|
758
|
+
const remaining = targets.filter(
|
|
759
|
+
(target) => !analyzed.some((fn) => fn.functionId === target.functionId)
|
|
760
|
+
);
|
|
761
|
+
for (const target of remaining) {
|
|
762
|
+
analyzed.push(
|
|
763
|
+
analyzeOne(target.node, target.functionId, target.name, sourceFile, moduleCallees)
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
return analyzed;
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
// src/derivation/derivation-proof.ts
|
|
770
|
+
var functionIsExact = (plan) => plan.fallbackReason === void 0 && plan.captureBoundaries.length === 0 && plan.controlFacts.length === 0;
|
|
771
|
+
|
|
772
|
+
// src/derivation/emit-capture-plan.ts
|
|
773
|
+
var captureWrapEdits = (wraps, capName) => {
|
|
774
|
+
const edits = [];
|
|
775
|
+
let order = 0;
|
|
776
|
+
for (const wrap of wraps) {
|
|
777
|
+
edits.push({
|
|
778
|
+
position: wrap.start,
|
|
779
|
+
text: `${wrap.shorthandPropertyName ? `${wrap.shorthandPropertyName}:` : ""}${capName}(${wrap.rootId},`,
|
|
780
|
+
order: order++
|
|
781
|
+
});
|
|
782
|
+
edits.push({
|
|
783
|
+
position: wrap.end,
|
|
784
|
+
text: ")",
|
|
785
|
+
order: order++
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
return edits;
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
// src/execution-recorder/automatic-runtime.ts
|
|
792
|
+
var AUTOMATIC_RUNTIME_SYMBOL = "rasputin.execution.runtime.v2";
|
|
793
|
+
var runtimeSymbol = Symbol.for(AUTOMATIC_RUNTIME_SYMBOL);
|
|
794
|
+
|
|
795
|
+
// src/auto-instrumentation/collect-instrumentation-delta.ts
|
|
796
|
+
import ts2 from "typescript";
|
|
146
797
|
var paramBindingFromName = (name, sourceFile) => {
|
|
147
|
-
if (
|
|
148
|
-
if (
|
|
798
|
+
if (ts2.isIdentifier(name)) return { kind: "identifier", name: name.text };
|
|
799
|
+
if (ts2.isArrayBindingPattern(name)) {
|
|
149
800
|
const elements = name.elements.map(
|
|
150
801
|
(element) => {
|
|
151
|
-
if (
|
|
802
|
+
if (ts2.isOmittedExpression(element)) return { kind: "omitted" };
|
|
152
803
|
const binding = paramBindingFromName(element.name, sourceFile);
|
|
153
804
|
if (element.dotDotDotToken) return { kind: "rest", binding };
|
|
154
805
|
return binding;
|
|
@@ -158,7 +809,7 @@ var paramBindingFromName = (name, sourceFile) => {
|
|
|
158
809
|
}
|
|
159
810
|
const properties = name.elements.map((element) => {
|
|
160
811
|
const binding = paramBindingFromName(element.name, sourceFile);
|
|
161
|
-
const key = element.propertyName ? element.propertyName.getText(sourceFile).replace(/^['"]|['"]$/g, "") :
|
|
812
|
+
const key = element.propertyName ? element.propertyName.getText(sourceFile).replace(/^['"]|['"]$/g, "") : ts2.isIdentifier(element.name) ? element.name.text : element.name.getText(sourceFile);
|
|
162
813
|
if (element.dotDotDotToken) {
|
|
163
814
|
const rest = { kind: "rest", binding };
|
|
164
815
|
return { key, binding: rest };
|
|
@@ -170,7 +821,7 @@ var paramBindingFromName = (name, sourceFile) => {
|
|
|
170
821
|
var collectFunctionParams = (node, sourceFile) => {
|
|
171
822
|
const params = [];
|
|
172
823
|
for (const parameter of node.parameters) {
|
|
173
|
-
if (
|
|
824
|
+
if (ts2.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
|
|
174
825
|
const binding = paramBindingFromName(parameter.name, sourceFile);
|
|
175
826
|
params.push(parameter.dotDotDotToken ? { kind: "rest", binding } : binding);
|
|
176
827
|
}
|
|
@@ -211,18 +862,18 @@ var mappedLocation = (sourceFile, position, absolutePath, mapLocation) => {
|
|
|
211
862
|
};
|
|
212
863
|
var unwrapExpression = (expression) => {
|
|
213
864
|
let current = expression;
|
|
214
|
-
while (
|
|
865
|
+
while (ts2.isParenthesizedExpression(current) || ts2.isNonNullExpression(current)) {
|
|
215
866
|
current = current.expression;
|
|
216
867
|
}
|
|
217
|
-
if (
|
|
868
|
+
if (ts2.isAwaitExpression(current)) return unwrapExpression(current.expression);
|
|
218
869
|
return current;
|
|
219
870
|
};
|
|
220
871
|
var calleeFromExpression = (expression, sourceFile) => {
|
|
221
872
|
const current = unwrapExpression(expression);
|
|
222
|
-
if (
|
|
873
|
+
if (ts2.isIdentifier(current)) {
|
|
223
874
|
return { calleeName: current.text, calleeText: current.text };
|
|
224
875
|
}
|
|
225
|
-
if (
|
|
876
|
+
if (ts2.isPropertyAccessExpression(current) && ts2.isIdentifier(current.name)) {
|
|
226
877
|
return {
|
|
227
878
|
calleeName: current.name.text,
|
|
228
879
|
calleeText: current.getText(sourceFile).replace(/\s+/g, "")
|
|
@@ -232,9 +883,9 @@ var calleeFromExpression = (expression, sourceFile) => {
|
|
|
232
883
|
};
|
|
233
884
|
var resultBindingForCall = (call) => {
|
|
234
885
|
let node = call.parent;
|
|
235
|
-
if (
|
|
236
|
-
if (!
|
|
237
|
-
if (!
|
|
886
|
+
if (ts2.isAwaitExpression(node)) node = node.parent;
|
|
887
|
+
if (!ts2.isVariableDeclaration(node)) return null;
|
|
888
|
+
if (!ts2.isIdentifier(node.name)) return null;
|
|
238
889
|
const initializer = node.initializer;
|
|
239
890
|
if (!initializer) return null;
|
|
240
891
|
const inner = unwrapExpression(initializer);
|
|
@@ -244,18 +895,18 @@ var resultBindingForCall = (call) => {
|
|
|
244
895
|
var argIdentifiersForCall = (call) => {
|
|
245
896
|
const identifiers = [];
|
|
246
897
|
call.arguments?.forEach((argument, index) => {
|
|
247
|
-
if (
|
|
898
|
+
if (ts2.isIdentifier(argument)) {
|
|
248
899
|
identifiers.push({ index, kind: "identifier", name: argument.text });
|
|
249
900
|
return;
|
|
250
901
|
}
|
|
251
|
-
if (!
|
|
902
|
+
if (!ts2.isObjectLiteralExpression(argument)) return;
|
|
252
903
|
const properties = [];
|
|
253
904
|
for (const property of argument.properties) {
|
|
254
|
-
if (
|
|
905
|
+
if (ts2.isShorthandPropertyAssignment(property)) {
|
|
255
906
|
properties.push({ key: property.name.text, name: property.name.text });
|
|
256
907
|
continue;
|
|
257
908
|
}
|
|
258
|
-
if (
|
|
909
|
+
if (ts2.isPropertyAssignment(property) && ts2.isIdentifier(property.name) && ts2.isIdentifier(property.initializer)) {
|
|
259
910
|
properties.push({ key: property.name.text, name: property.initializer.text });
|
|
260
911
|
}
|
|
261
912
|
}
|
|
@@ -282,7 +933,7 @@ var collectCallSite = (call, caller, context) => {
|
|
|
282
933
|
);
|
|
283
934
|
if (!start || !end) return null;
|
|
284
935
|
return {
|
|
285
|
-
|
|
936
|
+
callerFunctionId: caller.functionId,
|
|
286
937
|
calleeName: callee.calleeName,
|
|
287
938
|
calleeText: callee.calleeText,
|
|
288
939
|
filePath: start.filePath,
|
|
@@ -295,8 +946,8 @@ var collectCallSite = (call, caller, context) => {
|
|
|
295
946
|
};
|
|
296
947
|
};
|
|
297
948
|
var collectParamDestructure = (declaration, caller, context) => {
|
|
298
|
-
if (!declaration.initializer || !
|
|
299
|
-
if (!
|
|
949
|
+
if (!declaration.initializer || !ts2.isIdentifier(declaration.initializer)) return null;
|
|
950
|
+
if (!ts2.isObjectBindingPattern(declaration.name) && !ts2.isArrayBindingPattern(declaration.name)) {
|
|
300
951
|
return null;
|
|
301
952
|
}
|
|
302
953
|
const sourceParam = declaration.initializer.text;
|
|
@@ -309,7 +960,7 @@ var collectParamDestructure = (declaration, caller, context) => {
|
|
|
309
960
|
);
|
|
310
961
|
if (!start) return null;
|
|
311
962
|
return {
|
|
312
|
-
|
|
963
|
+
callerFunctionId: caller.functionId,
|
|
313
964
|
sourceParam,
|
|
314
965
|
filePath: start.filePath,
|
|
315
966
|
line: start.line,
|
|
@@ -320,33 +971,33 @@ var collectParamDestructure = (declaration, caller, context) => {
|
|
|
320
971
|
|
|
321
972
|
// src/auto-instrumentation/transform-source.ts
|
|
322
973
|
var scriptKindFor = (filePath) => {
|
|
323
|
-
if (/\.tsx$/i.test(filePath)) return
|
|
324
|
-
if (/\.jsx$/i.test(filePath)) return
|
|
325
|
-
if (/\.[cm]?ts$/i.test(filePath)) return
|
|
326
|
-
return
|
|
974
|
+
if (/\.tsx$/i.test(filePath)) return ts3.ScriptKind.TSX;
|
|
975
|
+
if (/\.jsx$/i.test(filePath)) return ts3.ScriptKind.JSX;
|
|
976
|
+
if (/\.[cm]?ts$/i.test(filePath)) return ts3.ScriptKind.TS;
|
|
977
|
+
return ts3.ScriptKind.JS;
|
|
327
978
|
};
|
|
328
979
|
var functionName = (node, sourceFile) => {
|
|
329
980
|
if (node.name) return node.name.getText(sourceFile).replace(/^['"]|['"]$/g, "");
|
|
330
981
|
const parent = node.parent;
|
|
331
|
-
if (
|
|
332
|
-
if (
|
|
333
|
-
if (
|
|
982
|
+
if (ts3.isVariableDeclaration(parent) && ts3.isIdentifier(parent.name)) return parent.name.text;
|
|
983
|
+
if (ts3.isPropertyAssignment(parent)) return parent.name.getText(sourceFile);
|
|
984
|
+
if (ts3.isPropertyDeclaration(parent) && parent.name) return parent.name.getText(sourceFile);
|
|
334
985
|
const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
335
986
|
return `anonymous@${location.line + 1}:${location.character + 1}`;
|
|
336
987
|
};
|
|
337
988
|
var functionLocationPosition = (node, sourceFile) => {
|
|
338
989
|
if (node.name) return node.name.getStart(sourceFile);
|
|
339
990
|
const parent = node.parent;
|
|
340
|
-
if ((
|
|
991
|
+
if ((ts3.isVariableDeclaration(parent) || ts3.isPropertyAssignment(parent)) && parent.name) {
|
|
341
992
|
return parent.name.getStart(sourceFile);
|
|
342
993
|
}
|
|
343
994
|
return node.getStart(sourceFile);
|
|
344
995
|
};
|
|
345
996
|
var bindingValue = (name, sourceFile) => {
|
|
346
|
-
if (
|
|
347
|
-
if (
|
|
997
|
+
if (ts3.isIdentifier(name)) return name.text;
|
|
998
|
+
if (ts3.isArrayBindingPattern(name)) {
|
|
348
999
|
const values = name.elements.map((element) => {
|
|
349
|
-
if (
|
|
1000
|
+
if (ts3.isOmittedExpression(element)) return "";
|
|
350
1001
|
const value = bindingValue(element.name, sourceFile);
|
|
351
1002
|
return element.dotDotDotToken ? `...${value}` : value;
|
|
352
1003
|
});
|
|
@@ -362,20 +1013,40 @@ var bindingValue = (name, sourceFile) => {
|
|
|
362
1013
|
};
|
|
363
1014
|
var arrowArguments = (node) => {
|
|
364
1015
|
const values = node.parameters.flatMap((parameter) => {
|
|
365
|
-
if (
|
|
1016
|
+
if (ts3.isIdentifier(parameter.name) && parameter.name.text === "this") return [];
|
|
366
1017
|
const value = bindingValue(parameter.name, node.getSourceFile());
|
|
367
1018
|
return [parameter.dotDotDotToken ? `...${value}` : value];
|
|
368
1019
|
});
|
|
369
1020
|
return `[${values.join(",")}]`;
|
|
370
1021
|
};
|
|
371
1022
|
var isSupportedFunction = (node) => {
|
|
372
|
-
if (!
|
|
1023
|
+
if (!ts3.isFunctionDeclaration(node) && !ts3.isFunctionExpression(node) && !ts3.isArrowFunction(node) && !ts3.isMethodDeclaration(node) && !ts3.isGetAccessorDeclaration(node) && !ts3.isSetAccessorDeclaration(node)) {
|
|
373
1024
|
return false;
|
|
374
1025
|
}
|
|
375
1026
|
if (!node.body || "asteriskToken" in node && node.asteriskToken) return false;
|
|
376
1027
|
return true;
|
|
377
1028
|
};
|
|
378
|
-
var
|
|
1029
|
+
var uniqueName = (source, base) => {
|
|
1030
|
+
let name = base;
|
|
1031
|
+
while (source.includes(name)) name += "_";
|
|
1032
|
+
return name;
|
|
1033
|
+
};
|
|
1034
|
+
var isDirectivePrologue = (statement) => ts3.isExpressionStatement(statement) && ts3.isStringLiteral(statement.expression);
|
|
1035
|
+
var insertPositionAfterPrologue = (source, sourceFile) => {
|
|
1036
|
+
const shebangEnd = source.startsWith("#!") ? source.indexOf("\n") + 1 : 0;
|
|
1037
|
+
let end = shebangEnd;
|
|
1038
|
+
for (const statement of sourceFile.statements) {
|
|
1039
|
+
if (!isDirectivePrologue(statement)) break;
|
|
1040
|
+
end = statement.end;
|
|
1041
|
+
}
|
|
1042
|
+
return end;
|
|
1043
|
+
};
|
|
1044
|
+
var preambleInsert = (source, position, preamble) => {
|
|
1045
|
+
if (position <= 0) return preamble;
|
|
1046
|
+
const previous = source[position - 1];
|
|
1047
|
+
return previous === ";" || previous === "\n" || previous === "\r" ? preamble : `;${preamble}`;
|
|
1048
|
+
};
|
|
1049
|
+
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)}));`;
|
|
379
1050
|
var collectWrappedFunction = (node, sourceFile, absolutePath, mapLocation) => {
|
|
380
1051
|
const start = mappedLocation(
|
|
381
1052
|
sourceFile,
|
|
@@ -387,13 +1058,15 @@ var collectWrappedFunction = (node, sourceFile, absolutePath, mapLocation) => {
|
|
|
387
1058
|
const end = mappedLocation(sourceFile, node.end, absolutePath, mapLocation) ?? start;
|
|
388
1059
|
const name = functionName(node, sourceFile).replace(/@\d+:\d+$/, "");
|
|
389
1060
|
const { params, paramNames } = collectFunctionParams(node, sourceFile);
|
|
1061
|
+
const plan = encodeFunctionPlan({
|
|
1062
|
+
filePath: start.filePath,
|
|
1063
|
+
name,
|
|
1064
|
+
line: start.line,
|
|
1065
|
+
column: start.column
|
|
1066
|
+
});
|
|
390
1067
|
return {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
name,
|
|
394
|
-
line: start.line,
|
|
395
|
-
column: start.column
|
|
396
|
-
}),
|
|
1068
|
+
plan,
|
|
1069
|
+
functionId: plan[0],
|
|
397
1070
|
filePath: start.filePath,
|
|
398
1071
|
name,
|
|
399
1072
|
startLine: start.line,
|
|
@@ -404,74 +1077,130 @@ var collectWrappedFunction = (node, sourceFile, absolutePath, mapLocation) => {
|
|
|
404
1077
|
paramNames
|
|
405
1078
|
};
|
|
406
1079
|
};
|
|
1080
|
+
var canOmitCallValues = (analysis, analyzed) => {
|
|
1081
|
+
if (!functionIsExact(analysis.plan) || analysis.hasThrow || analysis.hasCoercionGuard) {
|
|
1082
|
+
return false;
|
|
1083
|
+
}
|
|
1084
|
+
const callers = analyzed.filter(
|
|
1085
|
+
(caller) => caller.callEdges.some((edge) => edge.calleeFunctionId === analysis.functionId)
|
|
1086
|
+
);
|
|
1087
|
+
if (callers.length === 0) return false;
|
|
1088
|
+
return callers.every((caller) => caller.plan.fallbackReason === void 0 && !caller.hasThrow);
|
|
1089
|
+
};
|
|
407
1090
|
var transformSource = (source, options) => {
|
|
408
1091
|
if (source.includes("@rasputin-ignore-file")) return void 0;
|
|
409
|
-
const sourceFile =
|
|
1092
|
+
const sourceFile = ts3.createSourceFile(
|
|
410
1093
|
options.filePath,
|
|
411
1094
|
source,
|
|
412
|
-
|
|
1095
|
+
ts3.ScriptTarget.Latest,
|
|
413
1096
|
true,
|
|
414
1097
|
scriptKindFor(options.filePath)
|
|
415
1098
|
);
|
|
416
|
-
|
|
417
|
-
|
|
1099
|
+
const runName = uniqueName(source, "__rasputinRun__");
|
|
1100
|
+
const capName = uniqueName(source, "__rasputinCap__");
|
|
1101
|
+
const plansName = uniqueName(source, "__rasputinPlans__");
|
|
418
1102
|
const absolutePath = resolve4(options.filePath);
|
|
1103
|
+
const derivationMode = options.derivationMode ?? "analysis";
|
|
419
1104
|
const edits = [];
|
|
420
1105
|
let order = 0;
|
|
421
1106
|
const delta = {
|
|
422
1107
|
functions: [],
|
|
423
1108
|
callSites: [],
|
|
424
|
-
destructures: []
|
|
1109
|
+
destructures: [],
|
|
1110
|
+
derivationPlans: []
|
|
425
1111
|
};
|
|
426
1112
|
const collectContext = {
|
|
427
1113
|
sourceFile,
|
|
428
1114
|
mapLocation: options.mapLocation,
|
|
429
1115
|
absolutePath
|
|
430
1116
|
};
|
|
1117
|
+
const pending = [];
|
|
431
1118
|
const visit = (node, caller) => {
|
|
432
1119
|
if (isSupportedFunction(node)) {
|
|
433
1120
|
const collected = collectWrappedFunction(node, sourceFile, absolutePath, options.mapLocation);
|
|
434
1121
|
if (!collected) {
|
|
435
|
-
|
|
1122
|
+
ts3.forEachChild(node, (child) => visit(child, caller));
|
|
436
1123
|
return;
|
|
437
1124
|
}
|
|
1125
|
+
pending.push({ node, collected });
|
|
438
1126
|
delta.functions.push(collected);
|
|
439
|
-
|
|
440
|
-
const asyncCallback = node.modifiers?.some(
|
|
441
|
-
(modifier) => modifier.kind === ts2.SyntaxKind.AsyncKeyword
|
|
442
|
-
) ? "async " : "";
|
|
443
|
-
if (ts2.isBlock(node.body)) {
|
|
444
|
-
edits.push({
|
|
445
|
-
position: node.body.getStart(sourceFile) + 1,
|
|
446
|
-
text: `return ${helperName}(${JSON.stringify(collected.sourceToken)},${args},${asyncCallback}()=>{`,
|
|
447
|
-
order: order++
|
|
448
|
-
});
|
|
449
|
-
edits.push({ position: node.body.end - 1, text: "});", order: order++ });
|
|
450
|
-
} else {
|
|
451
|
-
edits.push({
|
|
452
|
-
position: node.body.getStart(sourceFile),
|
|
453
|
-
text: `${helperName}(${JSON.stringify(collected.sourceToken)},${args},${asyncCallback}()=>(`,
|
|
454
|
-
order: order++
|
|
455
|
-
});
|
|
456
|
-
edits.push({ position: node.body.end, text: "))", order: order++ });
|
|
457
|
-
}
|
|
458
|
-
ts2.forEachChild(node, (child) => visit(child, collected));
|
|
1127
|
+
ts3.forEachChild(node, (child) => visit(child, collected));
|
|
459
1128
|
return;
|
|
460
1129
|
}
|
|
461
|
-
if (caller && (
|
|
1130
|
+
if (caller && (ts3.isCallExpression(node) || ts3.isNewExpression(node))) {
|
|
462
1131
|
const site = collectCallSite(node, caller, collectContext);
|
|
463
1132
|
if (site) delta.callSites.push(site);
|
|
464
1133
|
}
|
|
465
|
-
if (caller &&
|
|
1134
|
+
if (caller && ts3.isVariableDeclaration(node)) {
|
|
466
1135
|
const destructure = collectParamDestructure(node, caller, collectContext);
|
|
467
1136
|
if (destructure) delta.destructures.push(destructure);
|
|
468
1137
|
}
|
|
469
|
-
|
|
1138
|
+
ts3.forEachChild(node, (child) => visit(child, caller));
|
|
470
1139
|
};
|
|
471
1140
|
visit(sourceFile);
|
|
472
|
-
if (
|
|
473
|
-
const
|
|
474
|
-
|
|
1141
|
+
if (pending.length === 0) return void 0;
|
|
1142
|
+
const analyzed = analyzeModuleFunctions(
|
|
1143
|
+
sourceFile,
|
|
1144
|
+
pending.map((entry) => ({
|
|
1145
|
+
functionId: entry.collected.functionId,
|
|
1146
|
+
name: entry.collected.name,
|
|
1147
|
+
node: entry.node
|
|
1148
|
+
}))
|
|
1149
|
+
);
|
|
1150
|
+
delta.derivationPlans = analyzed.map((fn) => fn.plan);
|
|
1151
|
+
const analyzedById = new Map(analyzed.map((fn) => [fn.functionId, fn]));
|
|
1152
|
+
for (const [planIndex, entry] of pending.entries()) {
|
|
1153
|
+
const analysis = analyzedById.get(entry.collected.functionId);
|
|
1154
|
+
const omitValues = derivationMode === "selective" && analysis && canOmitCallValues(analysis, analyzed);
|
|
1155
|
+
const valueFlag = omitValues ? ",1" : "";
|
|
1156
|
+
const args = ts3.isArrowFunction(entry.node) ? arrowArguments(entry.node) : "arguments";
|
|
1157
|
+
const asyncCallback = entry.node.modifiers?.some(
|
|
1158
|
+
(modifier) => modifier.kind === ts3.SyntaxKind.AsyncKeyword
|
|
1159
|
+
) ? "async " : "";
|
|
1160
|
+
if (ts3.isBlock(entry.node.body)) {
|
|
1161
|
+
edits.push({
|
|
1162
|
+
position: entry.node.body.getStart(sourceFile) + 1,
|
|
1163
|
+
text: `return ${runName}(${planIndex},${args},${asyncCallback}()=>{`,
|
|
1164
|
+
order: order++
|
|
1165
|
+
});
|
|
1166
|
+
edits.push({
|
|
1167
|
+
position: entry.node.body.end - 1,
|
|
1168
|
+
text: `}${valueFlag});`,
|
|
1169
|
+
order: order++
|
|
1170
|
+
});
|
|
1171
|
+
} else {
|
|
1172
|
+
edits.push({
|
|
1173
|
+
position: entry.node.body.getStart(sourceFile),
|
|
1174
|
+
text: `${runName}(${planIndex},${args},${asyncCallback}()=>(`,
|
|
1175
|
+
order: order++
|
|
1176
|
+
});
|
|
1177
|
+
edits.push({
|
|
1178
|
+
position: entry.node.body.end,
|
|
1179
|
+
text: `)${valueFlag})`,
|
|
1180
|
+
order: order++
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
if (analysis) {
|
|
1184
|
+
for (const edit of captureWrapEdits(analysis.captureWraps, capName)) {
|
|
1185
|
+
edits.push({ ...edit, order: order++ });
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
const insertAt = insertPositionAfterPrologue(source, sourceFile);
|
|
1190
|
+
edits.push({
|
|
1191
|
+
position: insertAt,
|
|
1192
|
+
text: preambleInsert(
|
|
1193
|
+
source,
|
|
1194
|
+
insertAt,
|
|
1195
|
+
modulePreamble(
|
|
1196
|
+
runName,
|
|
1197
|
+
capName,
|
|
1198
|
+
plansName,
|
|
1199
|
+
JSON.stringify(delta.functions.map((fn) => fn.plan))
|
|
1200
|
+
)
|
|
1201
|
+
),
|
|
1202
|
+
order: -1
|
|
1203
|
+
});
|
|
475
1204
|
edits.sort((left, right) => right.position - left.position || right.order - left.order);
|
|
476
1205
|
let transformed = source;
|
|
477
1206
|
for (const edit of edits) {
|
|
@@ -538,7 +1267,21 @@ import { dirname as dirname2, resolve as resolve5 } from "node:path";
|
|
|
538
1267
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
539
1268
|
var sdkPackageScopeRootFrom = (instrumentEntryUrl) => resolve5(dirname2(fileURLToPath3(instrumentEntryUrl)), "../../..");
|
|
540
1269
|
|
|
1270
|
+
// src/auto-instrumentation/warn-production-runtime-transform.ts
|
|
1271
|
+
var RUNTIME_TRANSFORM_WARNING_SUPPRESS_ENV = "RASPUTIN_SUPPRESS_RUNTIME_TRANSFORM_WARNING";
|
|
1272
|
+
var warnedSymbol = Symbol.for("rasputin.runtimeTransformWarning");
|
|
1273
|
+
var warnIfProductionRuntimeTransform = () => {
|
|
1274
|
+
if (process.env.NODE_ENV !== "production") return;
|
|
1275
|
+
if (process.env[RUNTIME_TRANSFORM_WARNING_SUPPRESS_ENV] === "1") return;
|
|
1276
|
+
if (Reflect.get(globalThis, warnedSymbol)) return;
|
|
1277
|
+
Reflect.set(globalThis, warnedSymbol, true);
|
|
1278
|
+
console.warn(
|
|
1279
|
+
"[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."
|
|
1280
|
+
);
|
|
1281
|
+
};
|
|
1282
|
+
|
|
541
1283
|
// src/instrument/node.ts
|
|
1284
|
+
warnIfProductionRuntimeTransform();
|
|
542
1285
|
registerRasputinNodeHooks({
|
|
543
1286
|
excludedRoots: [sdkPackageScopeRootFrom(import.meta.url)]
|
|
544
1287
|
});
|