@rasputin-ai/node 0.2.0 → 0.3.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 (48) hide show
  1. package/README.md +128 -15
  2. package/dist/auto-instrumentation/bun-plugin.d.ts +23 -0
  3. package/dist/auto-instrumentation/bun-plugin.d.ts.map +1 -0
  4. package/dist/auto-instrumentation/collect-instrumentation-delta.d.ts +58 -0
  5. package/dist/auto-instrumentation/collect-instrumentation-delta.d.ts.map +1 -0
  6. package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts +11 -0
  7. package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts.map +1 -0
  8. package/dist/auto-instrumentation/node-hooks.d.ts +6 -0
  9. package/dist/auto-instrumentation/node-hooks.d.ts.map +1 -0
  10. package/dist/auto-instrumentation/schedule-instrumentation-manifest-upload.d.ts +16 -0
  11. package/dist/auto-instrumentation/schedule-instrumentation-manifest-upload.d.ts.map +1 -0
  12. package/dist/auto-instrumentation/sdk-source-boundary.d.ts +7 -0
  13. package/dist/auto-instrumentation/sdk-source-boundary.d.ts.map +1 -0
  14. package/dist/auto-instrumentation/source-classification.d.ts +10 -0
  15. package/dist/auto-instrumentation/source-classification.d.ts.map +1 -0
  16. package/dist/auto-instrumentation/source-map-locations.d.ts +3 -0
  17. package/dist/auto-instrumentation/source-map-locations.d.ts.map +1 -0
  18. package/dist/auto-instrumentation/transform-source.d.ts +18 -0
  19. package/dist/auto-instrumentation/transform-source.d.ts.map +1 -0
  20. package/dist/execution-recorder/automatic-runtime.d.ts +18 -0
  21. package/dist/execution-recorder/automatic-runtime.d.ts.map +1 -0
  22. package/dist/execution-recorder/call-aware-event-buffer.d.ts +23 -0
  23. package/dist/execution-recorder/call-aware-event-buffer.d.ts.map +1 -0
  24. package/dist/execution-recorder/execution-recorder-types.d.ts +71 -0
  25. package/dist/execution-recorder/execution-recorder-types.d.ts.map +1 -0
  26. package/dist/execution-recorder/execution-recorder.d.ts +4 -0
  27. package/dist/execution-recorder/execution-recorder.d.ts.map +1 -0
  28. package/dist/execution-recorder/index.d.ts +3 -0
  29. package/dist/execution-recorder/index.d.ts.map +1 -0
  30. package/dist/execution-recorder/recorder-memory-budget.d.ts +37 -0
  31. package/dist/execution-recorder/recorder-memory-budget.d.ts.map +1 -0
  32. package/dist/execution-recorder/safe-serialize.d.ts +10 -0
  33. package/dist/execution-recorder/safe-serialize.d.ts.map +1 -0
  34. package/dist/execution-recorder/source-exclusions.d.ts +9 -0
  35. package/dist/execution-recorder/source-exclusions.d.ts.map +1 -0
  36. package/dist/index.d.ts +5 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1198 -6
  39. package/dist/instrument/bun.d.ts +2 -0
  40. package/dist/instrument/bun.d.ts.map +1 -0
  41. package/dist/instrument/bun.js +477 -0
  42. package/dist/instrument/node.d.ts +2 -0
  43. package/dist/instrument/node.d.ts.map +1 -0
  44. package/dist/instrument/node.js +532 -0
  45. package/dist/rasputin-init.d.ts +22 -3
  46. package/dist/rasputin-init.d.ts.map +1 -1
  47. package/dist/sdk-meta.d.ts +1 -1
  48. package/package.json +15 -4
@@ -0,0 +1,532 @@
1
+ // src/auto-instrumentation/node-hooks.ts
2
+ import * as nodeModule from "node:module";
3
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
4
+
5
+ // src/auto-instrumentation/instrumentation-manifest-registry.ts
6
+ import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
7
+
8
+ // src/execution-recorder/automatic-runtime.ts
9
+ import { isAbsolute, relative, resolve, sep } from "node:path";
10
+ var AUTOMATIC_RUNTIME_SYMBOL = "rasputin.execution.runtime.v1";
11
+ var AUTOMATIC_SOURCE_SEPARATOR = "\0";
12
+ var runtimeSymbol = Symbol.for(AUTOMATIC_RUNTIME_SYMBOL);
13
+ var encodeAutomaticFunctionSource = (source) => [resolve(source.filePath), source.name, String(source.line), String(source.column)].join(
14
+ AUTOMATIC_SOURCE_SEPARATOR
15
+ );
16
+
17
+ // src/auto-instrumentation/instrumentation-manifest-registry.ts
18
+ var INSTRUMENTATION_MANIFEST_SYMBOL = "rasputin.instrumentation.manifest.v1";
19
+ var registrySymbol = Symbol.for(INSTRUMENTATION_MANIFEST_SYMBOL);
20
+ var getRegistry = () => {
21
+ const existing = Reflect.get(globalThis, registrySymbol);
22
+ if (existing?.__rasputinManifestRegistry && existing.files instanceof Map && typeof existing.generation === "number" && typeof existing.uploadedGeneration === "number") {
23
+ return existing;
24
+ }
25
+ const registry = {
26
+ __rasputinManifestRegistry: true,
27
+ files: /* @__PURE__ */ new Map(),
28
+ generation: 0,
29
+ uploadedGeneration: 0
30
+ };
31
+ Reflect.set(globalThis, registrySymbol, registry);
32
+ return registry;
33
+ };
34
+ var recordInstrumentationManifestDelta = (filePath, delta) => {
35
+ const registry = getRegistry();
36
+ registry.files.set(resolve2(filePath), delta);
37
+ registry.generation += 1;
38
+ registry.onDirty?.();
39
+ };
40
+
41
+ // src/auto-instrumentation/source-classification.ts
42
+ import { isAbsolute as isAbsolute3, relative as relative3, resolve as resolve3, sep as sep3 } from "node:path";
43
+ var sourceExtension = /\.[cm]?[jt]sx?$/i;
44
+ var alwaysExcludedDirectory = /(^|[\\/])(node_modules|coverage|generated|\.git|\.next|\.svelte-kit|\.output|\.vercel|\.rasputin|\.turbo|\.yarn|\.pnpm|\.bun)([\\/]|$)/;
45
+ var buildOutputDirectory = /(^|[\\/])(dist|build|out)([\\/]|$)/;
46
+ var matches = (pattern, value) => {
47
+ pattern.lastIndex = 0;
48
+ const result = pattern.test(value);
49
+ pattern.lastIndex = 0;
50
+ return result;
51
+ };
52
+ var isWithinRoot = (filePath, root) => {
53
+ const fromRoot = relative3(resolve3(root), filePath);
54
+ return fromRoot === "" || !fromRoot.startsWith(`..${sep3}`) && fromRoot !== ".." && !isAbsolute3(fromRoot);
55
+ };
56
+ var isApplicationSource = (filePath, options = {}) => {
57
+ const absolutePath = resolve3(filePath);
58
+ if (!sourceExtension.test(absolutePath) || alwaysExcludedDirectory.test(absolutePath))
59
+ return false;
60
+ if (options.includeBuildOutput !== true && buildOutputDirectory.test(absolutePath)) return false;
61
+ if (options.excludedRoots?.some((root) => isWithinRoot(absolutePath, root))) return false;
62
+ if (options.exclude && matches(options.exclude, absolutePath)) return false;
63
+ if (options.include && !matches(options.include, absolutePath)) return false;
64
+ if (options.rootDir) {
65
+ if (!isWithinRoot(absolutePath, options.rootDir)) return false;
66
+ }
67
+ return true;
68
+ };
69
+
70
+ // src/auto-instrumentation/source-map-locations.ts
71
+ import { readFileSync } from "node:fs";
72
+ import { dirname, isAbsolute as isAbsolute4, join, resolve as resolve4 } from "node:path";
73
+ import { fileURLToPath, pathToFileURL } from "node:url";
74
+ import { LEAST_UPPER_BOUND, originalPositionFor, TraceMap } from "@jridgewell/trace-mapping";
75
+ var sourceMapComment = /(?:\/\/[#@][ \t]*sourceMappingURL=([^\s'"]+)|\/\*[#@][ \t]*sourceMappingURL=([^\s*'"]+)[ \t]*\*\/)\s*$/;
76
+ var decodeDataUrl = (url) => {
77
+ const base64 = /^data:application\/json(?:;charset=[^;,]+)?;base64,(.+)$/i.exec(url);
78
+ if (base64?.[1]) return Buffer.from(base64[1], "base64").toString("utf8");
79
+ const raw = /^data:application\/json(?:;charset=[^;,]+)?,(.+)$/i.exec(url);
80
+ return raw?.[1] ? decodeURIComponent(raw[1]) : void 0;
81
+ };
82
+ var sourceMapFor = (generatedPath, source) => {
83
+ const match = sourceMapComment.exec(source);
84
+ const reference = match?.[1] ?? match?.[2];
85
+ if (reference?.startsWith("data:")) {
86
+ const payload = decodeDataUrl(reference);
87
+ return payload ? { payload, mapUrl: pathToFileURL(generatedPath).href } : void 0;
88
+ }
89
+ const mapPath = reference ? resolve4(dirname(generatedPath), reference) : `${generatedPath}.map`;
90
+ return {
91
+ payload: readFileSync(mapPath, "utf8"),
92
+ mapUrl: pathToFileURL(mapPath).href
93
+ };
94
+ };
95
+ var toFilePath = (source, generatedPath) => {
96
+ if (source.startsWith("file:")) return fileURLToPath(source);
97
+ return isAbsolute4(source) ? source : join(dirname(generatedPath), source);
98
+ };
99
+ var createSourceMapLocationMapper = (generatedPath, source) => {
100
+ try {
101
+ const sourceMap = sourceMapFor(generatedPath, source);
102
+ if (!sourceMap) return void 0;
103
+ const traceMap = new TraceMap(sourceMap.payload, sourceMap.mapUrl);
104
+ return (generated) => {
105
+ let original = originalPositionFor(traceMap, {
106
+ line: generated.line,
107
+ column: Math.max(0, generated.column - 1)
108
+ });
109
+ if (!original.source || original.line == null) {
110
+ original = originalPositionFor(traceMap, {
111
+ line: generated.line,
112
+ column: 0,
113
+ bias: LEAST_UPPER_BOUND
114
+ });
115
+ }
116
+ if (!original.source || original.line == null) return void 0;
117
+ return {
118
+ filePath: toFilePath(original.source, generatedPath),
119
+ line: original.line,
120
+ column: (original.column ?? 0) + 1
121
+ };
122
+ };
123
+ } catch {
124
+ return void 0;
125
+ }
126
+ };
127
+
128
+ // src/auto-instrumentation/transform-source.ts
129
+ import { resolve as resolve5 } from "node:path";
130
+ import ts2 from "typescript";
131
+
132
+ // src/auto-instrumentation/collect-instrumentation-delta.ts
133
+ import ts from "typescript";
134
+ var paramBindingFromName = (name, sourceFile) => {
135
+ if (ts.isIdentifier(name)) return { kind: "identifier", name: name.text };
136
+ if (ts.isArrayBindingPattern(name)) {
137
+ const elements = name.elements.map(
138
+ (element) => {
139
+ if (ts.isOmittedExpression(element)) return { kind: "omitted" };
140
+ const binding = paramBindingFromName(element.name, sourceFile);
141
+ if (element.dotDotDotToken) return { kind: "rest", binding };
142
+ return binding;
143
+ }
144
+ );
145
+ return { kind: "array", elements };
146
+ }
147
+ const properties = name.elements.map((element) => {
148
+ const binding = paramBindingFromName(element.name, sourceFile);
149
+ const key = element.propertyName ? element.propertyName.getText(sourceFile).replace(/^['"]|['"]$/g, "") : ts.isIdentifier(element.name) ? element.name.text : element.name.getText(sourceFile);
150
+ if (element.dotDotDotToken) {
151
+ const rest = { kind: "rest", binding };
152
+ return { key, binding: rest };
153
+ }
154
+ return { key, binding };
155
+ });
156
+ return { kind: "object", properties };
157
+ };
158
+ var collectFunctionParams = (node, sourceFile) => {
159
+ const params = [];
160
+ for (const parameter of node.parameters) {
161
+ if (ts.isIdentifier(parameter.name) && parameter.name.text === "this") continue;
162
+ const binding = paramBindingFromName(parameter.name, sourceFile);
163
+ params.push(parameter.dotDotDotToken ? { kind: "rest", binding } : binding);
164
+ }
165
+ return { params, paramNames: identifierNames(params) };
166
+ };
167
+ var identifierNames = (bindings) => {
168
+ const names = /* @__PURE__ */ new Set();
169
+ const walk = (node) => {
170
+ if (node.kind === "identifier") {
171
+ names.add(node.name);
172
+ return;
173
+ }
174
+ if (node.kind === "rest") {
175
+ walk(node.binding);
176
+ return;
177
+ }
178
+ if (node.kind === "object") {
179
+ for (const property of node.properties) walk(property.binding);
180
+ return;
181
+ }
182
+ if (node.kind === "array") {
183
+ for (const element of node.elements) {
184
+ if (element.kind !== "omitted") walk(element);
185
+ }
186
+ }
187
+ };
188
+ for (const item of bindings) walk(item);
189
+ return names;
190
+ };
191
+ var mappedLocation = (sourceFile, position, absolutePath, mapLocation) => {
192
+ const generated = sourceFile.getLineAndCharacterOfPosition(position);
193
+ const raw = {
194
+ filePath: absolutePath,
195
+ line: generated.line + 1,
196
+ column: generated.character + 1
197
+ };
198
+ return mapLocation ? mapLocation(raw) : raw;
199
+ };
200
+ var unwrapExpression = (expression) => {
201
+ let current = expression;
202
+ while (ts.isParenthesizedExpression(current) || ts.isNonNullExpression(current)) {
203
+ current = current.expression;
204
+ }
205
+ if (ts.isAwaitExpression(current)) return unwrapExpression(current.expression);
206
+ return current;
207
+ };
208
+ var calleeFromExpression = (expression, sourceFile) => {
209
+ const current = unwrapExpression(expression);
210
+ if (ts.isIdentifier(current)) {
211
+ return { calleeName: current.text, calleeText: current.text };
212
+ }
213
+ if (ts.isPropertyAccessExpression(current) && ts.isIdentifier(current.name)) {
214
+ return {
215
+ calleeName: current.name.text,
216
+ calleeText: current.getText(sourceFile).replace(/\s+/g, "")
217
+ };
218
+ }
219
+ return null;
220
+ };
221
+ var resultBindingForCall = (call) => {
222
+ let node = call.parent;
223
+ if (ts.isAwaitExpression(node)) node = node.parent;
224
+ if (!ts.isVariableDeclaration(node)) return null;
225
+ if (!ts.isIdentifier(node.name)) return null;
226
+ const initializer = node.initializer;
227
+ if (!initializer) return null;
228
+ const inner = unwrapExpression(initializer);
229
+ if (inner !== call) return null;
230
+ return node.name.text;
231
+ };
232
+ var argIdentifiersForCall = (call) => {
233
+ const identifiers = [];
234
+ call.arguments?.forEach((argument, index) => {
235
+ if (ts.isIdentifier(argument)) {
236
+ identifiers.push({ index, kind: "identifier", name: argument.text });
237
+ return;
238
+ }
239
+ if (!ts.isObjectLiteralExpression(argument)) return;
240
+ const properties = [];
241
+ for (const property of argument.properties) {
242
+ if (ts.isShorthandPropertyAssignment(property)) {
243
+ properties.push({ key: property.name.text, name: property.name.text });
244
+ continue;
245
+ }
246
+ if (ts.isPropertyAssignment(property) && ts.isIdentifier(property.name) && ts.isIdentifier(property.initializer)) {
247
+ properties.push({ key: property.name.text, name: property.initializer.text });
248
+ }
249
+ }
250
+ if (properties.length > 0) {
251
+ identifiers.push({ index, kind: "object", properties });
252
+ }
253
+ });
254
+ return identifiers;
255
+ };
256
+ var collectCallSite = (call, caller, context) => {
257
+ const callee = calleeFromExpression(call.expression, context.sourceFile);
258
+ if (!callee) return null;
259
+ const start = mappedLocation(
260
+ context.sourceFile,
261
+ call.getStart(context.sourceFile),
262
+ context.absolutePath,
263
+ context.mapLocation
264
+ );
265
+ const end = mappedLocation(
266
+ context.sourceFile,
267
+ call.end,
268
+ context.absolutePath,
269
+ context.mapLocation
270
+ );
271
+ if (!start || !end) return null;
272
+ return {
273
+ callerSourceToken: caller.sourceToken,
274
+ calleeName: callee.calleeName,
275
+ calleeText: callee.calleeText,
276
+ filePath: start.filePath,
277
+ line: start.line,
278
+ column: start.column,
279
+ endLine: end.line,
280
+ endColumn: end.column,
281
+ resultBinding: resultBindingForCall(call),
282
+ argIdentifiers: argIdentifiersForCall(call)
283
+ };
284
+ };
285
+ var collectParamDestructure = (declaration, caller, context) => {
286
+ if (!declaration.initializer || !ts.isIdentifier(declaration.initializer)) return null;
287
+ if (!ts.isObjectBindingPattern(declaration.name) && !ts.isArrayBindingPattern(declaration.name)) {
288
+ return null;
289
+ }
290
+ const sourceParam = declaration.initializer.text;
291
+ if (!caller.paramNames.has(sourceParam)) return null;
292
+ const start = mappedLocation(
293
+ context.sourceFile,
294
+ declaration.getStart(context.sourceFile),
295
+ context.absolutePath,
296
+ context.mapLocation
297
+ );
298
+ if (!start) return null;
299
+ return {
300
+ callerSourceToken: caller.sourceToken,
301
+ sourceParam,
302
+ filePath: start.filePath,
303
+ line: start.line,
304
+ column: start.column,
305
+ binding: paramBindingFromName(declaration.name, context.sourceFile)
306
+ };
307
+ };
308
+
309
+ // src/auto-instrumentation/transform-source.ts
310
+ var scriptKindFor = (filePath) => {
311
+ if (/\.tsx$/i.test(filePath)) return ts2.ScriptKind.TSX;
312
+ if (/\.jsx$/i.test(filePath)) return ts2.ScriptKind.JSX;
313
+ if (/\.[cm]?ts$/i.test(filePath)) return ts2.ScriptKind.TS;
314
+ return ts2.ScriptKind.JS;
315
+ };
316
+ var functionName = (node, sourceFile) => {
317
+ if (node.name) return node.name.getText(sourceFile).replace(/^['"]|['"]$/g, "");
318
+ const parent = node.parent;
319
+ if (ts2.isVariableDeclaration(parent) && ts2.isIdentifier(parent.name)) return parent.name.text;
320
+ if (ts2.isPropertyAssignment(parent)) return parent.name.getText(sourceFile);
321
+ if (ts2.isPropertyDeclaration(parent) && parent.name) return parent.name.getText(sourceFile);
322
+ const location = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
323
+ return `anonymous@${location.line + 1}:${location.character + 1}`;
324
+ };
325
+ var functionLocationPosition = (node, sourceFile) => {
326
+ if (node.name) return node.name.getStart(sourceFile);
327
+ const parent = node.parent;
328
+ if ((ts2.isVariableDeclaration(parent) || ts2.isPropertyAssignment(parent)) && parent.name) {
329
+ return parent.name.getStart(sourceFile);
330
+ }
331
+ return node.getStart(sourceFile);
332
+ };
333
+ var bindingValue = (name, sourceFile) => {
334
+ if (ts2.isIdentifier(name)) return name.text;
335
+ if (ts2.isArrayBindingPattern(name)) {
336
+ const values = name.elements.map((element) => {
337
+ if (ts2.isOmittedExpression(element)) return "";
338
+ const value = bindingValue(element.name, sourceFile);
339
+ return element.dotDotDotToken ? `...${value}` : value;
340
+ });
341
+ return `[${values.join(",")}]`;
342
+ }
343
+ const properties = name.elements.map((element) => {
344
+ const value = bindingValue(element.name, sourceFile);
345
+ if (element.dotDotDotToken) return `...${value}`;
346
+ if (element.propertyName) return `${element.propertyName.getText(sourceFile)}:${value}`;
347
+ return value;
348
+ });
349
+ return `{${properties.join(",")}}`;
350
+ };
351
+ var arrowArguments = (node) => {
352
+ const values = node.parameters.flatMap((parameter) => {
353
+ if (ts2.isIdentifier(parameter.name) && parameter.name.text === "this") return [];
354
+ const value = bindingValue(parameter.name, node.getSourceFile());
355
+ return [parameter.dotDotDotToken ? `...${value}` : value];
356
+ });
357
+ return `[${values.join(",")}]`;
358
+ };
359
+ var isSupportedFunction = (node) => {
360
+ if (!ts2.isFunctionDeclaration(node) && !ts2.isFunctionExpression(node) && !ts2.isArrowFunction(node) && !ts2.isMethodDeclaration(node) && !ts2.isGetAccessorDeclaration(node) && !ts2.isSetAccessorDeclaration(node)) {
361
+ return false;
362
+ }
363
+ if (!node.body || "asteriskToken" in node && node.asteriskToken) return false;
364
+ return true;
365
+ };
366
+ var helperDeclaration = (helperName) => `const ${helperName}=(functionId,args,callback)=>{const runtime=Reflect.get(globalThis,Symbol.for(${JSON.stringify(AUTOMATIC_RUNTIME_SYMBOL)}));return runtime?runtime.run(functionId,args,callback):callback()};`;
367
+ var collectWrappedFunction = (node, sourceFile, absolutePath, mapLocation) => {
368
+ const start = mappedLocation(
369
+ sourceFile,
370
+ functionLocationPosition(node, sourceFile),
371
+ absolutePath,
372
+ mapLocation
373
+ );
374
+ if (!start) return void 0;
375
+ const end = mappedLocation(sourceFile, node.end, absolutePath, mapLocation) ?? start;
376
+ const name = functionName(node, sourceFile).replace(/@\d+:\d+$/, "");
377
+ const { params, paramNames } = collectFunctionParams(node, sourceFile);
378
+ return {
379
+ sourceToken: encodeAutomaticFunctionSource({
380
+ filePath: start.filePath,
381
+ name,
382
+ line: start.line,
383
+ column: start.column
384
+ }),
385
+ filePath: start.filePath,
386
+ name,
387
+ startLine: start.line,
388
+ startColumn: start.column,
389
+ endLine: end.line,
390
+ endColumn: end.column,
391
+ params,
392
+ paramNames
393
+ };
394
+ };
395
+ var transformSource = (source, options) => {
396
+ if (source.includes("@rasputin-ignore-file")) return void 0;
397
+ const sourceFile = ts2.createSourceFile(
398
+ options.filePath,
399
+ source,
400
+ ts2.ScriptTarget.Latest,
401
+ true,
402
+ scriptKindFor(options.filePath)
403
+ );
404
+ let helperName = "__rasputinAutoRun__";
405
+ while (source.includes(helperName)) helperName += "_";
406
+ const absolutePath = resolve5(options.filePath);
407
+ const edits = [];
408
+ let order = 0;
409
+ const delta = {
410
+ functions: [],
411
+ callSites: [],
412
+ destructures: []
413
+ };
414
+ const collectContext = {
415
+ sourceFile,
416
+ mapLocation: options.mapLocation,
417
+ absolutePath
418
+ };
419
+ const visit = (node, caller) => {
420
+ if (isSupportedFunction(node)) {
421
+ const collected = collectWrappedFunction(node, sourceFile, absolutePath, options.mapLocation);
422
+ if (!collected) {
423
+ ts2.forEachChild(node, (child) => visit(child, caller));
424
+ return;
425
+ }
426
+ delta.functions.push(collected);
427
+ const args = ts2.isArrowFunction(node) ? arrowArguments(node) : "arguments";
428
+ const asyncCallback = node.modifiers?.some(
429
+ (modifier) => modifier.kind === ts2.SyntaxKind.AsyncKeyword
430
+ ) ? "async " : "";
431
+ if (ts2.isBlock(node.body)) {
432
+ edits.push({
433
+ position: node.body.getStart(sourceFile) + 1,
434
+ text: `return ${helperName}(${JSON.stringify(collected.sourceToken)},${args},${asyncCallback}()=>{`,
435
+ order: order++
436
+ });
437
+ edits.push({ position: node.body.end - 1, text: "});", order: order++ });
438
+ } else {
439
+ edits.push({
440
+ position: node.body.getStart(sourceFile),
441
+ text: `${helperName}(${JSON.stringify(collected.sourceToken)},${args},${asyncCallback}()=>(`,
442
+ order: order++
443
+ });
444
+ edits.push({ position: node.body.end, text: "))", order: order++ });
445
+ }
446
+ ts2.forEachChild(node, (child) => visit(child, collected));
447
+ return;
448
+ }
449
+ if (caller && (ts2.isCallExpression(node) || ts2.isNewExpression(node))) {
450
+ const site = collectCallSite(node, caller, collectContext);
451
+ if (site) delta.callSites.push(site);
452
+ }
453
+ if (caller && ts2.isVariableDeclaration(node)) {
454
+ const destructure = collectParamDestructure(node, caller, collectContext);
455
+ if (destructure) delta.destructures.push(destructure);
456
+ }
457
+ ts2.forEachChild(node, (child) => visit(child, caller));
458
+ };
459
+ visit(sourceFile);
460
+ if (edits.length === 0) return void 0;
461
+ const shebangEnd = source.startsWith("#!") ? source.indexOf("\n") + 1 : 0;
462
+ edits.push({ position: shebangEnd, text: helperDeclaration(helperName), order: -1 });
463
+ edits.sort((left, right) => right.position - left.position || right.order - left.order);
464
+ let transformed = source;
465
+ for (const edit of edits) {
466
+ transformed = `${transformed.slice(0, edit.position)}${edit.text}${transformed.slice(edit.position)}`;
467
+ }
468
+ return { code: transformed, delta };
469
+ };
470
+
471
+ // src/auto-instrumentation/node-hooks.ts
472
+ var supportedFormat = /^(?:module|commonjs)(?:-typescript)?$/;
473
+ var registerHooks2 = nodeModule.registerHooks;
474
+ var sourceText = (source) => {
475
+ if (typeof source === "string") return source;
476
+ if (source instanceof ArrayBuffer) return Buffer.from(source).toString("utf8");
477
+ if (ArrayBuffer.isView(source)) {
478
+ return Buffer.from(source.buffer, source.byteOffset, source.byteLength).toString("utf8");
479
+ }
480
+ return void 0;
481
+ };
482
+ var registerRasputinNodeHooks = (options = {}) => {
483
+ if (!registerHooks2) {
484
+ console.warn(
485
+ "[rasputin] Automatic instrumentation is disabled because it requires Node.js 22.15.0 or newer."
486
+ );
487
+ return;
488
+ }
489
+ const debug = process.env.RASPUTIN_DEBUG === "1";
490
+ registerHooks2({
491
+ load(url, context, nextLoad) {
492
+ const result = nextLoad(url, context);
493
+ const format = result.format ?? context.format;
494
+ if (!url.startsWith("file:") || !format || !supportedFormat.test(format)) return result;
495
+ let filePath;
496
+ try {
497
+ filePath = fileURLToPath2(url);
498
+ } catch {
499
+ return result;
500
+ }
501
+ if (!isApplicationSource(filePath, {
502
+ includeBuildOutput: true,
503
+ excludedRoots: options.excludedRoots
504
+ }))
505
+ return result;
506
+ const source = sourceText(result.source);
507
+ if (source === void 0) return result;
508
+ try {
509
+ const transformed = transformSource(source, {
510
+ filePath,
511
+ mapLocation: createSourceMapLocationMapper(filePath, source)
512
+ });
513
+ if (!transformed) return result;
514
+ recordInstrumentationManifestDelta(filePath, transformed.delta);
515
+ if (debug) console.info(`[rasputin] instrumented ${filePath}`);
516
+ return { ...result, source: transformed.code };
517
+ } catch {
518
+ return result;
519
+ }
520
+ }
521
+ });
522
+ };
523
+
524
+ // src/auto-instrumentation/sdk-source-boundary.ts
525
+ import { dirname as dirname2, resolve as resolve6 } from "node:path";
526
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
527
+ var sdkPackageScopeRootFrom = (instrumentEntryUrl) => resolve6(dirname2(fileURLToPath3(instrumentEntryUrl)), "../../..");
528
+
529
+ // src/instrument/node.ts
530
+ registerRasputinNodeHooks({
531
+ excludedRoots: [sdkPackageScopeRootFrom(import.meta.url)]
532
+ });
@@ -1,4 +1,22 @@
1
- import { type RasputinClient, type RasputinOptions } from '@rasputin-ai/core';
1
+ /**
2
+ * Node/Bun composition root for error transport, process handlers, and the bounded execution
3
+ * recorder. Centralizing this wiring keeps initialization fail-safe and ensures every capture path
4
+ * attaches runtime state using the same limits and lifecycle rules.
5
+ */
6
+ import { type RasputinClient, type RasputinClientStats, type RasputinOptions } from '@rasputin-ai/core';
7
+ import { type ExecutionRecorderOptions, type ExecutionRecorderStats, type RasputinExecution } from './execution-recorder';
8
+ export type RasputinNodeOptions = RasputinOptions & {
9
+ /** Bounded runtime-state capture. Enabled by default; successful executions are discarded. */
10
+ executionRecorder?: ExecutionRecorderOptions;
11
+ };
12
+ export type RasputinNodeClientStats = RasputinClientStats & {
13
+ /** Process-local recorder safety, truncation, and memory-pressure counters. */
14
+ recorder: ExecutionRecorderStats;
15
+ };
16
+ export type RasputinNodeClient = Omit<RasputinClient, 'getStats'> & {
17
+ execution: RasputinExecution;
18
+ getStats: () => RasputinNodeClientStats;
19
+ };
2
20
  /**
3
21
  * Boot the Node/Bun SDK: client + process global handlers.
4
22
  * Call once at process start. Never throws into the host app.
@@ -6,10 +24,11 @@ import { type RasputinClient, type RasputinOptions } from '@rasputin-ai/core';
6
24
  * @example
7
25
  * RasputinInit({
8
26
  * projectApiKey: process.env.RASPUTIN_PROJECT_API_KEY!,
27
+ * release: process.env.RASPUTIN_RELEASE!,
9
28
  * environment: process.env.NODE_ENV ?? 'development',
10
- * release: process.env.RASPUTIN_RELEASE,
29
+ * enabled: ['staging', 'production'].includes(process.env.NODE_ENV ?? 'development'),
11
30
  * moduleUrl: import.meta.url,
12
31
  * });
13
32
  */
14
- export declare const RasputinInit: (options: RasputinOptions) => RasputinClient;
33
+ export declare const RasputinInit: (options: RasputinNodeOptions) => RasputinNodeClient;
15
34
  //# sourceMappingURL=rasputin-init.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rasputin-init.d.ts","sourceRoot":"","sources":["../src/rasputin-init.ts"],"names":[],"mappings":"AAAA,OAAO,EAGN,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,MAAM,mBAAmB,CAAC;AAI3B;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,YAAY,GAAI,SAAS,eAAe,KAAG,cAUvD,CAAC"}
1
+ {"version":3,"file":"rasputin-init.d.ts","sourceRoot":"","sources":["../src/rasputin-init.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAIN,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EAEpB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAEN,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,MAAM,sBAAsB,CAAC;AAK9B,MAAM,MAAM,mBAAmB,GAAG,eAAe,GAAG;IACnD,8FAA8F;IAC9F,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;CAC7C,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,mBAAmB,GAAG;IAC3D,+EAA+E;IAC/E,QAAQ,EAAE,sBAAsB,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC,GAAG;IACnE,SAAS,EAAE,iBAAiB,CAAC;IAC7B,QAAQ,EAAE,MAAM,uBAAuB,CAAC;CACxC,CAAC;AAkDF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,GAAI,SAAS,mBAAmB,KAAG,kBA4C3D,CAAC"}
@@ -1,4 +1,4 @@
1
1
  /** Generated by packages/sdk/scripts/generate-sdk-meta.ts — do not edit. */
2
2
  export declare const SDK_NAME = "@rasputin-ai/node";
3
- export declare const SDK_VERSION = "0.2.0";
3
+ export declare const SDK_VERSION = "0.3.0";
4
4
  //# sourceMappingURL=sdk-meta.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rasputin-ai/node",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Official Rasputin AI SDK for Node and Bun.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -8,6 +8,16 @@
8
8
  "types": "./dist/index.d.ts",
9
9
  "import": "./dist/index.js",
10
10
  "default": "./dist/index.js"
11
+ },
12
+ "./instrument/bun": {
13
+ "types": "./dist/instrument/bun.d.ts",
14
+ "import": "./dist/instrument/bun.js",
15
+ "default": "./dist/instrument/bun.js"
16
+ },
17
+ "./instrument/node": {
18
+ "types": "./dist/instrument/node.d.ts",
19
+ "import": "./dist/instrument/node.js",
20
+ "default": "./dist/instrument/node.js"
11
21
  }
12
22
  },
13
23
  "files": [
@@ -25,13 +35,14 @@
25
35
  "access": "public"
26
36
  },
27
37
  "dependencies": {
28
- "@rasputin-ai/core": "0.2.0"
38
+ "@jridgewell/trace-mapping": "0.3.31",
39
+ "@rasputin-ai/core": "0.3.0",
40
+ "typescript": "5"
29
41
  },
30
42
  "devDependencies": {
31
43
  "@rasputin-ai/core": "workspace:*",
32
44
  "@types/bun": "latest",
33
45
  "@types/node": "22.13.10",
34
- "esbuild": "0.25.12",
35
- "typescript": "5"
46
+ "esbuild": "0.25.12"
36
47
  }
37
48
  }