@rasputin-ai/elysia 0.5.0-alpha.9 → 0.5.1

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.
package/README.md CHANGED
@@ -1,180 +1,215 @@
1
- # Official Rasputin AI SDK for Elysia
2
-
3
- Report application errors to Rasputin.
4
-
5
- ## Setup
6
-
7
- Initialize once and register the plugin early in your app:
8
-
9
- ```ts
10
- import { Elysia } from 'elysia';
11
- import { RasputinInit } from '@rasputin-ai/elysia';
12
-
13
- const { client, plugin } = RasputinInit({
14
- projectApiKey: process.env.RASPUTIN_PROJECT_API_KEY!,
15
- release: process.env.RASPUTIN_RELEASE!,
16
- environment: process.env.NODE_ENV ?? 'development',
17
- enabled: ['staging', 'production'].includes(process.env.NODE_ENV ?? 'development'),
18
- });
19
-
20
- const app = new Elysia()
21
- .use(plugin) // Register Rasputin before other plugins when possible.
22
- .use(otherPlugins)
23
- .listen(3000);
24
- ```
25
-
26
- Route errors, uncaught exceptions, and unhandled rejections are reported automatically.
27
-
28
- ## Capture handled errors
29
-
30
- ```ts
31
- try {
32
- await processPayment();
33
- } catch (error) {
34
- client.captureException(error);
35
- }
36
- ```
37
-
38
- ## Configuration
39
-
40
- | Field | Required | Description |
41
- | --- | --- | --- |
42
- | `projectApiKey` | yes | Project API key from the Rasputin dashboard |
43
- | `environment` | yes | Environment such as `production`, `staging`, or `preview` |
44
- | `release` | yes | Git commit SHA for the deployed version |
45
- | `enabled` | no | Enables or disables the SDK; defaults to `true` |
46
- | `logSuccess` | no | Prints a configuration summary when setup succeeds; defaults to `true`. Failures always log. |
47
- | `moduleUrl` | no | `import.meta.url` from the initialization file; helps normalize local stack paths |
48
- | `sourceRoot` | no | Git repository folder for a single-package service, such as `apps/api` |
49
- | `sourceRoots` | no | Package-name to Git-folder mappings for ambiguous monorepos |
50
- | `repoRoot` | no | Local filesystem display root; it does not control instrumentation or Git identity |
51
-
52
- Rasputin normally identifies source from the nearest `package.json` and verifies it against the exact release on the server. Filesystem paths such as `/container` are never used as source identity.
53
-
54
- For deployment checks, call:
55
-
56
- ```ts
57
- const verification = await client.verifyConfiguration();
58
- ```
59
-
60
- `verified` means every instrumented source file maps uniquely to the configured Git release. If a monorepo is ambiguous, set `sourceRoot` or `sourceRoots` using repository-relative folders, never Docker paths.
61
-
62
- Rasputin also prints a configuration summary after the instrumentation manifest is uploaded. Set `logSuccess: false` if you do not want that banner in production logs. Mapping failures still warn.
63
-
64
- ## Shutdown
65
-
66
- Events are sent in the background. Flush before a short-lived process exits:
67
-
68
- ```ts
69
- await client.flush();
70
- ```
71
-
72
- Call `await client.close()` during graceful shutdown when the process stays alive afterward.
73
-
74
- ---
75
-
76
- # Runtime recorder
77
-
78
- Optionally attach the runtime state that led to an error — arguments, return values, and call history from the failing execution.
79
-
80
- The Elysia plugin scopes each HTTP request as an execution automatically. For background jobs and other non-HTTP work, wrap them manually (see below).
81
-
82
- ## Automatic instrumentation
83
-
84
- Build-time instrumentation is the production default. Transform compiled unbundled JavaScript after `tsc` so the running process never loads the TypeScript compiler:
85
-
86
- ```json
87
- {
88
- "scripts": {
89
- "build": "tsc && rasputin-instrument dist",
90
- "start:node": "node dist/app.js",
91
- "start:bun": "bun dist/app.js"
92
- }
93
- }
94
- ```
95
-
96
- Node `--import` and Bun `--preload` remain compatibility and development fallbacks. They transform application source as it loads, which pulls compiler infrastructure into the process and has materially higher RSS. Do not use those loader measurements in low-overhead comparisons. In `NODE_ENV=production` the loaders print a one-time warning recommending `rasputin-instrument`. Set `RASPUTIN_SUPPRESS_RUNTIME_TRANSFORM_WARNING=1` to hide it.
97
-
98
- ```json
99
- {
100
- "scripts": {
101
- "start:node": "node --import @rasputin-ai/elysia/instrument/node dist/app.js",
102
- "start:bun": "bun --preload @rasputin-ai/elysia/instrument/bun src/app.ts"
103
- }
104
- }
105
- ```
106
-
107
- Runtime state is retained only inside an active execution and attached when that execution throws.
108
-
109
- ## Manual capture
110
-
111
- For background jobs, scheduled tasks, or worker iterations, define an execution:
112
-
113
- ```ts
114
- await client.execution.run({ kind: 'job', name: 'sync-invoices' }, async () => {
115
- await syncInvoices();
116
- });
117
- ```
118
-
119
- When automatic instrumentation is unavailable, mark individual functions explicitly:
120
-
121
- ```ts
122
- const syncInvoices = client.execution.trace(
123
- 'src/jobs/sync-invoices.ts:syncInvoices',
124
- async () => {
125
- // ...
126
- },
127
- );
128
- ```
129
-
130
- ## Runtime support
131
-
132
- | Environment | Support |
133
- | --- | --- |
134
- | TypeScript compiled to multiple JavaScript files | Preferred: `rasputin-instrument dist` after `tsc`, with adjacent source maps |
135
- | Elysia 1.4 or newer within 1.x on Bun 1.3.x | Supported for directly loaded JavaScript and TypeScript using `instrument/bun` as a compatibility fallback |
136
- | Elysia 1.4 or newer within 1.x on Node.js 22.15 or newer | Supported for unbundled ESM/CJS JavaScript using `instrument/node` as a compatibility fallback; Node 22 and 24 are the supported LTS lines |
137
- | Single-file bundles (esbuild, tsup, webpack, Vite SSR, Next.js) | Automatic instrumentation not supported |
138
- | `tsx`, custom Node loader stacks, and Node's native TypeScript execution | Automatic instrumentation not supported |
139
- | Deno and edge runtimes | Not supported |
140
-
141
- Tested on Bun 1.3.13, Node.js 24.19.0, and Elysia 1.4.28. Node.js 22.15.0 is the minimum for automatic instrumentation (synchronous module hooks). Use `execution.run()` and `execution.trace()` when automatic instrumentation is not available.
142
-
143
- ## Recorder configuration
144
-
145
- Defaults work for most apps. Customize to exclude noisy sources, redact fields, or retain less state:
146
-
147
- | Field | Default | Description |
148
- | --- | ---: | --- |
149
- | `enabled` | `true` | Enables runtime-state capture |
150
- | `maxEventsPerExecution` | `500` | Maximum events retained for one execution |
151
- | `maxCapturedCallsPerFunction` | `3` | Detailed successful calls retained before similar calls are summarized |
152
- | `maxActiveMemoryBytes` | `64 MiB` | Maximum recorder memory shared across active executions |
153
- | `maxDepth` | `3` | Maximum captured value depth |
154
- | `maxObjectKeys` | `30` | Maximum properties retained from one object |
155
- | `maxArrayElements` | `20` | Maximum items retained from one array, map, or set |
156
- | `maxStringLength` | `500` | Maximum characters retained from one string |
157
- | `maxSerializedValueBytes` | `8 KiB` | Maximum retained size of one captured value |
158
- | `redactKeys` | `[]` | Additional case-insensitive property names to redact |
159
- | `excludeSources` | `[]` | Source globs or function-name patterns to exclude |
160
-
161
- ```ts
162
- const { client, plugin } = RasputinInit({
163
- // ...required options
164
- executionRecorder: {
165
- excludeSources: ['src/logger/**', 'packages/shared-logger/**'],
166
- redactKeys: ['customerEmail'],
167
- maxEventsPerExecution: 200,
168
- },
169
- });
170
- ```
171
-
172
- Runtime state can include application data. Use `redactKeys` for sensitive fields.
173
-
174
- ## Diagnostics
175
-
176
- ```ts
177
- const { recorder, transport } = client.getStats();
178
- ```
179
-
180
- Counters are local to the current process and reset on restart.
1
+ # Official Rasputin AI SDK for Elysia
2
+
3
+ Report application errors to Rasputin.
4
+
5
+ ## Setup
6
+
7
+ Initialize once and register the plugin early in your app:
8
+
9
+ ```ts
10
+ import { Elysia } from 'elysia';
11
+ import { RasputinInit } from '@rasputin-ai/elysia';
12
+
13
+ const { client, plugin } = RasputinInit({
14
+ projectApiKey: process.env.RASPUTIN_PROJECT_API_KEY!,
15
+ release: process.env.RASPUTIN_RELEASE!,
16
+ environment: process.env.NODE_ENV ?? 'development',
17
+ enabled: ['staging', 'production'].includes(process.env.NODE_ENV ?? 'development'),
18
+ });
19
+
20
+ const app = new Elysia()
21
+ .use(plugin) // Register Rasputin before other plugins when possible.
22
+ .use(otherPlugins)
23
+ .listen(3000);
24
+ ```
25
+
26
+ Route errors, uncaught exceptions, and unhandled rejections are reported automatically.
27
+
28
+ ## Capture handled errors
29
+
30
+ ```ts
31
+ try {
32
+ await processPayment();
33
+ } catch (error) {
34
+ client.captureException(error);
35
+ }
36
+ ```
37
+
38
+ ## Configuration
39
+
40
+ | Field | Required | Description |
41
+ | --- | --- | --- |
42
+ | `projectApiKey` | yes | Project API key from the Rasputin dashboard |
43
+ | `environment` | yes | Environment such as `production`, `staging`, or `preview` |
44
+ | `release` | yes | Git commit SHA for the deployed version |
45
+ | `enabled` | no | Enables or disables the SDK; defaults to `true` |
46
+ | `logSuccess` | no | Prints a configuration summary when setup succeeds; defaults to `true`. Failures always log. |
47
+ | `moduleUrl` | no | `import.meta.url` from the initialization file; helps normalize local stack paths |
48
+ | `sourceRoot` | no | Git repository folder for a single-package service, such as `apps/api` |
49
+ | `sourceRoots` | no | Package-name to Git-folder mappings for ambiguous monorepos |
50
+ | `repoRoot` | no | Local filesystem display root; it does not control instrumentation or Git identity |
51
+
52
+ Rasputin normally identifies source from the nearest `package.json` and verifies it against the exact release on the server. Filesystem paths such as `/container` are never used as source identity.
53
+
54
+ For deployment checks, call:
55
+
56
+ ```ts
57
+ const verification = await client.verifyConfiguration();
58
+ ```
59
+
60
+ `verified` means every instrumented source file maps uniquely to the configured Git release. If a monorepo is ambiguous, set `sourceRoot` or `sourceRoots` using repository-relative folders, never Docker paths.
61
+
62
+ Rasputin also prints a configuration summary after the instrumentation manifest is uploaded. Set `logSuccess: false` if you do not want that banner in production logs. Mapping failures still warn.
63
+
64
+ ## Shutdown
65
+
66
+ Events are sent in the background. Flush before a short-lived process exits:
67
+
68
+ ```ts
69
+ await client.flush();
70
+ ```
71
+
72
+ Call `await client.close()` during graceful shutdown when the process stays alive afterward.
73
+
74
+ ---
75
+
76
+ # Runtime recorder
77
+
78
+ Optionally attach the runtime state that led to an error — arguments, return values, and call history from the failing execution.
79
+
80
+ The Elysia plugin scopes each HTTP request as an execution automatically. For background jobs and other non-HTTP work, wrap them manually (see below).
81
+
82
+ ## Automatic instrumentation
83
+
84
+ Rasputin wraps your functions so it can show what ran when an error happened.
85
+
86
+ **Compiled JavaScript (use this in production)**
87
+
88
+ Build as you already do, then run `rasputin-instrument` on the output. Start the process normally — no extra flags:
89
+
90
+ ```json
91
+ {
92
+ "scripts": {
93
+ "build": "tsc && rasputin-instrument dist",
94
+ "start:node": "node dist/app.js",
95
+ "start:bun": "bun dist/app.js"
96
+ }
97
+ }
98
+ ```
99
+
100
+ Have TypeScript write `.js.map` files next to the output so recorded functions still point at your `.ts` sources. The instrumenter writes `.rasputin/instrumentation-manifest.json` beside `dist`, and `RasputinInit` loads it automatically.
101
+
102
+ **Running TypeScript directly**
103
+
104
+ `rasputin-instrument` rewrites compiled `.js` files on disk. It does not run when you start TypeScript with Bun (`bun src/app.ts`, `bun run src/app.ts`, and similar). Use `--preload` so Bun wraps files as they load:
105
+
106
+ ```json
107
+ {
108
+ "scripts": {
109
+ "start:bun": "bun --preload @rasputin-ai/elysia/instrument/bun src/app.ts"
110
+ }
111
+ }
112
+ ```
113
+
114
+ On Node you can wrap compiled files as they load instead of instrumenting at build time:
115
+
116
+ ```json
117
+ {
118
+ "scripts": {
119
+ "start:node": "node --import @rasputin-ai/elysia/instrument/node dist/app.js"
120
+ }
121
+ }
122
+ ```
123
+
124
+ Use `--preload` / `--import` while you run TypeScript locally. In production, instrument at build time instead — wrapping files at startup is slower and uses more memory.
125
+
126
+ Runtime state is kept only while a request or job is running, and is attached when that work throws.
127
+
128
+ ## Manual capture
129
+
130
+ For background jobs, scheduled tasks, or worker iterations, define an execution:
131
+
132
+ ```ts
133
+ const scope = client.execution.createScope({
134
+ kind: 'job',
135
+ name: 'sync-invoices',
136
+ })
137
+
138
+ try {
139
+ return await scope.run(() => syncInvoices())
140
+ } catch (error) {
141
+ scope.associateError(error)
142
+ throw error
143
+ } finally {
144
+ scope.finish()
145
+ }
146
+ ```
147
+
148
+ `scope.run()` executes the callback under this recording's async context and returns exactly what the callback returned. It does not wrap, replace, or observe Promises.
149
+
150
+ When automatic instrumentation is unavailable, mark individual functions explicitly:
151
+
152
+ ```ts
153
+ const syncInvoices = client.execution.trace(
154
+ 'src/jobs/sync-invoices.ts:syncInvoices',
155
+ async () => {
156
+ // ...
157
+ },
158
+ );
159
+ ```
160
+
161
+ ## Runtime support
162
+
163
+ | Environment | Support |
164
+ | --- | --- |
165
+ | TypeScript compiled to multiple JavaScript files | Use `rasputin-instrument dist` after `tsc`, with adjacent source maps |
166
+ | Elysia 1.4+ on Bun 1.3.x, running `.ts` or `.js` files directly | Use `--preload @rasputin-ai/elysia/instrument/bun` |
167
+ | Elysia 1.4+ on Node.js 22.15 or newer, unbundled ESM/CJS | Use `rasputin-instrument`, or `--import @rasputin-ai/elysia/instrument/node`; Node 22 and 24 are the supported LTS lines |
168
+
169
+ On Node.js 22, start the process with `--experimental-async-context-frame` (or set `NODE_OPTIONS`) so `AsyncLocalStorage` uses the faster implementation that Node.js 24 enables by default. Rasputin logs this once when the flag is missing; set `RASPUTIN_SUPPRESS_ASYNC_CONTEXT_FRAME_WARNING=1` to hide it.
170
+ | Single-file bundles (esbuild, tsup, webpack, Vite SSR, Next.js) | Automatic instrumentation is not supported |
171
+ | `tsx`, custom Node loader stacks, and Node's native TypeScript execution | Automatic instrumentation is not supported |
172
+ | Deno and edge runtimes | Not supported |
173
+
174
+ Tested on Bun 1.3.13, Node.js 24.19.0, and Elysia 1.4.28. Node.js 22.15.0 is the minimum for automatic instrumentation. Use `execution.createScope()` and `execution.trace()` when automatic instrumentation is not available.
175
+
176
+ ## Recorder configuration
177
+
178
+ Defaults work for most apps. Customize to exclude noisy sources, redact fields, or retain less state:
179
+
180
+ | Field | Default | Description |
181
+ | --- | ---: | --- |
182
+ | `enabled` | `true` | Enables runtime-state capture |
183
+ | `maxEventsPerExecution` | `500` | Maximum events retained for one execution |
184
+ | `maxCapturedCallsPerFunction` | `3` | Detailed successful calls retained before similar calls are summarized |
185
+ | `maxActiveMemoryBytes` | `64 MiB` | Maximum recorder memory shared across active executions |
186
+ | `maxDepth` | `3` | Maximum captured value depth |
187
+ | `maxObjectKeys` | `30` | Maximum properties retained from one object |
188
+ | `maxArrayElements` | `20` | Maximum items retained from one array, map, or set |
189
+ | `maxStringLength` | `500` | Maximum characters retained from one string |
190
+ | `maxSerializedValueBytes` | `8 KiB` | Maximum retained size of one captured value |
191
+ | `runtimeStateTargetBytes` | `256 KiB` | Soft size goal for the snapshot sent with an error; extra reconstruction data is dropped first |
192
+ | `maxRuntimeStateBytes` | `512 KiB` | Hard size limit for that snapshot; values are dropped before call history |
193
+ | `redactKeys` | `[]` | Additional property names to redact (case- and separator-insensitive) |
194
+ | `excludeSources` | `[]` | Source globs or function-name patterns to exclude |
195
+
196
+ ```ts
197
+ const { client, plugin } = RasputinInit({
198
+ // ...required options
199
+ executionRecorder: {
200
+ excludeSources: ['src/logger/**', 'packages/shared-logger/**'],
201
+ redactKeys: ['customerEmail'],
202
+ maxEventsPerExecution: 200,
203
+ },
204
+ });
205
+ ```
206
+
207
+ Runtime state can include application data. Use `redactKeys` for sensitive fields.
208
+
209
+ ## Diagnostics
210
+
211
+ ```ts
212
+ const { recorder, transport } = client.getStats();
213
+ ```
214
+
215
+ These counters describe what this process has recorded and sent since it started. They reset on restart. Compare two snapshots if you want activity over a window, rather than dividing lifetime totals by one request.
package/dist/index.js CHANGED
@@ -8,41 +8,58 @@ import {
8
8
  installAutomaticExecutionRuntime,
9
9
  installGlobalHandlers,
10
10
  scheduleInstrumentationManifestUpload
11
- } from "@rasputin-ai/node";
11
+ } from "@rasputin-ai/node/internal";
12
12
  import { Elysia } from "elysia";
13
13
 
14
14
  // src/sdk-meta.ts
15
15
  var SDK_NAME = "@rasputin-ai/elysia";
16
- var SDK_VERSION = "0.5.0-alpha.9";
16
+ var SDK_VERSION = "0.5.1";
17
17
 
18
18
  // src/rasputin-init.ts
19
- var withExecution = (client, execution, installRuntime, manifest) => {
20
- const uninstallRuntime = installRuntime ? installAutomaticExecutionRuntime(execution) : () => {
19
+ var publicExecution = (runtime) => ({
20
+ createScope: (metadata) => {
21
+ const scope = runtime.createScope(metadata);
22
+ return {
23
+ run: scope.run,
24
+ associateError: scope.associateError,
25
+ finish: scope.finish
26
+ };
27
+ },
28
+ trace: runtime.trace,
29
+ getStats: runtime.getStats
30
+ });
31
+ var withExecution = (client, runtime, installRuntime, manifest, uninstallHandlers = () => {
32
+ }) => {
33
+ const uninstallRuntime = installRuntime ? installAutomaticExecutionRuntime(runtime) : () => {
21
34
  };
22
35
  const manifestUpload = scheduleInstrumentationManifestUpload(manifest);
36
+ const execution = publicExecution(runtime);
23
37
  return {
24
38
  ...client,
25
39
  execution,
26
40
  getStats: () => ({ ...client.getStats(), recorder: execution.getStats() }),
27
41
  verifyConfiguration: manifestUpload.verify,
28
42
  captureException: (error, context) => {
29
- const runtimeState = context?.runtimeState ?? execution.getErrorState(
30
- error,
31
- context?.request ? { kind: "http", request: context.request } : void 0
32
- );
33
- const result = client.captureException(error, { ...context, runtimeState });
43
+ const result = client.captureException(error, context);
34
44
  manifestUpload.flushSoon();
35
45
  return result;
36
46
  },
37
47
  flush: async (timeoutMs) => {
48
+ const startedAt = Date.now();
38
49
  await client.flush(timeoutMs);
39
- await manifestUpload.wait();
50
+ if (timeoutMs === void 0) {
51
+ await manifestUpload.wait();
52
+ return;
53
+ }
54
+ await manifestUpload.wait(Math.max(0, timeoutMs - (Date.now() - startedAt)));
40
55
  },
41
56
  close: async () => {
57
+ uninstallHandlers();
42
58
  uninstallRuntime();
43
59
  manifestUpload.disconnect();
60
+ const startedAt = Date.now();
44
61
  await client.close();
45
- await manifestUpload.wait();
62
+ await manifestUpload.wait(Math.max(0, 2e3 - (Date.now() - startedAt)));
46
63
  }
47
64
  };
48
65
  };
@@ -54,8 +71,12 @@ var toStatus = (status) => {
54
71
  }
55
72
  return void 0;
56
73
  };
57
- var isPromiseLike = (value) => {
58
- return Boolean(value) && typeof value.then === "function";
74
+ var isNativeResponsePromise = (value) => {
75
+ try {
76
+ return Object.prototype.toString.call(value) === "[object Promise]";
77
+ } catch {
78
+ return false;
79
+ }
59
80
  };
60
81
  var finishExecution = (scope, metadata) => {
61
82
  try {
@@ -65,7 +86,7 @@ var finishExecution = (scope, metadata) => {
65
86
  };
66
87
  var rememberExecutionError = (scope, error) => {
67
88
  try {
68
- scope.getErrorState(error);
89
+ scope.associateError(error);
69
90
  } catch {
70
91
  }
71
92
  };
@@ -78,18 +99,18 @@ var buildPlugin = (client, execution, recorderEnabled) => {
78
99
  return (request) => scope.run(() => {
79
100
  try {
80
101
  const response = handler(request);
81
- if (isPromiseLike(response)) {
82
- return Promise.resolve(response).then(
102
+ if (isNativeResponsePromise(response)) {
103
+ Promise.prototype.then.call(
104
+ response,
83
105
  (value) => {
84
106
  finishExecution(scope, { request: { status: value.status } });
85
- return value;
86
107
  },
87
108
  (error) => {
88
109
  rememberExecutionError(scope, error);
89
110
  finishExecution(scope);
90
- throw error;
91
111
  }
92
112
  );
113
+ return response;
93
114
  }
94
115
  finishExecution(scope, { request: { status: response.status } });
95
116
  return response;
@@ -109,11 +130,7 @@ var buildPlugin = (client, execution, recorderEnabled) => {
109
130
  status: toStatus(set.status)
110
131
  };
111
132
  try {
112
- const runtimeState = execution.getErrorState(error, {
113
- kind: "http",
114
- request: requestContext
115
- });
116
- client.captureException(error, { request: requestContext, runtimeState });
133
+ client.captureException(error, { request: requestContext });
117
134
  } catch {
118
135
  }
119
136
  return;
@@ -122,45 +139,58 @@ var buildPlugin = (client, execution, recorderEnabled) => {
122
139
  function RasputinInit(options, hooks = {}) {
123
140
  try {
124
141
  const {
125
- installGlobalHandlers: installProcessHandlers = true,
142
+ installGlobalHandlers: installProcessHandlers = options.installGlobalHandlers !== false,
126
143
  fetch,
127
144
  announceDeploy,
128
- installTransportSignalHandlers
145
+ installTransportSignalHandlers = options.installTransportSignalHandlers
129
146
  } = hooks;
147
+ const enabled = isClientEnabled(options);
148
+ const recorderEnabled = enabled && options.executionRecorder?.enabled !== false;
149
+ const runtime = createExecutionRecorder({
150
+ ...options.executionRecorder,
151
+ enabled: recorderEnabled
152
+ });
130
153
  const client = createClient(options, {
131
154
  fetch,
132
155
  announceDeploy,
133
156
  installTransportSignalHandlers,
134
157
  sdkVersion: SDK_VERSION,
135
- sdkName: SDK_NAME
136
- });
137
- const recorderEnabled = isClientEnabled(options) && options.executionRecorder?.enabled !== false;
138
- const execution = createExecutionRecorder({
139
- ...options.executionRecorder,
140
- enabled: recorderEnabled
141
- });
142
- const wrappedClient = withExecution(client, execution, recorderEnabled, {
143
- projectApiKey: options.projectApiKey,
144
- release: options.release,
145
- apiUrl: options.apiUrl,
146
- fetch,
147
- enabled: isClientEnabled(options),
148
- sourceRoot: options.sourceRoot,
149
- sourceRoots: options.sourceRoots,
150
- logSuccess: options.logSuccess
158
+ sdkName: SDK_NAME,
159
+ resolveRuntimeState: (error, context) => runtime.getErrorState(
160
+ error,
161
+ context?.request ? { kind: "http", request: context.request } : void 0
162
+ )
151
163
  });
152
- if (isClientEnabled(options) && installProcessHandlers) {
153
- installGlobalHandlers(wrappedClient);
164
+ let uninstallHandlers = () => {
165
+ };
166
+ const wrappedClient = withExecution(
167
+ client,
168
+ runtime,
169
+ recorderEnabled,
170
+ {
171
+ projectApiKey: options.projectApiKey ?? "",
172
+ release: options.release,
173
+ apiUrl: options.apiUrl,
174
+ fetch,
175
+ enabled,
176
+ sourceRoot: options.sourceRoot,
177
+ sourceRoots: options.sourceRoots,
178
+ logSuccess: options.logSuccess
179
+ },
180
+ () => uninstallHandlers()
181
+ );
182
+ if (enabled && installProcessHandlers) {
183
+ uninstallHandlers = installGlobalHandlers(wrappedClient);
154
184
  }
155
185
  return {
156
186
  client: wrappedClient,
157
- plugin: buildPlugin(wrappedClient, execution, recorderEnabled)
187
+ plugin: buildPlugin(wrappedClient, runtime, recorderEnabled)
158
188
  };
159
189
  } catch {
160
190
  const client = createClient({ ...options, enabled: false });
161
- const execution = createExecutionRecorder({ enabled: false });
162
- const wrappedClient = withExecution(client, execution, false, {
163
- projectApiKey: options.projectApiKey,
191
+ const runtime = createExecutionRecorder({ enabled: false });
192
+ const wrappedClient = withExecution(client, runtime, false, {
193
+ projectApiKey: options.projectApiKey ?? "",
164
194
  release: options.release,
165
195
  apiUrl: options.apiUrl,
166
196
  enabled: false,
@@ -168,7 +198,7 @@ function RasputinInit(options, hooks = {}) {
168
198
  sourceRoots: options.sourceRoots,
169
199
  logSuccess: options.logSuccess
170
200
  });
171
- return { client: wrappedClient, plugin: buildPlugin(wrappedClient, execution, false) };
201
+ return { client: wrappedClient, plugin: buildPlugin(wrappedClient, runtime, false) };
172
202
  }
173
203
  }
174
204
  export {
@@ -5,18 +5,36 @@
5
5
  * the framework-neutral Node recorder.
6
6
  */
7
7
  import { type RasputinOptions } from '@rasputin-ai/core';
8
- import { type ExecutionRecorderOptions, type RasputinExecution, type RasputinNodeClient } from '@rasputin-ai/node';
8
+ import type { ExecutionRecorderOptions, RasputinExecution, RasputinNodeClient } from '@rasputin-ai/node';
9
9
  import { Elysia } from 'elysia';
10
+ /** Elysia plugin returned by `RasputinInit`. Register it with `.use(plugin)`. */
10
11
  export type RasputinElysiaPlugin = ReturnType<typeof buildPlugin>;
11
12
  export type RasputinElysia = {
13
+ /** Use this for `captureException`, `flush`, and wrapping background jobs. */
12
14
  client: RasputinNodeClient;
15
+ /** Register early with `.use(plugin)` so each HTTP request is recorded. */
13
16
  plugin: RasputinElysiaPlugin;
14
17
  };
15
18
  export type RasputinElysiaOptions = RasputinOptions & {
16
- /** Bounded runtime-state capture. Enabled by default; successful executions are discarded. */
19
+ /**
20
+ * Limits and redaction for the runtime recorder. Recording is on unless you
21
+ * set `enabled: false`. Successful requests and jobs are not kept.
22
+ */
17
23
  executionRecorder?: ExecutionRecorderOptions;
24
+ /**
25
+ * Observe `uncaughtException` / nonfatal `unhandledRejection`. Default true.
26
+ * Rasputin does not become the host crash/recovery owner: it exits only when
27
+ * it is the sole `uncaughtException` listener. Set false when the host owns
28
+ * process diagnostics.
29
+ */
30
+ installGlobalHandlers?: boolean;
31
+ /**
32
+ * Install SIGINT / SIGTERM / beforeExit flush hooks. Default true.
33
+ * Set false when the host owns process shutdown.
34
+ */
35
+ installTransportSignalHandlers?: boolean;
18
36
  };
19
- declare const buildPlugin: (client: RasputinNodeClient, execution: RasputinExecution, recorderEnabled: boolean) => Elysia<"", {
37
+ declare const buildPlugin: (client: RasputinNodeClient, execution: Pick<RasputinExecution, "createScope">, recorderEnabled: boolean) => Elysia<"", {
20
38
  decorator: {
21
39
  rasputin: RasputinNodeClient;
22
40
  };
@@ -1 +1 @@
1
- {"version":3,"file":"rasputin-init.d.ts","sourceRoot":"","sources":["../src/rasputin-init.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAKN,KAAK,eAAe,EACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAEN,KAAK,wBAAwB,EAI7B,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EAEvB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIhC,MAAM,MAAM,oBAAoB,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAElE,MAAM,MAAM,cAAc,GAAG;IAC5B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,MAAM,EAAE,oBAAoB,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,eAAe,GAAG;IACrD,8FAA8F;IAC9F,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;CAC7C,CAAC;AA4FF,QAAA,MAAM,WAAW,GAChB,QAAQ,kBAAkB,EAC1B,WAAW,iBAAiB,EAC5B,iBAAiB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0DxB,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,qBAAqB,GAAG,cAAc,CAAC"}
1
+ {"version":3,"file":"rasputin-init.d.ts","sourceRoot":"","sources":["../src/rasputin-init.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAKN,KAAK,eAAe,EACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EACX,wBAAwB,EAExB,iBAAiB,EACjB,kBAAkB,EAClB,MAAM,mBAAmB,CAAC;AAQ3B,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIhC,iFAAiF;AACjF,MAAM,MAAM,oBAAoB,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAElE,MAAM,MAAM,cAAc,GAAG;IAC5B,8EAA8E;IAC9E,MAAM,EAAE,kBAAkB,CAAC;IAC3B,2EAA2E;IAC3E,MAAM,EAAE,oBAAoB,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG,eAAe,GAAG;IACrD;;;OAGG;IACH,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;IAC7C;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;;OAGG;IACH,8BAA8B,CAAC,EAAE,OAAO,CAAC;CACzC,CAAC;AA+GF,QAAA,MAAM,WAAW,GAChB,QAAQ,kBAAkB,EAC1B,WAAW,IAAI,CAAC,iBAAiB,EAAE,aAAa,CAAC,EACjD,iBAAiB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsDxB,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,qBAAqB,GAAG,cAAc,CAAC"}
@@ -1,4 +1,4 @@
1
1
  /** Generated by scripts/generate-sdk-meta.ts — do not edit. */
2
2
  export declare const SDK_NAME = "@rasputin-ai/elysia";
3
- export declare const SDK_VERSION = "0.5.0-alpha.9";
3
+ export declare const SDK_VERSION = "0.5.1";
4
4
  //# sourceMappingURL=sdk-meta.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sdk-meta.d.ts","sourceRoot":"","sources":["../src/sdk-meta.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,eAAO,MAAM,QAAQ,wBAAwB,CAAC;AAC9C,eAAO,MAAM,WAAW,kBAAkB,CAAC"}
1
+ {"version":3,"file":"sdk-meta.d.ts","sourceRoot":"","sources":["../src/sdk-meta.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,eAAO,MAAM,QAAQ,wBAAwB,CAAC;AAC9C,eAAO,MAAM,WAAW,UAAU,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rasputin-ai/elysia",
3
- "version": "0.5.0-alpha.9",
3
+ "version": "0.5.1",
4
4
  "description": "Official Rasputin AI SDK for Elysia.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -35,8 +35,8 @@
35
35
  "access": "public"
36
36
  },
37
37
  "dependencies": {
38
- "@rasputin-ai/core": "0.5.0-alpha.9",
39
- "@rasputin-ai/node": "0.5.0-alpha.9"
38
+ "@rasputin-ai/core": "0.5.1",
39
+ "@rasputin-ai/node": "0.5.1"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "elysia": ">=1.4.0 <2"
@@ -44,7 +44,7 @@
44
44
  "devDependencies": {
45
45
  "@rasputin-ai/core": "workspace:*",
46
46
  "@rasputin-ai/node": "workspace:*",
47
- "@types/bun": "latest",
47
+ "@types/bun": "1.3.14",
48
48
  "@types/node": "22.13.10",
49
49
  "esbuild": "0.25.12",
50
50
  "elysia": "1.4.28",