@scalemule/nextjs 0.1.39 → 0.1.41

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/audio.mjs ADDED
@@ -0,0 +1,364 @@
1
+ 'use client';
2
+ "use client";
3
+
4
+ // src/components/audio-player.tsx
5
+ import {
6
+ useEffect,
7
+ useId,
8
+ useMemo,
9
+ useRef,
10
+ useState
11
+ } from "react";
12
+ import { jsx, jsxs } from "react/jsx-runtime";
13
+ var SPEEDS = [1, 1.25, 1.5, 2, 3];
14
+ var PLAY_EVENT = "scalemule:audio:play";
15
+ var positive = (n) => n != null && Number.isFinite(n) && n > 0 ? n : 0;
16
+ var durationOf = (audio) => positive(audio.duration_ms) / 1e3;
17
+ function time(seconds) {
18
+ const s = Math.floor(positive(seconds));
19
+ return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
20
+ }
21
+ function AudioPlayer(props) {
22
+ if (!props.audio.url) return null;
23
+ return /* @__PURE__ */ jsx(PlayerSession, { ...props }, props.audioKey ?? props.audio.url);
24
+ }
25
+ function PlayerSession({
26
+ audio,
27
+ variant = "waveform",
28
+ label,
29
+ className,
30
+ style,
31
+ preload = "none",
32
+ onRefresh,
33
+ showRefreshButton = false,
34
+ onPlaybackError,
35
+ playbackRateStorageKey = "scalemule:audio:playback-rate",
36
+ exclusivePlayback = true
37
+ }) {
38
+ const media = useRef(null);
39
+ const alive = useRef(true);
40
+ const request = useRef(null);
41
+ const triedRefresh = useRef(false);
42
+ const intent = useRef(false);
43
+ const resume = useRef(null);
44
+ const [source, setSource] = useState(audio);
45
+ const [duration, setDuration] = useState(durationOf(audio));
46
+ const [position, setPosition] = useState(0);
47
+ const [speed, setSpeed] = useState(1);
48
+ const [playing, setPlaying] = useState(false);
49
+ const [pendingPlay, setPendingPlay] = useState(false);
50
+ const [busy, setBusy] = useState(false);
51
+ const [error, setError] = useState(null);
52
+ const sliderId = useId();
53
+ const labelId = useId();
54
+ const rate = useRef(1);
55
+ useEffect(() => {
56
+ alive.current = true;
57
+ const element = media.current;
58
+ return () => {
59
+ alive.current = false;
60
+ intent.current = false;
61
+ request.current?.abort();
62
+ element?.pause();
63
+ };
64
+ }, []);
65
+ useEffect(() => {
66
+ request.current?.abort();
67
+ request.current = null;
68
+ setBusy(false);
69
+ resume.current = media.current?.currentTime ?? 0;
70
+ setSource(audio);
71
+ setDuration(durationOf(audio));
72
+ }, [audio.url, audio.expires_at, audio.duration_ms]);
73
+ useEffect(() => {
74
+ if (!playbackRateStorageKey) return;
75
+ try {
76
+ const saved = Number(window.localStorage.getItem(playbackRateStorageKey));
77
+ if (SPEEDS.includes(saved)) {
78
+ setSpeed(saved);
79
+ rate.current = saved;
80
+ }
81
+ } catch {
82
+ }
83
+ }, [playbackRateStorageKey]);
84
+ useEffect(() => {
85
+ if (media.current) {
86
+ media.current.playbackRate = speed;
87
+ media.current.preservesPitch = true;
88
+ }
89
+ }, [speed, source.url]);
90
+ useEffect(() => {
91
+ if (!exclusivePlayback) return;
92
+ const stop = (event) => {
93
+ if (event.detail !== media.current) {
94
+ intent.current = false;
95
+ setPendingPlay(false);
96
+ media.current?.pause();
97
+ }
98
+ };
99
+ document.addEventListener(PLAY_EVENT, stop);
100
+ return () => document.removeEventListener(PLAY_EVENT, stop);
101
+ }, [exclusivePlayback]);
102
+ function fail() {
103
+ if (!alive.current) return;
104
+ intent.current = false;
105
+ setPendingPlay(false);
106
+ setPlaying(false);
107
+ setBusy(false);
108
+ setError("Audio could not be played. Try again.");
109
+ onPlaybackError?.();
110
+ }
111
+ async function play() {
112
+ const element = media.current;
113
+ if (!element || !intent.current) return;
114
+ try {
115
+ await element.play();
116
+ } catch (e) {
117
+ if (!alive.current || !intent.current) return;
118
+ if (e.name === "NotAllowedError") {
119
+ fail();
120
+ return;
121
+ }
122
+ await recover();
123
+ }
124
+ }
125
+ async function recover(manual = false) {
126
+ if (!alive.current || request.current) return;
127
+ if (!onRefresh || !manual && triedRefresh.current) {
128
+ fail();
129
+ return;
130
+ }
131
+ triedRefresh.current = true;
132
+ const controller = new AbortController();
133
+ request.current = controller;
134
+ resume.current = media.current?.currentTime ?? position;
135
+ setBusy(true);
136
+ setError(null);
137
+ try {
138
+ const fresh = await onRefresh(controller.signal);
139
+ if (!alive.current || controller.signal.aborted) return;
140
+ if (!fresh.url) throw new Error("No audio URL");
141
+ setDuration(durationOf(fresh) || duration);
142
+ if (fresh.url === source.url) {
143
+ media.current?.load();
144
+ } else {
145
+ setSource(fresh);
146
+ }
147
+ } catch {
148
+ if (!controller.signal.aborted) fail();
149
+ } finally {
150
+ if (request.current === controller) {
151
+ request.current = null;
152
+ if (alive.current) setBusy(false);
153
+ }
154
+ }
155
+ }
156
+ function toggle() {
157
+ if (intent.current || playing) {
158
+ intent.current = false;
159
+ setPendingPlay(false);
160
+ media.current?.pause();
161
+ return;
162
+ }
163
+ intent.current = true;
164
+ setPendingPlay(true);
165
+ triedRefresh.current = false;
166
+ setError(null);
167
+ if (exclusivePlayback)
168
+ document.dispatchEvent(
169
+ new CustomEvent(PLAY_EVENT, { detail: media.current })
170
+ );
171
+ if (request.current) return;
172
+ const expiry = Date.parse(source.expires_at ?? "");
173
+ if (onRefresh && Number.isFinite(expiry) && expiry - Date.now() <= 6e4) {
174
+ void recover();
175
+ } else {
176
+ void play();
177
+ }
178
+ }
179
+ function seek(value) {
180
+ if (!media.current || !duration || !Number.isFinite(value)) return;
181
+ const target = Math.max(0, Math.min(duration, value));
182
+ try {
183
+ media.current.currentTime = target;
184
+ resume.current = target;
185
+ setPosition(target);
186
+ } catch {
187
+ }
188
+ }
189
+ function changeSpeed() {
190
+ const next = SPEEDS[(SPEEDS.indexOf(rate.current) + 1) % SPEEDS.length];
191
+ rate.current = next;
192
+ setSpeed(next);
193
+ if (playbackRateStorageKey) {
194
+ try {
195
+ window.localStorage.setItem(playbackRateStorageKey, String(next));
196
+ } catch {
197
+ }
198
+ }
199
+ }
200
+ const peaks = useMemo(() => {
201
+ const input = source.waveform_peaks;
202
+ if (!input?.length) return [];
203
+ const count = Math.min(64, input.length);
204
+ return Array.from({ length: count }, (_, i) => {
205
+ const value = input[Math.floor(i * input.length / count)];
206
+ return Number.isFinite(value) ? Math.min(1, Math.max(0.08, Math.abs(value))) : 0.08;
207
+ });
208
+ }, [source.waveform_peaks]);
209
+ const current = Math.min(positive(position), duration || Infinity);
210
+ const progress = duration ? Math.max(0, Math.min(100, current / duration * 100)) : 0;
211
+ const remaining = duration ? `${time((duration - current) / speed)} remaining` : "Duration available when played";
212
+ const title = label ?? (source.ai_generated === false ? "Listen to narration" : "Listen to AI narration");
213
+ return /* @__PURE__ */ jsxs(
214
+ "div",
215
+ {
216
+ className: `sm-audio sm-audio--${variant}${className ? ` ${className}` : ""}`,
217
+ style,
218
+ role: "group",
219
+ "aria-labelledby": labelId,
220
+ "data-audio-variant": variant,
221
+ children: [
222
+ /* @__PURE__ */ jsx(
223
+ "audio",
224
+ {
225
+ ref: media,
226
+ src: source.url ?? void 0,
227
+ preload,
228
+ onLoadedMetadata: (e) => {
229
+ const element = e.currentTarget;
230
+ const total = positive(element.duration) || durationOf(source);
231
+ setDuration(total);
232
+ element.playbackRate = rate.current;
233
+ element.preservesPitch = true;
234
+ if (resume.current != null) {
235
+ try {
236
+ element.currentTime = Math.min(
237
+ resume.current,
238
+ total || resume.current
239
+ );
240
+ } catch {
241
+ }
242
+ resume.current = null;
243
+ }
244
+ setPosition(positive(element.currentTime));
245
+ if (intent.current) void play();
246
+ },
247
+ onDurationChange: (e) => {
248
+ const d = positive(e.currentTarget.duration);
249
+ if (d) setDuration(d);
250
+ },
251
+ onTimeUpdate: (e) => setPosition(positive(e.currentTarget.currentTime)),
252
+ onPlay: () => {
253
+ intent.current = true;
254
+ setPlaying(true);
255
+ setError(null);
256
+ if (exclusivePlayback)
257
+ document.dispatchEvent(
258
+ new CustomEvent(PLAY_EVENT, { detail: media.current })
259
+ );
260
+ },
261
+ onPause: () => setPlaying(false),
262
+ onEnded: () => {
263
+ intent.current = false;
264
+ setPendingPlay(false);
265
+ setPlaying(false);
266
+ setPosition(duration);
267
+ },
268
+ onError: () => {
269
+ if (intent.current) void recover();
270
+ else if (!request.current) {
271
+ setError("Audio is unavailable. Press play to retry.");
272
+ }
273
+ }
274
+ }
275
+ ),
276
+ /* @__PURE__ */ jsx(
277
+ "button",
278
+ {
279
+ type: "button",
280
+ className: "sm-audio__play",
281
+ onClick: toggle,
282
+ "aria-label": playing || busy && pendingPlay ? "Pause narration" : "Play narration",
283
+ children: playing || busy && pendingPlay ? /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M6 4h4v16H6zm8 0h4v16h-4z" }) }) : /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M8 4v16l12-8z" }) })
284
+ }
285
+ ),
286
+ /* @__PURE__ */ jsxs("div", { className: "sm-audio__content", children: [
287
+ /* @__PURE__ */ jsx("span", { className: "sm-audio__label", id: labelId, children: title }),
288
+ /* @__PURE__ */ jsxs("div", { className: "sm-audio__times", children: [
289
+ /* @__PURE__ */ jsxs("span", { children: [
290
+ time(current),
291
+ " ",
292
+ /* @__PURE__ */ jsx("span", { className: "sm-audio__elapsed", children: "elapsed" })
293
+ ] }),
294
+ /* @__PURE__ */ jsx("span", { children: remaining })
295
+ ] }),
296
+ variant !== "inline" && /* @__PURE__ */ jsxs(
297
+ "div",
298
+ {
299
+ className: "sm-audio__seek",
300
+ style: { "--sm-audio-progress": `${progress}%` },
301
+ children: [
302
+ variant === "waveform" && peaks.length > 0 && /* @__PURE__ */ jsx("div", { className: "sm-audio__waveform", "aria-hidden": "true", children: peaks.map((peak, i) => /* @__PURE__ */ jsx(
303
+ "span",
304
+ {
305
+ style: {
306
+ height: `${peak * 100}%`,
307
+ background: (i + 0.5) / peaks.length * 100 <= progress ? "var(--sm-audio-accent)" : "var(--sm-audio-wave)"
308
+ }
309
+ },
310
+ i
311
+ )) }),
312
+ /* @__PURE__ */ jsx("label", { className: "sm-audio__sr", htmlFor: sliderId, children: "Seek narration" }),
313
+ /* @__PURE__ */ jsx(
314
+ "input",
315
+ {
316
+ id: sliderId,
317
+ className: "sm-audio__range",
318
+ type: "range",
319
+ min: "0",
320
+ max: duration || 1,
321
+ step: "0.1",
322
+ disabled: !duration,
323
+ value: Math.min(current, duration || 1),
324
+ "aria-valuetext": `${time(current)} elapsed; ${remaining}`,
325
+ onChange: (e) => seek(Number(e.currentTarget.value))
326
+ }
327
+ )
328
+ ]
329
+ }
330
+ )
331
+ ] }),
332
+ /* @__PURE__ */ jsxs("div", { className: "sm-audio__actions", children: [
333
+ /* @__PURE__ */ jsxs(
334
+ "button",
335
+ {
336
+ type: "button",
337
+ className: "sm-audio__speed",
338
+ onClick: changeSpeed,
339
+ "aria-label": `Change playback speed (current ${speed}\xD7)`,
340
+ children: [
341
+ speed,
342
+ "\xD7"
343
+ ]
344
+ }
345
+ ),
346
+ onRefresh && showRefreshButton && /* @__PURE__ */ jsx(
347
+ "button",
348
+ {
349
+ type: "button",
350
+ className: "sm-audio__refresh",
351
+ disabled: busy,
352
+ onClick: () => void recover(true),
353
+ children: "Refresh"
354
+ }
355
+ )
356
+ ] }),
357
+ (busy || error) && /* @__PURE__ */ jsx("p", { className: "sm-audio__status", role: "status", children: busy ? "Refreshing audio\u2026" : error })
358
+ ]
359
+ }
360
+ );
361
+ }
362
+ export {
363
+ AudioPlayer
364
+ };
package/dist/client.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a8 as StorageAdapter, A as ApiError } from './index-Tq5WdDfS.mjs';
1
+ import { a8 as StorageAdapter, A as ApiError } from './index-Bv1m_qQ8.mjs';
2
2
 
3
3
  /**
4
4
  * ScaleMule API Client
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { a8 as StorageAdapter, A as ApiError } from './index-Tq5WdDfS.js';
1
+ import { a8 as StorageAdapter, A as ApiError } from './index-Bv1m_qQ8.js';
2
2
 
3
3
  /**
4
4
  * ScaleMule API Client
package/dist/client.js CHANGED
@@ -12,9 +12,31 @@ var ScaleMuleApiError = class extends Error {
12
12
  this.code = error.code;
13
13
  this.field = error.field;
14
14
  this.status = status;
15
+ this.requestId = error.requestId;
16
+ this.traceId = error.traceId;
17
+ this.problem = error.problem;
15
18
  }
16
19
  };
17
20
 
21
+ // src/error-context.ts
22
+ function withErrorContext(error, responseData, headers) {
23
+ const enriched = { ...error };
24
+ const meta = responseData?.meta;
25
+ const requestId = meta?.request_id ?? headers?.get("x-request-id") ?? void 0;
26
+ if (requestId !== void 0 && enriched.requestId === void 0) {
27
+ enriched.requestId = requestId;
28
+ }
29
+ const traceId = meta?.trace_id;
30
+ if (traceId !== void 0 && enriched.traceId === void 0) {
31
+ enriched.traceId = traceId;
32
+ }
33
+ const rawError = responseData?.error;
34
+ if (rawError !== null && typeof rawError === "object" && enriched.problem === void 0) {
35
+ enriched.problem = rawError;
36
+ }
37
+ return enriched;
38
+ }
39
+
18
40
  // src/client.ts
19
41
  var GATEWAY_URLS = {
20
42
  dev: "https://api-dev.scalemule.com",
@@ -591,7 +613,8 @@ var ScaleMuleClient = class {
591
613
  }
592
614
  if (!response.ok) {
593
615
  const rawError = responseData?.error;
594
- const error = rawError && typeof rawError === "object" ? rawError : { code: `HTTP_${response.status}`, message: typeof rawError === "string" ? rawError : responseData?.message || text || response.statusText };
616
+ const baseError = rawError && typeof rawError === "object" ? rawError : { code: `HTTP_${response.status}`, message: typeof rawError === "string" ? rawError : responseData?.message || text || response.statusText };
617
+ const error = withErrorContext(baseError, responseData, response.headers);
595
618
  if (response.status === 401 && this.sessionToken && !options.isAutoRefresh && refreshAttempts < MAX_REFRESH_ATTEMPTS) {
596
619
  refreshAttempts++;
597
620
  if (this.debug) console.log("[ScaleMule] 401 received, attempting auto-refresh...");
@@ -853,10 +876,17 @@ var ScaleMuleClient = class {
853
876
  const unwrapped = data?.data !== void 0 ? data.data : data;
854
877
  resolve(unwrapped);
855
878
  } else {
856
- reject(new ScaleMuleApiError(data?.error || {
879
+ const uploadError = data?.error || {
857
880
  code: `HTTP_${xhr.status}`,
858
881
  message: data?.message || xhr.responseText || "Upload failed"
859
- }));
882
+ };
883
+ reject(
884
+ new ScaleMuleApiError(
885
+ withErrorContext(uploadError, data, {
886
+ get: (name) => xhr.getResponseHeader(name)
887
+ })
888
+ )
889
+ );
860
890
  }
861
891
  } catch {
862
892
  reject(new ScaleMuleApiError({ code: "PARSE_ERROR", message: "Failed to parse response" }));
package/dist/client.mjs CHANGED
@@ -10,9 +10,31 @@ var ScaleMuleApiError = class extends Error {
10
10
  this.code = error.code;
11
11
  this.field = error.field;
12
12
  this.status = status;
13
+ this.requestId = error.requestId;
14
+ this.traceId = error.traceId;
15
+ this.problem = error.problem;
13
16
  }
14
17
  };
15
18
 
19
+ // src/error-context.ts
20
+ function withErrorContext(error, responseData, headers) {
21
+ const enriched = { ...error };
22
+ const meta = responseData?.meta;
23
+ const requestId = meta?.request_id ?? headers?.get("x-request-id") ?? void 0;
24
+ if (requestId !== void 0 && enriched.requestId === void 0) {
25
+ enriched.requestId = requestId;
26
+ }
27
+ const traceId = meta?.trace_id;
28
+ if (traceId !== void 0 && enriched.traceId === void 0) {
29
+ enriched.traceId = traceId;
30
+ }
31
+ const rawError = responseData?.error;
32
+ if (rawError !== null && typeof rawError === "object" && enriched.problem === void 0) {
33
+ enriched.problem = rawError;
34
+ }
35
+ return enriched;
36
+ }
37
+
16
38
  // src/client.ts
17
39
  var GATEWAY_URLS = {
18
40
  dev: "https://api-dev.scalemule.com",
@@ -589,7 +611,8 @@ var ScaleMuleClient = class {
589
611
  }
590
612
  if (!response.ok) {
591
613
  const rawError = responseData?.error;
592
- const error = rawError && typeof rawError === "object" ? rawError : { code: `HTTP_${response.status}`, message: typeof rawError === "string" ? rawError : responseData?.message || text || response.statusText };
614
+ const baseError = rawError && typeof rawError === "object" ? rawError : { code: `HTTP_${response.status}`, message: typeof rawError === "string" ? rawError : responseData?.message || text || response.statusText };
615
+ const error = withErrorContext(baseError, responseData, response.headers);
593
616
  if (response.status === 401 && this.sessionToken && !options.isAutoRefresh && refreshAttempts < MAX_REFRESH_ATTEMPTS) {
594
617
  refreshAttempts++;
595
618
  if (this.debug) console.log("[ScaleMule] 401 received, attempting auto-refresh...");
@@ -851,10 +874,17 @@ var ScaleMuleClient = class {
851
874
  const unwrapped = data?.data !== void 0 ? data.data : data;
852
875
  resolve(unwrapped);
853
876
  } else {
854
- reject(new ScaleMuleApiError(data?.error || {
877
+ const uploadError = data?.error || {
855
878
  code: `HTTP_${xhr.status}`,
856
879
  message: data?.message || xhr.responseText || "Upload failed"
857
- }));
880
+ };
881
+ reject(
882
+ new ScaleMuleApiError(
883
+ withErrorContext(uploadError, data, {
884
+ get: (name) => xhr.getResponseHeader(name)
885
+ })
886
+ )
887
+ );
858
888
  }
859
889
  } catch {
860
890
  reject(new ScaleMuleApiError({ code: "PARSE_ERROR", message: "Failed to parse response" }));
@@ -121,6 +121,20 @@ declare class ScaleMuleApiError extends Error {
121
121
  status?: number;
122
122
  field?: string;
123
123
  details?: unknown;
124
+ /**
125
+ * Correlation id for the failed request. Comes from the response envelope
126
+ * (`meta.request_id`), falling back to the `x-request-id` response header
127
+ * echoed by the gateway. Surface this in support flows.
128
+ */
129
+ requestId?: string;
130
+ /** Distributed-trace id, when the backend supplies `meta.trace_id`. */
131
+ traceId?: string;
132
+ /**
133
+ * The raw `error` object from the response body, unmodified. Escape hatch
134
+ * for consumers (such as `@scalemule/signals` `fromError`) that need fields
135
+ * this class does not model yet.
136
+ */
137
+ problem?: unknown;
124
138
  constructor(error: ApiError, status?: number);
125
139
  }
126
140
  interface ApiResponse<T> {
@@ -132,6 +146,12 @@ interface ApiError {
132
146
  code: string;
133
147
  message: string;
134
148
  field?: string;
149
+ /** Correlation id: envelope `meta.request_id`, else the `x-request-id` header. */
150
+ requestId?: string;
151
+ /** Distributed-trace id from envelope `meta.trace_id`. */
152
+ traceId?: string;
153
+ /** The raw body `error` object, passed through untouched. */
154
+ problem?: unknown;
135
155
  }
136
156
  interface User {
137
157
  id: string;
@@ -121,6 +121,20 @@ declare class ScaleMuleApiError extends Error {
121
121
  status?: number;
122
122
  field?: string;
123
123
  details?: unknown;
124
+ /**
125
+ * Correlation id for the failed request. Comes from the response envelope
126
+ * (`meta.request_id`), falling back to the `x-request-id` response header
127
+ * echoed by the gateway. Surface this in support flows.
128
+ */
129
+ requestId?: string;
130
+ /** Distributed-trace id, when the backend supplies `meta.trace_id`. */
131
+ traceId?: string;
132
+ /**
133
+ * The raw `error` object from the response body, unmodified. Escape hatch
134
+ * for consumers (such as `@scalemule/signals` `fromError`) that need fields
135
+ * this class does not model yet.
136
+ */
137
+ problem?: unknown;
124
138
  constructor(error: ApiError, status?: number);
125
139
  }
126
140
  interface ApiResponse<T> {
@@ -132,6 +146,12 @@ interface ApiError {
132
146
  code: string;
133
147
  message: string;
134
148
  field?: string;
149
+ /** Correlation id: envelope `meta.request_id`, else the `x-request-id` header. */
150
+ requestId?: string;
151
+ /** Distributed-trace id from envelope `meta.trace_id`. */
152
+ traceId?: string;
153
+ /** The raw body `error` object, passed through untouched. */
154
+ problem?: unknown;
135
155
  }
136
156
  interface User {
137
157
  id: string;
package/dist/index.d.mts CHANGED
@@ -6,8 +6,8 @@ import { MoneyClient } from '@scalemule/money';
6
6
  export { MoneyClient, MoneyClientConfig, createMoneyClient } from '@scalemule/money';
7
7
  import { ScaleMuleClient } from './client.mjs';
8
8
  export { ClientConfig, RequestOptions, createClient } from './client.mjs';
9
- import { S as ScaleMuleConfig, U as User, L as LoginResponse, A as ApiError$1, a as UseAuthReturn, b as UseBillingReturn, c as ListFilesParams, d as UseContentReturn, e as UseUserReturn, f as UseAnalyticsOptions, g as UseAnalyticsReturn } from './index-Tq5WdDfS.mjs';
10
- export { h as AccountBalance, i as AnalyticsEvent, j as ApiResponse, B as BatchTrackRequest, k as BillingPayment, l as BillingPayout, m as BillingRefund, n as BillingTransaction, C as ChangeEmailRequest, o as ChangePasswordRequest, p as ClientContext, q as ConnectedAccount, D as DeviceFingerprint, r as DeviceInfo, E as EnhancedAnalyticsEvent, F as ForgotPasswordRequest, K as KnownAccountInfo, s as LinkedAccount, t as ListFilesResponse, u as LoginDeviceInfo, v as LoginRequest, w as LoginResponseWithMFA, x as LoginRiskInfo, M as MFAChallengeResponse, y as MFAMethod, z as MFASMSSetupResponse, G as MFASetupRequest, H as MFAStatus, I as MFATOTPSetupResponse, J as MFAVerifyRequest, O as OAuthCallbackRequest, N as OAuthCallbackResponse, P as OAuthConfig, Q as OAuthProvider, R as OAuthStartResponse, T as PageViewData, V as PayoutSchedule, W as PhoneLoginRequest, X as PhoneSendCodeRequest, Y as PhoneVerifyRequest, Z as Profile, _ as RefreshResponse, $ as RegisterRequest, a0 as ResetPasswordRequest, a1 as ScaleMuleApiError, a2 as ScaleMuleEnvironment, a3 as Session, a4 as SignedUploadCompleteRequest, a5 as SignedUploadRequest, a6 as SignedUploadResponse, a7 as SignedUploadUrl, a8 as StorageAdapter, a9 as StorageFile, aa as TrackEventResponse, ab as TransactionSummary, ac as UTMParams, ad as UpdateProfileRequest, ae as UploadOptions, af as UploadResponse, ag as VerifyEmailRequest } from './index-Tq5WdDfS.mjs';
9
+ import { S as ScaleMuleConfig, U as User, L as LoginResponse, A as ApiError$1, a as UseAuthReturn, b as UseBillingReturn, c as ListFilesParams, d as UseContentReturn, e as UseUserReturn, f as UseAnalyticsOptions, g as UseAnalyticsReturn } from './index-Bv1m_qQ8.mjs';
10
+ export { h as AccountBalance, i as AnalyticsEvent, j as ApiResponse, B as BatchTrackRequest, k as BillingPayment, l as BillingPayout, m as BillingRefund, n as BillingTransaction, C as ChangeEmailRequest, o as ChangePasswordRequest, p as ClientContext, q as ConnectedAccount, D as DeviceFingerprint, r as DeviceInfo, E as EnhancedAnalyticsEvent, F as ForgotPasswordRequest, K as KnownAccountInfo, s as LinkedAccount, t as ListFilesResponse, u as LoginDeviceInfo, v as LoginRequest, w as LoginResponseWithMFA, x as LoginRiskInfo, M as MFAChallengeResponse, y as MFAMethod, z as MFASMSSetupResponse, G as MFASetupRequest, H as MFAStatus, I as MFATOTPSetupResponse, J as MFAVerifyRequest, O as OAuthCallbackRequest, N as OAuthCallbackResponse, P as OAuthConfig, Q as OAuthProvider, R as OAuthStartResponse, T as PageViewData, V as PayoutSchedule, W as PhoneLoginRequest, X as PhoneSendCodeRequest, Y as PhoneVerifyRequest, Z as Profile, _ as RefreshResponse, $ as RegisterRequest, a0 as ResetPasswordRequest, a1 as ScaleMuleApiError, a2 as ScaleMuleEnvironment, a3 as Session, a4 as SignedUploadCompleteRequest, a5 as SignedUploadRequest, a6 as SignedUploadResponse, a7 as SignedUploadUrl, a8 as StorageAdapter, a9 as StorageFile, aa as TrackEventResponse, ab as TransactionSummary, ac as UTMParams, ad as UpdateProfileRequest, ae as UploadOptions, af as UploadResponse, ag as VerifyEmailRequest } from './index-Bv1m_qQ8.mjs';
11
11
 
12
12
  /**
13
13
  * Tri-state file visibility — `'private' | 'app_public' | 'anonymous_visible'`.
package/dist/index.d.ts CHANGED
@@ -6,8 +6,8 @@ import { MoneyClient } from '@scalemule/money';
6
6
  export { MoneyClient, MoneyClientConfig, createMoneyClient } from '@scalemule/money';
7
7
  import { ScaleMuleClient } from './client.js';
8
8
  export { ClientConfig, RequestOptions, createClient } from './client.js';
9
- import { S as ScaleMuleConfig, U as User, L as LoginResponse, A as ApiError$1, a as UseAuthReturn, b as UseBillingReturn, c as ListFilesParams, d as UseContentReturn, e as UseUserReturn, f as UseAnalyticsOptions, g as UseAnalyticsReturn } from './index-Tq5WdDfS.js';
10
- export { h as AccountBalance, i as AnalyticsEvent, j as ApiResponse, B as BatchTrackRequest, k as BillingPayment, l as BillingPayout, m as BillingRefund, n as BillingTransaction, C as ChangeEmailRequest, o as ChangePasswordRequest, p as ClientContext, q as ConnectedAccount, D as DeviceFingerprint, r as DeviceInfo, E as EnhancedAnalyticsEvent, F as ForgotPasswordRequest, K as KnownAccountInfo, s as LinkedAccount, t as ListFilesResponse, u as LoginDeviceInfo, v as LoginRequest, w as LoginResponseWithMFA, x as LoginRiskInfo, M as MFAChallengeResponse, y as MFAMethod, z as MFASMSSetupResponse, G as MFASetupRequest, H as MFAStatus, I as MFATOTPSetupResponse, J as MFAVerifyRequest, O as OAuthCallbackRequest, N as OAuthCallbackResponse, P as OAuthConfig, Q as OAuthProvider, R as OAuthStartResponse, T as PageViewData, V as PayoutSchedule, W as PhoneLoginRequest, X as PhoneSendCodeRequest, Y as PhoneVerifyRequest, Z as Profile, _ as RefreshResponse, $ as RegisterRequest, a0 as ResetPasswordRequest, a1 as ScaleMuleApiError, a2 as ScaleMuleEnvironment, a3 as Session, a4 as SignedUploadCompleteRequest, a5 as SignedUploadRequest, a6 as SignedUploadResponse, a7 as SignedUploadUrl, a8 as StorageAdapter, a9 as StorageFile, aa as TrackEventResponse, ab as TransactionSummary, ac as UTMParams, ad as UpdateProfileRequest, ae as UploadOptions, af as UploadResponse, ag as VerifyEmailRequest } from './index-Tq5WdDfS.js';
9
+ import { S as ScaleMuleConfig, U as User, L as LoginResponse, A as ApiError$1, a as UseAuthReturn, b as UseBillingReturn, c as ListFilesParams, d as UseContentReturn, e as UseUserReturn, f as UseAnalyticsOptions, g as UseAnalyticsReturn } from './index-Bv1m_qQ8.js';
10
+ export { h as AccountBalance, i as AnalyticsEvent, j as ApiResponse, B as BatchTrackRequest, k as BillingPayment, l as BillingPayout, m as BillingRefund, n as BillingTransaction, C as ChangeEmailRequest, o as ChangePasswordRequest, p as ClientContext, q as ConnectedAccount, D as DeviceFingerprint, r as DeviceInfo, E as EnhancedAnalyticsEvent, F as ForgotPasswordRequest, K as KnownAccountInfo, s as LinkedAccount, t as ListFilesResponse, u as LoginDeviceInfo, v as LoginRequest, w as LoginResponseWithMFA, x as LoginRiskInfo, M as MFAChallengeResponse, y as MFAMethod, z as MFASMSSetupResponse, G as MFASetupRequest, H as MFAStatus, I as MFATOTPSetupResponse, J as MFAVerifyRequest, O as OAuthCallbackRequest, N as OAuthCallbackResponse, P as OAuthConfig, Q as OAuthProvider, R as OAuthStartResponse, T as PageViewData, V as PayoutSchedule, W as PhoneLoginRequest, X as PhoneSendCodeRequest, Y as PhoneVerifyRequest, Z as Profile, _ as RefreshResponse, $ as RegisterRequest, a0 as ResetPasswordRequest, a1 as ScaleMuleApiError, a2 as ScaleMuleEnvironment, a3 as Session, a4 as SignedUploadCompleteRequest, a5 as SignedUploadRequest, a6 as SignedUploadResponse, a7 as SignedUploadUrl, a8 as StorageAdapter, a9 as StorageFile, aa as TrackEventResponse, ab as TransactionSummary, ac as UTMParams, ad as UpdateProfileRequest, ae as UploadOptions, af as UploadResponse, ag as VerifyEmailRequest } from './index-Bv1m_qQ8.js';
11
11
 
12
12
  /**
13
13
  * Tri-state file visibility — `'private' | 'app_public' | 'anonymous_visible'`.
package/dist/index.js CHANGED
@@ -35,9 +35,31 @@ var ScaleMuleApiError = class extends Error {
35
35
  this.code = error.code;
36
36
  this.field = error.field;
37
37
  this.status = status;
38
+ this.requestId = error.requestId;
39
+ this.traceId = error.traceId;
40
+ this.problem = error.problem;
38
41
  }
39
42
  };
40
43
 
44
+ // src/error-context.ts
45
+ function withErrorContext(error, responseData, headers) {
46
+ const enriched = { ...error };
47
+ const meta = responseData?.meta;
48
+ const requestId = meta?.request_id ?? headers?.get("x-request-id") ?? void 0;
49
+ if (requestId !== void 0 && enriched.requestId === void 0) {
50
+ enriched.requestId = requestId;
51
+ }
52
+ const traceId = meta?.trace_id;
53
+ if (traceId !== void 0 && enriched.traceId === void 0) {
54
+ enriched.traceId = traceId;
55
+ }
56
+ const rawError = responseData?.error;
57
+ if (rawError !== null && typeof rawError === "object" && enriched.problem === void 0) {
58
+ enriched.problem = rawError;
59
+ }
60
+ return enriched;
61
+ }
62
+
41
63
  // src/client.ts
42
64
  var GATEWAY_URLS = {
43
65
  dev: "https://api-dev.scalemule.com",
@@ -614,7 +636,8 @@ var ScaleMuleClient = class {
614
636
  }
615
637
  if (!response.ok) {
616
638
  const rawError = responseData?.error;
617
- const error = rawError && typeof rawError === "object" ? rawError : { code: `HTTP_${response.status}`, message: typeof rawError === "string" ? rawError : responseData?.message || text || response.statusText };
639
+ const baseError = rawError && typeof rawError === "object" ? rawError : { code: `HTTP_${response.status}`, message: typeof rawError === "string" ? rawError : responseData?.message || text || response.statusText };
640
+ const error = withErrorContext(baseError, responseData, response.headers);
618
641
  if (response.status === 401 && this.sessionToken && !options.isAutoRefresh && refreshAttempts < MAX_REFRESH_ATTEMPTS) {
619
642
  refreshAttempts++;
620
643
  if (this.debug) console.log("[ScaleMule] 401 received, attempting auto-refresh...");
@@ -876,10 +899,17 @@ var ScaleMuleClient = class {
876
899
  const unwrapped = data?.data !== void 0 ? data.data : data;
877
900
  resolve(unwrapped);
878
901
  } else {
879
- reject(new ScaleMuleApiError(data?.error || {
902
+ const uploadError = data?.error || {
880
903
  code: `HTTP_${xhr.status}`,
881
904
  message: data?.message || xhr.responseText || "Upload failed"
882
- }));
905
+ };
906
+ reject(
907
+ new ScaleMuleApiError(
908
+ withErrorContext(uploadError, data, {
909
+ get: (name) => xhr.getResponseHeader(name)
910
+ })
911
+ )
912
+ );
883
913
  }
884
914
  } catch {
885
915
  reject(new ScaleMuleApiError({ code: "PARSE_ERROR", message: "Failed to parse response" }));