@rasputin-ai/node 0.5.0-alpha.13 → 0.5.0-alpha.14

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 (79) hide show
  1. package/README.md +62 -33
  2. package/dist/auto-instrumentation/bun-plugin.d.ts.map +1 -1
  3. package/dist/auto-instrumentation/format-configuration-report.d.ts +4 -0
  4. package/dist/auto-instrumentation/format-configuration-report.d.ts.map +1 -1
  5. package/dist/auto-instrumentation/instrument-compiled-esm.d.ts +1 -3
  6. package/dist/auto-instrumentation/instrument-compiled-esm.d.ts.map +1 -1
  7. package/dist/auto-instrumentation/instrumentation-manifest-delta.d.ts +8 -4
  8. package/dist/auto-instrumentation/instrumentation-manifest-delta.d.ts.map +1 -1
  9. package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts +6 -0
  10. package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts.map +1 -1
  11. package/dist/auto-instrumentation/node-hooks.d.ts.map +1 -1
  12. package/dist/auto-instrumentation/parameter-initializer-may-run-code.d.ts +3 -0
  13. package/dist/auto-instrumentation/parameter-initializer-may-run-code.d.ts.map +1 -0
  14. package/dist/auto-instrumentation/schedule-instrumentation-manifest-upload.d.ts +1 -1
  15. package/dist/auto-instrumentation/schedule-instrumentation-manifest-upload.d.ts.map +1 -1
  16. package/dist/auto-instrumentation/source-map-locations.d.ts +1 -0
  17. package/dist/auto-instrumentation/source-map-locations.d.ts.map +1 -1
  18. package/dist/auto-instrumentation/suspension-whitelist.d.ts +33 -0
  19. package/dist/auto-instrumentation/suspension-whitelist.d.ts.map +1 -0
  20. package/dist/auto-instrumentation/transform-disk-cache.d.ts +7 -0
  21. package/dist/auto-instrumentation/transform-disk-cache.d.ts.map +1 -0
  22. package/dist/auto-instrumentation/transform-source.d.ts +9 -6
  23. package/dist/auto-instrumentation/transform-source.d.ts.map +1 -1
  24. package/dist/auto-instrumentation/verify-instrumentation-isomorphism.d.ts +2 -0
  25. package/dist/auto-instrumentation/verify-instrumentation-isomorphism.d.ts.map +1 -0
  26. package/dist/auto-instrumentation/verify-transformed-source.d.ts +2 -0
  27. package/dist/auto-instrumentation/verify-transformed-source.d.ts.map +1 -0
  28. package/dist/execution-recorder/automatic-runtime.d.ts +17 -7
  29. package/dist/execution-recorder/automatic-runtime.d.ts.map +1 -1
  30. package/dist/execution-recorder/encode-frozen.d.ts +16 -0
  31. package/dist/execution-recorder/encode-frozen.d.ts.map +1 -0
  32. package/dist/execution-recorder/execution-journal.d.ts.map +1 -1
  33. package/dist/execution-recorder/execution-recorder-types.d.ts +122 -38
  34. package/dist/execution-recorder/execution-recorder-types.d.ts.map +1 -1
  35. package/dist/execution-recorder/execution-recorder.d.ts +2 -2
  36. package/dist/execution-recorder/execution-recorder.d.ts.map +1 -1
  37. package/dist/execution-recorder/function-plan.d.ts +8 -1
  38. package/dist/execution-recorder/function-plan.d.ts.map +1 -1
  39. package/dist/execution-recorder/index.d.ts +1 -1
  40. package/dist/execution-recorder/index.d.ts.map +1 -1
  41. package/dist/execution-recorder/recorder-memory-budget.d.ts +1 -0
  42. package/dist/execution-recorder/recorder-memory-budget.d.ts.map +1 -1
  43. package/dist/execution-recorder/redact-keys.d.ts +4 -0
  44. package/dist/execution-recorder/redact-keys.d.ts.map +1 -0
  45. package/dist/execution-recorder/run-exactly-once.d.ts +7 -0
  46. package/dist/execution-recorder/run-exactly-once.d.ts.map +1 -0
  47. package/dist/execution-recorder/runtime-state-size-budget.d.ts +3 -3
  48. package/dist/execution-recorder/runtime-state-size-budget.d.ts.map +1 -1
  49. package/dist/execution-recorder/safe-serialize.d.ts +9 -3
  50. package/dist/execution-recorder/safe-serialize.d.ts.map +1 -1
  51. package/dist/execution-recorder/value-freeze.d.ts +43 -0
  52. package/dist/execution-recorder/value-freeze.d.ts.map +1 -0
  53. package/dist/execution-recorder/warn-missing-async-context-frame.d.ts +10 -0
  54. package/dist/execution-recorder/warn-missing-async-context-frame.d.ts.map +1 -0
  55. package/dist/index.d.ts +1 -6
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +2420 -954
  58. package/dist/install-global-handlers.d.ts +29 -8
  59. package/dist/install-global-handlers.d.ts.map +1 -1
  60. package/dist/instrument/build.js +947 -805
  61. package/dist/instrument/bun.js +1098 -778
  62. package/dist/instrument/node.js +1078 -780
  63. package/dist/internal.d.ts +7 -0
  64. package/dist/internal.d.ts.map +1 -0
  65. package/dist/internal.js +4072 -0
  66. package/dist/rasputin-init.d.ts +25 -5
  67. package/dist/rasputin-init.d.ts.map +1 -1
  68. package/dist/sdk-meta.d.ts +1 -1
  69. package/package.json +7 -2
  70. package/dist/derivation/analyze-function.d.ts +0 -47
  71. package/dist/derivation/analyze-function.d.ts.map +0 -1
  72. package/dist/derivation/build-dependency-graph.d.ts +0 -26
  73. package/dist/derivation/build-dependency-graph.d.ts.map +0 -1
  74. package/dist/derivation/derivation-ir.d.ts +0 -38
  75. package/dist/derivation/derivation-ir.d.ts.map +0 -1
  76. package/dist/derivation/emit-capture-plan.d.ts +0 -14
  77. package/dist/derivation/emit-capture-plan.d.ts.map +0 -1
  78. package/dist/derivation/plan-is-exact.d.ts +0 -7
  79. package/dist/derivation/plan-is-exact.d.ts.map +0 -1
@@ -3,7 +3,7 @@ import { plugin } from "bun";
3
3
 
4
4
  // src/auto-instrumentation/bun-plugin.ts
5
5
  import { readFile } from "node:fs/promises";
6
- import { resolve as resolve4 } from "node:path";
6
+ import { resolve as resolve5 } from "node:path";
7
7
 
8
8
  // src/auto-instrumentation/instrumentation-manifest-registry.ts
9
9
  import { resolve } from "node:path";
@@ -17,6 +17,16 @@ import {
17
17
  runtimeFunctionId,
18
18
  sourceLocatorFromFile
19
19
  } from "@rasputin-ai/core";
20
+ var withPreBodyParameterEvaluation = (plan) => [
21
+ plan[0],
22
+ plan[1],
23
+ plan[2],
24
+ plan[3],
25
+ plan[4],
26
+ plan[5],
27
+ plan[6],
28
+ 1
29
+ ];
20
30
  var encodeFunctionPlan = (source) => {
21
31
  const locator = sourceLocatorFromFile(source.filePath);
22
32
  const definition = {
@@ -39,19 +49,26 @@ var registrySymbol = Symbol.for(INSTRUMENTATION_MANIFEST_SYMBOL);
39
49
  var getRegistry = () => {
40
50
  const existing = Reflect.get(globalThis, registrySymbol);
41
51
  if (existing?.__rasputinManifestRegistry && existing.files instanceof Map && typeof existing.generation === "number" && typeof existing.uploadedGeneration === "number") {
42
- return existing;
52
+ const registry2 = existing;
53
+ if (typeof registry2.alreadyInstrumented !== "number") registry2.alreadyInstrumented = 0;
54
+ return registry2;
43
55
  }
44
56
  const registry = {
45
57
  __rasputinManifestRegistry: true,
46
58
  files: /* @__PURE__ */ new Map(),
47
59
  generation: 0,
48
- uploadedGeneration: 0
60
+ uploadedGeneration: 0,
61
+ alreadyInstrumented: 0
49
62
  };
50
63
  Reflect.set(globalThis, registrySymbol, registry);
51
64
  return registry;
52
65
  };
53
66
  var recordInstrumentationManifestDelta = (filePath, delta) => {
54
67
  const registry = getRegistry();
68
+ if (delta.alreadyInstrumented) {
69
+ registry.alreadyInstrumented += 1;
70
+ return;
71
+ }
55
72
  registry.files.set(resolve(filePath), delta);
56
73
  registry.generation += 1;
57
74
  registry.onDirty?.();
@@ -86,665 +103,74 @@ var isApplicationSource = (filePath, options = {}) => {
86
103
  return true;
87
104
  };
88
105
 
89
- // src/auto-instrumentation/transform-source.ts
90
- import { resolve as resolve3 } from "node:path";
91
- import ts3 from "typescript";
106
+ // src/auto-instrumentation/transform-disk-cache.ts
107
+ import { createHash } from "node:crypto";
108
+ import {
109
+ chmodSync,
110
+ lstatSync,
111
+ mkdirSync,
112
+ readdirSync,
113
+ readFileSync as readFileSync2,
114
+ statSync,
115
+ unlinkSync,
116
+ writeFileSync
117
+ } from "node:fs";
118
+ import { homedir } from "node:os";
119
+ import { dirname as dirname2, join as join2 } from "node:path";
92
120
 
93
- // src/derivation/analyze-function.ts
94
- import ts from "typescript";
121
+ // src/sdk-meta.ts
122
+ var SDK_VERSION = "0.5.0-alpha.14";
95
123
 
96
- // src/derivation/build-dependency-graph.ts
97
- var buildDependencyGraph = (functions, edges) => {
98
- const names = new Set(functions.map((fn) => fn.name));
99
- const localEdges = edges.filter((edge) => names.has(edge.calleeName));
100
- const calleesByCaller = /* @__PURE__ */ new Map();
101
- for (const edge of localEdges) {
102
- const existing = calleesByCaller.get(edge.callerFunctionId) ?? [];
103
- existing.push(edge.calleeName);
104
- calleesByCaller.set(edge.callerFunctionId, existing);
105
- }
106
- return {
107
- functions: functions.map((fn) => fn.functionId),
108
- edges: localEdges,
109
- calleesByCaller
110
- };
111
- };
112
- var topologicalFunctionOrder = (functions, graph) => {
113
- const idByName = new Map(functions.map((fn) => [fn.name, fn.functionId]));
114
- const remainingCallees = new Map(
115
- functions.map((fn) => {
116
- const unique = new Set(
117
- (graph.calleesByCaller.get(fn.functionId) ?? []).map((name) => idByName.get(name)).filter((id) => Boolean(id) && id !== fn.functionId)
118
- );
119
- return [fn.functionId, unique.size];
120
- })
121
- );
122
- const callersOf = /* @__PURE__ */ new Map();
123
- for (const fn of functions) {
124
- for (const calleeName of graph.calleesByCaller.get(fn.functionId) ?? []) {
125
- const calleeId = idByName.get(calleeName);
126
- if (!calleeId || calleeId === fn.functionId) continue;
127
- const callers = callersOf.get(calleeId) ?? [];
128
- callers.push(fn.functionId);
129
- callersOf.set(calleeId, callers);
130
- }
131
- }
132
- const queue = functions.filter((fn) => (remainingCallees.get(fn.functionId) ?? 0) === 0).map((fn) => fn.functionId);
133
- const ordered = [];
134
- const queued = new Set(queue);
135
- while (queue.length > 0) {
136
- const current = queue.shift();
137
- if (current === void 0) break;
138
- ordered.push(current);
139
- for (const caller of callersOf.get(current) ?? []) {
140
- const next = (remainingCallees.get(caller) ?? 0) - 1;
141
- remainingCallees.set(caller, next);
142
- if (next === 0 && !queued.has(caller)) {
143
- queued.add(caller);
144
- queue.push(caller);
145
- }
146
- }
147
- }
148
- for (const fn of functions) {
149
- if (!queued.has(fn.functionId)) ordered.push(fn.functionId);
150
- }
151
- return ordered;
124
+ // src/auto-instrumentation/source-map-locations.ts
125
+ import { readFileSync } from "node:fs";
126
+ import { dirname, isAbsolute as isAbsolute2, join, resolve as resolve3 } from "node:path";
127
+ import { fileURLToPath, pathToFileURL } from "node:url";
128
+ import { LEAST_UPPER_BOUND, originalPositionFor, TraceMap } from "@jridgewell/trace-mapping";
129
+ var sourceMapComment = /(?:\/\/[#@][ \t]*sourceMappingURL=([^\s'"]+)|\/\*[#@][ \t]*sourceMappingURL=([^\s*'"]+)[ \t]*\*\/)\s*$/;
130
+ var decodeDataUrl = (url) => {
131
+ const base64 = /^data:application\/json(?:;charset=[^;,]+)?;base64,(.+)$/i.exec(url);
132
+ if (base64?.[1]) return Buffer.from(base64[1], "base64").toString("utf8");
133
+ const raw = /^data:application\/json(?:;charset=[^;,]+)?,(.+)$/i.exec(url);
134
+ return raw?.[1] ? decodeURIComponent(raw[1]) : void 0;
152
135
  };
153
-
154
- // src/derivation/derivation-ir.ts
155
- var DerivationIrBuilder = class {
156
- expressions = [];
157
- nextId = 0;
158
- constValue(value) {
159
- return this.add({ id: this.nextId++, kind: "const", value });
160
- }
161
- param(index) {
162
- return this.add({ id: this.nextId++, kind: "param", index });
163
- }
164
- root(rootId) {
165
- return this.add({ id: this.nextId++, kind: "root", rootId });
166
- }
167
- alias(from) {
168
- return this.add({ id: this.nextId++, kind: "alias", from });
169
- }
170
- binary(op, left, right, guard) {
171
- return this.add({ id: this.nextId++, kind: "binary", op, left, right, guard });
172
- }
173
- unary(op, operand) {
174
- return this.add({ id: this.nextId++, kind: "unary", op, operand });
175
- }
176
- logical(op, left, right) {
177
- return this.add({ id: this.nextId++, kind: "logical", op, left, right });
178
- }
179
- cond(test, consequent, alternate) {
180
- return this.add({ id: this.nextId++, kind: "cond", test, consequent, alternate });
181
- }
182
- object(properties) {
183
- return this.add({ id: this.nextId++, kind: "object", properties });
184
- }
185
- array(elements) {
186
- return this.add({ id: this.nextId++, kind: "array", elements });
187
- }
188
- prop(object, key) {
189
- return this.add({ id: this.nextId++, kind: "prop", object, key });
190
- }
191
- index(array, index) {
192
- return this.add({ id: this.nextId++, kind: "index", array, index });
193
- }
194
- pureCall(calleeFunctionId, args) {
195
- return this.add({ id: this.nextId++, kind: "pure_call", calleeFunctionId, args });
196
- }
197
- template(parts) {
198
- return this.add({ id: this.nextId++, kind: "template", parts });
199
- }
200
- add(expression) {
201
- this.expressions.push(expression);
202
- return expression.id;
203
- }
204
- };
205
- var emptyPlan = (functionId) => ({
206
- planId: `dp_${functionId}`,
207
- functionId,
208
- expressions: [],
209
- variables: [],
210
- captureBoundaries: [],
211
- controlFacts: []
212
- });
213
-
214
- // src/derivation/analyze-function.ts
215
- var loc = (node, sourceFile) => {
216
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
217
- return { line: position.line + 1, column: position.character + 1 };
218
- };
219
- var lookup = (ctx, name) => {
220
- for (let index = ctx.scopes.length - 1; index >= 0; index--) {
221
- const binding = ctx.scopes[index]?.get(name);
222
- if (binding) return binding;
223
- }
224
- return void 0;
225
- };
226
- var fallback = (ctx, reason) => {
227
- ctx.fallback ??= reason;
228
- return { expressionId: ctx.ir.constValue({ type: "undefined" }), shape: "unknown" };
229
- };
230
- var capture = (ctx, node, reason) => {
231
- const rootId = ctx.nextRootId.value++;
232
- const position = loc(node, ctx.sourceFile);
233
- const expressionId = ctx.ir.root(rootId);
234
- ctx.captureWraps.push({
235
- rootId,
236
- reason,
237
- start: node.getStart(ctx.sourceFile),
238
- end: node.end,
239
- line: position.line,
240
- column: position.column,
241
- expressionId
242
- });
243
- return { expressionId, shape: "unknown", captured: true };
244
- };
245
- var binaryOp = (kind) => {
246
- switch (kind) {
247
- case ts.SyntaxKind.PlusToken:
248
- return "add";
249
- case ts.SyntaxKind.MinusToken:
250
- return "sub";
251
- case ts.SyntaxKind.AsteriskToken:
252
- return "mul";
253
- case ts.SyntaxKind.SlashToken:
254
- return "div";
255
- case ts.SyntaxKind.PercentToken:
256
- return "mod";
257
- case ts.SyntaxKind.EqualsEqualsToken:
258
- return "eq";
259
- case ts.SyntaxKind.ExclamationEqualsToken:
260
- return "neq";
261
- case ts.SyntaxKind.EqualsEqualsEqualsToken:
262
- return "seq";
263
- case ts.SyntaxKind.ExclamationEqualsEqualsToken:
264
- return "sneq";
265
- case ts.SyntaxKind.LessThanToken:
266
- return "lt";
267
- case ts.SyntaxKind.LessThanEqualsToken:
268
- return "lte";
269
- case ts.SyntaxKind.GreaterThanToken:
270
- return "gt";
271
- case ts.SyntaxKind.GreaterThanEqualsToken:
272
- return "gte";
273
- default:
274
- return void 0;
275
- }
276
- };
277
- var isNumericBinaryOp = (op) => op === "add" || op === "sub" || op === "mul" || op === "div" || op === "mod";
278
- var unwrapParens = (node) => {
279
- while (ts.isParenthesizedExpression(node)) node = node.expression;
280
- return node;
281
- };
282
- var numericLiteralValue = (node) => {
283
- const value = Number(node.text);
284
- return { type: "number", value };
285
- };
286
- var analyzeExpression = (ctx, node) => {
287
- if (ctx.fallback) return fallback(ctx, ctx.fallback);
288
- if (ts.isParenthesizedExpression(node)) return analyzeExpression(ctx, node.expression);
289
- if (ts.isAsExpression(node) || ts.isNonNullExpression(node)) {
290
- return analyzeExpression(ctx, node.expression);
291
- }
292
- if (node.kind === ts.SyntaxKind.TypeAssertionExpression) {
293
- return analyzeExpression(ctx, node.expression);
294
- }
295
- if (ts.isSatisfiesExpression(node)) return analyzeExpression(ctx, node.expression);
296
- if (node.kind === ts.SyntaxKind.NullKeyword) {
297
- return { expressionId: ctx.ir.constValue({ type: "null" }), shape: "primitive" };
298
- }
299
- if (node.kind === ts.SyntaxKind.TrueKeyword) {
300
- return {
301
- expressionId: ctx.ir.constValue({ type: "boolean", value: true }),
302
- shape: "primitive"
303
- };
304
- }
305
- if (node.kind === ts.SyntaxKind.FalseKeyword) {
306
- return {
307
- expressionId: ctx.ir.constValue({ type: "boolean", value: false }),
308
- shape: "primitive"
309
- };
310
- }
311
- if (ts.isNumericLiteral(node)) {
312
- return { expressionId: ctx.ir.constValue(numericLiteralValue(node)), shape: "primitive" };
313
- }
314
- if (ts.isPrefixUnaryExpression(node) && ts.isNumericLiteral(node.operand) && node.operator === ts.SyntaxKind.MinusToken) {
315
- const value = -Number(node.operand.text);
316
- return {
317
- expressionId: ctx.ir.constValue(
318
- Object.is(value, -0) ? { type: "number", value: 0, negativeZero: true } : { type: "number", value }
319
- ),
320
- shape: "primitive"
321
- };
322
- }
323
- if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
324
- return {
325
- expressionId: ctx.ir.constValue({ type: "string", value: node.text }),
326
- shape: "primitive"
327
- };
328
- }
329
- if (ts.isBigIntLiteral(node)) {
330
- return {
331
- expressionId: ctx.ir.constValue({ type: "bigint", value: node.text.replace(/n$/, "") }),
332
- shape: "primitive"
333
- };
334
- }
335
- if (ts.isIdentifier(node)) {
336
- if (node.text === "undefined") {
337
- return { expressionId: ctx.ir.constValue({ type: "undefined" }), shape: "primitive" };
338
- }
339
- const binding = lookup(ctx, node.text);
340
- if (!binding) return capture(ctx, node, "external_state");
341
- if (binding.mutable) return capture(ctx, node, "mutable_object");
342
- return {
343
- expressionId: ctx.ir.alias(binding.expressionId),
344
- shape: binding.shape
345
- };
346
- }
347
- if (ts.isTemplateExpression(node)) {
348
- const parts = [
349
- { kind: "lit", value: node.head.text }
350
- ];
351
- for (const span of node.templateSpans) {
352
- const nested = analyzeExpression(ctx, span.expression);
353
- if (nested.shape !== "primitive" && !nested.captured) {
354
- return capture(ctx, node, "unsupported_expression");
355
- }
356
- parts.push({ kind: "expr", expressionId: nested.expressionId });
357
- parts.push({ kind: "lit", value: span.literal.text });
358
- }
359
- return { expressionId: ctx.ir.template(parts), shape: "primitive" };
360
- }
361
- if (ts.isArrayLiteralExpression(node)) {
362
- if (node.elements.some((element) => ts.isSpreadElement(element))) {
363
- return capture(ctx, node, "unsupported_expression");
364
- }
365
- const elements = node.elements.map((element) => {
366
- if (ts.isOmittedExpression(element)) return { kind: "hole" };
367
- return analyzeExpression(ctx, element).expressionId;
368
- });
369
- return { expressionId: ctx.ir.array(elements), shape: "array" };
370
- }
371
- if (ts.isObjectLiteralExpression(node)) {
372
- const properties = [];
373
- for (const property of node.properties) {
374
- if (ts.isShorthandPropertyAssignment(property)) {
375
- const nested = analyzeExpression(ctx, property.name);
376
- if (nested.captured) {
377
- const wrap = ctx.captureWraps.find(
378
- (candidate) => candidate.expressionId === nested.expressionId
379
- );
380
- if (wrap) wrap.shorthandPropertyName = property.name.text;
381
- }
382
- properties.push({ key: property.name.text, value: nested.expressionId });
383
- continue;
384
- }
385
- if (!ts.isPropertyAssignment(property)) {
386
- return capture(ctx, node, "unsupported_expression");
387
- }
388
- if (ts.isPrivateIdentifier(property.name) || ts.isComputedPropertyName(property.name)) {
389
- return capture(ctx, node, "unsupported_expression");
390
- }
391
- const key = ts.isIdentifier(property.name) ? property.name.text : ts.isStringLiteral(property.name) || ts.isNumericLiteral(property.name) ? property.name.text : void 0;
392
- if (key === void 0) return capture(ctx, node, "unsupported_expression");
393
- properties.push({ key, value: analyzeExpression(ctx, property.initializer).expressionId });
394
- }
395
- return { expressionId: ctx.ir.object(properties), shape: "object" };
396
- }
397
- if (ts.isPropertyAccessExpression(node)) {
398
- if (node.questionDotToken) return capture(ctx, node, "unknown_property");
399
- const object = analyzeExpression(ctx, node.expression);
400
- if (object.shape === "object" && ts.isIdentifier(node.name)) {
401
- return { expressionId: ctx.ir.prop(object.expressionId, node.name.text), shape: "unknown" };
402
- }
403
- return capture(ctx, node, "unknown_property");
404
- }
405
- if (ts.isElementAccessExpression(node)) {
406
- if (node.questionDotToken) return capture(ctx, node, "unknown_property");
407
- const array = analyzeExpression(ctx, node.expression);
408
- if (array.shape === "array" && ts.isNumericLiteral(node.argumentExpression)) {
409
- return {
410
- expressionId: ctx.ir.index(array.expressionId, Number(node.argumentExpression.text)),
411
- shape: "unknown"
412
- };
413
- }
414
- return capture(ctx, node, "unknown_property");
415
- }
416
- if (ts.isCallExpression(node)) {
417
- if (node.questionDotToken || node.typeArguments?.length) {
418
- return capture(ctx, node, "unsupported_expression");
419
- }
420
- if (!ts.isIdentifier(node.expression)) return capture(ctx, node, "dynamic_callee");
421
- const calleeName = node.expression.text;
422
- if (lookup(ctx, calleeName)) return capture(ctx, node, "opaque_call");
423
- const provenId = ctx.moduleCallees.get(calleeName);
424
- ctx.callEdges.push({
425
- callerFunctionId: ctx.functionId,
426
- calleeName,
427
- ...provenId ? { calleeFunctionId: provenId } : {}
428
- });
429
- if (!provenId) return capture(ctx, node, "opaque_call");
430
- const args = node.arguments.map((argument) => {
431
- if (ts.isSpreadElement(argument)) return void 0;
432
- return analyzeExpression(ctx, argument).expressionId;
433
- });
434
- if (args.some((argument) => argument === void 0))
435
- return capture(ctx, node, "unsupported_expression");
436
- return {
437
- expressionId: ctx.ir.pureCall(
438
- provenId,
439
- args.filter((id) => id !== void 0)
440
- ),
441
- shape: "unknown"
442
- };
443
- }
444
- if (ts.isNewExpression(node) || ts.isAwaitExpression(node) || ts.isYieldExpression(node)) {
445
- return capture(
446
- ctx,
447
- node,
448
- node.kind === ts.SyntaxKind.AwaitExpression ? "nondeterministic" : "unsupported_expression"
449
- );
450
- }
451
- if (ts.isConditionalExpression(node)) {
452
- const test = analyzeExpression(ctx, node.condition);
453
- const consequent = analyzeExpression(ctx, node.whenTrue);
454
- const alternate = analyzeExpression(ctx, node.whenFalse);
455
- return {
456
- expressionId: ctx.ir.cond(test.expressionId, consequent.expressionId, alternate.expressionId),
457
- shape: consequent.shape === alternate.shape ? consequent.shape : "unknown"
458
- };
459
- }
460
- if (ts.isBinaryExpression(node)) {
461
- if (node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
462
- ctx.fallback ??= "mutable_state";
463
- return capture(ctx, node, "mutable_object");
464
- }
465
- if (node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === ts.SyntaxKind.BarBarToken || node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
466
- const left2 = analyzeExpression(ctx, node.left);
467
- const right2 = analyzeExpression(ctx, node.right);
468
- const op2 = node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ? "and" : node.operatorToken.kind === ts.SyntaxKind.BarBarToken ? "or" : "nullish";
469
- return {
470
- expressionId: ctx.ir.logical(op2, left2.expressionId, right2.expressionId),
471
- shape: "unknown"
472
- };
473
- }
474
- const op = binaryOp(node.operatorToken.kind);
475
- if (!op) return capture(ctx, node, "unsupported_expression");
476
- if (!ts.isExpression(node.left)) return capture(ctx, node, "unsupported_expression");
477
- const left = analyzeExpression(ctx, node.left);
478
- const right = analyzeExpression(ctx, node.right);
479
- const numeric = isNumericBinaryOp(op);
480
- const expressionId = ctx.ir.binary(
481
- op,
482
- left.expressionId,
483
- right.expressionId,
484
- numeric ? "numeric" : "primitive"
485
- );
486
- if (numeric && (left.captured || right.captured || left.shape !== "primitive" || right.shape !== "primitive")) {
487
- ctx.hasCoercionGuard = true;
488
- const fallbackRootId = ctx.nextRootId.value++;
489
- ctx.guardWraps.push({
490
- fallbackRootId,
491
- start: node.getStart(ctx.sourceFile),
492
- end: node.end,
493
- op
494
- });
495
- }
496
- return { expressionId, shape: "primitive" };
497
- }
498
- if (ts.isPrefixUnaryExpression(node)) {
499
- const operand = analyzeExpression(ctx, node.operand);
500
- if (node.operator === ts.SyntaxKind.ExclamationToken) {
501
- return { expressionId: ctx.ir.unary("not", operand.expressionId), shape: "primitive" };
502
- }
503
- if (node.operator === ts.SyntaxKind.PlusToken || node.operator === ts.SyntaxKind.MinusToken) {
504
- if (operand.captured || operand.shape !== "primitive") ctx.hasCoercionGuard = true;
505
- return {
506
- expressionId: ctx.ir.unary(
507
- node.operator === ts.SyntaxKind.MinusToken ? "minus" : "plus",
508
- operand.expressionId
509
- ),
510
- shape: "primitive"
511
- };
512
- }
513
- return capture(ctx, node, "unsupported_expression");
514
- }
515
- if (ts.isTypeOfExpression(node)) {
516
- const operand = unwrapParens(node.expression);
517
- if (ts.isIdentifier(operand) && operand.text !== "undefined" && !lookup(ctx, operand.text)) {
518
- return capture(ctx, node, "external_state");
519
- }
520
- const inner = analyzeExpression(ctx, node.expression);
521
- return { expressionId: ctx.ir.unary("typeof", inner.expressionId), shape: "primitive" };
522
- }
523
- return capture(ctx, node, "unsupported_expression");
524
- };
525
- var bindIdentifier = (ctx, name, result, declaration) => {
526
- const current = ctx.scopes[ctx.scopes.length - 1];
527
- current?.set(name.text, {
528
- expressionId: result.expressionId,
529
- shape: result.shape,
530
- mutable: false
531
- });
532
- const position = loc(name, ctx.sourceFile);
533
- const available = loc(declaration, ctx.sourceFile);
534
- ctx.variables.push({
535
- id: ctx.nextVariableId.value++,
536
- name: name.text,
537
- functionId: ctx.functionId,
538
- scopeId: `${ctx.functionId}:${ctx.scopes.length}`,
539
- line: position.line,
540
- column: position.column,
541
- assignmentId: ctx.nextAssignmentId.value++,
542
- expressionId: result.expressionId,
543
- availableAtLine: available.line,
544
- availableAtColumn: available.column
545
- });
546
- };
547
- var analyzeStatement = (ctx, node) => {
548
- if (ctx.fallback) return;
549
- if (ts.isEmptyStatement(node) || ts.isDebuggerStatement(node)) return;
550
- if (ts.isBlock(node)) {
551
- ctx.scopes.push(/* @__PURE__ */ new Map());
552
- for (const statement of node.statements) analyzeStatement(ctx, statement);
553
- ctx.scopes.pop();
554
- return;
555
- }
556
- if (ts.isReturnStatement(node)) {
557
- if (!node.expression) {
558
- ctx.returnExpressionId = ctx.ir.constValue({ type: "undefined" });
559
- return;
560
- }
561
- ctx.returnExpressionId = analyzeExpression(ctx, node.expression).expressionId;
562
- return;
563
- }
564
- if (ts.isVariableStatement(node)) {
565
- if (node.declarationList.flags & ts.NodeFlags.Using) {
566
- fallback(ctx, "side_effect");
567
- return;
568
- }
569
- for (const declaration of node.declarationList.declarations) {
570
- if (!ts.isIdentifier(declaration.name) || !declaration.initializer) {
571
- fallback(ctx, "unsupported_expression");
572
- return;
573
- }
574
- const result = analyzeExpression(ctx, declaration.initializer);
575
- bindIdentifier(ctx, declaration.name, result, declaration);
576
- }
577
- return;
578
- }
579
- if (ts.isExpressionStatement(node)) {
580
- analyzeExpression(ctx, node.expression);
581
- return;
582
- }
583
- if (ts.isThrowStatement(node)) {
584
- ctx.hasThrow = true;
585
- analyzeExpression(ctx, node.expression);
586
- return;
587
- }
588
- if (ts.isIfStatement(node) || ts.isSwitchStatement(node) || ts.isTryStatement(node)) {
589
- fallback(ctx, "unsupported_expression");
590
- return;
591
- }
592
- if (ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node) || ts.isWhileStatement(node) || ts.isDoStatement(node)) {
593
- fallback(ctx, "has_loop");
594
- return;
595
- }
596
- fallback(ctx, "unsupported_expression");
597
- };
598
- var bindParams = (ctx, node) => {
599
- let index = 0;
600
- for (const parameter of node.parameters) {
601
- if (ts.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
602
- if (!ts.isIdentifier(parameter.name) || parameter.dotDotDotToken) {
603
- fallback(ctx, "unsupported_expression");
604
- index++;
605
- continue;
606
- }
607
- const expressionId = ctx.ir.param(index);
608
- ctx.identifierParams.push(parameter.name.text);
609
- bindIdentifier(ctx, parameter.name, { expressionId, shape: "unknown" }, parameter);
610
- index++;
611
- }
612
- };
613
- var analyzeOne = (node, functionId, name, sourceFile, moduleCallees) => {
614
- const ctx = {
615
- sourceFile,
616
- functionId,
617
- name,
618
- ir: new DerivationIrBuilder(),
619
- variables: [],
620
- captureWraps: [],
621
- guardWraps: [],
622
- callEdges: [],
623
- scopes: [/* @__PURE__ */ new Map()],
624
- nextRootId: { value: 0 },
625
- nextVariableId: { value: 0 },
626
- nextAssignmentId: { value: 0 },
627
- hasThrow: false,
628
- hasCoercionGuard: false,
629
- moduleCallees,
630
- identifierParams: []
631
- };
632
- if (node.asteriskToken || node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)) {
633
- ctx.fallback = "async";
634
- } else {
635
- bindParams(ctx, node);
636
- if (ts.isBlock(node.body)) {
637
- for (const statement of node.body.statements) analyzeStatement(ctx, statement);
638
- } else {
639
- ctx.returnExpressionId = analyzeExpression(ctx, node.body).expressionId;
640
- }
641
- }
642
- const plan = {
643
- ...emptyPlan(functionId),
644
- expressions: ctx.ir.expressions,
645
- variables: ctx.variables,
646
- captureBoundaries: ctx.captureWraps.map((wrap) => ({
647
- id: wrap.rootId,
648
- reason: wrap.reason,
649
- expressionId: wrap.expressionId,
650
- line: wrap.line,
651
- column: wrap.column
652
- })),
653
- controlFacts: [],
654
- ...ctx.returnExpressionId === void 0 ? {} : { returnExpressionId: ctx.returnExpressionId },
655
- ...ctx.fallback ? { fallbackReason: ctx.fallback } : {}
656
- };
136
+ var sourceMapFor = (generatedPath, source) => {
137
+ const match = sourceMapComment.exec(source);
138
+ const reference = match?.[1] ?? match?.[2];
139
+ if (reference?.startsWith("data:")) {
140
+ const payload = decodeDataUrl(reference);
141
+ return payload ? { payload, mapUrl: pathToFileURL(generatedPath).href } : void 0;
142
+ }
143
+ const mapPath = reference ? resolve3(dirname(generatedPath), reference) : `${generatedPath}.map`;
657
144
  return {
658
- functionId,
659
- name,
660
- node,
661
- plan,
662
- captureWraps: ctx.fallback ? [] : ctx.captureWraps,
663
- guardWraps: ctx.fallback ? [] : ctx.guardWraps,
664
- callEdges: ctx.callEdges,
665
- hasThrow: ctx.hasThrow,
666
- hasCoercionGuard: ctx.hasCoercionGuard
145
+ payload: readFileSync(mapPath, "utf8"),
146
+ mapUrl: pathToFileURL(mapPath).href
667
147
  };
668
148
  };
669
- var analyzeModuleFunctions = (sourceFile, targets) => {
670
- const nameCounts = /* @__PURE__ */ new Map();
671
- for (const target of targets) {
672
- nameCounts.set(target.name, (nameCounts.get(target.name) ?? 0) + 1);
673
- }
674
- const firstPass = targets.map(
675
- (target) => analyzeOne(target.node, target.functionId, target.name, sourceFile, /* @__PURE__ */ new Map())
676
- );
677
- const graph = buildDependencyGraph(
678
- firstPass.map((fn) => ({ functionId: fn.functionId, name: fn.name })),
679
- firstPass.flatMap((fn) => fn.callEdges)
680
- );
681
- const order = topologicalFunctionOrder(
682
- firstPass.map((fn) => ({ functionId: fn.functionId, name: fn.name })),
683
- graph
684
- );
685
- const byId = new Map(targets.map((target) => [target.functionId, target]));
686
- const moduleCallees = /* @__PURE__ */ new Map();
687
- const analyzed = [];
688
- for (const functionId of order) {
689
- const target = byId.get(functionId);
690
- if (!target) continue;
691
- const result = analyzeOne(
692
- target.node,
693
- target.functionId,
694
- target.name,
695
- sourceFile,
696
- moduleCallees
697
- );
698
- if (nameCounts.get(result.name) === 1) {
699
- moduleCallees.set(result.name, result.functionId);
700
- }
701
- analyzed.push(result);
702
- }
703
- const remaining = targets.filter(
704
- (target) => !analyzed.some((fn) => fn.functionId === target.functionId)
705
- );
706
- for (const target of remaining) {
707
- analyzed.push(
708
- analyzeOne(target.node, target.functionId, target.name, sourceFile, moduleCallees)
709
- );
710
- }
711
- return analyzed;
712
- };
713
-
714
- // src/derivation/emit-capture-plan.ts
715
- var captureWrapEdits = (wraps, capName) => {
716
- const edits = [];
717
- let order = 0;
718
- for (const wrap of wraps) {
719
- edits.push({
720
- position: wrap.start,
721
- text: `${wrap.shorthandPropertyName ? `${wrap.shorthandPropertyName}:` : ""}${capName}(${wrap.rootId},`,
722
- order: order++
723
- });
724
- edits.push({
725
- position: wrap.end,
726
- text: ")",
727
- order: order++
728
- });
149
+ var sourceMapPayloadForCache = (generatedPath, source) => {
150
+ try {
151
+ return sourceMapFor(generatedPath, source)?.payload ?? "";
152
+ } catch {
153
+ return "";
729
154
  }
730
- return edits;
731
155
  };
732
156
 
733
- // src/derivation/plan-is-exact.ts
734
- var functionIsExact = (plan) => plan.fallbackReason === void 0 && plan.captureBoundaries.length === 0 && plan.controlFacts.length === 0;
157
+ // src/auto-instrumentation/transform-source.ts
158
+ import { basename, resolve as resolve4 } from "node:path";
159
+ import { originalPositionFor as originalPositionFor2, TraceMap as TraceMap2 } from "@jridgewell/trace-mapping";
160
+ import ts6 from "typescript";
735
161
 
736
162
  // src/execution-recorder/automatic-runtime.ts
737
- var AUTOMATIC_RUNTIME_SYMBOL = "rasputin.execution.runtime.v2";
163
+ var AUTOMATIC_RUNTIME_SYMBOL = "rasputin.execution.runtime.v3";
738
164
  var runtimeSymbol = Symbol.for(AUTOMATIC_RUNTIME_SYMBOL);
739
165
 
740
166
  // src/auto-instrumentation/collect-instrumentation-delta.ts
741
- import ts2 from "typescript";
167
+ import ts from "typescript";
742
168
  var paramBindingFromName = (name, sourceFile) => {
743
- if (ts2.isIdentifier(name)) return { kind: "identifier", name: name.text };
744
- if (ts2.isArrayBindingPattern(name)) {
169
+ if (ts.isIdentifier(name)) return { kind: "identifier", name: name.text };
170
+ if (ts.isArrayBindingPattern(name)) {
745
171
  const elements = name.elements.map(
746
172
  (element) => {
747
- if (ts2.isOmittedExpression(element)) return { kind: "omitted" };
173
+ if (ts.isOmittedExpression(element)) return { kind: "omitted" };
748
174
  const binding = paramBindingFromName(element.name, sourceFile);
749
175
  if (element.dotDotDotToken) return { kind: "rest", binding };
750
176
  return binding;
@@ -754,7 +180,7 @@ var paramBindingFromName = (name, sourceFile) => {
754
180
  }
755
181
  const properties = name.elements.map((element) => {
756
182
  const binding = paramBindingFromName(element.name, sourceFile);
757
- const key = element.propertyName ? element.propertyName.getText(sourceFile).replace(/^['"]|['"]$/g, "") : ts2.isIdentifier(element.name) ? element.name.text : element.name.getText(sourceFile);
183
+ const key = element.propertyName ? element.propertyName.getText(sourceFile).replace(/^['"]|['"]$/g, "") : ts.isIdentifier(element.name) ? element.name.text : element.name.getText(sourceFile);
758
184
  if (element.dotDotDotToken) {
759
185
  const rest = { kind: "rest", binding };
760
186
  return { key, binding: rest };
@@ -766,7 +192,7 @@ var paramBindingFromName = (name, sourceFile) => {
766
192
  var collectFunctionParams = (node, sourceFile) => {
767
193
  const params = [];
768
194
  for (const parameter of node.parameters) {
769
- if (ts2.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
195
+ if (ts.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
770
196
  const binding = paramBindingFromName(parameter.name, sourceFile);
771
197
  params.push(parameter.dotDotDotToken ? { kind: "rest", binding } : binding);
772
198
  }
@@ -807,18 +233,18 @@ var mappedLocation = (sourceFile, position, absolutePath, mapLocation) => {
807
233
  };
808
234
  var unwrapExpression = (expression) => {
809
235
  let current = expression;
810
- while (ts2.isParenthesizedExpression(current) || ts2.isNonNullExpression(current)) {
236
+ while (ts.isParenthesizedExpression(current) || ts.isNonNullExpression(current)) {
811
237
  current = current.expression;
812
238
  }
813
- if (ts2.isAwaitExpression(current)) return unwrapExpression(current.expression);
239
+ if (ts.isAwaitExpression(current)) return unwrapExpression(current.expression);
814
240
  return current;
815
241
  };
816
242
  var calleeFromExpression = (expression, sourceFile) => {
817
243
  const current = unwrapExpression(expression);
818
- if (ts2.isIdentifier(current)) {
244
+ if (ts.isIdentifier(current)) {
819
245
  return { calleeName: current.text, calleeText: current.text };
820
246
  }
821
- if (ts2.isPropertyAccessExpression(current) && ts2.isIdentifier(current.name)) {
247
+ if (ts.isPropertyAccessExpression(current) && ts.isIdentifier(current.name)) {
822
248
  return {
823
249
  calleeName: current.name.text,
824
250
  calleeText: current.getText(sourceFile).replace(/\s+/g, "")
@@ -828,9 +254,9 @@ var calleeFromExpression = (expression, sourceFile) => {
828
254
  };
829
255
  var resultBindingForCall = (call) => {
830
256
  let node = call.parent;
831
- if (ts2.isAwaitExpression(node)) node = node.parent;
832
- if (!ts2.isVariableDeclaration(node)) return null;
833
- if (!ts2.isIdentifier(node.name)) return null;
257
+ if (ts.isAwaitExpression(node)) node = node.parent;
258
+ if (!ts.isVariableDeclaration(node)) return null;
259
+ if (!ts.isIdentifier(node.name)) return null;
834
260
  const initializer = node.initializer;
835
261
  if (!initializer) return null;
836
262
  const inner = unwrapExpression(initializer);
@@ -840,18 +266,18 @@ var resultBindingForCall = (call) => {
840
266
  var argIdentifiersForCall = (call) => {
841
267
  const identifiers = [];
842
268
  call.arguments?.forEach((argument, index) => {
843
- if (ts2.isIdentifier(argument)) {
269
+ if (ts.isIdentifier(argument)) {
844
270
  identifiers.push({ index, kind: "identifier", name: argument.text });
845
271
  return;
846
272
  }
847
- if (!ts2.isObjectLiteralExpression(argument)) return;
273
+ if (!ts.isObjectLiteralExpression(argument)) return;
848
274
  const properties = [];
849
275
  for (const property of argument.properties) {
850
- if (ts2.isShorthandPropertyAssignment(property)) {
276
+ if (ts.isShorthandPropertyAssignment(property)) {
851
277
  properties.push({ key: property.name.text, name: property.name.text });
852
278
  continue;
853
279
  }
854
- if (ts2.isPropertyAssignment(property) && ts2.isIdentifier(property.name) && ts2.isIdentifier(property.initializer)) {
280
+ if (ts.isPropertyAssignment(property) && ts.isIdentifier(property.name) && ts.isIdentifier(property.initializer)) {
855
281
  properties.push({ key: property.name.text, name: property.initializer.text });
856
282
  }
857
283
  }
@@ -891,8 +317,8 @@ var collectCallSite = (call, caller, context) => {
891
317
  };
892
318
  };
893
319
  var collectParamDestructure = (declaration, caller, context) => {
894
- if (!declaration.initializer || !ts2.isIdentifier(declaration.initializer)) return null;
895
- if (!ts2.isObjectBindingPattern(declaration.name) && !ts2.isArrayBindingPattern(declaration.name)) {
320
+ if (!declaration.initializer || !ts.isIdentifier(declaration.initializer)) return null;
321
+ if (!ts.isObjectBindingPattern(declaration.name) && !ts.isArrayBindingPattern(declaration.name)) {
896
322
  return null;
897
323
  }
898
324
  const sourceParam = declaration.initializer.text;
@@ -914,58 +340,483 @@ var collectParamDestructure = (declaration, caller, context) => {
914
340
  };
915
341
  };
916
342
 
917
- // src/auto-instrumentation/transform-source.ts
343
+ // src/auto-instrumentation/parameter-initializer-may-run-code.ts
344
+ import ts2 from "typescript";
345
+ var unwrap = (expression) => {
346
+ let current = expression;
347
+ while (ts2.isParenthesizedExpression(current) || ts2.isNonNullExpression(current)) {
348
+ current = current.expression;
349
+ }
350
+ return current;
351
+ };
352
+ var isSafeLiteralInitializer = (expression) => {
353
+ const current = unwrap(expression);
354
+ if (ts2.isNumericLiteral(current) || ts2.isStringLiteral(current) || ts2.isNoSubstitutionTemplateLiteral(current) || ts2.isBigIntLiteral(current)) {
355
+ return true;
356
+ }
357
+ if (current.kind === ts2.SyntaxKind.TrueKeyword || current.kind === ts2.SyntaxKind.FalseKeyword || current.kind === ts2.SyntaxKind.NullKeyword) {
358
+ return true;
359
+ }
360
+ if (ts2.isIdentifier(current) && current.text === "undefined") return true;
361
+ if (ts2.isPrefixUnaryExpression(current) && (current.operator === ts2.SyntaxKind.PlusToken || current.operator === ts2.SyntaxKind.MinusToken)) {
362
+ return ts2.isNumericLiteral(unwrap(current.operand));
363
+ }
364
+ return false;
365
+ };
366
+ var functionHasPreBodyParameterEvaluation = (node) => {
367
+ for (const parameter of node.parameters) {
368
+ if (ts2.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
369
+ if (!parameter.initializer) continue;
370
+ if (!isSafeLiteralInitializer(parameter.initializer)) return true;
371
+ }
372
+ return false;
373
+ };
374
+
375
+ // src/auto-instrumentation/suspension-whitelist.ts
376
+ import ts3 from "typescript";
377
+ var nodeFlags = ts3.NodeFlags;
378
+ var AWAIT_USING_FLAG = nodeFlags.AwaitUsing ?? 0;
379
+ var USING_FLAG = nodeFlags.Using ?? 0;
380
+ var KNOWN_SUSPENSION_KIND_NAMES = [
381
+ "AwaitExpression",
382
+ "AwaitKeyword",
383
+ "YieldExpression",
384
+ "YieldKeyword",
385
+ "UsingKeyword"
386
+ ];
387
+ var isFunctionLike = (node) => ts3.isFunctionLike(node);
388
+ var isAsyncFunctionLike = (node) => (ts3.getCombinedModifierFlags(node) & ts3.ModifierFlags.Async) !== 0;
389
+ var isAsyncGeneratorLike = (node) => isFunctionLike(node) && isAsyncFunctionLike(node) && "asteriskToken" in node && Boolean(node.asteriskToken);
390
+ var isSyncGeneratorLike = (node) => isFunctionLike(node) && "asteriskToken" in node && Boolean(node.asteriskToken) && !isAsyncFunctionLike(node);
391
+ var visitOwnBody = (node, visitNode) => {
392
+ if (!node.body) return;
393
+ const visit = (candidate) => {
394
+ if (candidate !== node && isFunctionLike(candidate)) {
395
+ visitNode(candidate);
396
+ return;
397
+ }
398
+ if (visitNode(candidate)) return;
399
+ ts3.forEachChild(candidate, visit);
400
+ };
401
+ visit(node.body);
402
+ };
403
+ var nestedFunctionSpans = (node, sourceFile) => {
404
+ const spans = [];
405
+ visitOwnBody(node, (candidate) => {
406
+ if (candidate !== node && isFunctionLike(candidate)) {
407
+ spans.push([candidate.getStart(sourceFile), candidate.end]);
408
+ }
409
+ return void 0;
410
+ });
411
+ return spans.sort((left, right) => left[0] - right[0]);
412
+ };
413
+ var slicesOutside = (start, end, spans) => {
414
+ const slices = [];
415
+ let cursor = start;
416
+ for (const [from, to] of spans) {
417
+ if (to <= cursor) continue;
418
+ if (from >= end) break;
419
+ if (from > cursor) slices.push([cursor, Math.min(from, end)]);
420
+ cursor = Math.max(cursor, to);
421
+ }
422
+ if (cursor < end) slices.push([cursor, end]);
423
+ return slices;
424
+ };
425
+ var sliceHasAwaitKeyword = (source, start, end) => {
426
+ if (end <= start) return false;
427
+ const scanner = ts3.createScanner(
428
+ ts3.ScriptTarget.Latest,
429
+ true,
430
+ ts3.LanguageVariant.Standard,
431
+ source.slice(start, end)
432
+ );
433
+ while (true) {
434
+ const token = scanner.scan();
435
+ if (token === ts3.SyntaxKind.EndOfFileToken) return false;
436
+ if (token === ts3.SyntaxKind.AwaitKeyword) return true;
437
+ }
438
+ };
439
+ var ownBodyHasAwaitKeyword = (node, sourceFile) => {
440
+ if (!node.body) return false;
441
+ const spans = nestedFunctionSpans(node, sourceFile);
442
+ for (const [start, end] of slicesOutside(node.body.getStart(sourceFile), node.body.end, spans)) {
443
+ if (sliceHasAwaitKeyword(sourceFile.text, start, end)) return true;
444
+ }
445
+ return false;
446
+ };
447
+ var isAwaitUsingList = (node) => {
448
+ const text = node.getText().trimStart();
449
+ if (text.startsWith("await using")) return true;
450
+ if (AWAIT_USING_FLAG === 0 || USING_FLAG === 0) return false;
451
+ return (node.flags & AWAIT_USING_FLAG) === AWAIT_USING_FLAG && (node.flags & USING_FLAG) !== 0;
452
+ };
453
+ var unknownSuspensionKind = (node) => {
454
+ const name = ts3.SyntaxKind[node.kind];
455
+ if (typeof name !== "string" || !/Await|Yield|Using/.test(name)) return false;
456
+ return !KNOWN_SUSPENSION_KIND_NAMES.includes(name);
457
+ };
458
+ var ownSuspensionFacts = (node, sourceFile) => {
459
+ const facts = {
460
+ awaitExpressions: false,
461
+ forAwait: false,
462
+ awaitUsing: false,
463
+ unknown: false,
464
+ awaitKeyword: ownBodyHasAwaitKeyword(node, sourceFile)
465
+ };
466
+ visitOwnBody(node, (candidate) => {
467
+ if (ts3.isAwaitExpression(candidate)) facts.awaitExpressions = true;
468
+ if (ts3.isForOfStatement(candidate) && candidate.awaitModifier) facts.forAwait = true;
469
+ if (ts3.isVariableDeclarationList(candidate) && isAwaitUsingList(candidate)) {
470
+ facts.awaitUsing = true;
471
+ }
472
+ if (unknownSuspensionKind(candidate)) facts.unknown = true;
473
+ return void 0;
474
+ });
475
+ return facts;
476
+ };
477
+ var bodyHasDirectEval = (node) => {
478
+ let found = false;
479
+ visitOwnBody(node, (candidate) => {
480
+ if (ts3.isCallExpression(candidate) && ts3.isIdentifier(candidate.expression) && candidate.expression.text === "eval") {
481
+ found = true;
482
+ }
483
+ return found;
484
+ });
485
+ return found;
486
+ };
487
+ var decideAsyncInstrumentation = (node, sourceFile) => {
488
+ if (isAsyncGeneratorLike(node)) return { action: "skip", reason: "unsupported_async_generator" };
489
+ if (!isAsyncFunctionLike(node)) return { action: "inline" };
490
+ if (bodyHasDirectEval(node)) return { action: "skip", reason: "direct_eval" };
491
+ const facts = ownSuspensionFacts(node, sourceFile);
492
+ if (facts.unknown) return { action: "skip", reason: "unknown_suspension" };
493
+ if (facts.forAwait) return { action: "skip", reason: "unsupported_for_await" };
494
+ if (facts.awaitUsing) return { action: "skip", reason: "await_using" };
495
+ if (facts.awaitKeyword && !facts.awaitExpressions) {
496
+ return { action: "skip", reason: "unknown_suspension" };
497
+ }
498
+ if (facts.awaitExpressions) return { action: "suspend_resume" };
499
+ return { action: "inline" };
500
+ };
501
+
502
+ // src/auto-instrumentation/verify-instrumentation-isomorphism.ts
503
+ import ts4 from "typescript";
504
+ var RASPUTIN_IDENT = /^__rasputin/;
918
505
  var scriptKindFor = (filePath) => {
919
- if (/\.tsx$/i.test(filePath)) return ts3.ScriptKind.TSX;
920
- if (/\.jsx$/i.test(filePath)) return ts3.ScriptKind.JSX;
921
- if (/\.[cm]?ts$/i.test(filePath)) return ts3.ScriptKind.TS;
922
- return ts3.ScriptKind.JS;
506
+ if (/\.tsx$/i.test(filePath)) return ts4.ScriptKind.TSX;
507
+ if (/\.jsx$/i.test(filePath)) return ts4.ScriptKind.JSX;
508
+ if (/\.[cm]?ts$/i.test(filePath)) return ts4.ScriptKind.TS;
509
+ return ts4.ScriptKind.JS;
510
+ };
511
+ var parse = (filePath, source) => ts4.createSourceFile(filePath, source, ts4.ScriptTarget.Latest, true, scriptKindFor(filePath));
512
+ var isRasputinName = (name) => RASPUTIN_IDENT.test(name);
513
+ var identText = (node) => {
514
+ if (ts4.isIdentifier(node)) return node.text;
515
+ if (ts4.isPrivateIdentifier(node)) return node.text;
516
+ return void 0;
517
+ };
518
+ var calleeName = (expression) => {
519
+ if (ts4.isIdentifier(expression)) return expression.text;
520
+ if (ts4.isPropertyAccessExpression(expression)) {
521
+ const object = identText(expression.expression);
522
+ const property = identText(expression.name);
523
+ if (object && isRasputinName(object) && property) return `${object}.${property}`;
524
+ }
525
+ return void 0;
526
+ };
527
+ var isRasputinCall = (node, methods) => {
528
+ if (!ts4.isCallExpression(node)) return false;
529
+ const name = calleeName(node.expression);
530
+ if (!name) return false;
531
+ if (isRasputinName(name)) return !methods || methods.has(name.split(".")[1] ?? "");
532
+ return false;
533
+ };
534
+ var unwrapCall = (node) => {
535
+ let current = node;
536
+ while (isRasputinCall(current) && current.arguments.length === 1 && current.arguments[0]) {
537
+ current = current.arguments[0];
538
+ if (ts4.isParenthesizedExpression(current)) current = current.expression;
539
+ }
540
+ if (ts4.isParenthesizedExpression(current)) return current.expression;
541
+ return current;
542
+ };
543
+ var isRasputinStatement = (node) => {
544
+ if (ts4.isEmptyStatement(node)) return true;
545
+ if (ts4.isVariableStatement(node)) {
546
+ return node.declarationList.declarations.every((declaration) => {
547
+ const name = identText(declaration.name);
548
+ return Boolean(name && isRasputinName(name));
549
+ });
550
+ }
551
+ if (ts4.isExpressionStatement(node)) {
552
+ if (!isRasputinCall(node.expression)) return false;
553
+ const unwrapped = unwrapCall(node.expression);
554
+ return unwrapped === node.expression;
555
+ }
556
+ return false;
557
+ };
558
+ var isRasputinTryWrapper = (node) => {
559
+ const catchClause = node.catchClause;
560
+ if (!catchClause || !node.finallyBlock) return false;
561
+ const catchCalls = catchClause.block.statements.filter(
562
+ (statement) => !isRasputinStatement(statement)
563
+ );
564
+ const leftover = catchCalls[0];
565
+ const leftoverThrow = catchCalls.length === 1 && leftover !== void 0 && ts4.isThrowStatement(leftover) && ts4.isIdentifier(leftover.expression) && isRasputinName(leftover.expression.text);
566
+ if (!leftoverThrow && catchCalls.length > 0) return false;
567
+ return node.finallyBlock.statements.every((statement) => isRasputinStatement(statement));
568
+ };
569
+ var flattenStatements = (statements) => {
570
+ const flattened = [];
571
+ for (const statement of statements) {
572
+ if (isRasputinStatement(statement)) continue;
573
+ if (ts4.isTryStatement(statement) && isRasputinTryWrapper(statement)) {
574
+ flattened.push(...flattenStatements(statement.tryBlock.statements));
575
+ continue;
576
+ }
577
+ flattened.push(statement);
578
+ }
579
+ return flattened;
580
+ };
581
+ var extra = (node) => {
582
+ if (ts4.isIdentifier(node) || ts4.isPrivateIdentifier(node)) {
583
+ return isRasputinName(node.text) ? "" : node.text;
584
+ }
585
+ if (ts4.isStringLiteral(node) || ts4.isNumericLiteral(node) || ts4.isNoSubstitutionTemplateLiteral(node)) {
586
+ return node.text;
587
+ }
588
+ if (ts4.isToken(node) && !ts4.isIdentifier(node)) return ts4.SyntaxKind[node.kind] ?? "";
589
+ return "";
590
+ };
591
+ var signatureOf = (node) => {
592
+ const unwrapped = unwrapCall(node);
593
+ if (unwrapped !== node) return signatureOf(unwrapped);
594
+ if (ts4.isEmptyStatement(unwrapped) || isRasputinStatement(unwrapped)) return "";
595
+ if (ts4.isTryStatement(unwrapped) && isRasputinTryWrapper(unwrapped)) {
596
+ return flattenStatements(unwrapped.tryBlock.statements).map(signatureOf).filter(Boolean).join("");
597
+ }
598
+ if (ts4.isBlock(unwrapped)) {
599
+ const statements = flattenStatements(unwrapped.statements);
600
+ return `Block[${statements.map(signatureOf).filter(Boolean).join(",")}]`;
601
+ }
602
+ if (ts4.isCatchClause(unwrapped)) {
603
+ const statements = flattenStatements(unwrapped.block.statements);
604
+ const variable = unwrapped.variableDeclaration ? signatureOf(unwrapped.variableDeclaration) : "";
605
+ return `Catch${variable}[${statements.map(signatureOf).filter(Boolean).join(",")}]`;
606
+ }
607
+ if (ts4.isArrowFunction(unwrapped) && ts4.isBlock(unwrapped.body)) {
608
+ const statements = flattenStatements(unwrapped.body.statements);
609
+ const only = statements[0];
610
+ if (statements.length === 1 && only && ts4.isReturnStatement(only) && only.expression) {
611
+ const expression = only.expression;
612
+ const parts2 = [];
613
+ ts4.forEachChild(unwrapped, (child) => {
614
+ if (child === unwrapped.body) {
615
+ const part2 = signatureOf(expression);
616
+ if (part2) parts2.push(part2);
617
+ return;
618
+ }
619
+ const part = signatureOf(child);
620
+ if (part) parts2.push(part);
621
+ });
622
+ return `ArrowFunction[${parts2.join(",")}]`;
623
+ }
624
+ }
625
+ const parts = [];
626
+ ts4.forEachChild(unwrapped, (child) => {
627
+ const part = signatureOf(child);
628
+ if (part) parts.push(part);
629
+ });
630
+ const kind = ts4.SyntaxKind[unwrapped.kind] ?? "Node";
631
+ const label = extra(unwrapped);
632
+ return `${kind}${label}[${parts.join(",")}]`;
633
+ };
634
+ var instrumentationTreesAreIsomorphic = (filePath, original, transformed) => {
635
+ return signatureOf(parse(filePath, original)) === signatureOf(parse(filePath, transformed));
636
+ };
637
+
638
+ // src/auto-instrumentation/verify-transformed-source.ts
639
+ import ts5 from "typescript";
640
+ var transpileForVerification = (filePath, code) => ts5.transpileModule(code, {
641
+ compilerOptions: {
642
+ // ES2020 downlevel keeps native @decorators out of `new Function`.
643
+ target: ts5.ScriptTarget.ES2020,
644
+ module: ts5.ModuleKind.ESNext,
645
+ allowJs: true,
646
+ jsx: ts5.JsxEmit.React,
647
+ experimentalDecorators: true
648
+ },
649
+ reportDiagnostics: true,
650
+ fileName: filePath
651
+ });
652
+ var hasScanOrParseError = (diagnostics) => Boolean(
653
+ diagnostics?.some(
654
+ (diagnostic) => diagnostic.category === ts5.DiagnosticCategory.Error && diagnostic.code < 2e3
655
+ )
656
+ );
657
+ var scriptBodyForEngine = (emitted) => (
658
+ // File loaders strip a leading shebang; `new Function` does not.
659
+ emitted.replace(/^#![^\n]*\n/, "").replaceAll("import.meta", "({})")
660
+ );
661
+ var looksTyped = (filePath) => /\.[cm]?tsx?$/i.test(filePath);
662
+ var scriptKindFor2 = (filePath) => {
663
+ if (/\.tsx$/i.test(filePath)) return ts5.ScriptKind.TSX;
664
+ if (/\.jsx$/i.test(filePath)) return ts5.ScriptKind.JSX;
665
+ if (/\.[cm]?ts$/i.test(filePath)) return ts5.ScriptKind.TS;
666
+ return ts5.ScriptKind.JS;
667
+ };
668
+ var isIterationStatement = (node) => ts5.isForStatement(node) || ts5.isForOfStatement(node) || ts5.isForInStatement(node) || ts5.isWhileStatement(node) || ts5.isDoStatement(node);
669
+ var unwrapLabeled = (node) => {
670
+ let current = node;
671
+ while (ts5.isLabeledStatement(current)) current = current.statement;
672
+ return current;
673
+ };
674
+ var hasInvalidLabelJump = (sourceFile) => {
675
+ const visit = (node, labels) => {
676
+ if (ts5.isFunctionLike(node)) {
677
+ let nestedInvalid = false;
678
+ ts5.forEachChild(node, (child) => {
679
+ if (visit(child, /* @__PURE__ */ new Map())) nestedInvalid = true;
680
+ });
681
+ return nestedInvalid;
682
+ }
683
+ if (ts5.isLabeledStatement(node)) {
684
+ const next = new Map(labels);
685
+ next.set(node.label.text, isIterationStatement(unwrapLabeled(node.statement)));
686
+ return visit(node.statement, next);
687
+ }
688
+ if (ts5.isContinueStatement(node) && node.label) {
689
+ if (labels.get(node.label.text) !== true) return true;
690
+ }
691
+ if (ts5.isBreakStatement(node) && node.label && !labels.has(node.label.text)) {
692
+ return true;
693
+ }
694
+ let invalid = false;
695
+ ts5.forEachChild(node, (child) => {
696
+ if (visit(child, labels)) invalid = true;
697
+ });
698
+ return invalid;
699
+ };
700
+ return visit(sourceFile, /* @__PURE__ */ new Map());
701
+ };
702
+ var importSpecifier = `('[^']+'|"[^"]+")`;
703
+ var importAttributes = String.raw`(?:\s+with\s*\{[^}]*\})?`;
704
+ var stripModuleSyntax = (body) => body.replace(/\bexport\s+default\b/g, "var __rasputinVerifyDefault =").replace(new RegExp(String.raw`\bexport\s*\*[\s\S]*?from\s+${importSpecifier}`, "g"), "").replace(
705
+ new RegExp(
706
+ String.raw`\bexport\s*\{[^}]*\}\s*from\s+${importSpecifier}${importAttributes}`,
707
+ "g"
708
+ ),
709
+ ""
710
+ ).replace(/\bexport\s*\{[^}]*\}/g, "").replace(/\bexport\s+/g, "").replace(
711
+ new RegExp(
712
+ String.raw`\bimport(?:\s+type)?\s+[\s\S]*?from\s+${importSpecifier}${importAttributes}`,
713
+ "g"
714
+ ),
715
+ ""
716
+ ).replace(/\bimport\s+type\s+[\s\S]*?(?:;|\n)/g, "").replace(new RegExp(String.raw`\bimport\s+${importSpecifier}${importAttributes}`, "g"), "");
717
+ var engineAccepts = (body) => {
718
+ for (const candidate of [body, stripModuleSyntax(body)]) {
719
+ try {
720
+ new Function(candidate);
721
+ return true;
722
+ } catch {
723
+ try {
724
+ new Function(`return (async()=>{${candidate}
725
+ })`);
726
+ return true;
727
+ } catch {
728
+ }
729
+ }
730
+ }
731
+ return false;
732
+ };
733
+ var GLUED_INSTRUMENTATION = /\b(?:return|await|throw|typeof|void|yield|new|delete|in|instanceof|of|from|as|case|else)__rasputin/;
734
+ var UNTERMINATED_BARE_LEAVE = /\breturn\s+__rasputin\w*\.leave\(\)(?!\s*;|\()/;
735
+ var transformedSourceIsInvalid = (filePath, code) => {
736
+ if (GLUED_INSTRUMENTATION.test(code)) return true;
737
+ if (UNTERMINATED_BARE_LEAVE.test(code)) return true;
738
+ const sourceFile = ts5.createSourceFile(
739
+ filePath,
740
+ code,
741
+ ts5.ScriptTarget.Latest,
742
+ true,
743
+ scriptKindFor2(filePath)
744
+ );
745
+ if (hasInvalidLabelJump(sourceFile)) return true;
746
+ const { diagnostics, outputText } = transpileForVerification(filePath, code);
747
+ if (hasScanOrParseError(diagnostics)) return true;
748
+ if (looksTyped(filePath)) return !engineAccepts(scriptBodyForEngine(outputText));
749
+ if (engineAccepts(scriptBodyForEngine(code))) return false;
750
+ return !engineAccepts(scriptBodyForEngine(outputText));
751
+ };
752
+
753
+ // src/auto-instrumentation/transform-source.ts
754
+ var isIdentContinue = (code) => code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 36 || code === 95;
755
+ var isIdentStart = (code) => code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 36 || code === 95;
756
+ var separateFromPreviousToken = (source, position, text) => {
757
+ if (position <= 0 || text.length === 0) return text;
758
+ if (isIdentContinue(source.charCodeAt(position - 1)) && isIdentStart(text.charCodeAt(0))) {
759
+ return ` ${text}`;
760
+ }
761
+ return text;
762
+ };
763
+ var RASPUTIN_INSTRUMENTATION_MARKER = "rasputin.instrumented.v3";
764
+ var RASPUTIN_INSTRUMENTATION_PREAMBLE = `/*${RASPUTIN_INSTRUMENTATION_MARKER}*/`;
765
+ var sourceIsRasputinInstrumented = (source) => source.includes(RASPUTIN_INSTRUMENTATION_PREAMBLE);
766
+ var alreadyInstrumentedResult = (source) => ({
767
+ code: source,
768
+ delta: { functions: [], callSites: [], destructures: [], alreadyInstrumented: true }
769
+ });
770
+ var scriptKindFor3 = (filePath) => {
771
+ if (/\.tsx$/i.test(filePath)) return ts6.ScriptKind.TSX;
772
+ if (/\.jsx$/i.test(filePath)) return ts6.ScriptKind.JSX;
773
+ if (/\.[cm]?ts$/i.test(filePath)) return ts6.ScriptKind.TS;
774
+ return ts6.ScriptKind.JS;
923
775
  };
924
776
  var functionName = (node, sourceFile) => {
777
+ if (ts6.isConstructorDeclaration(node)) return "constructor";
925
778
  if (node.name) return node.name.getText(sourceFile).replace(/^['"]|['"]$/g, "");
926
779
  const parent = node.parent;
927
- if (ts3.isVariableDeclaration(parent) && ts3.isIdentifier(parent.name)) return parent.name.text;
928
- if (ts3.isPropertyAssignment(parent)) return parent.name.getText(sourceFile);
929
- if (ts3.isPropertyDeclaration(parent) && parent.name) return parent.name.getText(sourceFile);
780
+ if (ts6.isVariableDeclaration(parent) && ts6.isIdentifier(parent.name)) return parent.name.text;
781
+ if (ts6.isPropertyAssignment(parent)) return parent.name.getText(sourceFile);
782
+ if (ts6.isPropertyDeclaration(parent) && parent.name) return parent.name.getText(sourceFile);
930
783
  const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
931
784
  return `anonymous@${location.line + 1}:${location.character + 1}`;
932
785
  };
933
786
  var functionLocationPosition = (node, sourceFile) => {
934
787
  if (node.name) return node.name.getStart(sourceFile);
935
788
  const parent = node.parent;
936
- if ((ts3.isVariableDeclaration(parent) || ts3.isPropertyAssignment(parent)) && parent.name) {
789
+ if ((ts6.isVariableDeclaration(parent) || ts6.isPropertyAssignment(parent)) && parent.name) {
937
790
  return parent.name.getStart(sourceFile);
938
791
  }
939
792
  return node.getStart(sourceFile);
940
793
  };
941
- var bindingValue = (name, sourceFile) => {
942
- if (ts3.isIdentifier(name)) return name.text;
943
- if (ts3.isArrayBindingPattern(name)) {
944
- const values = name.elements.map((element) => {
945
- if (ts3.isOmittedExpression(element)) return "";
946
- const value = bindingValue(element.name, sourceFile);
947
- return element.dotDotDotToken ? `...${value}` : value;
948
- });
949
- return `[${values.join(",")}]`;
794
+ var functionBodyPrologueEnd = (body, sourceFile) => {
795
+ let end = body.getStart(sourceFile) + 1;
796
+ for (const statement of body.statements) {
797
+ if (!isDirectivePrologue(statement)) break;
798
+ end = statement.end;
950
799
  }
951
- const properties = name.elements.map((element) => {
952
- const value = bindingValue(element.name, sourceFile);
953
- if (element.dotDotDotToken) return `...${value}`;
954
- if (element.propertyName) return `${element.propertyName.getText(sourceFile)}:${value}`;
955
- return value;
956
- });
957
- return `{${properties.join(",")}}`;
800
+ return end;
958
801
  };
959
- var arrowArguments = (node) => {
802
+ var arrowArgsCapture = (node) => {
803
+ if (!node.parameters.every((parameter) => ts6.isIdentifier(parameter.name))) {
804
+ const count = node.parameters.filter(
805
+ (parameter) => !(ts6.isIdentifier(parameter.name) && parameter.name.text === "this")
806
+ ).length;
807
+ return {
808
+ args: `[${Array.from({ length: count }, () => "void 0").join(",")}]`,
809
+ omitValues: true
810
+ };
811
+ }
960
812
  const values = node.parameters.flatMap((parameter) => {
961
- if (ts3.isIdentifier(parameter.name) && parameter.name.text === "this") return [];
962
- const value = bindingValue(parameter.name, node.getSourceFile());
963
- return [parameter.dotDotDotToken ? `...${value}` : value];
813
+ if (!ts6.isIdentifier(parameter.name) || parameter.name.text === "this") return [];
814
+ return [parameter.dotDotDotToken ? `...${parameter.name.text}` : parameter.name.text];
964
815
  });
965
- return `[${values.join(",")}]`;
816
+ return { args: `[${values.join(",")}]`, omitValues: false };
966
817
  };
967
818
  var isSupportedFunction = (node) => {
968
- if (!ts3.isFunctionDeclaration(node) && !ts3.isFunctionExpression(node) && !ts3.isArrowFunction(node) && !ts3.isMethodDeclaration(node) && !ts3.isGetAccessorDeclaration(node) && !ts3.isSetAccessorDeclaration(node)) {
819
+ if (!ts6.isFunctionDeclaration(node) && !ts6.isFunctionExpression(node) && !ts6.isArrowFunction(node) && !ts6.isMethodDeclaration(node) && !ts6.isGetAccessorDeclaration(node) && !ts6.isSetAccessorDeclaration(node)) {
969
820
  return false;
970
821
  }
971
822
  if (!node.body || "asteriskToken" in node && node.asteriskToken) return false;
@@ -976,7 +827,7 @@ var uniqueName = (source, base) => {
976
827
  while (source.includes(name)) name += "_";
977
828
  return name;
978
829
  };
979
- var isDirectivePrologue = (statement) => ts3.isExpressionStatement(statement) && ts3.isStringLiteral(statement.expression);
830
+ var isDirectivePrologue = (statement) => ts6.isExpressionStatement(statement) && ts6.isStringLiteral(statement.expression);
980
831
  var insertPositionAfterPrologue = (source, sourceFile) => {
981
832
  const shebangEnd = source.startsWith("#!") ? source.indexOf("\n") + 1 : 0;
982
833
  let end = shebangEnd;
@@ -991,7 +842,7 @@ var preambleInsert = (source, position, preamble) => {
991
842
  const previous = source[position - 1];
992
843
  return previous === ";" || previous === "\n" || previous === "\r" ? preamble : `;${preamble}`;
993
844
  };
994
- var modulePreamble = (runName, capName, plansName, plansJson) => `const ${plansName}=${plansJson};const ${runName}=((g,k)=>{const r=g[k]??(g[k]={runtimes:[],modules:[],captureRoot(i,v){return v},register(p){const d={run:(i,a,c)=>c()};const m={plans:p,cell:d};this.modules.push(m);const n=this.runtimes.at(-1);if(n)n.bind(m);return(i,a,c,l)=>d.run(i,a,c,l)}});return r.register(${plansName})})(globalThis,Symbol.for(${JSON.stringify(AUTOMATIC_RUNTIME_SYMBOL)}));const ${capName}=((g,k)=>{const r=g[k];return(i,v)=>typeof r?.captureRoot==='function'?r.captureRoot(i,v):v})(globalThis,Symbol.for(${JSON.stringify(AUTOMATIC_RUNTIME_SYMBOL)}));`;
845
+ var modulePreamble = (runName, plansName, plansJson) => `${RASPUTIN_INSTRUMENTATION_PREAMBLE}const ${plansName}=${plansJson};const ${runName}=((g,k)=>{const r=g[k]??(g[k]={runtimes:[],modules:[],register(p){const d={run:(i,a,c)=>c(),enter(){return{leave(v){return v},fail(){},end(){},suspend(v){return v},resume(v){return v},run(c){return c()}}}};const m={plans:p,cell:d};this.modules.push(m);const n=this.runtimes.at(-1);if(n)n.bind(m);const f=(i,a,c,l)=>d.run(i,a,c,l);f.enter=(i,a,l)=>d.enter(i,a,l);return f}});return r.register(${plansName})})(globalThis,Symbol.for(${JSON.stringify(AUTOMATIC_RUNTIME_SYMBOL)}));`;
995
846
  var collectWrappedFunction = (node, sourceFile, absolutePath, mapLocation) => {
996
847
  const start = mappedLocation(
997
848
  sourceFile,
@@ -1019,40 +870,276 @@ var collectWrappedFunction = (node, sourceFile, absolutePath, mapLocation) => {
1019
870
  endLine: end.line,
1020
871
  endColumn: end.column,
1021
872
  params,
1022
- paramNames
873
+ paramNames,
874
+ instrumented: true
1023
875
  };
1024
876
  };
1025
- var canOmitCallValues = (analysis, analyzed) => {
1026
- if (!functionIsExact(analysis.plan) || analysis.hasThrow || analysis.hasCoercionGuard) {
1027
- return false;
877
+ var wrap = (edits, start, end, open, close, depth) => {
878
+ const width = Math.max(0, end - start);
879
+ edits.push({ position: start, text: open, deleteCount: 0, depth, width, kind: "open" });
880
+ edits.push({ position: end, text: close, deleteCount: 0, depth, width, kind: "close" });
881
+ };
882
+ var compareEdits = (left, right) => {
883
+ if (left.position !== right.position) return right.position - left.position;
884
+ if (left.kind !== right.kind) {
885
+ const rank = { replace: 0, close: 1, open: 2 };
886
+ return rank[left.kind] - rank[right.kind];
1028
887
  }
1029
- const callers = analyzed.filter(
1030
- (caller) => caller.callEdges.some((edge) => edge.calleeFunctionId === analysis.functionId)
1031
- );
1032
- if (callers.length === 0) return false;
1033
- return callers.every((caller) => caller.plan.fallbackReason === void 0 && !caller.hasThrow);
888
+ if (left.kind === "close") {
889
+ if (left.width !== right.width) return right.width - left.width;
890
+ return left.depth - right.depth;
891
+ }
892
+ if (left.kind === "open") {
893
+ if (left.width !== right.width) return left.width - right.width;
894
+ return right.depth - left.depth;
895
+ }
896
+ return 0;
897
+ };
898
+ var VLQ_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
899
+ var encodeVlq = (value) => {
900
+ let vlq = value < 0 ? -value << 1 | 1 : value << 1;
901
+ let encoded = "";
902
+ do {
903
+ let digit = vlq & 31;
904
+ vlq >>>= 5;
905
+ if (vlq > 0) digit |= 32;
906
+ encoded += VLQ_ALPHABET[digit];
907
+ } while (vlq > 0);
908
+ return encoded;
909
+ };
910
+ var SOURCEMAP_COMMENT = /(?:\/\/[#@][ \t]*sourceMappingURL=([^\s'"]+)|\/\*[#@][ \t]*sourceMappingURL=([^\s*'"]+)[ \t]*\*\/)\s*$/;
911
+ var decodeInlineSourceMap = (url) => {
912
+ const base64 = /^data:application\/json(?:;charset=[^;,]+)?;base64,(.+)$/i.exec(url);
913
+ if (base64?.[1]) {
914
+ try {
915
+ return Buffer.from(base64[1], "base64").toString("utf8");
916
+ } catch {
917
+ return void 0;
918
+ }
919
+ }
920
+ const raw = /^data:application\/json(?:;charset=[^;,]+)?,(.+)$/i.exec(url);
921
+ if (!raw?.[1]) return void 0;
922
+ try {
923
+ return decodeURIComponent(raw[1]);
924
+ } catch {
925
+ return void 0;
926
+ }
927
+ };
928
+ var upstreamTraceMap = (original) => {
929
+ const match = SOURCEMAP_COMMENT.exec(original);
930
+ const url = match?.[1] ?? match?.[2];
931
+ if (!url) return void 0;
932
+ const payload = url.startsWith("data:") ? decodeInlineSourceMap(url) : void 0;
933
+ if (!payload) return void 0;
934
+ try {
935
+ return new TraceMap2(payload);
936
+ } catch {
937
+ return void 0;
938
+ }
939
+ };
940
+ var lineStartsOf = (text) => {
941
+ const starts = [0];
942
+ for (let index = 0; index < text.length; index++) {
943
+ if (text[index] === "\n") starts.push(index + 1);
944
+ }
945
+ return starts;
946
+ };
947
+ var positionAt = (starts, offset) => {
948
+ let low = 0;
949
+ let high = starts.length - 1;
950
+ while (low < high) {
951
+ const mid = low + high + 1 >> 1;
952
+ if ((starts[mid] ?? 0) <= offset) low = mid;
953
+ else high = mid - 1;
954
+ }
955
+ return { line: low, column: offset - (starts[low] ?? 0) };
956
+ };
957
+ var sortedEdits = (edits) => [...edits].sort(compareEdits);
958
+ var originalOffsetToGenerated = (originalOffset, edits) => {
959
+ let generated = originalOffset;
960
+ for (const edit of edits) {
961
+ const delta = edit.text.length - edit.deleteCount;
962
+ if (originalOffset >= edit.position + edit.deleteCount) generated += delta;
963
+ else if (originalOffset >= edit.position) generated = edit.position + edit.text.length;
964
+ }
965
+ return generated;
966
+ };
967
+ var mappingOffsets = (original, edits) => {
968
+ const offsets = /* @__PURE__ */ new Set([0, original.length]);
969
+ for (const start of lineStartsOf(original)) offsets.add(start);
970
+ for (const edit of edits) {
971
+ offsets.add(edit.position);
972
+ offsets.add(edit.position + edit.deleteCount);
973
+ }
974
+ return [...offsets].filter((offset) => offset >= 0 && offset <= original.length).sort((left, right) => left - right);
975
+ };
976
+ var encodeColumnMappings = (original, generated, file, edits) => {
977
+ const upstream = upstreamTraceMap(original);
978
+ const sources = (upstream?.sources.filter((source) => Boolean(source)) ?? [file]).filter((source, index, all) => all.indexOf(source) === index);
979
+ if (sources.length === 0) sources.push(file);
980
+ const applied = sortedEdits(edits);
981
+ const originalStarts = lineStartsOf(original);
982
+ const generatedStarts = lineStartsOf(generated);
983
+ const pointsByLine = Array.from({ length: generatedStarts.length }, () => []);
984
+ const seen = /* @__PURE__ */ new Set();
985
+ for (const originalOffset of mappingOffsets(original, edits)) {
986
+ const generatedOffset = Math.min(
987
+ originalOffsetToGenerated(originalOffset, applied),
988
+ generated.length
989
+ );
990
+ const originalPosition = positionAt(originalStarts, originalOffset);
991
+ const generatedPosition = positionAt(generatedStarts, generatedOffset);
992
+ let sourceIndex = 0;
993
+ let mappedLine = originalPosition.line;
994
+ let mappedColumn = originalPosition.column;
995
+ if (upstream) {
996
+ const mapped = originalPositionFor2(upstream, {
997
+ line: originalPosition.line + 1,
998
+ column: originalPosition.column
999
+ });
1000
+ if (mapped.source && mapped.line != null && mapped.column != null) {
1001
+ const index = sources.indexOf(mapped.source);
1002
+ sourceIndex = index >= 0 ? index : 0;
1003
+ mappedLine = mapped.line - 1;
1004
+ mappedColumn = mapped.column;
1005
+ }
1006
+ }
1007
+ const key = `${generatedPosition.line}:${generatedPosition.column}`;
1008
+ if (seen.has(key)) continue;
1009
+ seen.add(key);
1010
+ pointsByLine[generatedPosition.line]?.push({
1011
+ generatedColumn: generatedPosition.column,
1012
+ sourceIndex,
1013
+ originalLine: mappedLine,
1014
+ originalColumn: mappedColumn
1015
+ });
1016
+ }
1017
+ const lines = pointsByLine.map((segments) => {
1018
+ segments.sort((left, right) => left.generatedColumn - right.generatedColumn);
1019
+ let lastGeneratedColumn = 0;
1020
+ let lastSourceIndex = 0;
1021
+ let lastOriginalLine = 0;
1022
+ let lastOriginalColumn = 0;
1023
+ return segments.map((point) => {
1024
+ const encoded = `${encodeVlq(point.generatedColumn - lastGeneratedColumn)}${encodeVlq(point.sourceIndex - lastSourceIndex)}${encodeVlq(point.originalLine - lastOriginalLine)}${encodeVlq(point.originalColumn - lastOriginalColumn)}`;
1025
+ lastGeneratedColumn = point.generatedColumn;
1026
+ lastSourceIndex = point.sourceIndex;
1027
+ lastOriginalLine = point.originalLine;
1028
+ lastOriginalColumn = point.originalColumn;
1029
+ return encoded;
1030
+ }).join(",");
1031
+ });
1032
+ return { sources, mappings: lines.join(";") };
1033
+ };
1034
+ var attachInlineSourceMap = (original, generated, filePath, edits) => {
1035
+ const file = basename(filePath);
1036
+ const withoutUpstream = generated.replace(SOURCEMAP_COMMENT, "");
1037
+ const { sources, mappings } = encodeColumnMappings(original, withoutUpstream, file, edits);
1038
+ const comment = `/*# sourceMappingURL=data:application/json;base64,${Buffer.from(
1039
+ JSON.stringify({ version: 3, file, sources, mappings })
1040
+ ).toString("base64")} */`;
1041
+ return withoutUpstream.endsWith("\n") ? `${withoutUpstream.slice(0, -1)}${comment}
1042
+ ` : `${withoutUpstream}${comment}`;
1043
+ };
1044
+ var insertAfterSuperOrPrologue = (body, sourceFile) => {
1045
+ const end = functionBodyPrologueEnd(body, sourceFile);
1046
+ for (const statement of body.statements) {
1047
+ if (isDirectivePrologue(statement)) continue;
1048
+ if (ts6.isExpressionStatement(statement) && ts6.isCallExpression(statement.expression) && statement.expression.expression.kind === ts6.SyntaxKind.SuperKeyword) {
1049
+ return statement.end;
1050
+ }
1051
+ break;
1052
+ }
1053
+ return end;
1054
+ };
1055
+ var failEpilogue = (handleName, errorName, args, restoreOnCatch) => `}catch(${errorName}){${restoreOnCatch ? `${handleName}.resume();` : ""}${handleName}.fail(${errorName},${args});throw ${errorName};}finally{${handleName}.end();}`;
1056
+ var walkOwnBody = (node, visitNode) => {
1057
+ if (!node.body) return;
1058
+ const visit = (candidate) => {
1059
+ if (candidate !== node && ts6.isFunctionLike(candidate)) return;
1060
+ visitNode(candidate);
1061
+ ts6.forEachChild(candidate, visit);
1062
+ };
1063
+ visit(node.body);
1064
+ };
1065
+ var collectAwaitWraps = (node, sourceFile, handleName, edits, depth) => {
1066
+ walkOwnBody(node, (candidate) => {
1067
+ if (!ts6.isAwaitExpression(candidate)) return;
1068
+ wrap(
1069
+ edits,
1070
+ candidate.expression.getStart(sourceFile),
1071
+ candidate.expression.end,
1072
+ `${handleName}.suspend(`,
1073
+ ")",
1074
+ depth + 3
1075
+ );
1076
+ wrap(
1077
+ edits,
1078
+ candidate.getStart(sourceFile),
1079
+ candidate.end,
1080
+ `${handleName}.resume(`,
1081
+ ")",
1082
+ depth + 2
1083
+ );
1084
+ });
1085
+ };
1086
+ var collectCatchFinallyResumes = (node, sourceFile, handleName, edits, depth) => {
1087
+ walkOwnBody(node, (candidate) => {
1088
+ if (!ts6.isTryStatement(candidate)) return;
1089
+ const blocks = [candidate.catchClause?.block, candidate.finallyBlock];
1090
+ for (const block of blocks) {
1091
+ if (!block) continue;
1092
+ edits.push({
1093
+ position: block.getStart(sourceFile) + 1,
1094
+ text: `${handleName}.resume();`,
1095
+ deleteCount: 0,
1096
+ depth,
1097
+ width: 0,
1098
+ kind: "replace"
1099
+ });
1100
+ }
1101
+ });
1102
+ };
1103
+ var collectReturnWraps = (node, sourceFile, handleName, edits, depth) => {
1104
+ if (!node.body || !ts6.isBlock(node.body)) return;
1105
+ const visit = (candidate, at) => {
1106
+ if (candidate !== node && ts6.isFunctionLike(candidate)) return;
1107
+ if (ts6.isReturnStatement(candidate) && candidate.expression) {
1108
+ const expression = candidate.expression;
1109
+ wrap(
1110
+ edits,
1111
+ expression.getStart(sourceFile),
1112
+ expression.end,
1113
+ `(${handleName}.leave((`,
1114
+ ")))",
1115
+ at
1116
+ );
1117
+ return;
1118
+ }
1119
+ ts6.forEachChild(candidate, (child) => visit(child, at + 1));
1120
+ };
1121
+ visit(node.body, depth + 1);
1034
1122
  };
1035
1123
  var transformSource = (source, options) => {
1036
1124
  if (source.includes("@rasputin-ignore-file")) return void 0;
1037
- const sourceFile = ts3.createSourceFile(
1125
+ if (sourceIsRasputinInstrumented(source)) return alreadyInstrumentedResult(source);
1126
+ const sourceFile = ts6.createSourceFile(
1038
1127
  options.filePath,
1039
1128
  source,
1040
- ts3.ScriptTarget.Latest,
1129
+ ts6.ScriptTarget.Latest,
1041
1130
  true,
1042
- scriptKindFor(options.filePath)
1131
+ scriptKindFor3(options.filePath)
1043
1132
  );
1044
1133
  const runName = uniqueName(source, "__rasputinRun__");
1045
- const capName = uniqueName(source, "__rasputinCap__");
1046
1134
  const plansName = uniqueName(source, "__rasputinPlans__");
1047
- const absolutePath = resolve3(options.filePath);
1048
- const derivationMode = options.derivationMode ?? "analysis";
1135
+ const handleName = uniqueName(source, "__rasputinH__");
1136
+ const errorName = uniqueName(source, "__rasputinE__");
1137
+ const absolutePath = resolve4(options.filePath);
1049
1138
  const edits = [];
1050
- let order = 0;
1051
1139
  const delta = {
1052
1140
  functions: [],
1053
1141
  callSites: [],
1054
- destructures: [],
1055
- derivationPlans: []
1142
+ destructures: []
1056
1143
  };
1057
1144
  const collectContext = {
1058
1145
  sourceFile,
@@ -1060,75 +1147,134 @@ var transformSource = (source, options) => {
1060
1147
  absolutePath
1061
1148
  };
1062
1149
  const pending = [];
1063
- const visit = (node, caller) => {
1150
+ const skipFunction = (collected, reason) => {
1151
+ collected.instrumented = false;
1152
+ collected.skipReason = reason;
1153
+ delta.functions.push(collected);
1154
+ if (process.env.RASPUTIN_DEBUG === "1") {
1155
+ console.info(
1156
+ `[rasputin] skipped instrumentation for ${collected.name} in ${options.filePath}: ${reason}`
1157
+ );
1158
+ }
1159
+ };
1160
+ const visit = (node, caller, depth) => {
1161
+ if (isAsyncGeneratorLike(node) && node.body) {
1162
+ const collected = collectWrappedFunction(
1163
+ node,
1164
+ sourceFile,
1165
+ absolutePath,
1166
+ options.mapLocation
1167
+ );
1168
+ if (collected) {
1169
+ collected.asyncFunction = true;
1170
+ skipFunction(collected, "unsupported_async_generator");
1171
+ }
1172
+ ts6.forEachChild(node, (child) => visit(child, caller, depth + 1));
1173
+ return;
1174
+ }
1175
+ if (isSyncGeneratorLike(node) && node.body) {
1176
+ const collected = collectWrappedFunction(
1177
+ node,
1178
+ sourceFile,
1179
+ absolutePath,
1180
+ options.mapLocation
1181
+ );
1182
+ if (collected) skipFunction(collected, "unsupported_sync_generator");
1183
+ ts6.forEachChild(node, (child) => visit(child, caller, depth + 1));
1184
+ return;
1185
+ }
1186
+ if (ts6.isConstructorDeclaration(node) && node.body) {
1187
+ const collected = collectWrappedFunction(
1188
+ node,
1189
+ sourceFile,
1190
+ absolutePath,
1191
+ options.mapLocation
1192
+ );
1193
+ if (collected) skipFunction(collected, "unsupported_constructor");
1194
+ ts6.forEachChild(node, (child) => visit(child, caller, depth + 1));
1195
+ return;
1196
+ }
1064
1197
  if (isSupportedFunction(node)) {
1065
1198
  const collected = collectWrappedFunction(node, sourceFile, absolutePath, options.mapLocation);
1066
1199
  if (!collected) {
1067
- ts3.forEachChild(node, (child) => visit(child, caller));
1200
+ ts6.forEachChild(node, (child) => visit(child, caller, depth + 1));
1201
+ return;
1202
+ }
1203
+ const asyncFunction = isAsyncFunctionLike(node);
1204
+ collected.asyncFunction = asyncFunction;
1205
+ const decision = asyncFunction ? decideAsyncInstrumentation(node, sourceFile) : { action: "inline" };
1206
+ if (decision.action === "skip") {
1207
+ skipFunction(collected, decision.reason);
1208
+ ts6.forEachChild(node, (child) => visit(child, caller, depth + 1));
1068
1209
  return;
1069
1210
  }
1070
- pending.push({ node, collected });
1211
+ if (functionHasPreBodyParameterEvaluation(node)) {
1212
+ collected.plan = withPreBodyParameterEvaluation(collected.plan);
1213
+ }
1214
+ pending.push({
1215
+ node,
1216
+ collected,
1217
+ depth,
1218
+ suspendResume: decision.action === "suspend_resume"
1219
+ });
1071
1220
  delta.functions.push(collected);
1072
- ts3.forEachChild(node, (child) => visit(child, collected));
1221
+ ts6.forEachChild(node, (child) => visit(child, collected, depth + 1));
1073
1222
  return;
1074
1223
  }
1075
- if (caller && (ts3.isCallExpression(node) || ts3.isNewExpression(node))) {
1224
+ if (caller && (ts6.isCallExpression(node) || ts6.isNewExpression(node))) {
1076
1225
  const site = collectCallSite(node, caller, collectContext);
1077
1226
  if (site) delta.callSites.push(site);
1078
1227
  }
1079
- if (caller && ts3.isVariableDeclaration(node)) {
1228
+ if (caller && ts6.isVariableDeclaration(node)) {
1080
1229
  const destructure = collectParamDestructure(node, caller, collectContext);
1081
1230
  if (destructure) delta.destructures.push(destructure);
1082
1231
  }
1083
- ts3.forEachChild(node, (child) => visit(child, caller));
1232
+ ts6.forEachChild(node, (child) => visit(child, caller, depth + 1));
1084
1233
  };
1085
- visit(sourceFile);
1086
- if (pending.length === 0) return void 0;
1087
- const analyzed = analyzeModuleFunctions(
1088
- sourceFile,
1089
- pending.map((entry) => ({
1090
- functionId: entry.collected.functionId,
1091
- name: entry.collected.name,
1092
- node: entry.node
1093
- }))
1094
- );
1095
- delta.derivationPlans = analyzed.map((fn) => fn.plan);
1096
- const analyzedById = new Map(analyzed.map((fn) => [fn.functionId, fn]));
1234
+ visit(sourceFile, void 0, 0);
1235
+ if (pending.length === 0) {
1236
+ return delta.functions.length > 0 ? { code: source, delta } : void 0;
1237
+ }
1097
1238
  for (const [planIndex, entry] of pending.entries()) {
1098
- const analysis = analyzedById.get(entry.collected.functionId);
1099
- const omitValues = derivationMode === "selective" && analysis && canOmitCallValues(analysis, analyzed);
1100
- const valueFlag = omitValues ? ",1" : "";
1101
- const args = ts3.isArrowFunction(entry.node) ? arrowArguments(entry.node) : "arguments";
1102
- const asyncCallback = entry.node.modifiers?.some(
1103
- (modifier) => modifier.kind === ts3.SyntaxKind.AsyncKeyword
1104
- ) ? "async " : "";
1105
- if (ts3.isBlock(entry.node.body)) {
1106
- edits.push({
1107
- position: entry.node.body.getStart(sourceFile) + 1,
1108
- text: `return ${runName}(${planIndex},${args},${asyncCallback}()=>{`,
1109
- order: order++
1110
- });
1111
- edits.push({
1112
- position: entry.node.body.end - 1,
1113
- text: `}${valueFlag});`,
1114
- order: order++
1115
- });
1116
- } else {
1117
- edits.push({
1118
- position: entry.node.body.getStart(sourceFile),
1119
- text: `${runName}(${planIndex},${args},${asyncCallback}()=>(`,
1120
- order: order++
1121
- });
1122
- edits.push({
1123
- position: entry.node.body.end,
1124
- text: `)${valueFlag})`,
1125
- order: order++
1126
- });
1127
- }
1128
- if (analysis) {
1129
- for (const edit of captureWrapEdits(analysis.captureWraps, capName)) {
1130
- edits.push({ ...edit, order: order++ });
1239
+ const captured = ts6.isArrowFunction(entry.node) ? arrowArgsCapture(entry.node) : { args: "arguments", omitValues: false };
1240
+ const omitArg = captured.omitValues ? ",true" : "";
1241
+ const enterCall = `${runName}.enter(${planIndex},${captured.args}${omitArg})`;
1242
+ const epilogue = failEpilogue(handleName, errorName, captured.args, entry.suspendResume);
1243
+ if (ts6.isBlock(entry.node.body)) {
1244
+ const prologueAt = insertAfterSuperOrPrologue(entry.node.body, sourceFile);
1245
+ const closeAt = entry.node.body.end - 1;
1246
+ wrap(
1247
+ edits,
1248
+ prologueAt,
1249
+ closeAt,
1250
+ `;const ${handleName}=${enterCall};try{`,
1251
+ epilogue,
1252
+ entry.depth
1253
+ );
1254
+ collectReturnWraps(entry.node, sourceFile, handleName, edits, entry.depth);
1255
+ if (entry.suspendResume) {
1256
+ collectAwaitWraps(entry.node, sourceFile, handleName, edits, entry.depth);
1257
+ collectCatchFinallyResumes(entry.node, sourceFile, handleName, edits, entry.depth);
1131
1258
  }
1259
+ } else if (entry.suspendResume) {
1260
+ wrap(
1261
+ edits,
1262
+ entry.node.body.getStart(sourceFile),
1263
+ entry.node.body.end,
1264
+ `{;const ${handleName}=${enterCall};try{return (${handleName}.leave((`,
1265
+ `)));${epilogue}}`,
1266
+ entry.depth
1267
+ );
1268
+ collectAwaitWraps(entry.node, sourceFile, handleName, edits, entry.depth);
1269
+ } else {
1270
+ wrap(
1271
+ edits,
1272
+ entry.node.body.getStart(sourceFile),
1273
+ entry.node.body.end,
1274
+ `{;const ${handleName}=${enterCall};try{return (${handleName}.leave((`,
1275
+ `)));${epilogue}}`,
1276
+ entry.depth
1277
+ );
1132
1278
  }
1133
1279
  }
1134
1280
  const insertAt = insertPositionAfterPrologue(source, sourceFile);
@@ -1139,19 +1285,189 @@ var transformSource = (source, options) => {
1139
1285
  insertAt,
1140
1286
  modulePreamble(
1141
1287
  runName,
1142
- capName,
1143
1288
  plansName,
1144
- JSON.stringify(delta.functions.map((fn) => fn.plan))
1289
+ JSON.stringify(pending.map((entry) => entry.collected.plan))
1145
1290
  )
1146
1291
  ),
1147
- order: -1
1292
+ deleteCount: 0,
1293
+ depth: -1,
1294
+ width: 0,
1295
+ kind: "replace"
1148
1296
  });
1149
- edits.sort((left, right) => right.position - left.position || right.order - left.order);
1297
+ edits.sort(compareEdits);
1150
1298
  let transformed = source;
1151
1299
  for (const edit of edits) {
1152
- transformed = `${transformed.slice(0, edit.position)}${edit.text}${transformed.slice(edit.position)}`;
1300
+ const text = separateFromPreviousToken(transformed, edit.position, edit.text);
1301
+ transformed = `${transformed.slice(0, edit.position)}${text}${transformed.slice(edit.position + edit.deleteCount)}`;
1302
+ }
1303
+ const verify = options.verifyTransform ?? true;
1304
+ const refuseTransformed = () => {
1305
+ for (const entry of pending) {
1306
+ entry.collected.instrumented = false;
1307
+ entry.collected.skipReason = "verification_failed";
1308
+ }
1309
+ if (process.env.RASPUTIN_DEBUG === "1") {
1310
+ console.info(
1311
+ `[rasputin] skipped instrumentation for ${options.filePath}: verification_failed`
1312
+ );
1313
+ }
1314
+ return { code: source, delta };
1315
+ };
1316
+ if (verify && transformedSourceIsInvalid(options.filePath, transformed)) {
1317
+ if (transformedSourceIsInvalid(options.filePath, source)) {
1318
+ if (process.env.RASPUTIN_DEBUG === "1") {
1319
+ console.info(
1320
+ `[rasputin] skipped instrumentation for ${options.filePath}: transformed source failed verification`
1321
+ );
1322
+ }
1323
+ return void 0;
1324
+ }
1325
+ return refuseTransformed();
1326
+ }
1327
+ if (verify && !instrumentationTreesAreIsomorphic(options.filePath, source, transformed)) {
1328
+ return refuseTransformed();
1329
+ }
1330
+ return { code: attachInlineSourceMap(source, transformed, options.filePath, edits), delta };
1331
+ };
1332
+
1333
+ // src/auto-instrumentation/transform-disk-cache.ts
1334
+ var projectCacheDir = () => {
1335
+ let directory = process.cwd();
1336
+ for (let depth = 0; depth < 10; depth++) {
1337
+ const modules = join2(directory, "node_modules");
1338
+ try {
1339
+ if (statSync(modules).isDirectory()) {
1340
+ return join2(modules, ".cache", "rasputin-transform", SDK_VERSION);
1341
+ }
1342
+ } catch {
1343
+ }
1344
+ const parent = dirname2(directory);
1345
+ if (parent === directory) break;
1346
+ directory = parent;
1347
+ }
1348
+ return void 0;
1349
+ };
1350
+ var resolveTransformCacheDir = () => process.env.RASPUTIN_TRANSFORM_CACHE_DIR ?? projectCacheDir() ?? join2(homedir(), ".cache", "rasputin", "transform", SDK_VERSION);
1351
+ var defaultCacheLimit = () => {
1352
+ const parsed = Number(process.env.RASPUTIN_TRANSFORM_CACHE_MAX);
1353
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : 256;
1354
+ };
1355
+ var cacheEnabled = () => process.env.RASPUTIN_TRANSFORM_CACHE !== "0";
1356
+ var directoryIsWorldWritable = (directory) => {
1357
+ try {
1358
+ const stats = lstatSync(directory);
1359
+ return !stats.isDirectory() || (stats.mode & 2) !== 0;
1360
+ } catch {
1361
+ return true;
1362
+ }
1363
+ };
1364
+ var directoryOwnerTrusted = (directory) => {
1365
+ if (process.platform === "win32") return true;
1366
+ try {
1367
+ const stats = lstatSync(directory);
1368
+ const uid = process.getuid?.();
1369
+ if (uid === void 0) return true;
1370
+ return stats.uid === uid;
1371
+ } catch {
1372
+ return false;
1373
+ }
1374
+ };
1375
+ var ensureTrustedCacheDir = (directory, explicit) => {
1376
+ try {
1377
+ mkdirSync(directory, { recursive: true, mode: 448 });
1378
+ try {
1379
+ chmodSync(directory, 448);
1380
+ } catch {
1381
+ }
1382
+ const stats = lstatSync(directory);
1383
+ if (!stats.isDirectory()) return false;
1384
+ if (explicit) return true;
1385
+ if (process.platform === "win32") return true;
1386
+ return !directoryIsWorldWritable(directory) && directoryOwnerTrusted(directory);
1387
+ } catch {
1388
+ return false;
1389
+ }
1390
+ };
1391
+ var cacheKey = (source, filePath, verifyTransform) => createHash("sha256").update(SDK_VERSION).update("\0").update(filePath).update("\0").update(source).update("\0").update(sourceMapPayloadForCache(filePath, source)).update("\0").update(verifyTransform ? "1" : "0").digest("hex");
1392
+ var revive = (cached) => ({
1393
+ code: cached.code,
1394
+ delta: {
1395
+ functions: cached.delta.functions.map((fn) => ({
1396
+ ...fn,
1397
+ instrumented: fn.instrumented !== false,
1398
+ paramNames: new Set(fn.paramNames)
1399
+ })),
1400
+ callSites: cached.delta.callSites,
1401
+ destructures: cached.delta.destructures
1402
+ }
1403
+ });
1404
+ var persistable = (result) => ({
1405
+ code: result.code,
1406
+ delta: {
1407
+ functions: result.delta.functions.map((fn) => ({
1408
+ ...fn,
1409
+ paramNames: [...fn.paramNames]
1410
+ })),
1411
+ callSites: result.delta.callSites,
1412
+ destructures: result.delta.destructures
1413
+ }
1414
+ });
1415
+ var evictOldest = (directory, keep) => {
1416
+ const entries = readdirSync(directory).map((name) => {
1417
+ const path = join2(directory, name);
1418
+ try {
1419
+ return { path, mtime: statSync(path).mtimeMs };
1420
+ } catch {
1421
+ return void 0;
1422
+ }
1423
+ }).filter((entry) => entry !== void 0).sort((left, right) => left.mtime - right.mtime);
1424
+ while (entries.length > keep) {
1425
+ const oldest = entries.shift();
1426
+ if (!oldest) break;
1427
+ try {
1428
+ unlinkSync(oldest.path);
1429
+ } catch {
1430
+ }
1153
1431
  }
1154
- return { code: transformed, delta };
1432
+ };
1433
+ var readCache = (directory, key) => {
1434
+ try {
1435
+ const parsed = JSON.parse(
1436
+ readFileSync2(join2(directory, `${key}.json`), "utf8")
1437
+ );
1438
+ if (typeof parsed.code !== "string" || !parsed.delta) return void 0;
1439
+ return revive(parsed);
1440
+ } catch {
1441
+ return void 0;
1442
+ }
1443
+ };
1444
+ var writeCache = (directory, key, result, limit) => {
1445
+ try {
1446
+ const path = join2(directory, `${key}.json`);
1447
+ writeFileSync(path, JSON.stringify(persistable(result)), { mode: 384 });
1448
+ try {
1449
+ chmodSync(path, 384);
1450
+ } catch {
1451
+ }
1452
+ evictOldest(directory, limit);
1453
+ } catch {
1454
+ }
1455
+ };
1456
+ var transformSourceCached = (source, options, cache = {}) => {
1457
+ const transformOptions = {
1458
+ ...options,
1459
+ verifyTransform: options.verifyTransform ?? process.env.RASPUTIN_VERIFY_TRANSFORM !== "0"
1460
+ };
1461
+ if (!cacheEnabled()) return transformSource(source, transformOptions);
1462
+ const explicit = cache.directory !== void 0;
1463
+ const directory = cache.directory ?? resolveTransformCacheDir();
1464
+ if (!ensureTrustedCacheDir(directory, explicit)) return transformSource(source, transformOptions);
1465
+ const key = cacheKey(source, options.filePath, transformOptions.verifyTransform === true);
1466
+ const hit = readCache(directory, key);
1467
+ if (hit) return hit;
1468
+ const result = transformSource(source, transformOptions);
1469
+ if (result) writeCache(directory, key, result, cache.limit ?? defaultCacheLimit());
1470
+ return result;
1155
1471
  };
1156
1472
 
1157
1473
  // src/auto-instrumentation/bun-plugin.ts
@@ -1164,13 +1480,13 @@ var loaderFor = (filePath) => {
1164
1480
  var loadedSourceFilter = /^(?!.*[\\/](?:node_modules|dist|build|out|coverage|generated|\.git|\.next|\.svelte-kit|\.output|\.vercel|\.rasputin|\.turbo|\.yarn|\.pnpm|\.bun)[\\/]).*\.[cm]?[jt]sx?$/i;
1165
1481
  var isLoadedApplicationSource = (filePath, options = {}) => isApplicationSource(filePath, { ...options, includeBuildOutput: false });
1166
1482
  var loadSourceForInstrumentation = async (filePath, options = {}) => {
1167
- const absolutePath = resolve4(filePath);
1483
+ const absolutePath = resolve5(filePath);
1168
1484
  const source = await readFile(absolutePath, "utf8");
1169
1485
  if (!isLoadedApplicationSource(absolutePath, options)) {
1170
1486
  return { contents: source, loader: loaderFor(absolutePath), transformed: false };
1171
1487
  }
1172
1488
  try {
1173
- const transformed = transformSource(source, { filePath: absolutePath });
1489
+ const transformed = transformSourceCached(source, { filePath: absolutePath });
1174
1490
  if (transformed) {
1175
1491
  recordInstrumentationManifestDelta(absolutePath, transformed.delta);
1176
1492
  }
@@ -1179,7 +1495,11 @@ var loadSourceForInstrumentation = async (filePath, options = {}) => {
1179
1495
  loader: loaderFor(absolutePath),
1180
1496
  transformed: transformed !== void 0
1181
1497
  };
1182
- } catch {
1498
+ } catch (error) {
1499
+ if (options.debug ?? process.env.RASPUTIN_DEBUG === "1") {
1500
+ const detail = error instanceof Error ? error.message : "unknown error";
1501
+ console.info(`[rasputin] skipped instrumentation for ${absolutePath}: ${detail}`);
1502
+ }
1183
1503
  return { contents: source, loader: loaderFor(absolutePath), transformed: false };
1184
1504
  }
1185
1505
  };
@@ -1190,11 +1510,11 @@ var createRasputinBunPlugin = (options = {}) => {
1190
1510
  setup(build) {
1191
1511
  if (debug) {
1192
1512
  console.info(
1193
- options.rootDir ? `[rasputin] automatic instrumentation active (source root: ${resolve4(options.rootDir)})` : "[rasputin] automatic instrumentation active (loaded local source)"
1513
+ options.rootDir ? `[rasputin] automatic instrumentation active (source root: ${resolve5(options.rootDir)})` : "[rasputin] automatic instrumentation active (loaded local source)"
1194
1514
  );
1195
1515
  }
1196
1516
  build.onLoad({ filter: loadedSourceFilter }, async ({ path }) => {
1197
- const absolutePath = resolve4(path);
1517
+ const absolutePath = resolve5(path);
1198
1518
  const result = await loadSourceForInstrumentation(absolutePath, options);
1199
1519
  if (debug && result.transformed) {
1200
1520
  console.info(`[rasputin] instrumented ${absolutePath}`);
@@ -1206,9 +1526,9 @@ var createRasputinBunPlugin = (options = {}) => {
1206
1526
  };
1207
1527
 
1208
1528
  // src/auto-instrumentation/sdk-source-boundary.ts
1209
- import { dirname, resolve as resolve5 } from "node:path";
1210
- import { fileURLToPath } from "node:url";
1211
- var sdkPackageScopeRootFrom = (instrumentEntryUrl) => resolve5(dirname(fileURLToPath(instrumentEntryUrl)), "../../..");
1529
+ import { dirname as dirname3, resolve as resolve6 } from "node:path";
1530
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
1531
+ var sdkPackageScopeRootFrom = (instrumentEntryUrl) => resolve6(dirname3(fileURLToPath2(instrumentEntryUrl)), "../../..");
1212
1532
 
1213
1533
  // src/auto-instrumentation/warn-production-runtime-transform.ts
1214
1534
  var RUNTIME_TRANSFORM_WARNING_SUPPRESS_ENV = "RASPUTIN_SUPPRESS_RUNTIME_TRANSFORM_WARNING";
@@ -1219,7 +1539,7 @@ var warnIfProductionRuntimeTransform = () => {
1219
1539
  if (Reflect.get(globalThis, warnedSymbol)) return;
1220
1540
  Reflect.set(globalThis, warnedSymbol, true);
1221
1541
  console.warn(
1222
- "[rasputin] Production is using load-time source transformation (`--import` / `--preload`). Prefer build-time `rasputin-instrument` so the process does not load the TypeScript compiler. Set RASPUTIN_SUPPRESS_RUNTIME_TRANSFORM_WARNING=1 to hide this warning."
1542
+ "[rasputin] This production process is wrapping source as it loads (`--import` / `--preload`). Instrument compiled output at build time with `rasputin-instrument` instead \u2014 wrapping at startup is slower and uses more memory. Set RASPUTIN_SUPPRESS_RUNTIME_TRANSFORM_WARNING=1 to hide this warning."
1223
1543
  );
1224
1544
  };
1225
1545