@rasputin-ai/node 0.5.0-alpha.8 → 0.5.0

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 (80) hide show
  1. package/README.md +219 -188
  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 +10 -2
  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 +124 -36
  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 +14 -0
  48. package/dist/execution-recorder/runtime-state-size-budget.d.ts.map +1 -0
  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 +2637 -894
  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 -797
  61. package/dist/instrument/bun.js +1098 -770
  62. package/dist/instrument/node.js +1078 -772
  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/dist/sdk-meta.d.ts.map +1 -1
  70. package/package.json +8 -3
  71. package/dist/derivation/analyze-function.d.ts +0 -42
  72. package/dist/derivation/analyze-function.d.ts.map +0 -1
  73. package/dist/derivation/build-dependency-graph.d.ts +0 -21
  74. package/dist/derivation/build-dependency-graph.d.ts.map +0 -1
  75. package/dist/derivation/derivation-ir.d.ts +0 -33
  76. package/dist/derivation/derivation-ir.d.ts.map +0 -1
  77. package/dist/derivation/derivation-proof.d.ts +0 -14
  78. package/dist/derivation/derivation-proof.d.ts.map +0 -1
  79. package/dist/derivation/emit-capture-plan.d.ts +0 -8
  80. package/dist/derivation/emit-capture-plan.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,657 +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";
92
-
93
- // src/derivation/analyze-function.ts
94
- import ts from "typescript";
95
-
96
- // src/derivation/build-dependency-graph.ts
97
- var buildDependencyGraph = (functions, edges) => {
98
- const names = new Set(functions.map((fn) => fn.name));
99
- const localEdges = edges.filter((edge) => names.has(edge.calleeName));
100
- const calleesByCaller = /* @__PURE__ */ new Map();
101
- for (const edge of localEdges) {
102
- const existing = calleesByCaller.get(edge.callerFunctionId) ?? [];
103
- existing.push(edge.calleeName);
104
- calleesByCaller.set(edge.callerFunctionId, existing);
105
- }
106
- return {
107
- functions: functions.map((fn) => fn.functionId),
108
- edges: localEdges,
109
- calleesByCaller
110
- };
111
- };
112
- var topologicalFunctionOrder = (functions, graph) => {
113
- const idByName = new Map(functions.map((fn) => [fn.name, fn.functionId]));
114
- const remainingCallees = new Map(
115
- functions.map((fn) => {
116
- const unique = new Set(
117
- (graph.calleesByCaller.get(fn.functionId) ?? []).map((name) => idByName.get(name)).filter((id) => Boolean(id) && id !== fn.functionId)
118
- );
119
- return [fn.functionId, unique.size];
120
- })
121
- );
122
- const callersOf = /* @__PURE__ */ new Map();
123
- for (const fn of functions) {
124
- for (const calleeName of graph.calleesByCaller.get(fn.functionId) ?? []) {
125
- const calleeId = idByName.get(calleeName);
126
- if (!calleeId || calleeId === fn.functionId) continue;
127
- const callers = callersOf.get(calleeId) ?? [];
128
- callers.push(fn.functionId);
129
- callersOf.set(calleeId, callers);
130
- }
131
- }
132
- const queue = functions.filter((fn) => (remainingCallees.get(fn.functionId) ?? 0) === 0).map((fn) => fn.functionId);
133
- const ordered = [];
134
- const queued = new Set(queue);
135
- while (queue.length > 0) {
136
- const current = queue.shift();
137
- if (current === void 0) break;
138
- ordered.push(current);
139
- for (const caller of callersOf.get(current) ?? []) {
140
- const next = (remainingCallees.get(caller) ?? 0) - 1;
141
- remainingCallees.set(caller, next);
142
- if (next === 0 && !queued.has(caller)) {
143
- queued.add(caller);
144
- queue.push(caller);
145
- }
146
- }
147
- }
148
- for (const fn of functions) {
149
- if (!queued.has(fn.functionId)) ordered.push(fn.functionId);
150
- }
151
- return ordered;
152
- };
153
-
154
- // src/derivation/derivation-ir.ts
155
- var DerivationIrBuilder = class {
156
- expressions = [];
157
- nextId = 0;
158
- constValue(value) {
159
- return this.add({ id: this.nextId++, kind: "const", value });
160
- }
161
- param(index) {
162
- return this.add({ id: this.nextId++, kind: "param", index });
163
- }
164
- root(rootId) {
165
- return this.add({ id: this.nextId++, kind: "root", rootId });
166
- }
167
- alias(from) {
168
- return this.add({ id: this.nextId++, kind: "alias", from });
169
- }
170
- binary(op, left, right, guard) {
171
- return this.add({ id: this.nextId++, kind: "binary", op, left, right, guard });
172
- }
173
- unary(op, operand) {
174
- return this.add({ id: this.nextId++, kind: "unary", op, operand });
175
- }
176
- logical(op, left, right) {
177
- return this.add({ id: this.nextId++, kind: "logical", op, left, right });
178
- }
179
- cond(test, consequent, alternate) {
180
- return this.add({ id: this.nextId++, kind: "cond", test, consequent, alternate });
181
- }
182
- object(properties) {
183
- return this.add({ id: this.nextId++, kind: "object", properties });
184
- }
185
- array(elements) {
186
- return this.add({ id: this.nextId++, kind: "array", elements });
187
- }
188
- prop(object, key) {
189
- return this.add({ id: this.nextId++, kind: "prop", object, key });
190
- }
191
- index(array, index) {
192
- return this.add({ id: this.nextId++, kind: "index", array, index });
193
- }
194
- pureCall(calleeFunctionId, args) {
195
- return this.add({ id: this.nextId++, kind: "pure_call", calleeFunctionId, args });
196
- }
197
- template(parts) {
198
- return this.add({ id: this.nextId++, kind: "template", parts });
199
- }
200
- add(expression) {
201
- this.expressions.push(expression);
202
- return expression.id;
203
- }
204
- };
205
- var emptyPlan = (functionId) => ({
206
- planId: `dp_${functionId}`,
207
- functionId,
208
- expressions: [],
209
- variables: [],
210
- captureBoundaries: [],
211
- controlFacts: []
212
- });
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";
213
120
 
214
- // src/derivation/derivation-proof.ts
215
- var functionIsExact = (plan) => plan.fallbackReason === void 0 && plan.captureBoundaries.length === 0 && plan.controlFacts.length === 0;
121
+ // src/sdk-meta.ts
122
+ var SDK_VERSION = "0.5.0";
216
123
 
217
- // src/derivation/analyze-function.ts
218
- var loc = (node, sourceFile) => {
219
- const position = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
220
- return { line: position.line + 1, column: position.character + 1 };
221
- };
222
- var lookup = (ctx, name) => {
223
- for (let index = ctx.scopes.length - 1; index >= 0; index--) {
224
- const binding = ctx.scopes[index]?.get(name);
225
- if (binding) return binding;
226
- }
227
- return void 0;
228
- };
229
- var fallback = (ctx, reason) => {
230
- ctx.fallback ??= reason;
231
- return { expressionId: ctx.ir.constValue({ type: "undefined" }), shape: "unknown" };
232
- };
233
- var capture = (ctx, node, reason) => {
234
- const rootId = ctx.nextRootId.value++;
235
- const position = loc(node, ctx.sourceFile);
236
- const expressionId = ctx.ir.root(rootId);
237
- ctx.captureWraps.push({
238
- rootId,
239
- reason,
240
- start: node.getStart(ctx.sourceFile),
241
- end: node.end,
242
- line: position.line,
243
- column: position.column,
244
- expressionId
245
- });
246
- return { expressionId, shape: "unknown", captured: true };
247
- };
248
- var binaryOp = (kind) => {
249
- switch (kind) {
250
- case ts.SyntaxKind.PlusToken:
251
- return "add";
252
- case ts.SyntaxKind.MinusToken:
253
- return "sub";
254
- case ts.SyntaxKind.AsteriskToken:
255
- return "mul";
256
- case ts.SyntaxKind.SlashToken:
257
- return "div";
258
- case ts.SyntaxKind.PercentToken:
259
- return "mod";
260
- case ts.SyntaxKind.EqualsEqualsToken:
261
- return "eq";
262
- case ts.SyntaxKind.ExclamationEqualsToken:
263
- return "neq";
264
- case ts.SyntaxKind.EqualsEqualsEqualsToken:
265
- return "seq";
266
- case ts.SyntaxKind.ExclamationEqualsEqualsToken:
267
- return "sneq";
268
- case ts.SyntaxKind.LessThanToken:
269
- return "lt";
270
- case ts.SyntaxKind.LessThanEqualsToken:
271
- return "lte";
272
- case ts.SyntaxKind.GreaterThanToken:
273
- return "gt";
274
- case ts.SyntaxKind.GreaterThanEqualsToken:
275
- return "gte";
276
- default:
277
- return void 0;
278
- }
279
- };
280
- var isNumericBinaryOp = (op) => op === "add" || op === "sub" || op === "mul" || op === "div" || op === "mod";
281
- var unwrapParens = (node) => {
282
- while (ts.isParenthesizedExpression(node)) node = node.expression;
283
- return node;
284
- };
285
- var numericLiteralValue = (node) => {
286
- const value = Number(node.text);
287
- return { type: "number", value };
288
- };
289
- var analyzeExpression = (ctx, node) => {
290
- if (ctx.fallback) return fallback(ctx, ctx.fallback);
291
- if (ts.isParenthesizedExpression(node)) return analyzeExpression(ctx, node.expression);
292
- if (ts.isAsExpression(node) || ts.isNonNullExpression(node)) {
293
- return analyzeExpression(ctx, node.expression);
294
- }
295
- if (node.kind === ts.SyntaxKind.TypeAssertionExpression) {
296
- return analyzeExpression(ctx, node.expression);
297
- }
298
- if (ts.isSatisfiesExpression(node)) return analyzeExpression(ctx, node.expression);
299
- if (node.kind === ts.SyntaxKind.NullKeyword) {
300
- return { expressionId: ctx.ir.constValue({ type: "null" }), shape: "primitive" };
301
- }
302
- if (node.kind === ts.SyntaxKind.TrueKeyword) {
303
- return {
304
- expressionId: ctx.ir.constValue({ type: "boolean", value: true }),
305
- shape: "primitive"
306
- };
307
- }
308
- if (node.kind === ts.SyntaxKind.FalseKeyword) {
309
- return {
310
- expressionId: ctx.ir.constValue({ type: "boolean", value: false }),
311
- shape: "primitive"
312
- };
313
- }
314
- if (ts.isNumericLiteral(node)) {
315
- return { expressionId: ctx.ir.constValue(numericLiteralValue(node)), shape: "primitive" };
316
- }
317
- if (ts.isPrefixUnaryExpression(node) && ts.isNumericLiteral(node.operand) && node.operator === ts.SyntaxKind.MinusToken) {
318
- const value = -Number(node.operand.text);
319
- return {
320
- expressionId: ctx.ir.constValue(
321
- Object.is(value, -0) ? { type: "number", value: 0, negativeZero: true } : { type: "number", value }
322
- ),
323
- shape: "primitive"
324
- };
325
- }
326
- if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
327
- return {
328
- expressionId: ctx.ir.constValue({ type: "string", value: node.text }),
329
- shape: "primitive"
330
- };
331
- }
332
- if (ts.isBigIntLiteral(node)) {
333
- return {
334
- expressionId: ctx.ir.constValue({ type: "bigint", value: node.text.replace(/n$/, "") }),
335
- shape: "primitive"
336
- };
337
- }
338
- if (ts.isIdentifier(node)) {
339
- if (node.text === "undefined") {
340
- return { expressionId: ctx.ir.constValue({ type: "undefined" }), shape: "primitive" };
341
- }
342
- const binding = lookup(ctx, node.text);
343
- if (!binding) return capture(ctx, node, "external_state");
344
- if (binding.mutable) return capture(ctx, node, "mutable_object");
345
- return {
346
- expressionId: ctx.ir.alias(binding.expressionId),
347
- shape: binding.shape
348
- };
349
- }
350
- if (ts.isTemplateExpression(node)) {
351
- const parts = [
352
- { kind: "lit", value: node.head.text }
353
- ];
354
- for (const span of node.templateSpans) {
355
- const nested = analyzeExpression(ctx, span.expression);
356
- if (nested.shape !== "primitive" && !nested.captured) {
357
- return capture(ctx, node, "unsupported_expression");
358
- }
359
- parts.push({ kind: "expr", expressionId: nested.expressionId });
360
- parts.push({ kind: "lit", value: span.literal.text });
361
- }
362
- return { expressionId: ctx.ir.template(parts), shape: "primitive" };
363
- }
364
- if (ts.isArrayLiteralExpression(node)) {
365
- if (node.elements.some((element) => ts.isSpreadElement(element))) {
366
- return capture(ctx, node, "unsupported_expression");
367
- }
368
- const elements = node.elements.map((element) => {
369
- if (ts.isOmittedExpression(element)) return { kind: "hole" };
370
- return analyzeExpression(ctx, element).expressionId;
371
- });
372
- return { expressionId: ctx.ir.array(elements), shape: "array" };
373
- }
374
- if (ts.isObjectLiteralExpression(node)) {
375
- const properties = [];
376
- for (const property of node.properties) {
377
- if (ts.isShorthandPropertyAssignment(property)) {
378
- const nested = analyzeExpression(ctx, property.name);
379
- if (nested.captured) {
380
- const wrap = ctx.captureWraps.find(
381
- (candidate) => candidate.expressionId === nested.expressionId
382
- );
383
- if (wrap) wrap.shorthandPropertyName = property.name.text;
384
- }
385
- properties.push({ key: property.name.text, value: nested.expressionId });
386
- continue;
387
- }
388
- if (!ts.isPropertyAssignment(property)) {
389
- return capture(ctx, node, "unsupported_expression");
390
- }
391
- if (ts.isPrivateIdentifier(property.name) || ts.isComputedPropertyName(property.name)) {
392
- return capture(ctx, node, "unsupported_expression");
393
- }
394
- const key = ts.isIdentifier(property.name) ? property.name.text : ts.isStringLiteral(property.name) || ts.isNumericLiteral(property.name) ? property.name.text : void 0;
395
- if (key === void 0) return capture(ctx, node, "unsupported_expression");
396
- properties.push({ key, value: analyzeExpression(ctx, property.initializer).expressionId });
397
- }
398
- return { expressionId: ctx.ir.object(properties), shape: "object" };
399
- }
400
- if (ts.isPropertyAccessExpression(node)) {
401
- if (node.questionDotToken) return capture(ctx, node, "unknown_property");
402
- const object = analyzeExpression(ctx, node.expression);
403
- if (object.shape === "object" && ts.isIdentifier(node.name)) {
404
- return { expressionId: ctx.ir.prop(object.expressionId, node.name.text), shape: "unknown" };
405
- }
406
- return capture(ctx, node, "unknown_property");
407
- }
408
- if (ts.isElementAccessExpression(node)) {
409
- if (node.questionDotToken) return capture(ctx, node, "unknown_property");
410
- const array = analyzeExpression(ctx, node.expression);
411
- if (array.shape === "array" && ts.isNumericLiteral(node.argumentExpression)) {
412
- return {
413
- expressionId: ctx.ir.index(array.expressionId, Number(node.argumentExpression.text)),
414
- shape: "unknown"
415
- };
416
- }
417
- return capture(ctx, node, "unknown_property");
418
- }
419
- if (ts.isCallExpression(node)) {
420
- if (node.questionDotToken || node.typeArguments?.length) {
421
- return capture(ctx, node, "unsupported_expression");
422
- }
423
- if (!ts.isIdentifier(node.expression)) return capture(ctx, node, "dynamic_callee");
424
- const calleeName = node.expression.text;
425
- if (lookup(ctx, calleeName)) return capture(ctx, node, "opaque_call");
426
- const provenId = ctx.provenExact.get(calleeName);
427
- ctx.callEdges.push({
428
- callerFunctionId: ctx.functionId,
429
- calleeName,
430
- ...provenId ? { calleeFunctionId: provenId } : {}
431
- });
432
- if (!provenId) return capture(ctx, node, "opaque_call");
433
- const args = node.arguments.map((argument) => {
434
- if (ts.isSpreadElement(argument)) return void 0;
435
- return analyzeExpression(ctx, argument).expressionId;
436
- });
437
- if (args.some((argument) => argument === void 0))
438
- return capture(ctx, node, "unsupported_expression");
439
- return {
440
- expressionId: ctx.ir.pureCall(
441
- provenId,
442
- args.filter((id) => id !== void 0)
443
- ),
444
- shape: "unknown"
445
- };
446
- }
447
- if (ts.isNewExpression(node) || ts.isAwaitExpression(node) || ts.isYieldExpression(node)) {
448
- return capture(
449
- ctx,
450
- node,
451
- node.kind === ts.SyntaxKind.AwaitExpression ? "nondeterministic" : "unsupported_expression"
452
- );
453
- }
454
- if (ts.isConditionalExpression(node)) {
455
- const test = analyzeExpression(ctx, node.condition);
456
- const consequent = analyzeExpression(ctx, node.whenTrue);
457
- const alternate = analyzeExpression(ctx, node.whenFalse);
458
- return {
459
- expressionId: ctx.ir.cond(test.expressionId, consequent.expressionId, alternate.expressionId),
460
- shape: consequent.shape === alternate.shape ? consequent.shape : "unknown"
461
- };
462
- }
463
- if (ts.isBinaryExpression(node)) {
464
- if (node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
465
- ctx.fallback ??= "mutable_state";
466
- return capture(ctx, node, "mutable_object");
467
- }
468
- if (node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || node.operatorToken.kind === ts.SyntaxKind.BarBarToken || node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
469
- const left2 = analyzeExpression(ctx, node.left);
470
- const right2 = analyzeExpression(ctx, node.right);
471
- const op2 = node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ? "and" : node.operatorToken.kind === ts.SyntaxKind.BarBarToken ? "or" : "nullish";
472
- return {
473
- expressionId: ctx.ir.logical(op2, left2.expressionId, right2.expressionId),
474
- shape: "unknown"
475
- };
476
- }
477
- const op = binaryOp(node.operatorToken.kind);
478
- if (!op) return capture(ctx, node, "unsupported_expression");
479
- if (!ts.isExpression(node.left)) return capture(ctx, node, "unsupported_expression");
480
- const left = analyzeExpression(ctx, node.left);
481
- const right = analyzeExpression(ctx, node.right);
482
- const numeric = isNumericBinaryOp(op);
483
- const expressionId = ctx.ir.binary(
484
- op,
485
- left.expressionId,
486
- right.expressionId,
487
- numeric ? "numeric" : "primitive"
488
- );
489
- if (numeric && (left.captured || right.captured || left.shape !== "primitive" || right.shape !== "primitive")) {
490
- ctx.hasCoercionGuard = true;
491
- const fallbackRootId = ctx.nextRootId.value++;
492
- ctx.guardWraps.push({
493
- fallbackRootId,
494
- start: node.getStart(ctx.sourceFile),
495
- end: node.end,
496
- op
497
- });
498
- }
499
- return { expressionId, shape: "primitive" };
500
- }
501
- if (ts.isPrefixUnaryExpression(node)) {
502
- const operand = analyzeExpression(ctx, node.operand);
503
- if (node.operator === ts.SyntaxKind.ExclamationToken) {
504
- return { expressionId: ctx.ir.unary("not", operand.expressionId), shape: "primitive" };
505
- }
506
- if (node.operator === ts.SyntaxKind.PlusToken || node.operator === ts.SyntaxKind.MinusToken) {
507
- if (operand.captured || operand.shape !== "primitive") ctx.hasCoercionGuard = true;
508
- return {
509
- expressionId: ctx.ir.unary(
510
- node.operator === ts.SyntaxKind.MinusToken ? "minus" : "plus",
511
- operand.expressionId
512
- ),
513
- shape: "primitive"
514
- };
515
- }
516
- return capture(ctx, node, "unsupported_expression");
517
- }
518
- if (ts.isTypeOfExpression(node)) {
519
- const operand = unwrapParens(node.expression);
520
- if (ts.isIdentifier(operand) && operand.text !== "undefined" && !lookup(ctx, operand.text)) {
521
- return capture(ctx, node, "external_state");
522
- }
523
- const inner = analyzeExpression(ctx, node.expression);
524
- return { expressionId: ctx.ir.unary("typeof", inner.expressionId), shape: "primitive" };
525
- }
526
- return capture(ctx, node, "unsupported_expression");
527
- };
528
- var bindIdentifier = (ctx, name, result, declaration) => {
529
- const current = ctx.scopes[ctx.scopes.length - 1];
530
- current?.set(name.text, {
531
- expressionId: result.expressionId,
532
- shape: result.shape,
533
- mutable: false
534
- });
535
- const position = loc(name, ctx.sourceFile);
536
- const available = loc(declaration, ctx.sourceFile);
537
- ctx.variables.push({
538
- id: ctx.nextVariableId.value++,
539
- name: name.text,
540
- functionId: ctx.functionId,
541
- scopeId: `${ctx.functionId}:${ctx.scopes.length}`,
542
- line: position.line,
543
- column: position.column,
544
- assignmentId: ctx.nextAssignmentId.value++,
545
- expressionId: result.expressionId,
546
- availableAtLine: available.line,
547
- availableAtColumn: available.column
548
- });
549
- };
550
- var analyzeStatement = (ctx, node) => {
551
- if (ctx.fallback) return;
552
- if (ts.isEmptyStatement(node) || ts.isDebuggerStatement(node)) return;
553
- if (ts.isBlock(node)) {
554
- ctx.scopes.push(/* @__PURE__ */ new Map());
555
- for (const statement of node.statements) analyzeStatement(ctx, statement);
556
- ctx.scopes.pop();
557
- return;
558
- }
559
- if (ts.isReturnStatement(node)) {
560
- if (!node.expression) {
561
- ctx.returnExpressionId = ctx.ir.constValue({ type: "undefined" });
562
- return;
563
- }
564
- ctx.returnExpressionId = analyzeExpression(ctx, node.expression).expressionId;
565
- return;
566
- }
567
- if (ts.isVariableStatement(node)) {
568
- if (node.declarationList.flags & ts.NodeFlags.Using) {
569
- fallback(ctx, "side_effect");
570
- return;
571
- }
572
- for (const declaration of node.declarationList.declarations) {
573
- if (!ts.isIdentifier(declaration.name) || !declaration.initializer) {
574
- fallback(ctx, "unsupported_expression");
575
- return;
576
- }
577
- const result = analyzeExpression(ctx, declaration.initializer);
578
- bindIdentifier(ctx, declaration.name, result, declaration);
579
- }
580
- return;
581
- }
582
- if (ts.isExpressionStatement(node)) {
583
- analyzeExpression(ctx, node.expression);
584
- return;
585
- }
586
- if (ts.isThrowStatement(node)) {
587
- ctx.hasThrow = true;
588
- analyzeExpression(ctx, node.expression);
589
- return;
590
- }
591
- if (ts.isIfStatement(node) || ts.isSwitchStatement(node) || ts.isTryStatement(node)) {
592
- fallback(ctx, "unsupported_expression");
593
- return;
594
- }
595
- if (ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node) || ts.isWhileStatement(node) || ts.isDoStatement(node)) {
596
- fallback(ctx, "has_loop");
597
- return;
598
- }
599
- fallback(ctx, "unsupported_expression");
600
- };
601
- var bindParams = (ctx, node) => {
602
- let index = 0;
603
- for (const parameter of node.parameters) {
604
- if (ts.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
605
- if (!ts.isIdentifier(parameter.name) || parameter.dotDotDotToken) {
606
- fallback(ctx, "unsupported_expression");
607
- index++;
608
- continue;
609
- }
610
- const expressionId = ctx.ir.param(index);
611
- ctx.identifierParams.push(parameter.name.text);
612
- bindIdentifier(ctx, parameter.name, { expressionId, shape: "unknown" }, parameter);
613
- index++;
614
- }
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;
615
135
  };
616
- var analyzeOne = (node, functionId, name, sourceFile, provenExact) => {
617
- const ctx = {
618
- sourceFile,
619
- functionId,
620
- name,
621
- ir: new DerivationIrBuilder(),
622
- variables: [],
623
- captureWraps: [],
624
- guardWraps: [],
625
- callEdges: [],
626
- scopes: [/* @__PURE__ */ new Map()],
627
- nextRootId: { value: 0 },
628
- nextVariableId: { value: 0 },
629
- nextAssignmentId: { value: 0 },
630
- hasThrow: false,
631
- hasCoercionGuard: false,
632
- provenExact,
633
- identifierParams: []
634
- };
635
- if (node.asteriskToken || node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)) {
636
- ctx.fallback = "async";
637
- } else {
638
- bindParams(ctx, node);
639
- if (ts.isBlock(node.body)) {
640
- for (const statement of node.body.statements) analyzeStatement(ctx, statement);
641
- } else {
642
- ctx.returnExpressionId = analyzeExpression(ctx, node.body).expressionId;
643
- }
644
- }
645
- const plan = {
646
- ...emptyPlan(functionId),
647
- expressions: ctx.ir.expressions,
648
- variables: ctx.variables,
649
- captureBoundaries: ctx.captureWraps.map((wrap) => ({
650
- id: wrap.rootId,
651
- reason: wrap.reason,
652
- expressionId: wrap.expressionId,
653
- line: wrap.line,
654
- column: wrap.column
655
- })),
656
- controlFacts: [],
657
- ...ctx.returnExpressionId === void 0 ? {} : { returnExpressionId: ctx.returnExpressionId },
658
- ...ctx.fallback ? { fallbackReason: ctx.fallback } : {}
659
- };
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`;
660
144
  return {
661
- functionId,
662
- name,
663
- node,
664
- plan,
665
- captureWraps: ctx.fallback ? [] : ctx.captureWraps,
666
- guardWraps: ctx.fallback ? [] : ctx.guardWraps,
667
- callEdges: ctx.callEdges,
668
- hasThrow: ctx.hasThrow,
669
- hasCoercionGuard: ctx.hasCoercionGuard
145
+ payload: readFileSync(mapPath, "utf8"),
146
+ mapUrl: pathToFileURL(mapPath).href
670
147
  };
671
148
  };
672
- var analyzeModuleFunctions = (sourceFile, targets) => {
673
- const nameCounts = /* @__PURE__ */ new Map();
674
- for (const target of targets) {
675
- nameCounts.set(target.name, (nameCounts.get(target.name) ?? 0) + 1);
676
- }
677
- const firstPass = targets.map(
678
- (target) => analyzeOne(target.node, target.functionId, target.name, sourceFile, /* @__PURE__ */ new Map())
679
- );
680
- const graph = buildDependencyGraph(
681
- firstPass.map((fn) => ({ functionId: fn.functionId, name: fn.name })),
682
- firstPass.flatMap((fn) => fn.callEdges)
683
- );
684
- const order = topologicalFunctionOrder(
685
- firstPass.map((fn) => ({ functionId: fn.functionId, name: fn.name })),
686
- graph
687
- );
688
- const byId = new Map(targets.map((target) => [target.functionId, target]));
689
- const provenExact = /* @__PURE__ */ new Map();
690
- const analyzed = [];
691
- for (const functionId of order) {
692
- const target = byId.get(functionId);
693
- if (!target) continue;
694
- const result = analyzeOne(target.node, target.functionId, target.name, sourceFile, provenExact);
695
- if (functionIsExact(result.plan) && nameCounts.get(result.name) === 1) {
696
- provenExact.set(result.name, result.functionId);
697
- }
698
- analyzed.push(result);
699
- }
700
- const remaining = targets.filter(
701
- (target) => !analyzed.some((fn) => fn.functionId === target.functionId)
702
- );
703
- for (const target of remaining) {
704
- analyzed.push(analyzeOne(target.node, target.functionId, target.name, sourceFile, provenExact));
149
+ var sourceMapPayloadForCache = (generatedPath, source) => {
150
+ try {
151
+ return sourceMapFor(generatedPath, source)?.payload ?? "";
152
+ } catch {
153
+ return "";
705
154
  }
706
- return analyzed;
707
155
  };
708
156
 
709
- // src/derivation/emit-capture-plan.ts
710
- var captureWrapEdits = (wraps, capName) => {
711
- const edits = [];
712
- let order = 0;
713
- for (const wrap of wraps) {
714
- edits.push({
715
- position: wrap.start,
716
- text: `${wrap.shorthandPropertyName ? `${wrap.shorthandPropertyName}:` : ""}${capName}(${wrap.rootId},`,
717
- order: order++
718
- });
719
- edits.push({
720
- position: wrap.end,
721
- text: ")",
722
- order: order++
723
- });
724
- }
725
- return edits;
726
- };
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";
727
161
 
728
162
  // src/execution-recorder/automatic-runtime.ts
729
- var AUTOMATIC_RUNTIME_SYMBOL = "rasputin.execution.runtime.v2";
163
+ var AUTOMATIC_RUNTIME_SYMBOL = "rasputin.execution.runtime.v3";
730
164
  var runtimeSymbol = Symbol.for(AUTOMATIC_RUNTIME_SYMBOL);
731
165
 
732
166
  // src/auto-instrumentation/collect-instrumentation-delta.ts
733
- import ts2 from "typescript";
167
+ import ts from "typescript";
734
168
  var paramBindingFromName = (name, sourceFile) => {
735
- if (ts2.isIdentifier(name)) return { kind: "identifier", name: name.text };
736
- if (ts2.isArrayBindingPattern(name)) {
169
+ if (ts.isIdentifier(name)) return { kind: "identifier", name: name.text };
170
+ if (ts.isArrayBindingPattern(name)) {
737
171
  const elements = name.elements.map(
738
172
  (element) => {
739
- if (ts2.isOmittedExpression(element)) return { kind: "omitted" };
173
+ if (ts.isOmittedExpression(element)) return { kind: "omitted" };
740
174
  const binding = paramBindingFromName(element.name, sourceFile);
741
175
  if (element.dotDotDotToken) return { kind: "rest", binding };
742
176
  return binding;
@@ -746,7 +180,7 @@ var paramBindingFromName = (name, sourceFile) => {
746
180
  }
747
181
  const properties = name.elements.map((element) => {
748
182
  const binding = paramBindingFromName(element.name, sourceFile);
749
- 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);
750
184
  if (element.dotDotDotToken) {
751
185
  const rest = { kind: "rest", binding };
752
186
  return { key, binding: rest };
@@ -758,7 +192,7 @@ var paramBindingFromName = (name, sourceFile) => {
758
192
  var collectFunctionParams = (node, sourceFile) => {
759
193
  const params = [];
760
194
  for (const parameter of node.parameters) {
761
- if (ts2.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
195
+ if (ts.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
762
196
  const binding = paramBindingFromName(parameter.name, sourceFile);
763
197
  params.push(parameter.dotDotDotToken ? { kind: "rest", binding } : binding);
764
198
  }
@@ -799,18 +233,18 @@ var mappedLocation = (sourceFile, position, absolutePath, mapLocation) => {
799
233
  };
800
234
  var unwrapExpression = (expression) => {
801
235
  let current = expression;
802
- while (ts2.isParenthesizedExpression(current) || ts2.isNonNullExpression(current)) {
236
+ while (ts.isParenthesizedExpression(current) || ts.isNonNullExpression(current)) {
803
237
  current = current.expression;
804
238
  }
805
- if (ts2.isAwaitExpression(current)) return unwrapExpression(current.expression);
239
+ if (ts.isAwaitExpression(current)) return unwrapExpression(current.expression);
806
240
  return current;
807
241
  };
808
242
  var calleeFromExpression = (expression, sourceFile) => {
809
243
  const current = unwrapExpression(expression);
810
- if (ts2.isIdentifier(current)) {
244
+ if (ts.isIdentifier(current)) {
811
245
  return { calleeName: current.text, calleeText: current.text };
812
246
  }
813
- if (ts2.isPropertyAccessExpression(current) && ts2.isIdentifier(current.name)) {
247
+ if (ts.isPropertyAccessExpression(current) && ts.isIdentifier(current.name)) {
814
248
  return {
815
249
  calleeName: current.name.text,
816
250
  calleeText: current.getText(sourceFile).replace(/\s+/g, "")
@@ -820,9 +254,9 @@ var calleeFromExpression = (expression, sourceFile) => {
820
254
  };
821
255
  var resultBindingForCall = (call) => {
822
256
  let node = call.parent;
823
- if (ts2.isAwaitExpression(node)) node = node.parent;
824
- if (!ts2.isVariableDeclaration(node)) return null;
825
- 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;
826
260
  const initializer = node.initializer;
827
261
  if (!initializer) return null;
828
262
  const inner = unwrapExpression(initializer);
@@ -832,18 +266,18 @@ var resultBindingForCall = (call) => {
832
266
  var argIdentifiersForCall = (call) => {
833
267
  const identifiers = [];
834
268
  call.arguments?.forEach((argument, index) => {
835
- if (ts2.isIdentifier(argument)) {
269
+ if (ts.isIdentifier(argument)) {
836
270
  identifiers.push({ index, kind: "identifier", name: argument.text });
837
271
  return;
838
272
  }
839
- if (!ts2.isObjectLiteralExpression(argument)) return;
273
+ if (!ts.isObjectLiteralExpression(argument)) return;
840
274
  const properties = [];
841
275
  for (const property of argument.properties) {
842
- if (ts2.isShorthandPropertyAssignment(property)) {
276
+ if (ts.isShorthandPropertyAssignment(property)) {
843
277
  properties.push({ key: property.name.text, name: property.name.text });
844
278
  continue;
845
279
  }
846
- 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)) {
847
281
  properties.push({ key: property.name.text, name: property.initializer.text });
848
282
  }
849
283
  }
@@ -883,8 +317,8 @@ var collectCallSite = (call, caller, context) => {
883
317
  };
884
318
  };
885
319
  var collectParamDestructure = (declaration, caller, context) => {
886
- if (!declaration.initializer || !ts2.isIdentifier(declaration.initializer)) return null;
887
- 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)) {
888
322
  return null;
889
323
  }
890
324
  const sourceParam = declaration.initializer.text;
@@ -906,58 +340,483 @@ var collectParamDestructure = (declaration, caller, context) => {
906
340
  };
907
341
  };
908
342
 
909
- // 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/;
910
505
  var scriptKindFor = (filePath) => {
911
- if (/\.tsx$/i.test(filePath)) return ts3.ScriptKind.TSX;
912
- if (/\.jsx$/i.test(filePath)) return ts3.ScriptKind.JSX;
913
- if (/\.[cm]?ts$/i.test(filePath)) return ts3.ScriptKind.TS;
914
- 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;
915
775
  };
916
776
  var functionName = (node, sourceFile) => {
777
+ if (ts6.isConstructorDeclaration(node)) return "constructor";
917
778
  if (node.name) return node.name.getText(sourceFile).replace(/^['"]|['"]$/g, "");
918
779
  const parent = node.parent;
919
- if (ts3.isVariableDeclaration(parent) && ts3.isIdentifier(parent.name)) return parent.name.text;
920
- if (ts3.isPropertyAssignment(parent)) return parent.name.getText(sourceFile);
921
- 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);
922
783
  const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
923
784
  return `anonymous@${location.line + 1}:${location.character + 1}`;
924
785
  };
925
786
  var functionLocationPosition = (node, sourceFile) => {
926
787
  if (node.name) return node.name.getStart(sourceFile);
927
788
  const parent = node.parent;
928
- if ((ts3.isVariableDeclaration(parent) || ts3.isPropertyAssignment(parent)) && parent.name) {
789
+ if ((ts6.isVariableDeclaration(parent) || ts6.isPropertyAssignment(parent)) && parent.name) {
929
790
  return parent.name.getStart(sourceFile);
930
791
  }
931
792
  return node.getStart(sourceFile);
932
793
  };
933
- var bindingValue = (name, sourceFile) => {
934
- if (ts3.isIdentifier(name)) return name.text;
935
- if (ts3.isArrayBindingPattern(name)) {
936
- const values = name.elements.map((element) => {
937
- if (ts3.isOmittedExpression(element)) return "";
938
- const value = bindingValue(element.name, sourceFile);
939
- return element.dotDotDotToken ? `...${value}` : value;
940
- });
941
- 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;
942
799
  }
943
- const properties = name.elements.map((element) => {
944
- const value = bindingValue(element.name, sourceFile);
945
- if (element.dotDotDotToken) return `...${value}`;
946
- if (element.propertyName) return `${element.propertyName.getText(sourceFile)}:${value}`;
947
- return value;
948
- });
949
- return `{${properties.join(",")}}`;
800
+ return end;
950
801
  };
951
- 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
+ }
952
812
  const values = node.parameters.flatMap((parameter) => {
953
- if (ts3.isIdentifier(parameter.name) && parameter.name.text === "this") return [];
954
- const value = bindingValue(parameter.name, node.getSourceFile());
955
- 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];
956
815
  });
957
- return `[${values.join(",")}]`;
816
+ return { args: `[${values.join(",")}]`, omitValues: false };
958
817
  };
959
818
  var isSupportedFunction = (node) => {
960
- 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)) {
961
820
  return false;
962
821
  }
963
822
  if (!node.body || "asteriskToken" in node && node.asteriskToken) return false;
@@ -968,7 +827,7 @@ var uniqueName = (source, base) => {
968
827
  while (source.includes(name)) name += "_";
969
828
  return name;
970
829
  };
971
- var isDirectivePrologue = (statement) => ts3.isExpressionStatement(statement) && ts3.isStringLiteral(statement.expression);
830
+ var isDirectivePrologue = (statement) => ts6.isExpressionStatement(statement) && ts6.isStringLiteral(statement.expression);
972
831
  var insertPositionAfterPrologue = (source, sourceFile) => {
973
832
  const shebangEnd = source.startsWith("#!") ? source.indexOf("\n") + 1 : 0;
974
833
  let end = shebangEnd;
@@ -983,7 +842,7 @@ var preambleInsert = (source, position, preamble) => {
983
842
  const previous = source[position - 1];
984
843
  return previous === ";" || previous === "\n" || previous === "\r" ? preamble : `;${preamble}`;
985
844
  };
986
- 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)}));`;
987
846
  var collectWrappedFunction = (node, sourceFile, absolutePath, mapLocation) => {
988
847
  const start = mappedLocation(
989
848
  sourceFile,
@@ -1011,40 +870,276 @@ var collectWrappedFunction = (node, sourceFile, absolutePath, mapLocation) => {
1011
870
  endLine: end.line,
1012
871
  endColumn: end.column,
1013
872
  params,
1014
- paramNames
873
+ paramNames,
874
+ instrumented: true
1015
875
  };
1016
876
  };
1017
- var canOmitCallValues = (analysis, analyzed) => {
1018
- if (!functionIsExact(analysis.plan) || analysis.hasThrow || analysis.hasCoercionGuard) {
1019
- 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];
1020
887
  }
1021
- const callers = analyzed.filter(
1022
- (caller) => caller.callEdges.some((edge) => edge.calleeFunctionId === analysis.functionId)
1023
- );
1024
- if (callers.length === 0) return false;
1025
- 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);
1026
1122
  };
1027
1123
  var transformSource = (source, options) => {
1028
1124
  if (source.includes("@rasputin-ignore-file")) return void 0;
1029
- const sourceFile = ts3.createSourceFile(
1125
+ if (sourceIsRasputinInstrumented(source)) return alreadyInstrumentedResult(source);
1126
+ const sourceFile = ts6.createSourceFile(
1030
1127
  options.filePath,
1031
1128
  source,
1032
- ts3.ScriptTarget.Latest,
1129
+ ts6.ScriptTarget.Latest,
1033
1130
  true,
1034
- scriptKindFor(options.filePath)
1131
+ scriptKindFor3(options.filePath)
1035
1132
  );
1036
1133
  const runName = uniqueName(source, "__rasputinRun__");
1037
- const capName = uniqueName(source, "__rasputinCap__");
1038
1134
  const plansName = uniqueName(source, "__rasputinPlans__");
1039
- const absolutePath = resolve3(options.filePath);
1040
- const derivationMode = options.derivationMode ?? "analysis";
1135
+ const handleName = uniqueName(source, "__rasputinH__");
1136
+ const errorName = uniqueName(source, "__rasputinE__");
1137
+ const absolutePath = resolve4(options.filePath);
1041
1138
  const edits = [];
1042
- let order = 0;
1043
1139
  const delta = {
1044
1140
  functions: [],
1045
1141
  callSites: [],
1046
- destructures: [],
1047
- derivationPlans: []
1142
+ destructures: []
1048
1143
  };
1049
1144
  const collectContext = {
1050
1145
  sourceFile,
@@ -1052,75 +1147,134 @@ var transformSource = (source, options) => {
1052
1147
  absolutePath
1053
1148
  };
1054
1149
  const pending = [];
1055
- 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
+ }
1056
1197
  if (isSupportedFunction(node)) {
1057
1198
  const collected = collectWrappedFunction(node, sourceFile, absolutePath, options.mapLocation);
1058
1199
  if (!collected) {
1059
- 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));
1060
1209
  return;
1061
1210
  }
1062
- 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
+ });
1063
1220
  delta.functions.push(collected);
1064
- ts3.forEachChild(node, (child) => visit(child, collected));
1221
+ ts6.forEachChild(node, (child) => visit(child, collected, depth + 1));
1065
1222
  return;
1066
1223
  }
1067
- if (caller && (ts3.isCallExpression(node) || ts3.isNewExpression(node))) {
1224
+ if (caller && (ts6.isCallExpression(node) || ts6.isNewExpression(node))) {
1068
1225
  const site = collectCallSite(node, caller, collectContext);
1069
1226
  if (site) delta.callSites.push(site);
1070
1227
  }
1071
- if (caller && ts3.isVariableDeclaration(node)) {
1228
+ if (caller && ts6.isVariableDeclaration(node)) {
1072
1229
  const destructure = collectParamDestructure(node, caller, collectContext);
1073
1230
  if (destructure) delta.destructures.push(destructure);
1074
1231
  }
1075
- ts3.forEachChild(node, (child) => visit(child, caller));
1232
+ ts6.forEachChild(node, (child) => visit(child, caller, depth + 1));
1076
1233
  };
1077
- visit(sourceFile);
1078
- if (pending.length === 0) return void 0;
1079
- const analyzed = analyzeModuleFunctions(
1080
- sourceFile,
1081
- pending.map((entry) => ({
1082
- functionId: entry.collected.functionId,
1083
- name: entry.collected.name,
1084
- node: entry.node
1085
- }))
1086
- );
1087
- delta.derivationPlans = analyzed.map((fn) => fn.plan);
1088
- 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
+ }
1089
1238
  for (const [planIndex, entry] of pending.entries()) {
1090
- const analysis = analyzedById.get(entry.collected.functionId);
1091
- const omitValues = derivationMode === "selective" && analysis && canOmitCallValues(analysis, analyzed);
1092
- const valueFlag = omitValues ? ",1" : "";
1093
- const args = ts3.isArrowFunction(entry.node) ? arrowArguments(entry.node) : "arguments";
1094
- const asyncCallback = entry.node.modifiers?.some(
1095
- (modifier) => modifier.kind === ts3.SyntaxKind.AsyncKeyword
1096
- ) ? "async " : "";
1097
- if (ts3.isBlock(entry.node.body)) {
1098
- edits.push({
1099
- position: entry.node.body.getStart(sourceFile) + 1,
1100
- text: `return ${runName}(${planIndex},${args},${asyncCallback}()=>{`,
1101
- order: order++
1102
- });
1103
- edits.push({
1104
- position: entry.node.body.end - 1,
1105
- text: `}${valueFlag});`,
1106
- order: order++
1107
- });
1108
- } else {
1109
- edits.push({
1110
- position: entry.node.body.getStart(sourceFile),
1111
- text: `${runName}(${planIndex},${args},${asyncCallback}()=>(`,
1112
- order: order++
1113
- });
1114
- edits.push({
1115
- position: entry.node.body.end,
1116
- text: `)${valueFlag})`,
1117
- order: order++
1118
- });
1119
- }
1120
- if (analysis) {
1121
- for (const edit of captureWrapEdits(analysis.captureWraps, capName)) {
1122
- 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);
1123
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
+ );
1124
1278
  }
1125
1279
  }
1126
1280
  const insertAt = insertPositionAfterPrologue(source, sourceFile);
@@ -1131,19 +1285,189 @@ var transformSource = (source, options) => {
1131
1285
  insertAt,
1132
1286
  modulePreamble(
1133
1287
  runName,
1134
- capName,
1135
1288
  plansName,
1136
- JSON.stringify(delta.functions.map((fn) => fn.plan))
1289
+ JSON.stringify(pending.map((entry) => entry.collected.plan))
1137
1290
  )
1138
1291
  ),
1139
- order: -1
1292
+ deleteCount: 0,
1293
+ depth: -1,
1294
+ width: 0,
1295
+ kind: "replace"
1140
1296
  });
1141
- edits.sort((left, right) => right.position - left.position || right.order - left.order);
1297
+ edits.sort(compareEdits);
1142
1298
  let transformed = source;
1143
1299
  for (const edit of edits) {
1144
- 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();
1145
1326
  }
1146
- return { code: transformed, delta };
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
+ }
1431
+ }
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;
1147
1471
  };
1148
1472
 
1149
1473
  // src/auto-instrumentation/bun-plugin.ts
@@ -1156,13 +1480,13 @@ var loaderFor = (filePath) => {
1156
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;
1157
1481
  var isLoadedApplicationSource = (filePath, options = {}) => isApplicationSource(filePath, { ...options, includeBuildOutput: false });
1158
1482
  var loadSourceForInstrumentation = async (filePath, options = {}) => {
1159
- const absolutePath = resolve4(filePath);
1483
+ const absolutePath = resolve5(filePath);
1160
1484
  const source = await readFile(absolutePath, "utf8");
1161
1485
  if (!isLoadedApplicationSource(absolutePath, options)) {
1162
1486
  return { contents: source, loader: loaderFor(absolutePath), transformed: false };
1163
1487
  }
1164
1488
  try {
1165
- const transformed = transformSource(source, { filePath: absolutePath });
1489
+ const transformed = transformSourceCached(source, { filePath: absolutePath });
1166
1490
  if (transformed) {
1167
1491
  recordInstrumentationManifestDelta(absolutePath, transformed.delta);
1168
1492
  }
@@ -1171,7 +1495,11 @@ var loadSourceForInstrumentation = async (filePath, options = {}) => {
1171
1495
  loader: loaderFor(absolutePath),
1172
1496
  transformed: transformed !== void 0
1173
1497
  };
1174
- } 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
+ }
1175
1503
  return { contents: source, loader: loaderFor(absolutePath), transformed: false };
1176
1504
  }
1177
1505
  };
@@ -1182,11 +1510,11 @@ var createRasputinBunPlugin = (options = {}) => {
1182
1510
  setup(build) {
1183
1511
  if (debug) {
1184
1512
  console.info(
1185
- 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)"
1186
1514
  );
1187
1515
  }
1188
1516
  build.onLoad({ filter: loadedSourceFilter }, async ({ path }) => {
1189
- const absolutePath = resolve4(path);
1517
+ const absolutePath = resolve5(path);
1190
1518
  const result = await loadSourceForInstrumentation(absolutePath, options);
1191
1519
  if (debug && result.transformed) {
1192
1520
  console.info(`[rasputin] instrumented ${absolutePath}`);
@@ -1198,9 +1526,9 @@ var createRasputinBunPlugin = (options = {}) => {
1198
1526
  };
1199
1527
 
1200
1528
  // src/auto-instrumentation/sdk-source-boundary.ts
1201
- import { dirname, resolve as resolve5 } from "node:path";
1202
- import { fileURLToPath } from "node:url";
1203
- 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)), "../../..");
1204
1532
 
1205
1533
  // src/auto-instrumentation/warn-production-runtime-transform.ts
1206
1534
  var RUNTIME_TRANSFORM_WARNING_SUPPRESS_ENV = "RASPUTIN_SUPPRESS_RUNTIME_TRANSFORM_WARNING";
@@ -1211,7 +1539,7 @@ var warnIfProductionRuntimeTransform = () => {
1211
1539
  if (Reflect.get(globalThis, warnedSymbol)) return;
1212
1540
  Reflect.set(globalThis, warnedSymbol, true);
1213
1541
  console.warn(
1214
- "[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."
1215
1543
  );
1216
1544
  };
1217
1545