retrace-sdk 0.10.0 → 0.11.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.
- package/dist/config.d.ts +4 -0
- package/dist/config.js +1 -0
- package/dist/recorder.js +4 -3
- package/dist/transport.d.ts +1 -0
- package/dist/transport.js +30 -6
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -7,6 +7,10 @@ export interface Config {
|
|
|
7
7
|
sampleRate: number;
|
|
8
8
|
/** Optional seed for deterministic sampling. When set, the same trace name always produces the same sample decision. */
|
|
9
9
|
sampleSeed: string | undefined;
|
|
10
|
+
/** Transport mode. "auto" (default) tries WebSocket then falls back to HTTP; "http" is
|
|
11
|
+
* request/response only (recommended for short-lived scripts and serverless — it never
|
|
12
|
+
* holds an open socket and always surfaces upload errors); "ws" forces WebSocket. */
|
|
13
|
+
transport: "auto" | "ws" | "http";
|
|
10
14
|
}
|
|
11
15
|
export declare function configure(opts: Partial<Config>): Config;
|
|
12
16
|
export declare function requireApiKey(): string;
|
package/dist/config.js
CHANGED
|
@@ -6,6 +6,7 @@ const config = {
|
|
|
6
6
|
enabled: !["false", "0"].includes((process.env.RETRACE_ENABLED || "true").toLowerCase()),
|
|
7
7
|
sampleRate: parseFloat(process.env.RETRACE_SAMPLE_RATE || "1"),
|
|
8
8
|
sampleSeed: process.env.RETRACE_SAMPLE_SEED || undefined,
|
|
9
|
+
transport: (["auto", "ws", "http"].includes(process.env.RETRACE_TRANSPORT || "") ? process.env.RETRACE_TRANSPORT : "auto"),
|
|
9
10
|
};
|
|
10
11
|
config.wsUrl = config.baseUrl.replace("https://", "wss://").replace("http://", "ws://");
|
|
11
12
|
export function configure(opts) {
|
package/dist/recorder.js
CHANGED
|
@@ -9,10 +9,11 @@ import { installAnthropicInterceptor } from "./interceptors/anthropic.js";
|
|
|
9
9
|
let sharedTransport = null;
|
|
10
10
|
function getSharedTransport() {
|
|
11
11
|
if (!sharedTransport) {
|
|
12
|
-
sharedTransport = createTransport();
|
|
13
|
-
// Flush pending data before process exits
|
|
12
|
+
sharedTransport = createTransport(getConfig().transport);
|
|
13
|
+
// Flush pending data before the process exits. Flushing (not just close) ensures the
|
|
14
|
+
// final trace is actually uploaded — close() alone can race the process exiting.
|
|
14
15
|
if (typeof process !== "undefined") {
|
|
15
|
-
process.on("beforeExit", () => {
|
|
16
|
+
process.on("beforeExit", () => { void flushSharedTransport(); });
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
return sharedTransport;
|
package/dist/transport.d.ts
CHANGED
package/dist/transport.js
CHANGED
|
@@ -6,6 +6,7 @@ export class WSTransport {
|
|
|
6
6
|
closed = false;
|
|
7
7
|
backoff = 1000;
|
|
8
8
|
queue = [];
|
|
9
|
+
reconnectTimer = null;
|
|
9
10
|
onError;
|
|
10
11
|
get isConnected() { return this.connected; }
|
|
11
12
|
connect() {
|
|
@@ -15,6 +16,10 @@ export class WSTransport {
|
|
|
15
16
|
const url = `${cfg.wsUrl}/ws/v1/stream`;
|
|
16
17
|
this.ws = new WebSocket(url);
|
|
17
18
|
this.ws.on("open", () => {
|
|
19
|
+
// Unref the underlying socket so a short-lived script (the common SDK usage) can exit
|
|
20
|
+
// once its work is done instead of hanging on an open WebSocket. Graceful shutdown
|
|
21
|
+
// still drains via flush()/beforeExit.
|
|
22
|
+
this.ws?._socket?.unref?.();
|
|
18
23
|
this.ws.send(JSON.stringify({ type: "auth", api_key: cfg.apiKey }));
|
|
19
24
|
});
|
|
20
25
|
this.ws.on("message", (raw) => {
|
|
@@ -60,7 +65,9 @@ export class WSTransport {
|
|
|
60
65
|
this.connected = false;
|
|
61
66
|
this.ws = null;
|
|
62
67
|
if (!this.closed) {
|
|
63
|
-
setTimeout(() => this.reconnect(), this.backoff * (0.5 + Math.random() * 0.5));
|
|
68
|
+
this.reconnectTimer = setTimeout(() => this.reconnect(), this.backoff * (0.5 + Math.random() * 0.5));
|
|
69
|
+
// Don't let the reconnect timer keep the event loop (and the process) alive.
|
|
70
|
+
this.reconnectTimer?.unref?.();
|
|
64
71
|
this.backoff = Math.min(this.backoff * 2, 30000);
|
|
65
72
|
}
|
|
66
73
|
});
|
|
@@ -92,6 +99,10 @@ export class WSTransport {
|
|
|
92
99
|
}
|
|
93
100
|
close() {
|
|
94
101
|
this.closed = true;
|
|
102
|
+
if (this.reconnectTimer) {
|
|
103
|
+
clearTimeout(this.reconnectTimer);
|
|
104
|
+
this.reconnectTimer = null;
|
|
105
|
+
}
|
|
95
106
|
if (this.ws) {
|
|
96
107
|
this.ws.close();
|
|
97
108
|
this.ws = null;
|
|
@@ -136,17 +147,30 @@ export class HTTPTransport {
|
|
|
136
147
|
// Retry up to 3 times with exponential backoff; awaited so shutdown can drain it.
|
|
137
148
|
for (let n = 1; n <= 3; n++) {
|
|
138
149
|
try {
|
|
139
|
-
await fetch(url, {
|
|
150
|
+
const res = await fetch(url, {
|
|
140
151
|
method: "POST",
|
|
141
152
|
headers: { "x-retrace-key": cfg.apiKey, "Content-Type": "application/json" },
|
|
142
153
|
body: payload,
|
|
143
154
|
});
|
|
144
|
-
|
|
155
|
+
if (res.ok)
|
|
156
|
+
return;
|
|
157
|
+
const txt = await res.text().catch(() => "");
|
|
158
|
+
// 4xx (except 429) is a client/payload error that won't succeed on retry — surface
|
|
159
|
+
// it loudly and stop, rather than silently dropping the trace.
|
|
160
|
+
if (res.status < 500 && res.status !== 429) {
|
|
161
|
+
console.error(`[retrace] trace upload rejected (HTTP ${res.status}): ${txt.slice(0, 300)}`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
// 5xx / 429 — transient; retry.
|
|
165
|
+
if (n === 3)
|
|
166
|
+
console.error(`[retrace] trace upload failed after ${n} attempts (HTTP ${res.status}): ${txt.slice(0, 200)}`);
|
|
145
167
|
}
|
|
146
|
-
catch {
|
|
147
|
-
if (n
|
|
148
|
-
|
|
168
|
+
catch (err) {
|
|
169
|
+
if (n === 3)
|
|
170
|
+
console.error(`[retrace] trace upload network error after ${n} attempts: ${err?.message ?? err}`);
|
|
149
171
|
}
|
|
172
|
+
if (n < 3)
|
|
173
|
+
await new Promise((r) => setTimeout(r, 1000 * n));
|
|
150
174
|
}
|
|
151
175
|
}
|
|
152
176
|
buildSpans() {
|