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