@rasputin-ai/node 0.5.0-alpha.5 → 0.5.0-alpha.7

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.
Files changed (37) hide show
  1. package/dist/auto-instrumentation/instrument-compiled-esm.d.ts +2 -0
  2. package/dist/auto-instrumentation/instrument-compiled-esm.d.ts.map +1 -1
  3. package/dist/auto-instrumentation/instrumentation-manifest-delta.d.ts +2 -1
  4. package/dist/auto-instrumentation/instrumentation-manifest-delta.d.ts.map +1 -1
  5. package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts +1 -1
  6. package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts.map +1 -1
  7. package/dist/auto-instrumentation/transform-source.d.ts +6 -0
  8. package/dist/auto-instrumentation/transform-source.d.ts.map +1 -1
  9. package/dist/derivation/analyze-function.d.ts +40 -0
  10. package/dist/derivation/analyze-function.d.ts.map +1 -0
  11. package/dist/derivation/build-dependency-graph.d.ts +21 -0
  12. package/dist/derivation/build-dependency-graph.d.ts.map +1 -0
  13. package/dist/derivation/derivation-ir.d.ts +33 -0
  14. package/dist/derivation/derivation-ir.d.ts.map +1 -0
  15. package/dist/derivation/derivation-proof.d.ts +14 -0
  16. package/dist/derivation/derivation-proof.d.ts.map +1 -0
  17. package/dist/derivation/emit-capture-plan.d.ts +8 -0
  18. package/dist/derivation/emit-capture-plan.d.ts.map +1 -0
  19. package/dist/execution-recorder/automatic-runtime.d.ts +3 -1
  20. package/dist/execution-recorder/automatic-runtime.d.ts.map +1 -1
  21. package/dist/execution-recorder/execution-journal.d.ts +84 -0
  22. package/dist/execution-recorder/execution-journal.d.ts.map +1 -0
  23. package/dist/execution-recorder/execution-recorder-types.d.ts +19 -2
  24. package/dist/execution-recorder/execution-recorder-types.d.ts.map +1 -1
  25. package/dist/execution-recorder/execution-recorder.d.ts.map +1 -1
  26. package/dist/execution-recorder/recorder-memory-budget.d.ts +1 -0
  27. package/dist/execution-recorder/recorder-memory-budget.d.ts.map +1 -1
  28. package/dist/execution-recorder/safe-serialize.d.ts +1 -0
  29. package/dist/execution-recorder/safe-serialize.d.ts.map +1 -1
  30. package/dist/index.js +1058 -317
  31. package/dist/instrument/build.js +761 -71
  32. package/dist/instrument/bun.js +742 -66
  33. package/dist/instrument/node.js +742 -66
  34. package/dist/sdk-meta.d.ts +1 -1
  35. package/package.json +2 -2
  36. package/dist/execution-recorder/call-aware-event-buffer.d.ts +0 -37
  37. package/dist/execution-recorder/call-aware-event-buffer.d.ts.map +0 -1
@@ -46,7 +46,7 @@ var definitionFromEncodedPlan = (plan) => {
46
46
  };
47
47
 
48
48
  // src/auto-instrumentation/instrumentation-manifest-registry.ts
49
- var INSTRUMENTATION_MANIFEST_SYMBOL = "rasputin.instrumentation.manifest.v2";
49
+ var INSTRUMENTATION_MANIFEST_SYMBOL = "rasputin.instrumentation.manifest.v3";
50
50
  var registrySymbol = Symbol.for(INSTRUMENTATION_MANIFEST_SYMBOL);
51
51
  var getRegistry = () => {
52
52
  const existing = Reflect.get(globalThis, registrySymbol);
@@ -77,6 +77,7 @@ var snapshotInstrumentationManifest = (options = {}) => {
77
77
  const functions = [];
78
78
  const callSites = [];
79
79
  const destructures = [];
80
+ const derivationPlans = [];
80
81
  for (const delta of getRegistry().files.values()) {
81
82
  for (const fn of delta.functions) {
82
83
  const definition = definitionFromEncodedPlan(fn.plan);
@@ -128,6 +129,7 @@ var snapshotInstrumentationManifest = (options = {}) => {
128
129
  binding: destructure.binding
129
130
  });
130
131
  }
132
+ derivationPlans.push(...delta.derivationPlans ?? []);
131
133
  }
132
134
  const registry = getRegistry();
133
135
  if (registry.prebuilt && registry.files.size === 0) {
@@ -155,7 +157,8 @@ var snapshotInstrumentationManifest = (options = {}) => {
155
157
  } : {},
156
158
  functions,
157
159
  callSites,
158
- destructures
160
+ destructures,
161
+ derivationPlans
159
162
  };
160
163
  };
161
164
  var resetInstrumentationManifestRegistry = () => {
@@ -251,20 +254,649 @@ var createSourceMapLocationMapper = (generatedPath, source) => {
251
254
 
252
255
  // src/auto-instrumentation/transform-source.ts
253
256
  import { resolve as resolve4 } from "node:path";
254
- import ts2 from "typescript";
257
+ import ts3 from "typescript";
258
+
259
+ // src/derivation/analyze-function.ts
260
+ import ts from "typescript";
261
+
262
+ // src/derivation/build-dependency-graph.ts
263
+ var buildDependencyGraph = (functions, edges) => {
264
+ const names = new Set(functions.map((fn) => fn.name));
265
+ const localEdges = edges.filter((edge) => names.has(edge.calleeName));
266
+ const calleesByCaller = /* @__PURE__ */ new Map();
267
+ for (const edge of localEdges) {
268
+ const existing = calleesByCaller.get(edge.callerFunctionId) ?? [];
269
+ existing.push(edge.calleeName);
270
+ calleesByCaller.set(edge.callerFunctionId, existing);
271
+ }
272
+ return {
273
+ functions: functions.map((fn) => fn.functionId),
274
+ edges: localEdges,
275
+ calleesByCaller
276
+ };
277
+ };
278
+ var topologicalFunctionOrder = (functions, graph) => {
279
+ const idByName = new Map(functions.map((fn) => [fn.name, fn.functionId]));
280
+ const remainingCallees = new Map(
281
+ functions.map((fn) => {
282
+ const unique = new Set(
283
+ (graph.calleesByCaller.get(fn.functionId) ?? []).map((name) => idByName.get(name)).filter((id) => Boolean(id) && id !== fn.functionId)
284
+ );
285
+ return [fn.functionId, unique.size];
286
+ })
287
+ );
288
+ const callersOf = /* @__PURE__ */ new Map();
289
+ for (const fn of functions) {
290
+ for (const calleeName of graph.calleesByCaller.get(fn.functionId) ?? []) {
291
+ const calleeId = idByName.get(calleeName);
292
+ if (!calleeId || calleeId === fn.functionId) continue;
293
+ const callers = callersOf.get(calleeId) ?? [];
294
+ callers.push(fn.functionId);
295
+ callersOf.set(calleeId, callers);
296
+ }
297
+ }
298
+ const queue = functions.filter((fn) => (remainingCallees.get(fn.functionId) ?? 0) === 0).map((fn) => fn.functionId);
299
+ const ordered = [];
300
+ const queued = new Set(queue);
301
+ while (queue.length > 0) {
302
+ const current = queue.shift();
303
+ if (current === void 0) break;
304
+ ordered.push(current);
305
+ for (const caller of callersOf.get(current) ?? []) {
306
+ const next = (remainingCallees.get(caller) ?? 0) - 1;
307
+ remainingCallees.set(caller, next);
308
+ if (next === 0 && !queued.has(caller)) {
309
+ queued.add(caller);
310
+ queue.push(caller);
311
+ }
312
+ }
313
+ }
314
+ for (const fn of functions) {
315
+ if (!queued.has(fn.functionId)) ordered.push(fn.functionId);
316
+ }
317
+ return ordered;
318
+ };
319
+
320
+ // src/derivation/derivation-ir.ts
321
+ var DerivationIrBuilder = class {
322
+ expressions = [];
323
+ nextId = 0;
324
+ constValue(value) {
325
+ return this.add({ id: this.nextId++, kind: "const", value });
326
+ }
327
+ param(index) {
328
+ return this.add({ id: this.nextId++, kind: "param", index });
329
+ }
330
+ root(rootId) {
331
+ return this.add({ id: this.nextId++, kind: "root", rootId });
332
+ }
333
+ alias(from) {
334
+ return this.add({ id: this.nextId++, kind: "alias", from });
335
+ }
336
+ binary(op, left, right, guard) {
337
+ return this.add({ id: this.nextId++, kind: "binary", op, left, right, guard });
338
+ }
339
+ unary(op, operand) {
340
+ return this.add({ id: this.nextId++, kind: "unary", op, operand });
341
+ }
342
+ logical(op, left, right) {
343
+ return this.add({ id: this.nextId++, kind: "logical", op, left, right });
344
+ }
345
+ cond(test, consequent, alternate) {
346
+ return this.add({ id: this.nextId++, kind: "cond", test, consequent, alternate });
347
+ }
348
+ object(properties) {
349
+ return this.add({ id: this.nextId++, kind: "object", properties });
350
+ }
351
+ array(elements) {
352
+ return this.add({ id: this.nextId++, kind: "array", elements });
353
+ }
354
+ prop(object, key) {
355
+ return this.add({ id: this.nextId++, kind: "prop", object, key });
356
+ }
357
+ index(array, index) {
358
+ return this.add({ id: this.nextId++, kind: "index", array, index });
359
+ }
360
+ pureCall(calleeFunctionId, args2) {
361
+ return this.add({ id: this.nextId++, kind: "pure_call", calleeFunctionId, args: args2 });
362
+ }
363
+ template(parts) {
364
+ return this.add({ id: this.nextId++, kind: "template", parts });
365
+ }
366
+ add(expression) {
367
+ this.expressions.push(expression);
368
+ return expression.id;
369
+ }
370
+ };
371
+ var emptyPlan = (functionId) => ({
372
+ planId: `dp_${functionId}`,
373
+ functionId,
374
+ expressions: [],
375
+ variables: [],
376
+ captureBoundaries: [],
377
+ controlFacts: []
378
+ });
379
+
380
+ // src/derivation/derivation-proof.ts
381
+ var functionIsExact = (plan) => plan.fallbackReason === void 0 && plan.captureBoundaries.length === 0 && plan.controlFacts.length === 0;
382
+
383
+ // src/derivation/analyze-function.ts
384
+ var loc = (node, sourceFile) => {
385
+ const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
386
+ return { line: position.line + 1, column: position.character + 1 };
387
+ };
388
+ var lookup = (ctx, name) => {
389
+ for (let index = ctx.scopes.length - 1; index >= 0; index--) {
390
+ const binding = ctx.scopes[index]?.get(name);
391
+ if (binding) return binding;
392
+ }
393
+ return void 0;
394
+ };
395
+ var fallback = (ctx, reason) => {
396
+ ctx.fallback ??= reason;
397
+ return { expressionId: ctx.ir.constValue({ type: "undefined" }), shape: "unknown" };
398
+ };
399
+ var capture = (ctx, node, reason) => {
400
+ const rootId = ctx.nextRootId.value++;
401
+ const position = loc(node, ctx.sourceFile);
402
+ const expressionId = ctx.ir.root(rootId);
403
+ ctx.captureWraps.push({
404
+ rootId,
405
+ reason,
406
+ start: node.getStart(ctx.sourceFile),
407
+ end: node.end,
408
+ line: position.line,
409
+ column: position.column,
410
+ expressionId
411
+ });
412
+ return { expressionId, shape: "unknown", captured: true };
413
+ };
414
+ var binaryOp = (kind) => {
415
+ switch (kind) {
416
+ case ts.SyntaxKind.PlusToken:
417
+ return "add";
418
+ case ts.SyntaxKind.MinusToken:
419
+ return "sub";
420
+ case ts.SyntaxKind.AsteriskToken:
421
+ return "mul";
422
+ case ts.SyntaxKind.SlashToken:
423
+ return "div";
424
+ case ts.SyntaxKind.PercentToken:
425
+ return "mod";
426
+ case ts.SyntaxKind.EqualsEqualsToken:
427
+ return "eq";
428
+ case ts.SyntaxKind.ExclamationEqualsToken:
429
+ return "neq";
430
+ case ts.SyntaxKind.EqualsEqualsEqualsToken:
431
+ return "seq";
432
+ case ts.SyntaxKind.ExclamationEqualsEqualsToken:
433
+ return "sneq";
434
+ case ts.SyntaxKind.LessThanToken:
435
+ return "lt";
436
+ case ts.SyntaxKind.LessThanEqualsToken:
437
+ return "lte";
438
+ case ts.SyntaxKind.GreaterThanToken:
439
+ return "gt";
440
+ case ts.SyntaxKind.GreaterThanEqualsToken:
441
+ return "gte";
442
+ default:
443
+ return void 0;
444
+ }
445
+ };
446
+ var isNumericBinaryOp = (op) => op === "add" || op === "sub" || op === "mul" || op === "div" || op === "mod";
447
+ var unwrapParens = (node) => {
448
+ while (ts.isParenthesizedExpression(node)) node = node.expression;
449
+ return node;
450
+ };
451
+ var numericLiteralValue = (node) => {
452
+ const value = Number(node.text);
453
+ return { type: "number", value };
454
+ };
455
+ var analyzeExpression = (ctx, node) => {
456
+ if (ctx.fallback) return fallback(ctx, ctx.fallback);
457
+ if (ts.isParenthesizedExpression(node)) return analyzeExpression(ctx, node.expression);
458
+ if (ts.isAsExpression(node) || ts.isNonNullExpression(node)) {
459
+ return analyzeExpression(ctx, node.expression);
460
+ }
461
+ if (node.kind === ts.SyntaxKind.TypeAssertionExpression) {
462
+ return analyzeExpression(ctx, node.expression);
463
+ }
464
+ if (ts.isSatisfiesExpression(node)) return analyzeExpression(ctx, node.expression);
465
+ if (node.kind === ts.SyntaxKind.NullKeyword) {
466
+ return { expressionId: ctx.ir.constValue({ type: "null" }), shape: "primitive" };
467
+ }
468
+ if (node.kind === ts.SyntaxKind.TrueKeyword) {
469
+ return {
470
+ expressionId: ctx.ir.constValue({ type: "boolean", value: true }),
471
+ shape: "primitive"
472
+ };
473
+ }
474
+ if (node.kind === ts.SyntaxKind.FalseKeyword) {
475
+ return {
476
+ expressionId: ctx.ir.constValue({ type: "boolean", value: false }),
477
+ shape: "primitive"
478
+ };
479
+ }
480
+ if (ts.isNumericLiteral(node)) {
481
+ return { expressionId: ctx.ir.constValue(numericLiteralValue(node)), shape: "primitive" };
482
+ }
483
+ if (ts.isPrefixUnaryExpression(node) && ts.isNumericLiteral(node.operand) && node.operator === ts.SyntaxKind.MinusToken) {
484
+ const value = -Number(node.operand.text);
485
+ return {
486
+ expressionId: ctx.ir.constValue(
487
+ Object.is(value, -0) ? { type: "number", value: 0, negativeZero: true } : { type: "number", value }
488
+ ),
489
+ shape: "primitive"
490
+ };
491
+ }
492
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
493
+ return {
494
+ expressionId: ctx.ir.constValue({ type: "string", value: node.text }),
495
+ shape: "primitive"
496
+ };
497
+ }
498
+ if (ts.isBigIntLiteral(node)) {
499
+ return {
500
+ expressionId: ctx.ir.constValue({ type: "bigint", value: node.text.replace(/n$/, "") }),
501
+ shape: "primitive"
502
+ };
503
+ }
504
+ if (ts.isIdentifier(node)) {
505
+ if (node.text === "undefined") {
506
+ return { expressionId: ctx.ir.constValue({ type: "undefined" }), shape: "primitive" };
507
+ }
508
+ const binding = lookup(ctx, node.text);
509
+ if (!binding) return capture(ctx, node, "external_state");
510
+ if (binding.mutable) return capture(ctx, node, "mutable_object");
511
+ return {
512
+ expressionId: ctx.ir.alias(binding.expressionId),
513
+ shape: binding.shape
514
+ };
515
+ }
516
+ if (ts.isTemplateExpression(node)) {
517
+ const parts = [
518
+ { kind: "lit", value: node.head.text }
519
+ ];
520
+ for (const span of node.templateSpans) {
521
+ const nested = analyzeExpression(ctx, span.expression);
522
+ if (nested.shape !== "primitive" && !nested.captured) {
523
+ return capture(ctx, node, "unsupported_expression");
524
+ }
525
+ parts.push({ kind: "expr", expressionId: nested.expressionId });
526
+ parts.push({ kind: "lit", value: span.literal.text });
527
+ }
528
+ return { expressionId: ctx.ir.template(parts), shape: "primitive" };
529
+ }
530
+ if (ts.isArrayLiteralExpression(node)) {
531
+ if (node.elements.some((element) => ts.isSpreadElement(element))) {
532
+ return capture(ctx, node, "unsupported_expression");
533
+ }
534
+ const elements = node.elements.map((element) => {
535
+ if (ts.isOmittedExpression(element)) return { kind: "hole" };
536
+ return analyzeExpression(ctx, element).expressionId;
537
+ });
538
+ return { expressionId: ctx.ir.array(elements), shape: "array" };
539
+ }
540
+ if (ts.isObjectLiteralExpression(node)) {
541
+ const properties = [];
542
+ for (const property of node.properties) {
543
+ if (ts.isShorthandPropertyAssignment(property)) {
544
+ const nested = analyzeExpression(ctx, property.name);
545
+ properties.push({ key: property.name.text, value: nested.expressionId });
546
+ continue;
547
+ }
548
+ if (!ts.isPropertyAssignment(property)) {
549
+ return capture(ctx, node, "unsupported_expression");
550
+ }
551
+ if (ts.isPrivateIdentifier(property.name) || ts.isComputedPropertyName(property.name)) {
552
+ return capture(ctx, node, "unsupported_expression");
553
+ }
554
+ const key = ts.isIdentifier(property.name) ? property.name.text : ts.isStringLiteral(property.name) || ts.isNumericLiteral(property.name) ? property.name.text : void 0;
555
+ if (key === void 0) return capture(ctx, node, "unsupported_expression");
556
+ properties.push({ key, value: analyzeExpression(ctx, property.initializer).expressionId });
557
+ }
558
+ return { expressionId: ctx.ir.object(properties), shape: "object" };
559
+ }
560
+ if (ts.isPropertyAccessExpression(node)) {
561
+ if (node.questionDotToken) return capture(ctx, node, "unknown_property");
562
+ const object = analyzeExpression(ctx, node.expression);
563
+ if (object.shape === "object" && ts.isIdentifier(node.name)) {
564
+ return { expressionId: ctx.ir.prop(object.expressionId, node.name.text), shape: "unknown" };
565
+ }
566
+ return capture(ctx, node, "unknown_property");
567
+ }
568
+ if (ts.isElementAccessExpression(node)) {
569
+ if (node.questionDotToken) return capture(ctx, node, "unknown_property");
570
+ const array = analyzeExpression(ctx, node.expression);
571
+ if (array.shape === "array" && ts.isNumericLiteral(node.argumentExpression)) {
572
+ return {
573
+ expressionId: ctx.ir.index(array.expressionId, Number(node.argumentExpression.text)),
574
+ shape: "unknown"
575
+ };
576
+ }
577
+ return capture(ctx, node, "unknown_property");
578
+ }
579
+ if (ts.isCallExpression(node)) {
580
+ if (node.questionDotToken || node.typeArguments?.length) {
581
+ return capture(ctx, node, "unsupported_expression");
582
+ }
583
+ if (!ts.isIdentifier(node.expression)) return capture(ctx, node, "dynamic_callee");
584
+ const calleeName = node.expression.text;
585
+ if (lookup(ctx, calleeName)) return capture(ctx, node, "opaque_call");
586
+ const provenId = ctx.provenExact.get(calleeName);
587
+ ctx.callEdges.push({
588
+ callerFunctionId: ctx.functionId,
589
+ calleeName,
590
+ ...provenId ? { calleeFunctionId: provenId } : {}
591
+ });
592
+ if (!provenId) return capture(ctx, node, "opaque_call");
593
+ const args2 = node.arguments.map((argument) => {
594
+ if (ts.isSpreadElement(argument)) return void 0;
595
+ return analyzeExpression(ctx, argument).expressionId;
596
+ });
597
+ if (args2.some((argument) => argument === void 0))
598
+ return capture(ctx, node, "unsupported_expression");
599
+ return {
600
+ expressionId: ctx.ir.pureCall(
601
+ provenId,
602
+ args2.filter((id) => id !== void 0)
603
+ ),
604
+ shape: "unknown"
605
+ };
606
+ }
607
+ if (ts.isNewExpression(node) || ts.isAwaitExpression(node) || ts.isYieldExpression(node)) {
608
+ return capture(
609
+ ctx,
610
+ node,
611
+ node.kind === ts.SyntaxKind.AwaitExpression ? "nondeterministic" : "unsupported_expression"
612
+ );
613
+ }
614
+ if (ts.isConditionalExpression(node)) {
615
+ const test = analyzeExpression(ctx, node.condition);
616
+ const consequent = analyzeExpression(ctx, node.whenTrue);
617
+ const alternate = analyzeExpression(ctx, node.whenFalse);
618
+ return {
619
+ expressionId: ctx.ir.cond(test.expressionId, consequent.expressionId, alternate.expressionId),
620
+ shape: consequent.shape === alternate.shape ? consequent.shape : "unknown"
621
+ };
622
+ }
623
+ if (ts.isBinaryExpression(node)) {
624
+ if (node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
625
+ ctx.fallback ??= "mutable_state";
626
+ return capture(ctx, node, "mutable_object");
627
+ }
628
+ if (node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === ts.SyntaxKind.BarBarToken || node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
629
+ const left2 = analyzeExpression(ctx, node.left);
630
+ const right2 = analyzeExpression(ctx, node.right);
631
+ const op2 = node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ? "and" : node.operatorToken.kind === ts.SyntaxKind.BarBarToken ? "or" : "nullish";
632
+ return {
633
+ expressionId: ctx.ir.logical(op2, left2.expressionId, right2.expressionId),
634
+ shape: "unknown"
635
+ };
636
+ }
637
+ const op = binaryOp(node.operatorToken.kind);
638
+ if (!op) return capture(ctx, node, "unsupported_expression");
639
+ if (!ts.isExpression(node.left)) return capture(ctx, node, "unsupported_expression");
640
+ const left = analyzeExpression(ctx, node.left);
641
+ const right = analyzeExpression(ctx, node.right);
642
+ const numeric = isNumericBinaryOp(op);
643
+ const expressionId = ctx.ir.binary(
644
+ op,
645
+ left.expressionId,
646
+ right.expressionId,
647
+ numeric ? "numeric" : "primitive"
648
+ );
649
+ if (numeric && (left.captured || right.captured || left.shape !== "primitive" || right.shape !== "primitive")) {
650
+ ctx.hasCoercionGuard = true;
651
+ const fallbackRootId = ctx.nextRootId.value++;
652
+ ctx.guardWraps.push({
653
+ fallbackRootId,
654
+ start: node.getStart(ctx.sourceFile),
655
+ end: node.end,
656
+ op
657
+ });
658
+ }
659
+ return { expressionId, shape: "primitive" };
660
+ }
661
+ if (ts.isPrefixUnaryExpression(node)) {
662
+ const operand = analyzeExpression(ctx, node.operand);
663
+ if (node.operator === ts.SyntaxKind.ExclamationToken) {
664
+ return { expressionId: ctx.ir.unary("not", operand.expressionId), shape: "primitive" };
665
+ }
666
+ if (node.operator === ts.SyntaxKind.PlusToken || node.operator === ts.SyntaxKind.MinusToken) {
667
+ if (operand.captured || operand.shape !== "primitive") ctx.hasCoercionGuard = true;
668
+ return {
669
+ expressionId: ctx.ir.unary(
670
+ node.operator === ts.SyntaxKind.MinusToken ? "minus" : "plus",
671
+ operand.expressionId
672
+ ),
673
+ shape: "primitive"
674
+ };
675
+ }
676
+ return capture(ctx, node, "unsupported_expression");
677
+ }
678
+ if (ts.isTypeOfExpression(node)) {
679
+ const operand = unwrapParens(node.expression);
680
+ if (ts.isIdentifier(operand) && operand.text !== "undefined" && !lookup(ctx, operand.text)) {
681
+ return capture(ctx, node, "external_state");
682
+ }
683
+ const inner = analyzeExpression(ctx, node.expression);
684
+ return { expressionId: ctx.ir.unary("typeof", inner.expressionId), shape: "primitive" };
685
+ }
686
+ return capture(ctx, node, "unsupported_expression");
687
+ };
688
+ var bindIdentifier = (ctx, name, result2, declaration) => {
689
+ const current = ctx.scopes[ctx.scopes.length - 1];
690
+ current?.set(name.text, {
691
+ expressionId: result2.expressionId,
692
+ shape: result2.shape,
693
+ mutable: false
694
+ });
695
+ const position = loc(name, ctx.sourceFile);
696
+ const available = loc(declaration, ctx.sourceFile);
697
+ ctx.variables.push({
698
+ id: ctx.nextVariableId.value++,
699
+ name: name.text,
700
+ functionId: ctx.functionId,
701
+ scopeId: `${ctx.functionId}:${ctx.scopes.length}`,
702
+ line: position.line,
703
+ column: position.column,
704
+ assignmentId: ctx.nextAssignmentId.value++,
705
+ expressionId: result2.expressionId,
706
+ availableAtLine: available.line,
707
+ availableAtColumn: available.column
708
+ });
709
+ };
710
+ var analyzeStatement = (ctx, node) => {
711
+ if (ctx.fallback) return;
712
+ if (ts.isEmptyStatement(node) || ts.isDebuggerStatement(node)) return;
713
+ if (ts.isBlock(node)) {
714
+ ctx.scopes.push(/* @__PURE__ */ new Map());
715
+ for (const statement of node.statements) analyzeStatement(ctx, statement);
716
+ ctx.scopes.pop();
717
+ return;
718
+ }
719
+ if (ts.isReturnStatement(node)) {
720
+ if (!node.expression) {
721
+ ctx.returnExpressionId = ctx.ir.constValue({ type: "undefined" });
722
+ return;
723
+ }
724
+ ctx.returnExpressionId = analyzeExpression(ctx, node.expression).expressionId;
725
+ return;
726
+ }
727
+ if (ts.isVariableStatement(node)) {
728
+ if (node.declarationList.flags & ts.NodeFlags.Using) {
729
+ fallback(ctx, "side_effect");
730
+ return;
731
+ }
732
+ for (const declaration of node.declarationList.declarations) {
733
+ if (!ts.isIdentifier(declaration.name) || !declaration.initializer) {
734
+ fallback(ctx, "unsupported_expression");
735
+ return;
736
+ }
737
+ const result2 = analyzeExpression(ctx, declaration.initializer);
738
+ bindIdentifier(ctx, declaration.name, result2, declaration);
739
+ }
740
+ return;
741
+ }
742
+ if (ts.isExpressionStatement(node)) {
743
+ analyzeExpression(ctx, node.expression);
744
+ return;
745
+ }
746
+ if (ts.isThrowStatement(node)) {
747
+ ctx.hasThrow = true;
748
+ analyzeExpression(ctx, node.expression);
749
+ return;
750
+ }
751
+ if (ts.isIfStatement(node) || ts.isSwitchStatement(node) || ts.isTryStatement(node)) {
752
+ fallback(ctx, "unsupported_expression");
753
+ return;
754
+ }
755
+ if (ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node) || ts.isWhileStatement(node) || ts.isDoStatement(node)) {
756
+ fallback(ctx, "has_loop");
757
+ return;
758
+ }
759
+ fallback(ctx, "unsupported_expression");
760
+ };
761
+ var bindParams = (ctx, node) => {
762
+ let index = 0;
763
+ for (const parameter of node.parameters) {
764
+ if (ts.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
765
+ if (!ts.isIdentifier(parameter.name) || parameter.dotDotDotToken) {
766
+ fallback(ctx, "unsupported_expression");
767
+ index++;
768
+ continue;
769
+ }
770
+ const expressionId = ctx.ir.param(index);
771
+ ctx.identifierParams.push(parameter.name.text);
772
+ bindIdentifier(ctx, parameter.name, { expressionId, shape: "unknown" }, parameter);
773
+ index++;
774
+ }
775
+ };
776
+ var analyzeOne = (node, functionId, name, sourceFile, provenExact) => {
777
+ const ctx = {
778
+ sourceFile,
779
+ functionId,
780
+ name,
781
+ ir: new DerivationIrBuilder(),
782
+ variables: [],
783
+ captureWraps: [],
784
+ guardWraps: [],
785
+ callEdges: [],
786
+ scopes: [/* @__PURE__ */ new Map()],
787
+ nextRootId: { value: 0 },
788
+ nextVariableId: { value: 0 },
789
+ nextAssignmentId: { value: 0 },
790
+ hasThrow: false,
791
+ hasCoercionGuard: false,
792
+ provenExact,
793
+ identifierParams: []
794
+ };
795
+ if (node.asteriskToken || node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)) {
796
+ ctx.fallback = "async";
797
+ } else {
798
+ bindParams(ctx, node);
799
+ if (ts.isBlock(node.body)) {
800
+ for (const statement of node.body.statements) analyzeStatement(ctx, statement);
801
+ } else {
802
+ ctx.returnExpressionId = analyzeExpression(ctx, node.body).expressionId;
803
+ }
804
+ }
805
+ const plan = {
806
+ ...emptyPlan(functionId),
807
+ expressions: ctx.ir.expressions,
808
+ variables: ctx.variables,
809
+ captureBoundaries: ctx.captureWraps.map((wrap) => ({
810
+ id: wrap.rootId,
811
+ reason: wrap.reason,
812
+ expressionId: wrap.expressionId,
813
+ line: wrap.line,
814
+ column: wrap.column
815
+ })),
816
+ controlFacts: [],
817
+ ...ctx.returnExpressionId === void 0 ? {} : { returnExpressionId: ctx.returnExpressionId },
818
+ ...ctx.fallback ? { fallbackReason: ctx.fallback } : {}
819
+ };
820
+ return {
821
+ functionId,
822
+ name,
823
+ node,
824
+ plan,
825
+ captureWraps: ctx.fallback ? [] : ctx.captureWraps,
826
+ guardWraps: ctx.fallback ? [] : ctx.guardWraps,
827
+ callEdges: ctx.callEdges,
828
+ hasThrow: ctx.hasThrow,
829
+ hasCoercionGuard: ctx.hasCoercionGuard
830
+ };
831
+ };
832
+ var analyzeModuleFunctions = (sourceFile, targets) => {
833
+ const nameCounts = /* @__PURE__ */ new Map();
834
+ for (const target of targets) {
835
+ nameCounts.set(target.name, (nameCounts.get(target.name) ?? 0) + 1);
836
+ }
837
+ const firstPass = targets.map(
838
+ (target) => analyzeOne(target.node, target.functionId, target.name, sourceFile, /* @__PURE__ */ new Map())
839
+ );
840
+ const graph = buildDependencyGraph(
841
+ firstPass.map((fn) => ({ functionId: fn.functionId, name: fn.name })),
842
+ firstPass.flatMap((fn) => fn.callEdges)
843
+ );
844
+ const order = topologicalFunctionOrder(
845
+ firstPass.map((fn) => ({ functionId: fn.functionId, name: fn.name })),
846
+ graph
847
+ );
848
+ const byId = new Map(targets.map((target) => [target.functionId, target]));
849
+ const provenExact = /* @__PURE__ */ new Map();
850
+ const analyzed = [];
851
+ for (const functionId of order) {
852
+ const target = byId.get(functionId);
853
+ if (!target) continue;
854
+ const result2 = analyzeOne(target.node, target.functionId, target.name, sourceFile, provenExact);
855
+ if (functionIsExact(result2.plan) && nameCounts.get(result2.name) === 1) {
856
+ provenExact.set(result2.name, result2.functionId);
857
+ }
858
+ analyzed.push(result2);
859
+ }
860
+ const remaining = targets.filter(
861
+ (target) => !analyzed.some((fn) => fn.functionId === target.functionId)
862
+ );
863
+ for (const target of remaining) {
864
+ analyzed.push(analyzeOne(target.node, target.functionId, target.name, sourceFile, provenExact));
865
+ }
866
+ return analyzed;
867
+ };
868
+
869
+ // src/derivation/emit-capture-plan.ts
870
+ var captureWrapEdits = (wraps, capName) => {
871
+ const edits = [];
872
+ let order = 0;
873
+ for (const wrap of wraps) {
874
+ edits.push({
875
+ position: wrap.start,
876
+ text: `${capName}(${wrap.rootId},`,
877
+ order: order++
878
+ });
879
+ edits.push({
880
+ position: wrap.end,
881
+ text: ")",
882
+ order: order++
883
+ });
884
+ }
885
+ return edits;
886
+ };
255
887
 
256
888
  // src/execution-recorder/automatic-runtime.ts
257
889
  var AUTOMATIC_RUNTIME_SYMBOL = "rasputin.execution.runtime.v2";
258
890
  var runtimeSymbol = Symbol.for(AUTOMATIC_RUNTIME_SYMBOL);
259
891
 
260
892
  // src/auto-instrumentation/collect-instrumentation-delta.ts
261
- import ts from "typescript";
893
+ import ts2 from "typescript";
262
894
  var paramBindingFromName = (name, sourceFile) => {
263
- if (ts.isIdentifier(name)) return { kind: "identifier", name: name.text };
264
- if (ts.isArrayBindingPattern(name)) {
895
+ if (ts2.isIdentifier(name)) return { kind: "identifier", name: name.text };
896
+ if (ts2.isArrayBindingPattern(name)) {
265
897
  const elements = name.elements.map(
266
898
  (element) => {
267
- if (ts.isOmittedExpression(element)) return { kind: "omitted" };
899
+ if (ts2.isOmittedExpression(element)) return { kind: "omitted" };
268
900
  const binding = paramBindingFromName(element.name, sourceFile);
269
901
  if (element.dotDotDotToken) return { kind: "rest", binding };
270
902
  return binding;
@@ -274,7 +906,7 @@ var paramBindingFromName = (name, sourceFile) => {
274
906
  }
275
907
  const properties = name.elements.map((element) => {
276
908
  const binding = paramBindingFromName(element.name, sourceFile);
277
- const key = element.propertyName ? element.propertyName.getText(sourceFile).replace(/^['"]|['"]$/g, "") : ts.isIdentifier(element.name) ? element.name.text : element.name.getText(sourceFile);
909
+ const key = element.propertyName ? element.propertyName.getText(sourceFile).replace(/^['"]|['"]$/g, "") : ts2.isIdentifier(element.name) ? element.name.text : element.name.getText(sourceFile);
278
910
  if (element.dotDotDotToken) {
279
911
  const rest = { kind: "rest", binding };
280
912
  return { key, binding: rest };
@@ -286,7 +918,7 @@ var paramBindingFromName = (name, sourceFile) => {
286
918
  var collectFunctionParams = (node, sourceFile) => {
287
919
  const params = [];
288
920
  for (const parameter of node.parameters) {
289
- if (ts.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
921
+ if (ts2.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
290
922
  const binding = paramBindingFromName(parameter.name, sourceFile);
291
923
  params.push(parameter.dotDotDotToken ? { kind: "rest", binding } : binding);
292
924
  }
@@ -327,18 +959,18 @@ var mappedLocation = (sourceFile, position, absolutePath, mapLocation) => {
327
959
  };
328
960
  var unwrapExpression = (expression) => {
329
961
  let current = expression;
330
- while (ts.isParenthesizedExpression(current) || ts.isNonNullExpression(current)) {
962
+ while (ts2.isParenthesizedExpression(current) || ts2.isNonNullExpression(current)) {
331
963
  current = current.expression;
332
964
  }
333
- if (ts.isAwaitExpression(current)) return unwrapExpression(current.expression);
965
+ if (ts2.isAwaitExpression(current)) return unwrapExpression(current.expression);
334
966
  return current;
335
967
  };
336
968
  var calleeFromExpression = (expression, sourceFile) => {
337
969
  const current = unwrapExpression(expression);
338
- if (ts.isIdentifier(current)) {
970
+ if (ts2.isIdentifier(current)) {
339
971
  return { calleeName: current.text, calleeText: current.text };
340
972
  }
341
- if (ts.isPropertyAccessExpression(current) && ts.isIdentifier(current.name)) {
973
+ if (ts2.isPropertyAccessExpression(current) && ts2.isIdentifier(current.name)) {
342
974
  return {
343
975
  calleeName: current.name.text,
344
976
  calleeText: current.getText(sourceFile).replace(/\s+/g, "")
@@ -348,9 +980,9 @@ var calleeFromExpression = (expression, sourceFile) => {
348
980
  };
349
981
  var resultBindingForCall = (call) => {
350
982
  let node = call.parent;
351
- if (ts.isAwaitExpression(node)) node = node.parent;
352
- if (!ts.isVariableDeclaration(node)) return null;
353
- if (!ts.isIdentifier(node.name)) return null;
983
+ if (ts2.isAwaitExpression(node)) node = node.parent;
984
+ if (!ts2.isVariableDeclaration(node)) return null;
985
+ if (!ts2.isIdentifier(node.name)) return null;
354
986
  const initializer = node.initializer;
355
987
  if (!initializer) return null;
356
988
  const inner = unwrapExpression(initializer);
@@ -360,18 +992,18 @@ var resultBindingForCall = (call) => {
360
992
  var argIdentifiersForCall = (call) => {
361
993
  const identifiers = [];
362
994
  call.arguments?.forEach((argument, index) => {
363
- if (ts.isIdentifier(argument)) {
995
+ if (ts2.isIdentifier(argument)) {
364
996
  identifiers.push({ index, kind: "identifier", name: argument.text });
365
997
  return;
366
998
  }
367
- if (!ts.isObjectLiteralExpression(argument)) return;
999
+ if (!ts2.isObjectLiteralExpression(argument)) return;
368
1000
  const properties = [];
369
1001
  for (const property of argument.properties) {
370
- if (ts.isShorthandPropertyAssignment(property)) {
1002
+ if (ts2.isShorthandPropertyAssignment(property)) {
371
1003
  properties.push({ key: property.name.text, name: property.name.text });
372
1004
  continue;
373
1005
  }
374
- if (ts.isPropertyAssignment(property) && ts.isIdentifier(property.name) && ts.isIdentifier(property.initializer)) {
1006
+ if (ts2.isPropertyAssignment(property) && ts2.isIdentifier(property.name) && ts2.isIdentifier(property.initializer)) {
375
1007
  properties.push({ key: property.name.text, name: property.initializer.text });
376
1008
  }
377
1009
  }
@@ -411,8 +1043,8 @@ var collectCallSite = (call, caller, context) => {
411
1043
  };
412
1044
  };
413
1045
  var collectParamDestructure = (declaration, caller, context) => {
414
- if (!declaration.initializer || !ts.isIdentifier(declaration.initializer)) return null;
415
- if (!ts.isObjectBindingPattern(declaration.name) && !ts.isArrayBindingPattern(declaration.name)) {
1046
+ if (!declaration.initializer || !ts2.isIdentifier(declaration.initializer)) return null;
1047
+ if (!ts2.isObjectBindingPattern(declaration.name) && !ts2.isArrayBindingPattern(declaration.name)) {
416
1048
  return null;
417
1049
  }
418
1050
  const sourceParam = declaration.initializer.text;
@@ -436,33 +1068,33 @@ var collectParamDestructure = (declaration, caller, context) => {
436
1068
 
437
1069
  // src/auto-instrumentation/transform-source.ts
438
1070
  var scriptKindFor = (filePath) => {
439
- if (/\.tsx$/i.test(filePath)) return ts2.ScriptKind.TSX;
440
- if (/\.jsx$/i.test(filePath)) return ts2.ScriptKind.JSX;
441
- if (/\.[cm]?ts$/i.test(filePath)) return ts2.ScriptKind.TS;
442
- return ts2.ScriptKind.JS;
1071
+ if (/\.tsx$/i.test(filePath)) return ts3.ScriptKind.TSX;
1072
+ if (/\.jsx$/i.test(filePath)) return ts3.ScriptKind.JSX;
1073
+ if (/\.[cm]?ts$/i.test(filePath)) return ts3.ScriptKind.TS;
1074
+ return ts3.ScriptKind.JS;
443
1075
  };
444
1076
  var functionName = (node, sourceFile) => {
445
1077
  if (node.name) return node.name.getText(sourceFile).replace(/^['"]|['"]$/g, "");
446
1078
  const parent = node.parent;
447
- if (ts2.isVariableDeclaration(parent) && ts2.isIdentifier(parent.name)) return parent.name.text;
448
- if (ts2.isPropertyAssignment(parent)) return parent.name.getText(sourceFile);
449
- if (ts2.isPropertyDeclaration(parent) && parent.name) return parent.name.getText(sourceFile);
1079
+ if (ts3.isVariableDeclaration(parent) && ts3.isIdentifier(parent.name)) return parent.name.text;
1080
+ if (ts3.isPropertyAssignment(parent)) return parent.name.getText(sourceFile);
1081
+ if (ts3.isPropertyDeclaration(parent) && parent.name) return parent.name.getText(sourceFile);
450
1082
  const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
451
1083
  return `anonymous@${location.line + 1}:${location.character + 1}`;
452
1084
  };
453
1085
  var functionLocationPosition = (node, sourceFile) => {
454
1086
  if (node.name) return node.name.getStart(sourceFile);
455
1087
  const parent = node.parent;
456
- if ((ts2.isVariableDeclaration(parent) || ts2.isPropertyAssignment(parent)) && parent.name) {
1088
+ if ((ts3.isVariableDeclaration(parent) || ts3.isPropertyAssignment(parent)) && parent.name) {
457
1089
  return parent.name.getStart(sourceFile);
458
1090
  }
459
1091
  return node.getStart(sourceFile);
460
1092
  };
461
1093
  var bindingValue = (name, sourceFile) => {
462
- if (ts2.isIdentifier(name)) return name.text;
463
- if (ts2.isArrayBindingPattern(name)) {
1094
+ if (ts3.isIdentifier(name)) return name.text;
1095
+ if (ts3.isArrayBindingPattern(name)) {
464
1096
  const values = name.elements.map((element) => {
465
- if (ts2.isOmittedExpression(element)) return "";
1097
+ if (ts3.isOmittedExpression(element)) return "";
466
1098
  const value = bindingValue(element.name, sourceFile);
467
1099
  return element.dotDotDotToken ? `...${value}` : value;
468
1100
  });
@@ -478,14 +1110,14 @@ var bindingValue = (name, sourceFile) => {
478
1110
  };
479
1111
  var arrowArguments = (node) => {
480
1112
  const values = node.parameters.flatMap((parameter) => {
481
- if (ts2.isIdentifier(parameter.name) && parameter.name.text === "this") return [];
1113
+ if (ts3.isIdentifier(parameter.name) && parameter.name.text === "this") return [];
482
1114
  const value = bindingValue(parameter.name, node.getSourceFile());
483
1115
  return [parameter.dotDotDotToken ? `...${value}` : value];
484
1116
  });
485
1117
  return `[${values.join(",")}]`;
486
1118
  };
487
1119
  var isSupportedFunction = (node) => {
488
- if (!ts2.isFunctionDeclaration(node) && !ts2.isFunctionExpression(node) && !ts2.isArrowFunction(node) && !ts2.isMethodDeclaration(node) && !ts2.isGetAccessorDeclaration(node) && !ts2.isSetAccessorDeclaration(node)) {
1120
+ if (!ts3.isFunctionDeclaration(node) && !ts3.isFunctionExpression(node) && !ts3.isArrowFunction(node) && !ts3.isMethodDeclaration(node) && !ts3.isGetAccessorDeclaration(node) && !ts3.isSetAccessorDeclaration(node)) {
489
1121
  return false;
490
1122
  }
491
1123
  if (!node.body || "asteriskToken" in node && node.asteriskToken) return false;
@@ -496,7 +1128,7 @@ var uniqueName = (source, base) => {
496
1128
  while (source.includes(name)) name += "_";
497
1129
  return name;
498
1130
  };
499
- var isDirectivePrologue = (statement) => ts2.isExpressionStatement(statement) && ts2.isStringLiteral(statement.expression);
1131
+ var isDirectivePrologue = (statement) => ts3.isExpressionStatement(statement) && ts3.isStringLiteral(statement.expression);
500
1132
  var insertPositionAfterPrologue = (source, sourceFile) => {
501
1133
  const shebangEnd = source.startsWith("#!") ? source.indexOf("\n") + 1 : 0;
502
1134
  let end = shebangEnd;
@@ -511,7 +1143,7 @@ var preambleInsert = (source, position, preamble) => {
511
1143
  const previous = source[position - 1];
512
1144
  return previous === ";" || previous === "\n" || previous === "\r" ? preamble : `;${preamble}`;
513
1145
  };
514
- var modulePreamble = (runName, plansName, plansJson) => `const ${plansName}=${plansJson};const ${runName}=((g,k)=>{const r=g[k]??(g[k]={runtimes:[],modules:[],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)=>d.run(i,a,c)}});return r.register(${plansName})})(globalThis,Symbol.for(${JSON.stringify(AUTOMATIC_RUNTIME_SYMBOL)}));`;
1146
+ 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)}));`;
515
1147
  var collectWrappedFunction = (node, sourceFile, absolutePath, mapLocation) => {
516
1148
  const start = mappedLocation(
517
1149
  sourceFile,
@@ -542,80 +1174,127 @@ var collectWrappedFunction = (node, sourceFile, absolutePath, mapLocation) => {
542
1174
  paramNames
543
1175
  };
544
1176
  };
1177
+ var canOmitCallValues = (analysis, analyzed) => {
1178
+ if (!functionIsExact(analysis.plan) || analysis.hasThrow || analysis.hasCoercionGuard) {
1179
+ return false;
1180
+ }
1181
+ const callers = analyzed.filter(
1182
+ (caller) => caller.callEdges.some((edge) => edge.calleeFunctionId === analysis.functionId)
1183
+ );
1184
+ if (callers.length === 0) return false;
1185
+ return callers.every((caller) => caller.plan.fallbackReason === void 0 && !caller.hasThrow);
1186
+ };
545
1187
  var transformSource = (source, options) => {
546
1188
  if (source.includes("@rasputin-ignore-file")) return void 0;
547
- const sourceFile = ts2.createSourceFile(
1189
+ const sourceFile = ts3.createSourceFile(
548
1190
  options.filePath,
549
1191
  source,
550
- ts2.ScriptTarget.Latest,
1192
+ ts3.ScriptTarget.Latest,
551
1193
  true,
552
1194
  scriptKindFor(options.filePath)
553
1195
  );
554
1196
  const runName = uniqueName(source, "__rasputinRun__");
1197
+ const capName = uniqueName(source, "__rasputinCap__");
555
1198
  const plansName = uniqueName(source, "__rasputinPlans__");
556
1199
  const absolutePath = resolve4(options.filePath);
1200
+ const derivationMode = options.derivationMode ?? "analysis";
557
1201
  const edits = [];
558
1202
  let order = 0;
559
1203
  const delta = {
560
1204
  functions: [],
561
1205
  callSites: [],
562
- destructures: []
1206
+ destructures: [],
1207
+ derivationPlans: []
563
1208
  };
564
1209
  const collectContext = {
565
1210
  sourceFile,
566
1211
  mapLocation: options.mapLocation,
567
1212
  absolutePath
568
1213
  };
1214
+ const pending = [];
569
1215
  const visit = (node, caller) => {
570
1216
  if (isSupportedFunction(node)) {
571
1217
  const collected = collectWrappedFunction(node, sourceFile, absolutePath, options.mapLocation);
572
1218
  if (!collected) {
573
- ts2.forEachChild(node, (child) => visit(child, caller));
1219
+ ts3.forEachChild(node, (child) => visit(child, caller));
574
1220
  return;
575
1221
  }
576
- const planIndex = delta.functions.length;
1222
+ pending.push({ node, collected });
577
1223
  delta.functions.push(collected);
578
- const args2 = ts2.isArrowFunction(node) ? arrowArguments(node) : "arguments";
579
- const asyncCallback = node.modifiers?.some(
580
- (modifier) => modifier.kind === ts2.SyntaxKind.AsyncKeyword
581
- ) ? "async " : "";
582
- if (ts2.isBlock(node.body)) {
583
- edits.push({
584
- position: node.body.getStart(sourceFile) + 1,
585
- text: `return ${runName}(${planIndex},${args2},${asyncCallback}()=>{`,
586
- order: order++
587
- });
588
- edits.push({ position: node.body.end - 1, text: "});", order: order++ });
589
- } else {
590
- edits.push({
591
- position: node.body.getStart(sourceFile),
592
- text: `${runName}(${planIndex},${args2},${asyncCallback}()=>(`,
593
- order: order++
594
- });
595
- edits.push({ position: node.body.end, text: "))", order: order++ });
596
- }
597
- ts2.forEachChild(node, (child) => visit(child, collected));
1224
+ ts3.forEachChild(node, (child) => visit(child, collected));
598
1225
  return;
599
1226
  }
600
- if (caller && (ts2.isCallExpression(node) || ts2.isNewExpression(node))) {
1227
+ if (caller && (ts3.isCallExpression(node) || ts3.isNewExpression(node))) {
601
1228
  const site = collectCallSite(node, caller, collectContext);
602
1229
  if (site) delta.callSites.push(site);
603
1230
  }
604
- if (caller && ts2.isVariableDeclaration(node)) {
1231
+ if (caller && ts3.isVariableDeclaration(node)) {
605
1232
  const destructure = collectParamDestructure(node, caller, collectContext);
606
1233
  if (destructure) delta.destructures.push(destructure);
607
1234
  }
608
- ts2.forEachChild(node, (child) => visit(child, caller));
1235
+ ts3.forEachChild(node, (child) => visit(child, caller));
609
1236
  };
610
1237
  visit(sourceFile);
611
- if (edits.length === 0) return void 0;
1238
+ if (pending.length === 0) return void 0;
1239
+ const analyzed = analyzeModuleFunctions(
1240
+ sourceFile,
1241
+ pending.map((entry) => ({
1242
+ functionId: entry.collected.functionId,
1243
+ name: entry.collected.name,
1244
+ node: entry.node
1245
+ }))
1246
+ );
1247
+ delta.derivationPlans = analyzed.map((fn) => fn.plan);
1248
+ const analyzedById = new Map(analyzed.map((fn) => [fn.functionId, fn]));
1249
+ for (const [planIndex, entry] of pending.entries()) {
1250
+ const analysis = analyzedById.get(entry.collected.functionId);
1251
+ const omitValues = derivationMode === "selective" && analysis && canOmitCallValues(analysis, analyzed);
1252
+ const valueFlag = omitValues ? ",1" : "";
1253
+ const args2 = ts3.isArrowFunction(entry.node) ? arrowArguments(entry.node) : "arguments";
1254
+ const asyncCallback = entry.node.modifiers?.some(
1255
+ (modifier) => modifier.kind === ts3.SyntaxKind.AsyncKeyword
1256
+ ) ? "async " : "";
1257
+ if (ts3.isBlock(entry.node.body)) {
1258
+ edits.push({
1259
+ position: entry.node.body.getStart(sourceFile) + 1,
1260
+ text: `return ${runName}(${planIndex},${args2},${asyncCallback}()=>{`,
1261
+ order: order++
1262
+ });
1263
+ edits.push({
1264
+ position: entry.node.body.end - 1,
1265
+ text: `}${valueFlag});`,
1266
+ order: order++
1267
+ });
1268
+ } else {
1269
+ edits.push({
1270
+ position: entry.node.body.getStart(sourceFile),
1271
+ text: `${runName}(${planIndex},${args2},${asyncCallback}()=>(`,
1272
+ order: order++
1273
+ });
1274
+ edits.push({
1275
+ position: entry.node.body.end,
1276
+ text: `)${valueFlag})`,
1277
+ order: order++
1278
+ });
1279
+ }
1280
+ if (analysis) {
1281
+ for (const edit of captureWrapEdits(analysis.captureWraps, capName)) {
1282
+ edits.push({ ...edit, order: order++ });
1283
+ }
1284
+ }
1285
+ }
612
1286
  const insertAt = insertPositionAfterPrologue(source, sourceFile);
613
1287
  edits.push({
614
1288
  position: insertAt,
615
1289
  text: preambleInsert(
616
1290
  source,
617
1291
  insertAt,
618
- modulePreamble(runName, plansName, JSON.stringify(delta.functions.map((fn) => fn.plan)))
1292
+ modulePreamble(
1293
+ runName,
1294
+ capName,
1295
+ plansName,
1296
+ JSON.stringify(delta.functions.map((fn) => fn.plan))
1297
+ )
619
1298
  ),
620
1299
  order: -1
621
1300
  });
@@ -667,7 +1346,8 @@ var instrumentCompiledDirectory = (options) => {
667
1346
  if (source.includes("__rasputinRun__") || source.includes("__rasputinPlans__")) continue;
668
1347
  const transformed = transformSource(source, {
669
1348
  filePath,
670
- mapLocation: createSourceMapLocationMapper(filePath, source)
1349
+ mapLocation: createSourceMapLocationMapper(filePath, source),
1350
+ ...options.derivationMode ? { derivationMode: options.derivationMode } : {}
671
1351
  });
672
1352
  if (!transformed) continue;
673
1353
  const outputPath = join2(outputDir, relative2(inputDir, filePath));
@@ -693,7 +1373,15 @@ var sdkPackageScopeRootFrom = (instrumentEntryUrl) => resolve6(dirname3(fileURLT
693
1373
 
694
1374
  // src/instrument/build.ts
695
1375
  var printUsage = () => {
696
- console.error("Usage: rasputin-instrument <input-dir> [--out <output-dir>] [--manifest <path>]");
1376
+ console.error(
1377
+ "Usage: rasputin-instrument <input-dir> [--out <output-dir>] [--manifest <path>] [--derivation-mode analysis|selective]"
1378
+ );
1379
+ };
1380
+ var parseDerivationMode = (value) => {
1381
+ if (value === void 0) return void 0;
1382
+ if (value === "analysis" || value === "selective") return value;
1383
+ console.error(`[rasputin] unknown --derivation-mode ${value} (expected analysis or selective)`);
1384
+ process.exit(1);
697
1385
  };
698
1386
  var parseArgs = (argv) => {
699
1387
  const inputDir = argv.find((argument) => !argument.startsWith("--"));
@@ -704,7 +1392,8 @@ var parseArgs = (argv) => {
704
1392
  return {
705
1393
  inputDir,
706
1394
  outputDir: option("--out"),
707
- manifestPath: option("--manifest")
1395
+ manifestPath: option("--manifest"),
1396
+ derivationMode: parseDerivationMode(option("--derivation-mode"))
708
1397
  };
709
1398
  };
710
1399
  var args = parseArgs(process.argv.slice(2));
@@ -716,7 +1405,8 @@ var result = instrumentCompiledDirectory({
716
1405
  inputDir: args.inputDir,
717
1406
  outputDir: args.outputDir,
718
1407
  manifestPath: args.manifestPath,
719
- excludedRoots: [sdkPackageScopeRootFrom(import.meta.url)]
1408
+ excludedRoots: [sdkPackageScopeRootFrom(import.meta.url)],
1409
+ ...args.derivationMode ? { derivationMode: args.derivationMode } : {}
720
1410
  });
721
1411
  console.info(
722
1412
  `[rasputin] instrumented ${result.transformedFiles.length} file${result.transformedFiles.length === 1 ? "" : "s"}`