@scalemule/sdk 0.0.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/dist/index.mjs ADDED
@@ -0,0 +1,4309 @@
1
+ import {
2
+ UploadResumeStore
3
+ } from "./chunk-3FTGBRLU.mjs";
4
+
5
+ // src/types.ts
6
+ var ErrorCodes = {
7
+ // Auth & access
8
+ UNAUTHORIZED: "unauthorized",
9
+ FORBIDDEN: "forbidden",
10
+ // Resources
11
+ NOT_FOUND: "not_found",
12
+ CONFLICT: "conflict",
13
+ // Input
14
+ VALIDATION_ERROR: "validation_error",
15
+ // Rate limiting & quotas
16
+ RATE_LIMITED: "rate_limited",
17
+ QUOTA_EXCEEDED: "quota_exceeded",
18
+ // Server
19
+ INTERNAL_ERROR: "internal_error",
20
+ // Network (SDK-generated, not from server)
21
+ NETWORK_ERROR: "network_error",
22
+ TIMEOUT: "timeout",
23
+ ABORTED: "aborted",
24
+ // Storage-specific
25
+ FILE_SCANNING: "file_scanning",
26
+ FILE_THREAT: "file_threat",
27
+ FILE_QUARANTINED: "file_quarantined",
28
+ // Upload
29
+ UPLOAD_ERROR: "upload_error"
30
+ };
31
+
32
+ // src/context.ts
33
+ function validateIP(ip) {
34
+ if (!ip) return void 0;
35
+ const trimmed = ip.trim();
36
+ if (!trimmed) return void 0;
37
+ const ipv4 = /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/;
38
+ const ipv6 = /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^::(?:[0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4}$|^(?:[0-9a-fA-F]{1,4}:){1,7}:$|^(?:[0-9a-fA-F]{1,4}:){0,6}::(?:[0-9a-fA-F]{1,4}:){0,5}[0-9a-fA-F]{1,4}$/;
39
+ const mapped = /^::ffff:(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/i;
40
+ if (ipv4.test(trimmed) || ipv6.test(trimmed) || mapped.test(trimmed)) {
41
+ return trimmed;
42
+ }
43
+ return void 0;
44
+ }
45
+ function extractClientContext(request) {
46
+ const h = request.headers;
47
+ const getHeader = (name) => {
48
+ const v = h[name] ?? h[name.toLowerCase()];
49
+ return Array.isArray(v) ? v[0] : v;
50
+ };
51
+ let ip;
52
+ const cfIp = getHeader("cf-connecting-ip");
53
+ if (cfIp) ip = validateIP(cfIp);
54
+ if (!ip) {
55
+ const doIp = getHeader("do-connecting-ip");
56
+ if (doIp) ip = validateIP(doIp);
57
+ }
58
+ if (!ip) {
59
+ const realIp = getHeader("x-real-ip");
60
+ if (realIp) ip = validateIP(realIp);
61
+ }
62
+ if (!ip) {
63
+ const xff = getHeader("x-forwarded-for");
64
+ if (xff) ip = validateIP(xff.split(",")[0]?.trim());
65
+ }
66
+ if (!ip) {
67
+ const vercel = getHeader("x-vercel-forwarded-for");
68
+ if (vercel) ip = validateIP(vercel.split(",")[0]?.trim());
69
+ }
70
+ if (!ip) {
71
+ const akamai = getHeader("true-client-ip");
72
+ if (akamai) ip = validateIP(akamai);
73
+ }
74
+ if (!ip && request.socket?.remoteAddress) {
75
+ ip = validateIP(request.socket.remoteAddress);
76
+ }
77
+ return {
78
+ ip,
79
+ userAgent: getHeader("user-agent") || void 0,
80
+ deviceFingerprint: getHeader("x-device-fingerprint") || void 0,
81
+ referrer: getHeader("referer") || void 0
82
+ };
83
+ }
84
+ function buildClientContextHeaders(context) {
85
+ if (!context) return {};
86
+ const headers = {};
87
+ if (context.ip) {
88
+ headers["x-sm-forwarded-client-ip"] = context.ip;
89
+ headers["X-Client-IP"] = context.ip;
90
+ }
91
+ if (context.userAgent) headers["X-Client-User-Agent"] = context.userAgent;
92
+ if (context.deviceFingerprint) headers["X-Client-Device-Fingerprint"] = context.deviceFingerprint;
93
+ if (context.referrer) headers["X-Client-Referrer"] = context.referrer;
94
+ return headers;
95
+ }
96
+
97
+ // src/client.ts
98
+ var SDK_VERSION = "0.0.1";
99
+ var DEFAULT_TIMEOUT = 3e4;
100
+ var DEFAULT_MAX_RETRIES = 2;
101
+ var DEFAULT_BACKOFF_MS = 300;
102
+ var MAX_BACKOFF_MS = 3e4;
103
+ var SESSION_STORAGE_KEY = "scalemule_session";
104
+ var USER_ID_STORAGE_KEY = "scalemule_user_id";
105
+ var OFFLINE_QUEUE_KEY = "scalemule_offline_queue";
106
+ var GATEWAY_URLS = {
107
+ dev: "https://api-dev.scalemule.com",
108
+ prod: "https://api.scalemule.com"
109
+ };
110
+ var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
111
+ function sleep(ms) {
112
+ return new Promise((resolve) => setTimeout(resolve, ms));
113
+ }
114
+ function getBackoffDelay(attempt, baseDelay) {
115
+ const exponential = baseDelay * Math.pow(2, attempt);
116
+ const jitter = Math.random() * 0.3 * exponential;
117
+ return Math.min(exponential + jitter, MAX_BACKOFF_MS);
118
+ }
119
+ function generateIdempotencyKey() {
120
+ return `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
121
+ }
122
+ function sanitizeFilename(filename) {
123
+ let sanitized = filename.replace(/[\x00-\x1f\x7f]/g, "");
124
+ sanitized = sanitized.replace(/["\\/\n\r]/g, "_").normalize("NFC").replace(/[\u200b-\u200f\ufeff\u2028\u2029]/g, "");
125
+ if (!sanitized || sanitized.trim() === "") {
126
+ sanitized = "unnamed";
127
+ }
128
+ if (sanitized.length > 200) {
129
+ const ext = sanitized.split(".").pop();
130
+ const base = sanitized.substring(0, 190);
131
+ sanitized = ext ? `${base}.${ext}` : base;
132
+ }
133
+ return sanitized.trim();
134
+ }
135
+ function statusToErrorCode(status) {
136
+ switch (status) {
137
+ case 400:
138
+ return "validation_error";
139
+ case 401:
140
+ return "unauthorized";
141
+ case 403:
142
+ return "forbidden";
143
+ case 404:
144
+ return "not_found";
145
+ case 409:
146
+ return "conflict";
147
+ case 422:
148
+ return "validation_error";
149
+ case 429:
150
+ return "rate_limited";
151
+ default:
152
+ return status >= 500 ? "internal_error" : `http_${status}`;
153
+ }
154
+ }
155
+ function createDefaultStorage() {
156
+ if (typeof window !== "undefined" && window.localStorage) {
157
+ return {
158
+ getItem: (key) => window.localStorage.getItem(key),
159
+ setItem: (key, value) => window.localStorage.setItem(key, value),
160
+ removeItem: (key) => window.localStorage.removeItem(key)
161
+ };
162
+ }
163
+ const memory = /* @__PURE__ */ new Map();
164
+ return {
165
+ getItem: (key) => memory.get(key) ?? null,
166
+ setItem: (key, value) => {
167
+ memory.set(key, value);
168
+ },
169
+ removeItem: (key) => {
170
+ memory.delete(key);
171
+ }
172
+ };
173
+ }
174
+ var RateLimitQueue = class {
175
+ constructor() {
176
+ this.queue = [];
177
+ this.processing = false;
178
+ this.rateLimitedUntil = 0;
179
+ }
180
+ enqueue(execute, priority = 0) {
181
+ return new Promise((resolve, reject) => {
182
+ this.queue.push({
183
+ execute,
184
+ resolve,
185
+ reject,
186
+ priority
187
+ });
188
+ this.queue.sort((a, b) => b.priority - a.priority);
189
+ this.processQueue();
190
+ });
191
+ }
192
+ async processQueue() {
193
+ if (this.processing || this.queue.length === 0) return;
194
+ this.processing = true;
195
+ while (this.queue.length > 0) {
196
+ const now = Date.now();
197
+ if (now < this.rateLimitedUntil) {
198
+ await sleep(this.rateLimitedUntil - now);
199
+ }
200
+ const request = this.queue.shift();
201
+ if (!request) continue;
202
+ try {
203
+ const result = await request.execute();
204
+ if (result.error?.code === "rate_limited") {
205
+ this.queue.unshift(request);
206
+ const retryAfter = result.error.details?.retryAfter || 60;
207
+ this.rateLimitedUntil = Date.now() + retryAfter * 1e3;
208
+ } else {
209
+ request.resolve(result);
210
+ }
211
+ } catch (error) {
212
+ request.reject(error);
213
+ }
214
+ }
215
+ this.processing = false;
216
+ }
217
+ updateFromHeaders(headers) {
218
+ const retryAfter = headers.get("Retry-After");
219
+ if (retryAfter) {
220
+ const seconds = parseInt(retryAfter, 10);
221
+ if (!isNaN(seconds)) {
222
+ this.rateLimitedUntil = Date.now() + seconds * 1e3;
223
+ }
224
+ }
225
+ }
226
+ get length() {
227
+ return this.queue.length;
228
+ }
229
+ get isRateLimited() {
230
+ return Date.now() < this.rateLimitedUntil;
231
+ }
232
+ };
233
+ var OfflineQueue = class {
234
+ constructor(storage) {
235
+ this.queue = [];
236
+ this.onOnlineCallback = null;
237
+ this.storage = storage;
238
+ this.loadFromStorage();
239
+ this.setupListeners();
240
+ }
241
+ setupListeners() {
242
+ if (typeof window === "undefined") return;
243
+ window.addEventListener("online", () => {
244
+ if (this.onOnlineCallback) this.onOnlineCallback();
245
+ });
246
+ }
247
+ async loadFromStorage() {
248
+ try {
249
+ const data = await this.storage.getItem(OFFLINE_QUEUE_KEY);
250
+ if (data) this.queue = JSON.parse(data);
251
+ } catch {
252
+ }
253
+ }
254
+ async save() {
255
+ try {
256
+ await this.storage.setItem(OFFLINE_QUEUE_KEY, JSON.stringify(this.queue));
257
+ } catch {
258
+ }
259
+ }
260
+ async add(method, path, body) {
261
+ this.queue.push({
262
+ id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
263
+ method,
264
+ path,
265
+ body: body ? JSON.stringify(body) : void 0,
266
+ timestamp: Date.now()
267
+ });
268
+ await this.save();
269
+ }
270
+ getAll() {
271
+ return [...this.queue];
272
+ }
273
+ async remove(id) {
274
+ this.queue = this.queue.filter((item) => item.id !== id);
275
+ await this.save();
276
+ }
277
+ async clear() {
278
+ this.queue = [];
279
+ await this.save();
280
+ }
281
+ setOnlineCallback(cb) {
282
+ this.onOnlineCallback = cb;
283
+ }
284
+ get length() {
285
+ return this.queue.length;
286
+ }
287
+ get online() {
288
+ return typeof navigator === "undefined" || navigator.onLine;
289
+ }
290
+ };
291
+ var ScaleMuleClient = class {
292
+ constructor(config) {
293
+ this.sessionToken = null;
294
+ this.userId = null;
295
+ this.rateLimitQueue = null;
296
+ this.offlineQueue = null;
297
+ this.apiKey = config.apiKey;
298
+ this.baseUrl = config.baseUrl || GATEWAY_URLS[config.environment || "prod"];
299
+ this.debug = config.debug || false;
300
+ this.storage = config.storage || createDefaultStorage();
301
+ this.defaultTimeout = config.timeout || DEFAULT_TIMEOUT;
302
+ this.maxRetries = config.retry?.maxRetries ?? DEFAULT_MAX_RETRIES;
303
+ this.backoffMs = config.retry?.backoffMs ?? DEFAULT_BACKOFF_MS;
304
+ if (config.enableRateLimitQueue) {
305
+ this.rateLimitQueue = new RateLimitQueue();
306
+ }
307
+ if (config.enableOfflineQueue) {
308
+ this.offlineQueue = new OfflineQueue(this.storage);
309
+ this.offlineQueue.setOnlineCallback(() => this.syncOfflineQueue());
310
+ }
311
+ }
312
+ // --------------------------------------------------------------------------
313
+ // Session Management
314
+ // --------------------------------------------------------------------------
315
+ async initialize() {
316
+ const token = await this.storage.getItem(SESSION_STORAGE_KEY);
317
+ const userId = await this.storage.getItem(USER_ID_STORAGE_KEY);
318
+ if (token) this.sessionToken = token;
319
+ if (userId) this.userId = userId;
320
+ if (this.debug) console.log("[ScaleMule] Initialized, session:", !!token);
321
+ }
322
+ async setSession(token, userId) {
323
+ this.sessionToken = token;
324
+ this.userId = userId;
325
+ await this.storage.setItem(SESSION_STORAGE_KEY, token);
326
+ await this.storage.setItem(USER_ID_STORAGE_KEY, userId);
327
+ }
328
+ async clearSession() {
329
+ this.sessionToken = null;
330
+ this.userId = null;
331
+ await this.storage.removeItem(SESSION_STORAGE_KEY);
332
+ await this.storage.removeItem(USER_ID_STORAGE_KEY);
333
+ }
334
+ setAccessToken(token) {
335
+ this.sessionToken = token;
336
+ }
337
+ clearAccessToken() {
338
+ this.sessionToken = null;
339
+ }
340
+ getSessionToken() {
341
+ return this.sessionToken;
342
+ }
343
+ getUserId() {
344
+ return this.userId;
345
+ }
346
+ isAuthenticated() {
347
+ return this.sessionToken !== null;
348
+ }
349
+ getBaseUrl() {
350
+ return this.baseUrl;
351
+ }
352
+ getApiKey() {
353
+ return this.apiKey;
354
+ }
355
+ isOnline() {
356
+ if (this.offlineQueue) return this.offlineQueue.online;
357
+ return typeof navigator === "undefined" || navigator.onLine;
358
+ }
359
+ getOfflineQueueLength() {
360
+ return this.offlineQueue?.length || 0;
361
+ }
362
+ getRateLimitQueueLength() {
363
+ return this.rateLimitQueue?.length || 0;
364
+ }
365
+ isRateLimited() {
366
+ return this.rateLimitQueue?.isRateLimited || false;
367
+ }
368
+ // --------------------------------------------------------------------------
369
+ // Core Request Method
370
+ // --------------------------------------------------------------------------
371
+ async request(path, init = {}) {
372
+ const url = `${this.baseUrl}${path}`;
373
+ const method = (init.method || "GET").toUpperCase();
374
+ const timeout = init.timeout || this.defaultTimeout;
375
+ const maxRetries = init.skipRetry ? 0 : init.retries ?? this.maxRetries;
376
+ const headers = {
377
+ "x-api-key": this.apiKey,
378
+ "User-Agent": `ScaleMule-SDK-TypeScript/${SDK_VERSION}`,
379
+ ...init.headers
380
+ };
381
+ if (!init.skipAuth && this.sessionToken) {
382
+ headers["Authorization"] = `Bearer ${this.sessionToken}`;
383
+ }
384
+ let bodyStr;
385
+ if (init.body !== void 0 && init.body !== null) {
386
+ bodyStr = typeof init.body === "string" ? init.body : JSON.stringify(init.body);
387
+ if (!headers["Content-Type"]) {
388
+ headers["Content-Type"] = "application/json";
389
+ }
390
+ }
391
+ if (this.debug) {
392
+ console.log(`[ScaleMule] ${method} ${path}`);
393
+ }
394
+ const idempotencyKey = method === "POST" ? generateIdempotencyKey() : void 0;
395
+ let lastError = null;
396
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
397
+ if (attempt > 0 && idempotencyKey) {
398
+ headers["x-idempotency-key"] = idempotencyKey;
399
+ }
400
+ const controller = new AbortController();
401
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
402
+ if (init.signal) {
403
+ if (init.signal.aborted) {
404
+ clearTimeout(timeoutId);
405
+ return { data: null, error: { code: "aborted", message: "Request aborted", status: 0 } };
406
+ }
407
+ init.signal.addEventListener("abort", () => controller.abort(), { once: true });
408
+ }
409
+ try {
410
+ const response = await fetch(url, {
411
+ method,
412
+ headers,
413
+ body: bodyStr,
414
+ signal: controller.signal
415
+ });
416
+ clearTimeout(timeoutId);
417
+ if (this.rateLimitQueue) {
418
+ this.rateLimitQueue.updateFromHeaders(response.headers);
419
+ }
420
+ let responseData;
421
+ const contentType = response.headers.get("Content-Type") || "";
422
+ if (contentType.includes("application/json")) {
423
+ responseData = await response.json();
424
+ } else {
425
+ const text = await response.text();
426
+ try {
427
+ responseData = JSON.parse(text);
428
+ } catch {
429
+ responseData = { message: text };
430
+ }
431
+ }
432
+ if (!response.ok) {
433
+ const error = {
434
+ code: responseData?.error?.code || responseData?.code || statusToErrorCode(response.status),
435
+ message: responseData?.error?.message || responseData?.message || response.statusText,
436
+ status: response.status,
437
+ details: responseData?.error?.details || responseData?.details
438
+ };
439
+ if (response.status === 429) {
440
+ const retryAfter = response.headers.get("Retry-After");
441
+ if (retryAfter) {
442
+ error.details = { ...error.details, retryAfter: parseInt(retryAfter, 10) };
443
+ }
444
+ }
445
+ if (attempt < maxRetries && RETRYABLE_STATUS_CODES.has(response.status)) {
446
+ lastError = error;
447
+ const delay = getBackoffDelay(attempt, this.backoffMs);
448
+ if (this.debug) {
449
+ console.log(`[ScaleMule] Retry ${attempt + 1}/${maxRetries} in ${Math.round(delay)}ms`);
450
+ }
451
+ await sleep(delay);
452
+ continue;
453
+ }
454
+ return { data: null, error };
455
+ }
456
+ const data = responseData?.data !== void 0 ? responseData.data : responseData;
457
+ return { data, error: null };
458
+ } catch (err) {
459
+ clearTimeout(timeoutId);
460
+ const isAbort = err instanceof Error && err.name === "AbortError";
461
+ const error = {
462
+ code: isAbort ? init.signal?.aborted ? "aborted" : "timeout" : "network_error",
463
+ message: err instanceof Error ? err.message : "Network request failed",
464
+ status: 0
465
+ };
466
+ if (attempt < maxRetries && !init.signal?.aborted) {
467
+ lastError = error;
468
+ const delay = getBackoffDelay(attempt, this.backoffMs);
469
+ if (this.debug) {
470
+ console.log(`[ScaleMule] Retry ${attempt + 1}/${maxRetries} in ${Math.round(delay)}ms (${error.code})`);
471
+ }
472
+ await sleep(delay);
473
+ continue;
474
+ }
475
+ return { data: null, error };
476
+ }
477
+ }
478
+ return { data: null, error: lastError || { code: "internal_error", message: "Request failed", status: 0 } };
479
+ }
480
+ // --------------------------------------------------------------------------
481
+ // HTTP Verb Shortcuts
482
+ // --------------------------------------------------------------------------
483
+ async get(path, options) {
484
+ return this.request(path, { ...options, method: "GET" });
485
+ }
486
+ async post(path, body, options) {
487
+ return this.request(path, { ...options, method: "POST", body });
488
+ }
489
+ async put(path, body, options) {
490
+ return this.request(path, { ...options, method: "PUT", body });
491
+ }
492
+ async patch(path, body, options) {
493
+ return this.request(path, { ...options, method: "PATCH", body });
494
+ }
495
+ async del(path, options) {
496
+ return this.request(path, { ...options, method: "DELETE" });
497
+ }
498
+ // --------------------------------------------------------------------------
499
+ // File Upload
500
+ // --------------------------------------------------------------------------
501
+ /**
502
+ * Upload a file using multipart/form-data.
503
+ *
504
+ * Supports progress tracking via XMLHttpRequest (browser only).
505
+ * Supports cancellation via AbortController signal.
506
+ * Retries with exponential backoff on transient failures.
507
+ */
508
+ async upload(path, file, additionalFields, options) {
509
+ const fileName = file.name || "file";
510
+ const sanitizedName = sanitizeFilename(fileName);
511
+ const sanitizedFile = sanitizedName !== fileName ? new File([file], sanitizedName, { type: file.type }) : file;
512
+ const buildFormData = () => {
513
+ const fd = new FormData();
514
+ fd.append("file", sanitizedFile);
515
+ if (additionalFields) {
516
+ for (const [key, value] of Object.entries(additionalFields)) {
517
+ fd.append(key, value);
518
+ }
519
+ }
520
+ return fd;
521
+ };
522
+ const url = `${this.baseUrl}${path}`;
523
+ if (this.debug) console.log(`[ScaleMule] UPLOAD ${path}`);
524
+ if (options?.signal?.aborted) {
525
+ return { data: null, error: { code: "aborted", message: "Upload aborted", status: 0 } };
526
+ }
527
+ if (options?.onProgress && typeof XMLHttpRequest !== "undefined") {
528
+ return this.uploadWithXHR(url, buildFormData, options.onProgress, options?.signal);
529
+ }
530
+ const maxRetries = options?.retries ?? this.maxRetries;
531
+ let lastError = null;
532
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
533
+ const headers = { "x-api-key": this.apiKey };
534
+ if (this.sessionToken) headers["Authorization"] = `Bearer ${this.sessionToken}`;
535
+ try {
536
+ const response = await fetch(url, {
537
+ method: "POST",
538
+ headers,
539
+ body: buildFormData(),
540
+ signal: options?.signal
541
+ });
542
+ const data = await response.json();
543
+ if (!response.ok) {
544
+ const error = {
545
+ code: data?.error?.code || statusToErrorCode(response.status),
546
+ message: data?.error?.message || data?.message || response.statusText,
547
+ status: response.status,
548
+ details: data?.error?.details
549
+ };
550
+ if (attempt < maxRetries && RETRYABLE_STATUS_CODES.has(response.status)) {
551
+ lastError = error;
552
+ await sleep(getBackoffDelay(attempt, this.backoffMs));
553
+ continue;
554
+ }
555
+ return { data: null, error };
556
+ }
557
+ const result = data?.data !== void 0 ? data.data : data;
558
+ return { data: result, error: null };
559
+ } catch (err) {
560
+ if (options?.signal?.aborted) {
561
+ return { data: null, error: { code: "aborted", message: "Upload aborted", status: 0 } };
562
+ }
563
+ lastError = {
564
+ code: "upload_error",
565
+ message: err instanceof Error ? err.message : "Upload failed",
566
+ status: 0
567
+ };
568
+ if (attempt < maxRetries) {
569
+ await sleep(getBackoffDelay(attempt, this.backoffMs));
570
+ continue;
571
+ }
572
+ }
573
+ }
574
+ return { data: null, error: lastError || { code: "upload_error", message: "Upload failed", status: 0 } };
575
+ }
576
+ /**
577
+ * Single upload with XMLHttpRequest for progress tracking.
578
+ * Supports abort via AbortSignal.
579
+ */
580
+ uploadWithXHR(url, buildFormData, onProgress, signal, maxRetries = this.maxRetries) {
581
+ return new Promise(async (resolve) => {
582
+ let lastError = null;
583
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
584
+ const result = await new Promise((res) => {
585
+ const xhr = new XMLHttpRequest();
586
+ if (signal) {
587
+ if (signal.aborted) {
588
+ res({ data: null, error: { code: "aborted", message: "Upload aborted", status: 0 } });
589
+ return;
590
+ }
591
+ signal.addEventListener("abort", () => xhr.abort(), { once: true });
592
+ }
593
+ xhr.upload.addEventListener("progress", (event) => {
594
+ if (event.lengthComputable) {
595
+ onProgress(Math.round(event.loaded / event.total * 100));
596
+ }
597
+ });
598
+ xhr.addEventListener("load", () => {
599
+ try {
600
+ const data = JSON.parse(xhr.responseText);
601
+ if (xhr.status >= 200 && xhr.status < 300) {
602
+ const result2 = data?.data !== void 0 ? data.data : data;
603
+ res({ data: result2, error: null });
604
+ } else {
605
+ res({
606
+ data: null,
607
+ error: {
608
+ code: data?.error?.code || statusToErrorCode(xhr.status),
609
+ message: data?.error?.message || data?.message || "Upload failed",
610
+ status: xhr.status,
611
+ details: data?.error?.details
612
+ }
613
+ });
614
+ }
615
+ } catch {
616
+ res({ data: null, error: { code: "internal_error", message: "Failed to parse response", status: 0 } });
617
+ }
618
+ });
619
+ xhr.addEventListener("error", () => {
620
+ res({ data: null, error: { code: "upload_error", message: "Upload failed", status: 0 } });
621
+ });
622
+ xhr.addEventListener("abort", () => {
623
+ res({ data: null, error: { code: "aborted", message: "Upload aborted", status: 0 } });
624
+ });
625
+ xhr.open("POST", url);
626
+ xhr.setRequestHeader("x-api-key", this.apiKey);
627
+ if (this.sessionToken) {
628
+ xhr.setRequestHeader("Authorization", `Bearer ${this.sessionToken}`);
629
+ }
630
+ xhr.send(buildFormData());
631
+ });
632
+ if (result.error === null) {
633
+ resolve(result);
634
+ return;
635
+ }
636
+ const isRetryable = result.error.code === "upload_error" || result.error.code === "network_error" || RETRYABLE_STATUS_CODES.has(result.error.status);
637
+ if (result.error.code === "aborted") {
638
+ resolve(result);
639
+ return;
640
+ }
641
+ if (attempt < maxRetries && isRetryable) {
642
+ lastError = result.error;
643
+ onProgress(0);
644
+ await sleep(getBackoffDelay(attempt, this.backoffMs));
645
+ continue;
646
+ }
647
+ resolve(result);
648
+ return;
649
+ }
650
+ resolve({
651
+ data: null,
652
+ error: lastError || { code: "upload_error", message: "Upload failed", status: 0 }
653
+ });
654
+ });
655
+ }
656
+ // --------------------------------------------------------------------------
657
+ // Offline Queue Sync
658
+ // --------------------------------------------------------------------------
659
+ async syncOfflineQueue() {
660
+ if (!this.offlineQueue) return;
661
+ const items = this.offlineQueue.getAll();
662
+ if (this.debug && items.length > 0) {
663
+ console.log(`[ScaleMule] Syncing ${items.length} offline requests`);
664
+ }
665
+ for (const item of items) {
666
+ try {
667
+ await this.request(item.path, {
668
+ method: item.method,
669
+ body: item.body ? JSON.parse(item.body) : void 0,
670
+ skipRetry: true
671
+ });
672
+ await this.offlineQueue.remove(item.id);
673
+ } catch {
674
+ break;
675
+ }
676
+ }
677
+ }
678
+ };
679
+
680
+ // src/service.ts
681
+ var ServiceModule = class {
682
+ constructor(client) {
683
+ this.client = client;
684
+ }
685
+ // --------------------------------------------------------------------------
686
+ // Client context → headers resolution
687
+ // --------------------------------------------------------------------------
688
+ /**
689
+ * Merge `clientContext` from RequestOptions into `headers`.
690
+ * Explicit headers take precedence over context-derived ones.
691
+ */
692
+ resolveOptions(options) {
693
+ if (!options?.clientContext) return options;
694
+ const contextHeaders = buildClientContextHeaders(options.clientContext);
695
+ const { clientContext: _, ...rest } = options;
696
+ return { ...rest, headers: { ...contextHeaders, ...rest.headers } };
697
+ }
698
+ // --------------------------------------------------------------------------
699
+ // HTTP verb shortcuts (path relative to basePath)
700
+ // --------------------------------------------------------------------------
701
+ _get(path, options) {
702
+ return this.client.get(`${this.basePath}${path}`, this.resolveOptions(options));
703
+ }
704
+ post(path, body, options) {
705
+ return this.client.post(`${this.basePath}${path}`, body, this.resolveOptions(options));
706
+ }
707
+ put(path, body, options) {
708
+ return this.client.put(`${this.basePath}${path}`, body, this.resolveOptions(options));
709
+ }
710
+ patch(path, body, options) {
711
+ return this.client.patch(`${this.basePath}${path}`, body, this.resolveOptions(options));
712
+ }
713
+ del(path, options) {
714
+ return this.client.del(`${this.basePath}${path}`, this.resolveOptions(options));
715
+ }
716
+ // --------------------------------------------------------------------------
717
+ // Paginated list
718
+ // --------------------------------------------------------------------------
719
+ /**
720
+ * Fetch a paginated list from the backend.
721
+ *
722
+ * Normalizes varying backend pagination shapes into the standard
723
+ * PaginatedResponse<T> envelope. Supports backends that return:
724
+ * - { data: T[], metadata: { total, ... } } (preferred)
725
+ * - { items: T[], total, page, per_page } (legacy)
726
+ * - T[] (bare array)
727
+ *
728
+ * Extra params beyond page/perPage are forwarded as query string parameters.
729
+ */
730
+ async _list(path, params, options) {
731
+ const qs = buildQueryString(params);
732
+ const fullPath = qs ? `${this.basePath}${path}?${qs}` : `${this.basePath}${path}`;
733
+ const response = await this.client.get(fullPath, this.resolveOptions(options));
734
+ if (response.error) {
735
+ return {
736
+ data: [],
737
+ metadata: { total: 0, totalPages: 0, page: asNum(params?.page) ?? 1, perPage: asNum(params?.perPage) ?? 20 },
738
+ error: response.error
739
+ };
740
+ }
741
+ return normalizePaginatedResponse(response.data, params);
742
+ }
743
+ // --------------------------------------------------------------------------
744
+ // File upload (delegates to client.upload)
745
+ // --------------------------------------------------------------------------
746
+ _upload(path, file, additionalFields, options) {
747
+ return this.client.upload(`${this.basePath}${path}`, file, additionalFields, this.resolveOptions(options));
748
+ }
749
+ // --------------------------------------------------------------------------
750
+ // Query string helper (available to subclasses)
751
+ // --------------------------------------------------------------------------
752
+ /**
753
+ * Append query parameters to a relative path.
754
+ * Use with verb methods: `this.get(this.withQuery('/items', { status: 'active' }))`
755
+ * Does NOT add basePath — the verb methods handle that.
756
+ */
757
+ withQuery(path, params) {
758
+ const qs = buildQueryString(params);
759
+ return qs ? `${path}?${qs}` : path;
760
+ }
761
+ };
762
+ function buildQueryString(params) {
763
+ if (!params) return "";
764
+ const pairs = [];
765
+ for (const [key, value] of Object.entries(params)) {
766
+ if (value === void 0 || value === null) continue;
767
+ pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
768
+ }
769
+ return pairs.join("&");
770
+ }
771
+ function normalizePaginatedResponse(raw, params) {
772
+ if (raw === null || raw === void 0) {
773
+ return {
774
+ data: [],
775
+ metadata: { total: 0, totalPages: 0, page: 1, perPage: 20 },
776
+ error: null
777
+ };
778
+ }
779
+ if (Array.isArray(raw)) {
780
+ return {
781
+ data: raw,
782
+ metadata: {
783
+ total: raw.length,
784
+ totalPages: 1,
785
+ page: 1,
786
+ perPage: raw.length
787
+ },
788
+ error: null
789
+ };
790
+ }
791
+ const obj = raw;
792
+ const dataArray = obj.data ?? obj.items ?? [];
793
+ const metadata = {
794
+ total: asNumber(obj.metadata, "total") ?? asNumber(obj, "total") ?? dataArray.length,
795
+ totalPages: asNumber(obj.metadata, "totalPages") ?? asNumber(obj.metadata, "total_pages") ?? asNumber(obj, "total_pages") ?? asNumber(obj, "totalPages") ?? 0,
796
+ page: asNumber(obj.metadata, "page") ?? asNumber(obj, "page") ?? asNum(params?.page) ?? 1,
797
+ perPage: asNumber(obj.metadata, "perPage") ?? asNumber(obj.metadata, "per_page") ?? asNumber(obj, "per_page") ?? asNumber(obj, "perPage") ?? asNum(params?.perPage) ?? 20
798
+ };
799
+ if (metadata.totalPages === 0 && metadata.total > 0 && metadata.perPage > 0) {
800
+ metadata.totalPages = Math.ceil(metadata.total / metadata.perPage);
801
+ }
802
+ const nextCursor = asString(obj.metadata, "nextCursor") ?? asString(obj.metadata, "next_cursor") ?? asString(obj, "next_cursor") ?? asString(obj, "nextCursor");
803
+ if (nextCursor) {
804
+ metadata.nextCursor = nextCursor;
805
+ }
806
+ return { data: dataArray, metadata, error: null };
807
+ }
808
+ function asNumber(parent, key) {
809
+ if (parent === null || parent === void 0 || typeof parent !== "object") return void 0;
810
+ const value = parent[key];
811
+ return typeof value === "number" ? value : void 0;
812
+ }
813
+ function asNum(value) {
814
+ return typeof value === "number" ? value : void 0;
815
+ }
816
+ function asString(parent, key) {
817
+ if (parent === null || parent === void 0 || typeof parent !== "object") return void 0;
818
+ const value = parent[key];
819
+ return typeof value === "string" ? value : void 0;
820
+ }
821
+
822
+ // src/utils/phone.ts
823
+ import {
824
+ PHONE_COUNTRIES,
825
+ normalizePhoneNumber,
826
+ normalizeAndValidatePhone,
827
+ composePhoneNumber,
828
+ isValidE164Phone,
829
+ findPhoneCountryByCode,
830
+ findPhoneCountryByDialCode,
831
+ detectCountryFromE164,
832
+ countryFlag
833
+ } from "@scalemule/ui/phone";
834
+
835
+ // src/services/auth.ts
836
+ var AuthMfaApi = class extends ServiceModule {
837
+ constructor() {
838
+ super(...arguments);
839
+ this.basePath = "/v1/auth/mfa";
840
+ }
841
+ async getStatus() {
842
+ return this._get("/status");
843
+ }
844
+ async setupTotp() {
845
+ return this.post("/totp/setup");
846
+ }
847
+ async verifySetup(data) {
848
+ return this.post("/totp/verify-setup", data);
849
+ }
850
+ async enableSms() {
851
+ return this.post("/sms/enable");
852
+ }
853
+ async enableEmail() {
854
+ return this.post("/email/enable");
855
+ }
856
+ async disable(data) {
857
+ return this.post("/disable", data);
858
+ }
859
+ async regenerateBackupCodes() {
860
+ return this.post("/backup-codes/regenerate");
861
+ }
862
+ async sendCode(data) {
863
+ return this.post("/send-code", data);
864
+ }
865
+ async verify(data) {
866
+ return this.post("/verify", data);
867
+ }
868
+ };
869
+ var AuthSessionsApi = class extends ServiceModule {
870
+ constructor() {
871
+ super(...arguments);
872
+ this.basePath = "/v1/auth/sessions";
873
+ }
874
+ async list() {
875
+ return this._get("");
876
+ }
877
+ async revoke(sessionId) {
878
+ return this.del(`/${sessionId}`);
879
+ }
880
+ async revokeAll() {
881
+ return this.del("/others");
882
+ }
883
+ };
884
+ var AuthDevicesApi = class extends ServiceModule {
885
+ constructor() {
886
+ super(...arguments);
887
+ this.basePath = "/v1/auth/devices";
888
+ }
889
+ async list() {
890
+ return this._get("");
891
+ }
892
+ async trust(deviceId) {
893
+ return this.post(`/${deviceId}/trust`);
894
+ }
895
+ async block(deviceId) {
896
+ return this.post(`/${deviceId}/block`);
897
+ }
898
+ async delete(deviceId) {
899
+ return this.del(`/${deviceId}`);
900
+ }
901
+ };
902
+ var AuthLoginHistoryApi = class extends ServiceModule {
903
+ constructor() {
904
+ super(...arguments);
905
+ this.basePath = "/v1/auth";
906
+ }
907
+ async list(params) {
908
+ return this._get(this.withQuery("/login-history", params));
909
+ }
910
+ async getSummary() {
911
+ return this._get("/login-activity");
912
+ }
913
+ };
914
+ var AuthService = class extends ServiceModule {
915
+ constructor(client) {
916
+ super(client);
917
+ this.basePath = "/v1/auth";
918
+ this.mfa = new AuthMfaApi(client);
919
+ this.sessions = new AuthSessionsApi(client);
920
+ this.devices = new AuthDevicesApi(client);
921
+ this.loginHistory = new AuthLoginHistoryApi(client);
922
+ }
923
+ sanitizePhoneField(value) {
924
+ if (typeof value !== "string") return value;
925
+ const normalized = normalizePhoneNumber(value);
926
+ return normalized || void 0;
927
+ }
928
+ // --------------------------------------------------------------------------
929
+ // Core Auth
930
+ // --------------------------------------------------------------------------
931
+ async register(data, options) {
932
+ const payload = {
933
+ ...data,
934
+ phone: this.sanitizePhoneField(data.phone)
935
+ };
936
+ return this.post("/register", payload, options);
937
+ }
938
+ async login(data, options) {
939
+ return this.post("/login", data, options);
940
+ }
941
+ async logout(options) {
942
+ return this.post("/logout", void 0, options);
943
+ }
944
+ async me(options) {
945
+ return this._get("/me", options);
946
+ }
947
+ /** Refresh the session. Alias: refreshToken() */
948
+ async refreshSession(data, options) {
949
+ return this.post("/refresh", data ?? {}, options);
950
+ }
951
+ /** @deprecated Use refreshSession() */
952
+ async refreshToken(data) {
953
+ return this.refreshSession(data);
954
+ }
955
+ // --------------------------------------------------------------------------
956
+ // Passwordless Auth
957
+ // --------------------------------------------------------------------------
958
+ /**
959
+ * Send a one-time password for passwordless sign-in.
960
+ * @experimental Endpoint availability depends on backend deployment.
961
+ */
962
+ async signInWithOtp(data, options) {
963
+ const payload = {
964
+ ...data,
965
+ phone: this.sanitizePhoneField(data.phone)
966
+ };
967
+ return this.post("/otp/send", payload, options);
968
+ }
969
+ /**
970
+ * Verify OTP code and create a session.
971
+ * @experimental Endpoint availability depends on backend deployment.
972
+ */
973
+ async verifyOtp(data, options) {
974
+ const payload = {
975
+ ...data,
976
+ phone: this.sanitizePhoneField(data.phone)
977
+ };
978
+ return this.post("/otp/verify", payload, options);
979
+ }
980
+ /**
981
+ * Send a magic link for passwordless sign-in.
982
+ * @experimental Endpoint availability depends on backend deployment.
983
+ */
984
+ async signInWithMagicLink(data, options) {
985
+ return this.post("/magic-link/send", data, options);
986
+ }
987
+ /**
988
+ * Verify a magic link token and create a session.
989
+ * @experimental Endpoint availability depends on backend deployment.
990
+ */
991
+ async verifyMagicLink(data, options) {
992
+ return this.post("/magic-link/verify", data, options);
993
+ }
994
+ // --------------------------------------------------------------------------
995
+ // Phone OTP (existing backend endpoints)
996
+ // --------------------------------------------------------------------------
997
+ async sendPhoneOtp(data, options) {
998
+ const payload = {
999
+ ...data,
1000
+ phone: this.sanitizePhoneField(data.phone) ?? ""
1001
+ };
1002
+ return this.post("/phone/send-otp", payload, options);
1003
+ }
1004
+ async verifyPhoneOtp(data, options) {
1005
+ const payload = {
1006
+ ...data,
1007
+ phone: this.sanitizePhoneField(data.phone) ?? ""
1008
+ };
1009
+ return this.post("/phone/verify-otp", payload, options);
1010
+ }
1011
+ async resendPhoneOtp(data, options) {
1012
+ const payload = {
1013
+ ...data,
1014
+ phone: this.sanitizePhoneField(data.phone) ?? ""
1015
+ };
1016
+ return this.post("/phone/resend-otp", payload, options);
1017
+ }
1018
+ /** Login with phone OTP (sends + verifies in one flow) */
1019
+ async loginWithPhone(data, options) {
1020
+ const payload = {
1021
+ ...data,
1022
+ phone: this.sanitizePhoneField(data.phone) ?? "",
1023
+ purpose: "login"
1024
+ };
1025
+ return this.post("/phone/verify-otp", payload, options);
1026
+ }
1027
+ // --------------------------------------------------------------------------
1028
+ // Password Management
1029
+ // --------------------------------------------------------------------------
1030
+ async forgotPassword(data, options) {
1031
+ return this.post("/forgot-password", data, options);
1032
+ }
1033
+ async resetPassword(data, options) {
1034
+ return this.post("/reset-password", data, options);
1035
+ }
1036
+ async changePassword(data, options) {
1037
+ return this.post("/password/change", data, options);
1038
+ }
1039
+ // --------------------------------------------------------------------------
1040
+ // Email & Phone Management
1041
+ // --------------------------------------------------------------------------
1042
+ async verifyEmail(data, options) {
1043
+ return this.post("/verify-email", data, options);
1044
+ }
1045
+ /** Resend email verification. Alias: resendEmailVerification() */
1046
+ async resendVerification(data, options) {
1047
+ return this.post("/resend-verification", data ?? {}, options);
1048
+ }
1049
+ /** @deprecated Use resendVerification() */
1050
+ async resendEmailVerification(data) {
1051
+ return this.resendVerification(data);
1052
+ }
1053
+ async changeEmail(data, options) {
1054
+ return this.post("/email/change", data, options);
1055
+ }
1056
+ async changePhone(data, options) {
1057
+ const payload = {
1058
+ ...data,
1059
+ new_phone: this.sanitizePhoneField(data.new_phone) ?? ""
1060
+ };
1061
+ return this.post("/phone/change", payload, options);
1062
+ }
1063
+ // --------------------------------------------------------------------------
1064
+ // Account
1065
+ // --------------------------------------------------------------------------
1066
+ async deleteAccount(options) {
1067
+ return this.del("/me", options);
1068
+ }
1069
+ async exportData(options) {
1070
+ return this._get("/me/export", options);
1071
+ }
1072
+ // --------------------------------------------------------------------------
1073
+ // OAuth
1074
+ // --------------------------------------------------------------------------
1075
+ async getOAuthUrl(provider, redirectUri, options) {
1076
+ return this._get(this.withQuery(`/oauth/${provider}/authorize`, { redirect_uri: redirectUri }), options);
1077
+ }
1078
+ async handleOAuthCallback(data, options) {
1079
+ const { provider, ...rest } = data;
1080
+ return this._get(this.withQuery(`/oauth/${provider}/callback`, rest), options);
1081
+ }
1082
+ async listOAuthProviders(options) {
1083
+ return this._get("/oauth/providers", options);
1084
+ }
1085
+ async unlinkOAuthProvider(provider, options) {
1086
+ return this.del(`/oauth/providers/${provider}`, options);
1087
+ }
1088
+ // --------------------------------------------------------------------------
1089
+ // Token Management
1090
+ // --------------------------------------------------------------------------
1091
+ async refreshAccessToken(data, options) {
1092
+ return this.post("/token/refresh", data ?? {}, options);
1093
+ }
1094
+ async revokeRefreshToken(data, options) {
1095
+ return this.post("/token/revoke", data, options);
1096
+ }
1097
+ // --------------------------------------------------------------------------
1098
+ // Flat methods for backward compatibility (delegate to sub-APIs)
1099
+ // --------------------------------------------------------------------------
1100
+ /** @deprecated Use auth.sessions.list() */
1101
+ async listSessions() {
1102
+ return this.sessions.list();
1103
+ }
1104
+ /** @deprecated Use auth.sessions.revoke() */
1105
+ async revokeSession(sessionId) {
1106
+ return this.sessions.revoke(sessionId);
1107
+ }
1108
+ /** @deprecated Use auth.sessions.revokeAll() */
1109
+ async revokeOtherSessions() {
1110
+ return this.sessions.revokeAll();
1111
+ }
1112
+ /** @deprecated Use auth.devices.list() */
1113
+ async listDevices() {
1114
+ return this.devices.list();
1115
+ }
1116
+ /** @deprecated Use auth.devices.trust() */
1117
+ async trustDevice(deviceId) {
1118
+ return this.devices.trust(deviceId);
1119
+ }
1120
+ /** @deprecated Use auth.devices.block() */
1121
+ async blockDevice(deviceId) {
1122
+ return this.devices.block(deviceId);
1123
+ }
1124
+ /** @deprecated Use auth.devices.delete() */
1125
+ async deleteDevice(deviceId) {
1126
+ return this.devices.delete(deviceId);
1127
+ }
1128
+ /** @deprecated Use auth.loginHistory.list() */
1129
+ async getLoginHistory(params) {
1130
+ return this.loginHistory.list(params);
1131
+ }
1132
+ /** @deprecated Use auth.loginHistory.getSummary() */
1133
+ async getLoginActivitySummary() {
1134
+ return this.loginHistory.getSummary();
1135
+ }
1136
+ /** @deprecated Use auth.mfa.getStatus() */
1137
+ async getMfaStatus() {
1138
+ return this.mfa.getStatus();
1139
+ }
1140
+ /** @deprecated Use auth.mfa.setupTotp() */
1141
+ async setupTotp() {
1142
+ return this.mfa.setupTotp();
1143
+ }
1144
+ /** @deprecated Use auth.mfa.verifySetup() */
1145
+ async verifyTotpSetup(data) {
1146
+ return this.mfa.verifySetup(data);
1147
+ }
1148
+ /** @deprecated Use auth.mfa.enableSms() */
1149
+ async enableSmsMfa() {
1150
+ return this.mfa.enableSms();
1151
+ }
1152
+ /** @deprecated Use auth.mfa.enableEmail() */
1153
+ async enableEmailMfa() {
1154
+ return this.mfa.enableEmail();
1155
+ }
1156
+ /** @deprecated Use auth.mfa.disable() */
1157
+ async disableMfa(data) {
1158
+ return this.mfa.disable(data);
1159
+ }
1160
+ /** @deprecated Use auth.mfa.regenerateBackupCodes() */
1161
+ async regenerateBackupCodes() {
1162
+ return this.mfa.regenerateBackupCodes();
1163
+ }
1164
+ /** @deprecated Use auth.mfa.sendCode() */
1165
+ async sendMfaCode(data) {
1166
+ return this.mfa.sendCode(data);
1167
+ }
1168
+ /** @deprecated Use auth.mfa.verify() */
1169
+ async verifyMfa(data) {
1170
+ return this.mfa.verify(data);
1171
+ }
1172
+ };
1173
+
1174
+ // src/services/logger.ts
1175
+ var BATCH_MAX_SIZE = 100;
1176
+ var LoggerService = class extends ServiceModule {
1177
+ constructor() {
1178
+ super(...arguments);
1179
+ this.basePath = "/v1/logger";
1180
+ }
1181
+ /**
1182
+ * Write a single log entry.
1183
+ * Accepts both new schema (LogInput) and legacy shape ({ level, message }) for backward compatibility.
1184
+ */
1185
+ async log(data, options) {
1186
+ const body = this.normalizeLogInput(data);
1187
+ return this.post("/logs", body, options);
1188
+ }
1189
+ /**
1190
+ * Write log entries in batch.
1191
+ * Auto-chunks into groups of 100 (backend hard limit) and sends sequentially.
1192
+ * Returns total ingested count across all chunks.
1193
+ */
1194
+ async logBatch(logs, options) {
1195
+ if (logs.length === 0) {
1196
+ return { data: { ingested: 0 }, error: null };
1197
+ }
1198
+ let totalIngested = 0;
1199
+ for (let i = 0; i < logs.length; i += BATCH_MAX_SIZE) {
1200
+ const chunk = logs.slice(i, i + BATCH_MAX_SIZE);
1201
+ const result = await this.post("/logs/batch", { logs: chunk }, options);
1202
+ if (result.error) {
1203
+ return {
1204
+ data: { ingested: totalIngested },
1205
+ error: result.error
1206
+ };
1207
+ }
1208
+ totalIngested += result.data?.ingested ?? chunk.length;
1209
+ }
1210
+ return { data: { ingested: totalIngested }, error: null };
1211
+ }
1212
+ /**
1213
+ * Query logs with filters. Returns paginated response.
1214
+ */
1215
+ async queryLogs(filters, requestOptions) {
1216
+ return this._get(this.withQuery("/logs", filters), requestOptions);
1217
+ }
1218
+ // Convenience methods
1219
+ async debug(service, message, meta, options) {
1220
+ return this.log({ service, severity: "debug", message, metadata: meta }, options);
1221
+ }
1222
+ async info(service, message, meta, options) {
1223
+ return this.log({ service, severity: "info", message, metadata: meta }, options);
1224
+ }
1225
+ async warn(service, message, meta, options) {
1226
+ return this.log({ service, severity: "warn", message, metadata: meta }, options);
1227
+ }
1228
+ async error(service, message, meta, options) {
1229
+ return this.log({ service, severity: "error", message, metadata: meta }, options);
1230
+ }
1231
+ // --------------------------------------------------------------------------
1232
+ // Internal
1233
+ // --------------------------------------------------------------------------
1234
+ /** Normalize legacy { level, message } to { severity, service, message } */
1235
+ normalizeLogInput(data) {
1236
+ if ("severity" in data && "service" in data) {
1237
+ return data;
1238
+ }
1239
+ const legacy = data;
1240
+ return {
1241
+ service: "sdk",
1242
+ severity: legacy.level || "info",
1243
+ message: legacy.message,
1244
+ metadata: legacy.metadata
1245
+ };
1246
+ }
1247
+ };
1248
+
1249
+ // src/services/upload-telemetry.ts
1250
+ var EVENT_SEVERITY = {
1251
+ "upload.failed": "error",
1252
+ "upload.stalled": "error",
1253
+ "upload.multipart.aborted": "error",
1254
+ "upload.retried": "warn",
1255
+ "upload.aborted": "warn",
1256
+ "upload.multipart.part_failed": "warn",
1257
+ "upload.multipart.url_refreshed": "warn",
1258
+ "upload.compression.skipped": "warn",
1259
+ "upload.started": "info",
1260
+ "upload.completed": "info",
1261
+ "upload.resumed": "info",
1262
+ "upload.multipart.started": "info",
1263
+ "upload.multipart.completed": "info",
1264
+ "upload.compression.completed": "info",
1265
+ "upload.progress": "debug",
1266
+ "upload.multipart.part_completed": "debug",
1267
+ "upload.compression.started": "debug"
1268
+ };
1269
+ var UploadTelemetry = class {
1270
+ constructor(client, config) {
1271
+ this.buffer = [];
1272
+ this.debugLogBuffer = [];
1273
+ this.flushTimer = null;
1274
+ this.flushing = false;
1275
+ this.client = client;
1276
+ this.logger = new LoggerService(client);
1277
+ this.config = {
1278
+ enabled: config?.enabled ?? true,
1279
+ flushIntervalMs: config?.flushIntervalMs ?? 2e3,
1280
+ maxBufferSize: config?.maxBufferSize ?? 50
1281
+ };
1282
+ if (this.config.enabled) {
1283
+ this.startFlushTimer();
1284
+ }
1285
+ }
1286
+ /** Emit a telemetry event. Never throws. */
1287
+ emit(sessionId, event, metadata = {}) {
1288
+ if (!this.config.enabled) return;
1289
+ const payload = {
1290
+ upload_session_id: sessionId,
1291
+ event,
1292
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1293
+ metadata
1294
+ };
1295
+ const severity = EVENT_SEVERITY[event] || "info";
1296
+ if (severity !== "debug") {
1297
+ this.sendToLogger(payload, severity);
1298
+ } else {
1299
+ this.debugLogBuffer.push({
1300
+ service: "storage.upload",
1301
+ severity,
1302
+ message: `Upload ${event}: session=${sessionId}`,
1303
+ metadata: { upload_session_id: sessionId, event, ...metadata },
1304
+ trace_id: sessionId
1305
+ });
1306
+ }
1307
+ this.buffer.push(payload);
1308
+ if (this.buffer.length >= this.config.maxBufferSize) {
1309
+ this.flush();
1310
+ }
1311
+ }
1312
+ /** Flush buffered events immediately. Never throws. */
1313
+ async flush() {
1314
+ if (!this.config.enabled || this.buffer.length === 0 && this.debugLogBuffer.length === 0 || this.flushing) return;
1315
+ this.flushing = true;
1316
+ const batch = this.buffer.splice(0);
1317
+ const debugLogs = this.debugLogBuffer.splice(0);
1318
+ try {
1319
+ if (batch.length > 0) {
1320
+ const events = batch.map((p) => ({
1321
+ event: p.event,
1322
+ properties: {
1323
+ upload_session_id: p.upload_session_id,
1324
+ ...p.metadata
1325
+ },
1326
+ timestamp: p.timestamp
1327
+ }));
1328
+ await this.client.post("/v1/analytics/v2/events/batch", { events }).catch(() => {
1329
+ });
1330
+ }
1331
+ if (debugLogs.length > 0) {
1332
+ await this.logger.logBatch(debugLogs).catch(() => {
1333
+ });
1334
+ }
1335
+ } catch {
1336
+ } finally {
1337
+ this.flushing = false;
1338
+ }
1339
+ }
1340
+ /** Stop the flush timer and drain remaining events. */
1341
+ async destroy() {
1342
+ if (this.flushTimer !== null) {
1343
+ clearInterval(this.flushTimer);
1344
+ this.flushTimer = null;
1345
+ }
1346
+ await this.flush();
1347
+ }
1348
+ startFlushTimer() {
1349
+ if (typeof setInterval !== "undefined") {
1350
+ this.flushTimer = setInterval(() => {
1351
+ this.flush();
1352
+ }, this.config.flushIntervalMs);
1353
+ if (this.flushTimer && typeof this.flushTimer === "object" && "unref" in this.flushTimer) {
1354
+ this.flushTimer.unref();
1355
+ }
1356
+ }
1357
+ }
1358
+ /** Send a log entry to the logger service (fire-and-forget) */
1359
+ sendToLogger(payload, severity) {
1360
+ this.logger.log({
1361
+ service: "storage.upload",
1362
+ severity,
1363
+ message: `Upload ${payload.event}: session=${payload.upload_session_id}`,
1364
+ metadata: {
1365
+ upload_session_id: payload.upload_session_id,
1366
+ event: payload.event,
1367
+ ...payload.metadata
1368
+ },
1369
+ trace_id: payload.upload_session_id
1370
+ }).catch(() => {
1371
+ });
1372
+ }
1373
+ };
1374
+ function generateUploadSessionId() {
1375
+ const timestamp = Date.now().toString(36);
1376
+ const random = Math.random().toString(36).slice(2, 10);
1377
+ return `us_${timestamp}_${random}`;
1378
+ }
1379
+
1380
+ // src/services/storage.ts
1381
+ var RETRY_DELAYS = [0, 1e3, 3e3];
1382
+ var RETRYABLE_STATUS_CODES2 = /* @__PURE__ */ new Set([500, 502, 503, 504]);
1383
+ var NON_RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([400, 403, 404, 413]);
1384
+ var DEFAULT_STALL_TIMEOUT_MS = 45e3;
1385
+ var SLOW_NETWORK_STALL_TIMEOUT_MS = 9e4;
1386
+ var MULTIPART_THRESHOLD = 8 * 1024 * 1024;
1387
+ var MULTIPART_THRESHOLD_SLOW = 4 * 1024 * 1024;
1388
+ var StorageService = class extends ServiceModule {
1389
+ constructor() {
1390
+ super(...arguments);
1391
+ this.basePath = "/v1/storage";
1392
+ this.telemetry = null;
1393
+ }
1394
+ // --------------------------------------------------------------------------
1395
+ // Upload (unified: direct PUT or multipart, transparent to caller)
1396
+ // --------------------------------------------------------------------------
1397
+ /**
1398
+ * Upload a file using the optimal strategy.
1399
+ *
1400
+ * Small files (< 8MB): 3-step presigned URL flow with retry + stall guard.
1401
+ * Large files (>= 8MB): Multipart with windowed presigns, resumable.
1402
+ *
1403
+ * @returns The completed file record with id, url, etc.
1404
+ */
1405
+ async upload(file, options) {
1406
+ if (options?.signal?.aborted) {
1407
+ return { data: null, error: { code: "aborted", message: "Upload aborted", status: 0 } };
1408
+ }
1409
+ const sessionId = generateUploadSessionId();
1410
+ const telemetryEnabled = options?.telemetry !== false;
1411
+ const telemetry = telemetryEnabled ? this.getOrCreateTelemetry() : null;
1412
+ const startTime = Date.now();
1413
+ telemetry?.emit(sessionId, "upload.started", {
1414
+ size_bytes: file.size,
1415
+ content_type: file.type,
1416
+ strategy: this.shouldUseMultipart(file, options) ? "multipart" : "direct",
1417
+ network_type: getNetworkEffectiveType()
1418
+ });
1419
+ try {
1420
+ let uploadFile = file;
1421
+ if (!options?.skipCompression && typeof window !== "undefined") {
1422
+ const compressed = await this.maybeCompress(file, options?.compression, sessionId, telemetry);
1423
+ if (compressed) uploadFile = compressed;
1424
+ }
1425
+ if (this.shouldUseMultipart(uploadFile, options)) {
1426
+ return await this.uploadMultipart(uploadFile, options, sessionId, telemetry);
1427
+ }
1428
+ return await this.uploadDirect(uploadFile, options, sessionId, telemetry);
1429
+ } catch (err) {
1430
+ const message = err instanceof Error ? err.message : "Upload failed";
1431
+ telemetry?.emit(sessionId, "upload.failed", { error: message, duration_ms: Date.now() - startTime });
1432
+ return { data: null, error: { code: "upload_error", message, status: 0 } };
1433
+ }
1434
+ }
1435
+ // --------------------------------------------------------------------------
1436
+ // Direct Upload (3-step with retry + stall)
1437
+ // --------------------------------------------------------------------------
1438
+ async uploadDirect(file, options, sessionId, telemetry) {
1439
+ const directStart = Date.now();
1440
+ const requestOpts = this.withSessionHeader(sessionId, options);
1441
+ const filename = options?.filename || file.name || "file";
1442
+ const initResult = await this.post("/signed-url/upload", {
1443
+ filename,
1444
+ content_type: file.type || "application/octet-stream",
1445
+ size_bytes: file.size,
1446
+ is_public: options?.isPublic ?? true,
1447
+ metadata: options?.metadata
1448
+ }, requestOpts);
1449
+ if (initResult.error) {
1450
+ telemetry?.emit(sessionId, "upload.failed", { step: "presign", error: initResult.error.message });
1451
+ return { data: null, error: initResult.error };
1452
+ }
1453
+ const { file_id, upload_url, completion_token } = initResult.data;
1454
+ const uploadResult = await this.uploadToPresignedUrlWithRetry(
1455
+ upload_url,
1456
+ file,
1457
+ options?.onProgress,
1458
+ options?.signal,
1459
+ sessionId,
1460
+ telemetry
1461
+ );
1462
+ if (uploadResult.error) {
1463
+ if (uploadResult.error.code === "upload_stalled") {
1464
+ telemetry?.emit(sessionId, "upload.stalled", { step: "s3_put", file_id });
1465
+ }
1466
+ telemetry?.emit(sessionId, "upload.failed", {
1467
+ step: "s3_put",
1468
+ error: uploadResult.error.message,
1469
+ file_id,
1470
+ reason: uploadResult.error.code
1471
+ });
1472
+ return { data: null, error: uploadResult.error };
1473
+ }
1474
+ const completeResult = await this.post("/signed-url/complete", {
1475
+ file_id,
1476
+ completion_token
1477
+ }, requestOpts);
1478
+ if (completeResult.error) {
1479
+ telemetry?.emit(sessionId, "upload.failed", { step: "complete", error: completeResult.error.message, file_id, duration_ms: Date.now() - directStart });
1480
+ } else {
1481
+ telemetry?.emit(sessionId, "upload.completed", { file_id, size_bytes: file.size, duration_ms: Date.now() - directStart });
1482
+ }
1483
+ return completeResult;
1484
+ }
1485
+ // --------------------------------------------------------------------------
1486
+ // Multipart Upload
1487
+ // --------------------------------------------------------------------------
1488
+ async uploadMultipart(file, options, sessionId, telemetry) {
1489
+ const multipartStart = Date.now();
1490
+ const requestOpts = this.withSessionHeader(sessionId, options);
1491
+ const filename = options?.filename || file.name || "file";
1492
+ telemetry?.emit(sessionId, "upload.multipart.started", { size_bytes: file.size });
1493
+ let resumeStore = null;
1494
+ let resumeData = null;
1495
+ if (options?.resume !== "off" && typeof window !== "undefined") {
1496
+ try {
1497
+ const { UploadResumeStore: UploadResumeStore2 } = await import("./upload-resume-RXLHBH5E.mjs");
1498
+ resumeStore = new UploadResumeStore2();
1499
+ await resumeStore.open();
1500
+ const resumeKey = await UploadResumeStore2.generateResumeKey(
1501
+ this.client.getApiKey?.() || "",
1502
+ this.client.getUserId?.() || "",
1503
+ filename,
1504
+ file.size,
1505
+ file.lastModified
1506
+ );
1507
+ resumeData = await resumeStore.get(resumeKey);
1508
+ if (resumeData) {
1509
+ telemetry?.emit(sessionId, "upload.resumed", {
1510
+ original_session_id: resumeData.upload_session_id,
1511
+ completed_parts: resumeData.completed_parts.length,
1512
+ total_parts: resumeData.total_parts
1513
+ });
1514
+ }
1515
+ } catch (err) {
1516
+ telemetry?.emit(sessionId, "upload.retried", { step: "resume_load", error: err instanceof Error ? err.message : "Resume store unavailable" });
1517
+ resumeStore = null;
1518
+ resumeData = null;
1519
+ }
1520
+ }
1521
+ let startData;
1522
+ let completionToken;
1523
+ let completedParts = /* @__PURE__ */ new Map();
1524
+ if (resumeData) {
1525
+ startData = {
1526
+ upload_session_id: resumeData.upload_session_id,
1527
+ file_id: resumeData.file_id,
1528
+ completion_token: "",
1529
+ // will request part URLs separately
1530
+ part_size_bytes: resumeData.part_size_bytes || this.defaultChunkSize(file.size),
1531
+ total_parts: resumeData.total_parts,
1532
+ part_urls: [],
1533
+ expires_at: ""
1534
+ };
1535
+ completionToken = resumeData.completion_token;
1536
+ for (const part of resumeData.completed_parts) {
1537
+ completedParts.set(part.part_number, part.etag);
1538
+ }
1539
+ } else {
1540
+ let clientUploadKey;
1541
+ try {
1542
+ const { UploadResumeStore: UploadResumeStore2 } = await import("./upload-resume-RXLHBH5E.mjs");
1543
+ clientUploadKey = await UploadResumeStore2.generateResumeKey(
1544
+ this.client.getApiKey?.() || "",
1545
+ this.client.getUserId?.() || "",
1546
+ filename,
1547
+ file.size,
1548
+ file.lastModified
1549
+ );
1550
+ } catch {
1551
+ }
1552
+ const startResult = await this.post("/signed-url/multipart/start", {
1553
+ filename,
1554
+ content_type: file.type || "application/octet-stream",
1555
+ size_bytes: file.size,
1556
+ is_public: options?.isPublic ?? true,
1557
+ metadata: options?.metadata,
1558
+ chunk_size: options?.chunkSize,
1559
+ ...clientUploadKey ? { client_upload_key: clientUploadKey } : {}
1560
+ }, requestOpts);
1561
+ if (startResult.error) {
1562
+ telemetry?.emit(sessionId, "upload.multipart.aborted", { error: startResult.error.message });
1563
+ return { data: null, error: startResult.error };
1564
+ }
1565
+ startData = startResult.data;
1566
+ completionToken = startData.completion_token;
1567
+ }
1568
+ const { upload_session_id, file_id, part_size_bytes, total_parts } = startData;
1569
+ if (resumeStore && !resumeData) {
1570
+ try {
1571
+ const { UploadResumeStore: UploadResumeStore2 } = await import("./upload-resume-RXLHBH5E.mjs");
1572
+ const resumeKey = await UploadResumeStore2.generateResumeKey(
1573
+ this.client.getApiKey?.() || "",
1574
+ this.client.getUserId?.() || "",
1575
+ filename,
1576
+ file.size,
1577
+ file.lastModified
1578
+ );
1579
+ await resumeStore.save(resumeKey, {
1580
+ upload_session_id,
1581
+ file_id,
1582
+ completion_token: completionToken,
1583
+ total_parts,
1584
+ part_size_bytes,
1585
+ completed_parts: [],
1586
+ created_at: Date.now()
1587
+ });
1588
+ } catch (err) {
1589
+ telemetry?.emit(sessionId, "upload.retried", { step: "resume_save", error: err instanceof Error ? err.message : "Resume save failed" });
1590
+ }
1591
+ }
1592
+ const maxConcurrency = options?.maxConcurrency || this.defaultConcurrency();
1593
+ let availableUrls = /* @__PURE__ */ new Map();
1594
+ if (startData.part_urls.length > 0) {
1595
+ for (const pu of startData.part_urls) {
1596
+ availableUrls.set(pu.part_number, pu);
1597
+ }
1598
+ }
1599
+ const pendingParts = [];
1600
+ for (let i = 1; i <= total_parts; i++) {
1601
+ if (!completedParts.has(i)) {
1602
+ pendingParts.push(i);
1603
+ }
1604
+ }
1605
+ let uploadedCount = completedParts.size;
1606
+ let lastProgressMilestone = 0;
1607
+ const reportProgress = () => {
1608
+ const percent = Math.round(uploadedCount / total_parts * 100);
1609
+ if (options?.onProgress) {
1610
+ options.onProgress(percent);
1611
+ }
1612
+ const milestone = Math.floor(percent / 25) * 25;
1613
+ if (milestone > lastProgressMilestone && milestone < 100) {
1614
+ telemetry?.emit(sessionId, "upload.progress", { percent: milestone, uploaded_parts: uploadedCount, total_parts });
1615
+ lastProgressMilestone = milestone;
1616
+ }
1617
+ };
1618
+ reportProgress();
1619
+ let partIndex = 0;
1620
+ while (partIndex < pendingParts.length) {
1621
+ if (options?.signal?.aborted) {
1622
+ await this.abortMultipart(upload_session_id, completionToken, requestOpts);
1623
+ telemetry?.emit(sessionId, "upload.aborted", { file_id });
1624
+ return { data: null, error: { code: "aborted", message: "Upload aborted", status: 0 } };
1625
+ }
1626
+ const remainingUrlCount = pendingParts.slice(partIndex).filter((p) => availableUrls.has(p)).length;
1627
+ if (remainingUrlCount <= 4) {
1628
+ const neededParts = pendingParts.slice(partIndex, partIndex + 16).filter((p) => !availableUrls.has(p));
1629
+ if (neededParts.length > 0) {
1630
+ const urlResult = await this.post(
1631
+ "/signed-url/multipart/part-urls",
1632
+ { upload_session_id, part_numbers: neededParts, completion_token: completionToken },
1633
+ requestOpts
1634
+ );
1635
+ if (urlResult.data) {
1636
+ for (const pu of urlResult.data.part_urls) {
1637
+ availableUrls.set(pu.part_number, pu);
1638
+ }
1639
+ }
1640
+ }
1641
+ }
1642
+ const batch = pendingParts.slice(partIndex, partIndex + maxConcurrency);
1643
+ const batchPromises = batch.map(async (partNum) => {
1644
+ const partUrl = availableUrls.get(partNum);
1645
+ if (!partUrl) {
1646
+ const urlResult = await this.post(
1647
+ "/signed-url/multipart/part-urls",
1648
+ { upload_session_id, part_numbers: [partNum], completion_token: completionToken },
1649
+ requestOpts
1650
+ );
1651
+ if (!urlResult.data?.part_urls?.[0]) {
1652
+ return { partNum, error: "Failed to get part URL" };
1653
+ }
1654
+ availableUrls.set(partNum, urlResult.data.part_urls[0]);
1655
+ }
1656
+ const url = availableUrls.get(partNum).url;
1657
+ const start = (partNum - 1) * part_size_bytes;
1658
+ const end = Math.min(start + part_size_bytes, file.size);
1659
+ const partBlob = file.slice(start, end);
1660
+ const result = await this.uploadPartWithRetry(url, partBlob, options?.signal, partNum, sessionId, telemetry);
1661
+ if (result.error) {
1662
+ if (result.code === "upload_stalled") {
1663
+ telemetry?.emit(sessionId, "upload.stalled", { step: "multipart_part", part_number: partNum });
1664
+ }
1665
+ telemetry?.emit(sessionId, "upload.multipart.part_failed", {
1666
+ part_number: partNum,
1667
+ error: result.error,
1668
+ code: result.code
1669
+ });
1670
+ if (result.status === 403) {
1671
+ telemetry?.emit(sessionId, "upload.multipart.url_refreshed", { part_number: partNum });
1672
+ const refreshResult = await this.post(
1673
+ "/signed-url/multipart/part-urls",
1674
+ { upload_session_id, part_numbers: [partNum], completion_token: completionToken },
1675
+ requestOpts
1676
+ );
1677
+ if (refreshResult.data?.part_urls?.[0]) {
1678
+ availableUrls.set(partNum, refreshResult.data.part_urls[0]);
1679
+ const retryResult = await this.uploadPartWithRetry(
1680
+ refreshResult.data.part_urls[0].url,
1681
+ partBlob,
1682
+ options?.signal,
1683
+ partNum,
1684
+ sessionId,
1685
+ telemetry
1686
+ );
1687
+ if (retryResult.etag) {
1688
+ return { partNum, etag: retryResult.etag };
1689
+ }
1690
+ }
1691
+ }
1692
+ return { partNum, error: result.error };
1693
+ }
1694
+ return { partNum, etag: result.etag };
1695
+ });
1696
+ const results = await Promise.all(batchPromises);
1697
+ for (const result of results) {
1698
+ if (result.etag) {
1699
+ completedParts.set(result.partNum, result.etag);
1700
+ uploadedCount++;
1701
+ telemetry?.emit(sessionId, "upload.multipart.part_completed", { part_number: result.partNum });
1702
+ if (resumeStore) {
1703
+ try {
1704
+ const { UploadResumeStore: UploadResumeStore2 } = await import("./upload-resume-RXLHBH5E.mjs");
1705
+ const resumeKey = await UploadResumeStore2.generateResumeKey(
1706
+ this.client.getApiKey?.() || "",
1707
+ this.client.getUserId?.() || "",
1708
+ filename,
1709
+ file.size,
1710
+ file.lastModified
1711
+ );
1712
+ await resumeStore.updatePart(resumeKey, result.partNum, result.etag);
1713
+ } catch (err) {
1714
+ telemetry?.emit(sessionId, "upload.retried", { step: "resume_update", part_number: result.partNum, error: err instanceof Error ? err.message : "Resume update failed" });
1715
+ }
1716
+ }
1717
+ } else {
1718
+ const errorMsg = result.error || "Part upload returned no ETag";
1719
+ await this.abortMultipart(upload_session_id, completionToken, requestOpts);
1720
+ telemetry?.emit(sessionId, "upload.multipart.aborted", { file_id, error: errorMsg });
1721
+ return {
1722
+ data: null,
1723
+ error: { code: "upload_error", message: `Part ${result.partNum} failed: ${errorMsg}`, status: 0 }
1724
+ };
1725
+ }
1726
+ }
1727
+ reportProgress();
1728
+ partIndex += batch.length;
1729
+ }
1730
+ const parts = Array.from(completedParts.entries()).sort(([a], [b]) => a - b).map(([part_number, etag]) => ({ part_number, etag }));
1731
+ const completeResult = await this.post(
1732
+ "/signed-url/multipart/complete",
1733
+ { upload_session_id, file_id, completion_token: completionToken, parts },
1734
+ requestOpts
1735
+ );
1736
+ if (resumeStore) {
1737
+ try {
1738
+ const { UploadResumeStore: UploadResumeStore2 } = await import("./upload-resume-RXLHBH5E.mjs");
1739
+ const resumeKey = await UploadResumeStore2.generateResumeKey(
1740
+ this.client.getApiKey?.() || "",
1741
+ this.client.getUserId?.() || "",
1742
+ filename,
1743
+ file.size,
1744
+ file.lastModified
1745
+ );
1746
+ await resumeStore.remove(resumeKey);
1747
+ } catch (err) {
1748
+ telemetry?.emit(sessionId, "upload.retried", { step: "resume_cleanup", error: err instanceof Error ? err.message : "Resume cleanup failed" });
1749
+ }
1750
+ }
1751
+ if (completeResult.error) {
1752
+ telemetry?.emit(sessionId, "upload.multipart.aborted", { file_id, error: completeResult.error.message, duration_ms: Date.now() - multipartStart });
1753
+ return { data: null, error: completeResult.error };
1754
+ }
1755
+ telemetry?.emit(sessionId, "upload.multipart.completed", { file_id, size_bytes: file.size, duration_ms: Date.now() - multipartStart });
1756
+ telemetry?.emit(sessionId, "upload.completed", { file_id, size_bytes: file.size, duration_ms: Date.now() - multipartStart });
1757
+ options?.onProgress?.(100);
1758
+ const d = completeResult.data;
1759
+ return {
1760
+ data: {
1761
+ id: d.file_id,
1762
+ filename: d.filename,
1763
+ content_type: d.content_type,
1764
+ size_bytes: d.size_bytes,
1765
+ url: d.url,
1766
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
1767
+ },
1768
+ error: null
1769
+ };
1770
+ }
1771
+ // --------------------------------------------------------------------------
1772
+ // Multipart Abort
1773
+ // --------------------------------------------------------------------------
1774
+ async abortMultipart(uploadSessionId, completionToken, requestOpts) {
1775
+ try {
1776
+ await this.post("/signed-url/multipart/abort", {
1777
+ upload_session_id: uploadSessionId,
1778
+ ...completionToken ? { completion_token: completionToken } : {}
1779
+ }, requestOpts);
1780
+ } catch {
1781
+ }
1782
+ }
1783
+ // --------------------------------------------------------------------------
1784
+ // Split Upload (server-side: presigned URL → client S3 upload → complete)
1785
+ // --------------------------------------------------------------------------
1786
+ /**
1787
+ * Get a presigned URL for direct upload to S3.
1788
+ * Use this when the browser uploads directly (with progress tracking)
1789
+ * and the server only brokers the URLs.
1790
+ *
1791
+ * Flow: server calls getUploadUrl() → returns URL to client → client PUTs to S3 → server calls completeUpload()
1792
+ */
1793
+ async getUploadUrl(filename, contentType, options, requestOptions) {
1794
+ return this.post("/signed-url/upload", {
1795
+ filename,
1796
+ content_type: contentType,
1797
+ is_public: options?.isPublic ?? true,
1798
+ expires_in: options?.expiresIn ?? 3600,
1799
+ metadata: options?.metadata
1800
+ }, requestOptions);
1801
+ }
1802
+ /**
1803
+ * Complete a presigned upload after the file has been uploaded to S3.
1804
+ * Triggers scan and makes the file available.
1805
+ */
1806
+ async completeUpload(fileId, completionToken, options, requestOptions) {
1807
+ return this.post("/signed-url/complete", {
1808
+ file_id: fileId,
1809
+ completion_token: completionToken,
1810
+ size_bytes: options?.sizeBytes,
1811
+ checksum: options?.checksum
1812
+ }, requestOptions);
1813
+ }
1814
+ // --------------------------------------------------------------------------
1815
+ // Multipart Public API (for advanced/server-side usage)
1816
+ // --------------------------------------------------------------------------
1817
+ /** Start a multipart upload session. */
1818
+ async startMultipartUpload(params, requestOptions) {
1819
+ return this.post("/signed-url/multipart/start", params, requestOptions);
1820
+ }
1821
+ /** Get presigned URLs for specific part numbers. */
1822
+ async getMultipartPartUrls(uploadSessionId, partNumbers, completionToken, requestOptions) {
1823
+ return this.post("/signed-url/multipart/part-urls", {
1824
+ upload_session_id: uploadSessionId,
1825
+ part_numbers: partNumbers,
1826
+ ...completionToken ? { completion_token: completionToken } : {}
1827
+ }, requestOptions);
1828
+ }
1829
+ /** Complete a multipart upload. */
1830
+ async completeMultipartUpload(params, requestOptions) {
1831
+ return this.post("/signed-url/multipart/complete", params, requestOptions);
1832
+ }
1833
+ /** Abort a multipart upload. */
1834
+ async abortMultipartUpload(uploadSessionId, completionToken, requestOptions) {
1835
+ return this.post("/signed-url/multipart/abort", {
1836
+ upload_session_id: uploadSessionId,
1837
+ ...completionToken ? { completion_token: completionToken } : {}
1838
+ }, requestOptions);
1839
+ }
1840
+ // --------------------------------------------------------------------------
1841
+ // File Operations
1842
+ // --------------------------------------------------------------------------
1843
+ /** Get file metadata (no signed URL). */
1844
+ async getInfo(fileId, options) {
1845
+ return this._get(`/files/${fileId}/info`, options);
1846
+ }
1847
+ /**
1848
+ * Get a signed view URL for inline display (img src, thumbnails).
1849
+ * Returns CloudFront signed URL (fast, ~1us) or S3 presigned fallback.
1850
+ */
1851
+ async getViewUrl(fileId, options) {
1852
+ return this.post(`/signed-url/view/${fileId}`, {}, options);
1853
+ }
1854
+ /**
1855
+ * Get signed view URLs for multiple files (batch, up to 100).
1856
+ * Single network call, returns all URLs.
1857
+ * The shared `expires_at` is a conservative lower bound — reflects the shortest-lived
1858
+ * URL in the batch. Individual URLs may remain valid longer if their files are public.
1859
+ */
1860
+ async getViewUrls(fileIds, options) {
1861
+ return this.post("/signed-url/view-batch", { file_ids: fileIds }, options);
1862
+ }
1863
+ /**
1864
+ * Get a signed download URL (Content-Disposition: attachment).
1865
+ */
1866
+ async getDownloadUrl(fileId, options) {
1867
+ return this.post(`/signed-url/download/${fileId}`, void 0, options);
1868
+ }
1869
+ /** Delete a file (soft delete). */
1870
+ async delete(fileId, options) {
1871
+ return this.del(`/files/${fileId}`, options);
1872
+ }
1873
+ /** List the current user's files (paginated). */
1874
+ async list(params, options) {
1875
+ return this.listMethod("/my-files", params, options);
1876
+ }
1877
+ /** Check file view/access status. */
1878
+ async getViewStatus(fileId, options) {
1879
+ return this._get(`/files/${fileId}/view-status`, options);
1880
+ }
1881
+ /**
1882
+ * Update a file's visibility (public/private).
1883
+ * Only the file owner can toggle this. Changes URL TTL — does not move the S3 object.
1884
+ * Public files get 7-day signed URLs; private files get 1-hour signed URLs.
1885
+ */
1886
+ async updateVisibility(fileId, isPublic, options) {
1887
+ return this.patch(`/files/${fileId}/visibility`, { is_public: isPublic }, options);
1888
+ }
1889
+ // --------------------------------------------------------------------------
1890
+ // Legacy methods (backward compat)
1891
+ // --------------------------------------------------------------------------
1892
+ /** @deprecated Use upload() instead */
1893
+ async uploadFile(file, options) {
1894
+ return this.upload(file, {
1895
+ isPublic: options?.is_public,
1896
+ metadata: options?.metadata,
1897
+ onProgress: options?.onProgress,
1898
+ signal: options?.signal
1899
+ });
1900
+ }
1901
+ /** @deprecated Use getInfo() instead */
1902
+ async getFile(id) {
1903
+ return this.getInfo(id);
1904
+ }
1905
+ /** @deprecated Use delete() instead */
1906
+ async deleteFile(id) {
1907
+ return this.delete(id);
1908
+ }
1909
+ /** @deprecated Use list() instead */
1910
+ async listFiles(params) {
1911
+ return this.list(params);
1912
+ }
1913
+ // --------------------------------------------------------------------------
1914
+ // Private: Upload to presigned URL with retry + stall guard
1915
+ // --------------------------------------------------------------------------
1916
+ async uploadToPresignedUrlWithRetry(url, file, onProgress, signal, sessionId, telemetry) {
1917
+ for (let attempt = 0; attempt < RETRY_DELAYS.length; attempt++) {
1918
+ if (signal?.aborted) {
1919
+ return { data: null, error: { code: "aborted", message: "Upload aborted", status: 0 } };
1920
+ }
1921
+ if (attempt > 0) {
1922
+ const delay = RETRY_DELAYS[attempt] ?? 0;
1923
+ if (delay > 0) await sleep2(delay);
1924
+ telemetry?.emit(sessionId || "", "upload.retried", { attempt });
1925
+ }
1926
+ const result = await this.uploadToPresignedUrl(url, file, onProgress, signal);
1927
+ if (!result.error) return result;
1928
+ if (result.error.code === "aborted") return result;
1929
+ if (result.error.status && NON_RETRYABLE_STATUS_CODES.has(result.error.status)) {
1930
+ return result;
1931
+ }
1932
+ const isRetryable = result.error.status === 0 || RETRYABLE_STATUS_CODES2.has(result.error.status);
1933
+ if (!isRetryable || attempt === RETRY_DELAYS.length - 1) {
1934
+ return result;
1935
+ }
1936
+ }
1937
+ return { data: null, error: { code: "upload_error", message: "Upload failed after retries", status: 0 } };
1938
+ }
1939
+ /**
1940
+ * Upload file directly to S3 presigned URL.
1941
+ * Uses XHR for progress tracking in browser, fetch otherwise.
1942
+ * Includes stall detection.
1943
+ */
1944
+ async uploadToPresignedUrl(url, file, onProgress, signal) {
1945
+ if (signal?.aborted) {
1946
+ return { data: null, error: { code: "aborted", message: "Upload aborted", status: 0 } };
1947
+ }
1948
+ if (typeof XMLHttpRequest !== "undefined") {
1949
+ return this.uploadWithXHR(url, file, onProgress, signal);
1950
+ }
1951
+ const stallTimeout = DEFAULT_STALL_TIMEOUT_MS;
1952
+ const controller = new AbortController();
1953
+ let parentSignalCleanup;
1954
+ const combinedSignal = signal ? AbortSignal.any?.([signal, controller.signal]) ?? (() => {
1955
+ const onAbort = () => controller.abort();
1956
+ signal.addEventListener("abort", onAbort, { once: true });
1957
+ parentSignalCleanup = () => signal.removeEventListener("abort", onAbort);
1958
+ return controller.signal;
1959
+ })() : controller.signal;
1960
+ const timer = setTimeout(() => controller.abort(), stallTimeout);
1961
+ try {
1962
+ const response = await fetch(url, {
1963
+ method: "PUT",
1964
+ body: file,
1965
+ headers: {
1966
+ "Content-Type": file.type || "application/octet-stream"
1967
+ },
1968
+ signal: combinedSignal
1969
+ });
1970
+ clearTimeout(timer);
1971
+ parentSignalCleanup?.();
1972
+ if (!response.ok) {
1973
+ return {
1974
+ data: null,
1975
+ error: {
1976
+ code: "upload_error",
1977
+ message: `S3 upload failed: ${response.status} ${response.statusText}`,
1978
+ status: response.status
1979
+ }
1980
+ };
1981
+ }
1982
+ onProgress?.(100);
1983
+ return { data: null, error: null };
1984
+ } catch (err) {
1985
+ clearTimeout(timer);
1986
+ parentSignalCleanup?.();
1987
+ if (signal?.aborted) {
1988
+ return { data: null, error: { code: "aborted", message: "Upload aborted", status: 0 } };
1989
+ }
1990
+ const isStall = controller.signal.aborted && !signal?.aborted;
1991
+ return {
1992
+ data: null,
1993
+ error: {
1994
+ code: isStall ? "upload_stalled" : "upload_error",
1995
+ message: isStall ? `Upload stalled (no progress for ${stallTimeout / 1e3}s)` : err instanceof Error ? err.message : "S3 upload failed",
1996
+ status: 0
1997
+ }
1998
+ };
1999
+ }
2000
+ }
2001
+ uploadWithXHR(url, file, onProgress, signal) {
2002
+ return new Promise((resolve) => {
2003
+ const xhr = new XMLHttpRequest();
2004
+ const stallTimeout = getStallTimeout();
2005
+ let stallTimer = null;
2006
+ const resetStallTimer = () => {
2007
+ if (stallTimer !== null) clearTimeout(stallTimer);
2008
+ stallTimer = setTimeout(() => {
2009
+ xhr.abort();
2010
+ resolve({
2011
+ data: null,
2012
+ error: {
2013
+ code: "upload_stalled",
2014
+ message: `Upload stalled (no progress for ${stallTimeout / 1e3}s)`,
2015
+ status: 0
2016
+ }
2017
+ });
2018
+ }, stallTimeout);
2019
+ };
2020
+ const clearStallTimer = () => {
2021
+ if (stallTimer !== null) {
2022
+ clearTimeout(stallTimer);
2023
+ stallTimer = null;
2024
+ }
2025
+ };
2026
+ if (signal) {
2027
+ if (signal.aborted) {
2028
+ resolve({ data: null, error: { code: "aborted", message: "Upload aborted", status: 0 } });
2029
+ return;
2030
+ }
2031
+ signal.addEventListener("abort", () => {
2032
+ clearStallTimer();
2033
+ xhr.abort();
2034
+ }, { once: true });
2035
+ }
2036
+ xhr.upload.addEventListener("progress", (event) => {
2037
+ resetStallTimer();
2038
+ if (event.lengthComputable && onProgress) {
2039
+ onProgress(Math.round(event.loaded / event.total * 100));
2040
+ }
2041
+ });
2042
+ xhr.addEventListener("load", () => {
2043
+ clearStallTimer();
2044
+ if (xhr.status >= 200 && xhr.status < 300) {
2045
+ onProgress?.(100);
2046
+ resolve({ data: null, error: null });
2047
+ } else {
2048
+ resolve({
2049
+ data: null,
2050
+ error: {
2051
+ code: "upload_error",
2052
+ message: `S3 upload failed: ${xhr.status}`,
2053
+ status: xhr.status
2054
+ }
2055
+ });
2056
+ }
2057
+ });
2058
+ xhr.addEventListener("error", () => {
2059
+ clearStallTimer();
2060
+ resolve({
2061
+ data: null,
2062
+ error: { code: "upload_error", message: "S3 upload failed", status: 0 }
2063
+ });
2064
+ });
2065
+ xhr.addEventListener("abort", () => {
2066
+ clearStallTimer();
2067
+ if (!signal?.aborted) return;
2068
+ resolve({
2069
+ data: null,
2070
+ error: { code: "aborted", message: "Upload aborted", status: 0 }
2071
+ });
2072
+ });
2073
+ xhr.open("PUT", url);
2074
+ xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
2075
+ xhr.send(file);
2076
+ resetStallTimer();
2077
+ });
2078
+ }
2079
+ // --------------------------------------------------------------------------
2080
+ // Private: Part upload with retry
2081
+ // --------------------------------------------------------------------------
2082
+ async uploadPartWithRetry(url, blob, signal, partNumber, sessionId, telemetry) {
2083
+ for (let attempt = 0; attempt < RETRY_DELAYS.length; attempt++) {
2084
+ if (signal?.aborted) return { error: "Upload aborted", code: "aborted" };
2085
+ if (attempt > 0) {
2086
+ const delay = RETRY_DELAYS[attempt] ?? 0;
2087
+ if (delay > 0) await sleep2(delay);
2088
+ telemetry?.emit(sessionId || "", "upload.retried", { attempt, part_number: partNumber });
2089
+ }
2090
+ const controller = new AbortController();
2091
+ let partSignalCleanup;
2092
+ const combinedSignal = signal ? AbortSignal.any?.([signal, controller.signal]) ?? (() => {
2093
+ const onAbort = () => controller.abort();
2094
+ signal.addEventListener("abort", onAbort, { once: true });
2095
+ partSignalCleanup = () => signal.removeEventListener("abort", onAbort);
2096
+ return controller.signal;
2097
+ })() : controller.signal;
2098
+ const timer = setTimeout(() => controller.abort(), DEFAULT_STALL_TIMEOUT_MS);
2099
+ try {
2100
+ const response = await fetch(url, {
2101
+ method: "PUT",
2102
+ body: blob,
2103
+ signal: combinedSignal
2104
+ });
2105
+ clearTimeout(timer);
2106
+ partSignalCleanup?.();
2107
+ if (response.ok) {
2108
+ const etag = response.headers.get("etag");
2109
+ if (!etag) {
2110
+ if (attempt === RETRY_DELAYS.length - 1) {
2111
+ return { error: "Part upload succeeded but ETag missing \u2014 cannot verify integrity", code: "s3_error" };
2112
+ }
2113
+ continue;
2114
+ }
2115
+ return { etag };
2116
+ }
2117
+ if (NON_RETRYABLE_STATUS_CODES.has(response.status)) {
2118
+ return { error: `Part upload failed: ${response.status}`, status: response.status, code: "s3_error" };
2119
+ }
2120
+ if (attempt === RETRY_DELAYS.length - 1) {
2121
+ return { error: `Part upload failed after retries: ${response.status}`, status: response.status, code: "s3_error" };
2122
+ }
2123
+ } catch (err) {
2124
+ clearTimeout(timer);
2125
+ partSignalCleanup?.();
2126
+ if (signal?.aborted) return { error: "Upload aborted", code: "aborted" };
2127
+ const isStall = controller.signal.aborted && !signal?.aborted;
2128
+ if (attempt === RETRY_DELAYS.length - 1) {
2129
+ return {
2130
+ error: isStall ? `Part upload stalled (no progress for ${DEFAULT_STALL_TIMEOUT_MS / 1e3}s)` : err instanceof Error ? err.message : "Part upload failed",
2131
+ code: isStall ? "upload_stalled" : "network_error"
2132
+ };
2133
+ }
2134
+ }
2135
+ }
2136
+ return { error: "Part upload failed after retries", code: "network_error" };
2137
+ }
2138
+ // --------------------------------------------------------------------------
2139
+ // Private: Helpers
2140
+ // --------------------------------------------------------------------------
2141
+ shouldUseMultipart(file, options) {
2142
+ if (options?.forceMultipart) return true;
2143
+ const threshold = isSlowNetwork() ? MULTIPART_THRESHOLD_SLOW : MULTIPART_THRESHOLD;
2144
+ return file.size >= threshold;
2145
+ }
2146
+ defaultChunkSize(fileSize) {
2147
+ if (fileSize > 512 * 1024 * 1024) return 16 * 1024 * 1024;
2148
+ const effectiveType = getNetworkEffectiveType();
2149
+ if (effectiveType === "slow-2g" || effectiveType === "2g") return 5 * 1024 * 1024;
2150
+ if (effectiveType === "3g") return 5 * 1024 * 1024;
2151
+ return 8 * 1024 * 1024;
2152
+ }
2153
+ defaultConcurrency() {
2154
+ const effectiveType = getNetworkEffectiveType();
2155
+ if (effectiveType === "slow-2g" || effectiveType === "2g") return 1;
2156
+ if (effectiveType === "3g") return 2;
2157
+ return 4;
2158
+ }
2159
+ async maybeCompress(file, config, sessionId, telemetry) {
2160
+ try {
2161
+ const { maybeCompressImage } = await import("./upload-compression-CWKEDQYS.mjs");
2162
+ return await maybeCompressImage(file, config, sessionId, telemetry);
2163
+ } catch {
2164
+ return null;
2165
+ }
2166
+ }
2167
+ getOrCreateTelemetry() {
2168
+ if (!this.telemetry) {
2169
+ this.telemetry = new UploadTelemetry(this.client);
2170
+ }
2171
+ return this.telemetry;
2172
+ }
2173
+ /** Build RequestOptions with X-Upload-Session-Id header for cross-boundary correlation */
2174
+ withSessionHeader(sessionId, options) {
2175
+ const headers = { "X-Upload-Session-Id": sessionId };
2176
+ if (options?.clientContext) {
2177
+ return { clientContext: options.clientContext, headers };
2178
+ }
2179
+ return { headers };
2180
+ }
2181
+ /**
2182
+ * Use ServiceModule's list method but with a cleaner name internally
2183
+ * (can't call protected `list` from public method with same name).
2184
+ */
2185
+ listMethod(path, params, options) {
2186
+ return super._list(path, params, options);
2187
+ }
2188
+ };
2189
+ function sleep2(ms) {
2190
+ return new Promise((resolve) => setTimeout(resolve, ms));
2191
+ }
2192
+ function getStallTimeout() {
2193
+ return isSlowNetwork() ? SLOW_NETWORK_STALL_TIMEOUT_MS : DEFAULT_STALL_TIMEOUT_MS;
2194
+ }
2195
+ function isSlowNetwork() {
2196
+ const effectiveType = getNetworkEffectiveType();
2197
+ return effectiveType === "slow-2g" || effectiveType === "2g" || effectiveType === "3g";
2198
+ }
2199
+ function getNetworkEffectiveType() {
2200
+ if (typeof navigator !== "undefined" && "connection" in navigator) {
2201
+ const conn = navigator.connection;
2202
+ return conn?.effectiveType || "4g";
2203
+ }
2204
+ return "4g";
2205
+ }
2206
+
2207
+ // src/services/upload-strategy.ts
2208
+ var MULTIPART_THRESHOLD2 = 8 * 1024 * 1024;
2209
+ var MULTIPART_THRESHOLD_SLOW2 = 4 * 1024 * 1024;
2210
+ var DEFAULT_STALL_TIMEOUT_MS2 = 45e3;
2211
+ var SLOW_STALL_TIMEOUT_MS = 9e4;
2212
+ var CHUNK_SIZES = {
2213
+ "slow-2g": 5 * 1024 * 1024,
2214
+ "2g": 5 * 1024 * 1024,
2215
+ "3g": 5 * 1024 * 1024,
2216
+ "4g": 8 * 1024 * 1024,
2217
+ unknown: 8 * 1024 * 1024
2218
+ };
2219
+ var LARGE_FILE_CHUNK_SIZE = 16 * 1024 * 1024;
2220
+ var CONCURRENCY = {
2221
+ "slow-2g": 1,
2222
+ "2g": 1,
2223
+ "3g": 2,
2224
+ "4g": 4,
2225
+ unknown: 4
2226
+ };
2227
+ function resolveStrategy(fileSize, overrides) {
2228
+ const network = detectNetworkClass();
2229
+ const isSlowNetwork2 = network === "slow-2g" || network === "2g" || network === "3g";
2230
+ const threshold = isSlowNetwork2 ? MULTIPART_THRESHOLD_SLOW2 : MULTIPART_THRESHOLD2;
2231
+ const strategy = overrides?.forceMultipart || fileSize >= threshold ? "multipart" : "direct";
2232
+ const chunkSize = overrides?.chunkSize || (fileSize > 512 * 1024 * 1024 ? LARGE_FILE_CHUNK_SIZE : CHUNK_SIZES[network]);
2233
+ const concurrency = overrides?.concurrency || adaptConcurrency(network);
2234
+ const stallTimeoutMs = isSlowNetwork2 ? SLOW_STALL_TIMEOUT_MS : DEFAULT_STALL_TIMEOUT_MS2;
2235
+ return { strategy, chunkSize, concurrency, stallTimeoutMs };
2236
+ }
2237
+ function detectNetworkClass() {
2238
+ if (typeof navigator === "undefined") return "unknown";
2239
+ const conn = navigator.connection;
2240
+ if (!conn) return "unknown";
2241
+ const effectiveType = conn.effectiveType;
2242
+ if (effectiveType === "slow-2g") return "slow-2g";
2243
+ if (effectiveType === "2g") return "2g";
2244
+ if (effectiveType === "3g") return "3g";
2245
+ if (effectiveType === "4g") return "4g";
2246
+ return "unknown";
2247
+ }
2248
+ function getMeasuredBandwidthMbps() {
2249
+ if (typeof navigator === "undefined") return null;
2250
+ const conn = navigator.connection;
2251
+ return conn?.downlink ?? null;
2252
+ }
2253
+ function adaptConcurrency(network) {
2254
+ const bandwidth = getMeasuredBandwidthMbps();
2255
+ if (bandwidth === null) return CONCURRENCY[network];
2256
+ if (bandwidth < 0.5) return 1;
2257
+ if (bandwidth < 2) return 2;
2258
+ if (bandwidth < 10) return 3;
2259
+ return 5;
2260
+ }
2261
+
2262
+ // src/services/upload-engine.ts
2263
+ var DEFAULT_CONFIG = {
2264
+ multipartEnabled: true,
2265
+ multipartAllowlist: [],
2266
+ telemetry: {}
2267
+ };
2268
+ function createUploadPlan(fileSize, contentType, options = {}, engineConfig = {}) {
2269
+ const config = { ...DEFAULT_CONFIG, ...engineConfig };
2270
+ const multipartAllowed = config.multipartEnabled && (config.multipartAllowlist.length === 0 || options.appId != null && config.multipartAllowlist.includes(options.appId));
2271
+ const resolved = resolveStrategy(fileSize, {
2272
+ forceMultipart: multipartAllowed && options.forceMultipart,
2273
+ chunkSize: options.chunkSize,
2274
+ concurrency: options.maxConcurrency
2275
+ });
2276
+ const strategy = resolved.strategy === "multipart" && !multipartAllowed ? "direct" : resolved.strategy;
2277
+ const isBrowser = typeof window !== "undefined";
2278
+ const isCompressibleType = contentType.startsWith("image/") && !contentType.includes("gif") && !contentType.includes("svg") && !contentType.includes("webp") && !contentType.includes("avif");
2279
+ const shouldCompress = isBrowser && !options.skipCompression && isCompressibleType && fileSize >= 100 * 1024;
2280
+ const shouldResume = isBrowser && strategy === "multipart" && options.resume !== "off";
2281
+ const totalParts = strategy === "multipart" ? Math.ceil(fileSize / resolved.chunkSize) : 1;
2282
+ return {
2283
+ strategy,
2284
+ chunkSize: resolved.chunkSize,
2285
+ concurrency: resolved.concurrency,
2286
+ stallTimeoutMs: resolved.stallTimeoutMs,
2287
+ shouldCompress,
2288
+ shouldResume,
2289
+ totalParts
2290
+ };
2291
+ }
2292
+ function calculateTotalParts(fileSize, chunkSize) {
2293
+ return Math.ceil(fileSize / chunkSize);
2294
+ }
2295
+ function getPartRange(partNumber, chunkSize, totalSize) {
2296
+ const start = (partNumber - 1) * chunkSize;
2297
+ const end = Math.min(start + chunkSize, totalSize);
2298
+ return { start, end, size: end - start };
2299
+ }
2300
+
2301
+ // src/services/realtime.ts
2302
+ var DEFAULT_RECONNECT_BASE_MS = 1e3;
2303
+ var MAX_RECONNECT_MS = 3e4;
2304
+ var HEARTBEAT_INTERVAL_MS = 3e4;
2305
+ var RealtimeService = class extends ServiceModule {
2306
+ constructor() {
2307
+ super(...arguments);
2308
+ this.basePath = "/v1/realtime";
2309
+ this.ws = null;
2310
+ this.subscriptions = /* @__PURE__ */ new Map();
2311
+ this.presenceCallbacks = /* @__PURE__ */ new Map();
2312
+ this.statusCallbacks = /* @__PURE__ */ new Set();
2313
+ this._status = "disconnected";
2314
+ this.reconnectAttempt = 0;
2315
+ this.reconnectTimer = null;
2316
+ this.heartbeatTimer = null;
2317
+ this.authenticated = false;
2318
+ }
2319
+ /** Current connection status */
2320
+ get status() {
2321
+ return this._status;
2322
+ }
2323
+ // --------------------------------------------------------------------------
2324
+ // Subscribe / Unsubscribe
2325
+ // --------------------------------------------------------------------------
2326
+ /**
2327
+ * Subscribe to a channel. Connects WebSocket on first call.
2328
+ * Returns an unsubscribe function.
2329
+ */
2330
+ subscribe(channel, callback) {
2331
+ if (!this.subscriptions.has(channel)) {
2332
+ this.subscriptions.set(channel, /* @__PURE__ */ new Set());
2333
+ }
2334
+ this.subscriptions.get(channel).add(callback);
2335
+ if (this._status === "disconnected") {
2336
+ this.connect();
2337
+ } else if (this.authenticated) {
2338
+ this.sendWs({ type: "subscribe", channel });
2339
+ }
2340
+ return () => {
2341
+ const subs = this.subscriptions.get(channel);
2342
+ if (subs) {
2343
+ subs.delete(callback);
2344
+ if (subs.size === 0) {
2345
+ this.subscriptions.delete(channel);
2346
+ if (this.ws?.readyState === WebSocket.OPEN) {
2347
+ this.sendWs({ type: "unsubscribe", channel });
2348
+ }
2349
+ }
2350
+ }
2351
+ };
2352
+ }
2353
+ // --------------------------------------------------------------------------
2354
+ // Publish
2355
+ // --------------------------------------------------------------------------
2356
+ /** Publish data to a channel via WebSocket. */
2357
+ publish(channel, data) {
2358
+ if (this._status !== "connected" || !this.authenticated) {
2359
+ throw new Error("Cannot publish: not connected");
2360
+ }
2361
+ this.sendWs({ type: "publish", channel, data });
2362
+ }
2363
+ // --------------------------------------------------------------------------
2364
+ // Presence
2365
+ // --------------------------------------------------------------------------
2366
+ /** Join a presence channel with optional user data. */
2367
+ joinPresence(channel, userData) {
2368
+ if (this._status !== "connected") {
2369
+ throw new Error("Cannot join presence: not connected");
2370
+ }
2371
+ this.sendWs({ type: "presence_join", channel, user_data: userData });
2372
+ }
2373
+ /** Leave a presence channel. */
2374
+ leavePresence(channel) {
2375
+ if (this.ws?.readyState === WebSocket.OPEN) {
2376
+ this.sendWs({ type: "presence_leave", channel });
2377
+ }
2378
+ }
2379
+ /** Listen for presence events on a channel. Returns unsubscribe function. */
2380
+ onPresence(channel, callback) {
2381
+ if (!this.presenceCallbacks.has(channel)) {
2382
+ this.presenceCallbacks.set(channel, /* @__PURE__ */ new Set());
2383
+ }
2384
+ this.presenceCallbacks.get(channel).add(callback);
2385
+ return () => {
2386
+ const cbs = this.presenceCallbacks.get(channel);
2387
+ if (cbs) {
2388
+ cbs.delete(callback);
2389
+ if (cbs.size === 0) this.presenceCallbacks.delete(channel);
2390
+ }
2391
+ };
2392
+ }
2393
+ // --------------------------------------------------------------------------
2394
+ // Server-side broadcast (HTTP endpoints)
2395
+ // --------------------------------------------------------------------------
2396
+ /** Broadcast to all connections for this application. */
2397
+ async broadcast(event, data, options) {
2398
+ return this.post("/broadcast", { event, data }, options);
2399
+ }
2400
+ /** Broadcast to a specific channel. */
2401
+ async broadcastToChannel(channel, event, data, options) {
2402
+ return this.post(`/broadcast/channel/${channel}`, { event, data }, options);
2403
+ }
2404
+ /** Send to a specific user's connections. */
2405
+ async sendToUser(userId, event, data, options) {
2406
+ return this.post(`/broadcast/user/${userId}`, { event, data }, options);
2407
+ }
2408
+ // --------------------------------------------------------------------------
2409
+ // Connection Lifecycle
2410
+ // --------------------------------------------------------------------------
2411
+ /** Listen for connection status changes. */
2412
+ onStatusChange(callback) {
2413
+ this.statusCallbacks.add(callback);
2414
+ return () => {
2415
+ this.statusCallbacks.delete(callback);
2416
+ };
2417
+ }
2418
+ /** Disconnect and clean up all subscriptions. */
2419
+ disconnect() {
2420
+ this.clearTimers();
2421
+ this.subscriptions.clear();
2422
+ this.presenceCallbacks.clear();
2423
+ this.statusCallbacks.clear();
2424
+ this.authenticated = false;
2425
+ this.reconnectAttempt = 0;
2426
+ if (this.ws) {
2427
+ this.ws.onclose = null;
2428
+ this.ws.close();
2429
+ this.ws = null;
2430
+ }
2431
+ this.setStatus("disconnected");
2432
+ }
2433
+ // --------------------------------------------------------------------------
2434
+ // Private: WebSocket management
2435
+ // --------------------------------------------------------------------------
2436
+ connect() {
2437
+ if (this._status === "connecting" || this._status === "connected") return;
2438
+ const baseUrl = this.client.getBaseUrl();
2439
+ const wsUrl = baseUrl.replace(/^http/, "ws") + "/v1/realtime/ws";
2440
+ this.setStatus(this.reconnectAttempt > 0 ? "reconnecting" : "connecting");
2441
+ try {
2442
+ this.ws = new WebSocket(wsUrl);
2443
+ } catch {
2444
+ this.scheduleReconnect();
2445
+ return;
2446
+ }
2447
+ this.ws.onopen = () => {
2448
+ this.reconnectAttempt = 0;
2449
+ this.authenticate();
2450
+ this.startHeartbeat();
2451
+ };
2452
+ this.ws.onmessage = (event) => {
2453
+ try {
2454
+ const msg = JSON.parse(event.data);
2455
+ this.handleMessage(msg);
2456
+ } catch {
2457
+ }
2458
+ };
2459
+ this.ws.onclose = () => {
2460
+ this.authenticated = false;
2461
+ this.clearHeartbeat();
2462
+ this.scheduleReconnect();
2463
+ };
2464
+ this.ws.onerror = () => {
2465
+ };
2466
+ }
2467
+ authenticate() {
2468
+ const token = this.client.getSessionToken();
2469
+ this.sendWs({
2470
+ type: "auth",
2471
+ token: token || void 0
2472
+ });
2473
+ }
2474
+ handleMessage(msg) {
2475
+ switch (msg.type) {
2476
+ case "auth_success":
2477
+ this.authenticated = true;
2478
+ this.setStatus("connected");
2479
+ for (const channel of this.subscriptions.keys()) {
2480
+ this.sendWs({ type: "subscribe", channel });
2481
+ }
2482
+ break;
2483
+ case "subscribed":
2484
+ break;
2485
+ case "message":
2486
+ this.dispatchMessage(msg.channel, msg.data);
2487
+ break;
2488
+ case "error":
2489
+ break;
2490
+ case "presence_state":
2491
+ this.dispatchPresence({
2492
+ type: "state",
2493
+ channel: msg.channel,
2494
+ members: msg.members
2495
+ });
2496
+ break;
2497
+ case "presence_join":
2498
+ this.dispatchPresence({
2499
+ type: "join",
2500
+ channel: msg.channel,
2501
+ user: msg.user
2502
+ });
2503
+ break;
2504
+ case "presence_leave":
2505
+ this.dispatchPresence({
2506
+ type: "leave",
2507
+ channel: msg.channel,
2508
+ user_id: msg.user_id
2509
+ });
2510
+ break;
2511
+ }
2512
+ }
2513
+ dispatchMessage(channel, data) {
2514
+ const subs = this.subscriptions.get(channel);
2515
+ if (subs) {
2516
+ for (const cb of subs) {
2517
+ try {
2518
+ cb(data, channel);
2519
+ } catch {
2520
+ }
2521
+ }
2522
+ }
2523
+ }
2524
+ dispatchPresence(event) {
2525
+ const cbs = this.presenceCallbacks.get(event.channel);
2526
+ if (cbs) {
2527
+ for (const cb of cbs) {
2528
+ try {
2529
+ cb(event);
2530
+ } catch {
2531
+ }
2532
+ }
2533
+ }
2534
+ }
2535
+ sendWs(data) {
2536
+ if (this.ws?.readyState === WebSocket.OPEN) {
2537
+ this.ws.send(JSON.stringify(data));
2538
+ }
2539
+ }
2540
+ // --------------------------------------------------------------------------
2541
+ // Private: Reconnection
2542
+ // --------------------------------------------------------------------------
2543
+ scheduleReconnect() {
2544
+ if (this.subscriptions.size === 0 && this.presenceCallbacks.size === 0) {
2545
+ this.setStatus("disconnected");
2546
+ return;
2547
+ }
2548
+ this.setStatus("reconnecting");
2549
+ const delay = this.getReconnectDelay();
2550
+ this.reconnectTimer = setTimeout(() => {
2551
+ this.reconnectAttempt++;
2552
+ this.connect();
2553
+ }, delay);
2554
+ }
2555
+ getReconnectDelay() {
2556
+ const exponential = DEFAULT_RECONNECT_BASE_MS * Math.pow(2, this.reconnectAttempt);
2557
+ const jitter = Math.random() * 0.3 * exponential;
2558
+ return Math.min(exponential + jitter, MAX_RECONNECT_MS);
2559
+ }
2560
+ // --------------------------------------------------------------------------
2561
+ // Private: Heartbeat
2562
+ // --------------------------------------------------------------------------
2563
+ startHeartbeat() {
2564
+ this.clearHeartbeat();
2565
+ this.heartbeatTimer = setInterval(() => {
2566
+ if (this.ws?.readyState === WebSocket.OPEN) {
2567
+ this.ws.send("ping");
2568
+ }
2569
+ }, HEARTBEAT_INTERVAL_MS);
2570
+ }
2571
+ clearHeartbeat() {
2572
+ if (this.heartbeatTimer) {
2573
+ clearInterval(this.heartbeatTimer);
2574
+ this.heartbeatTimer = null;
2575
+ }
2576
+ }
2577
+ clearTimers() {
2578
+ this.clearHeartbeat();
2579
+ if (this.reconnectTimer) {
2580
+ clearTimeout(this.reconnectTimer);
2581
+ this.reconnectTimer = null;
2582
+ }
2583
+ }
2584
+ // --------------------------------------------------------------------------
2585
+ // Private: Status
2586
+ // --------------------------------------------------------------------------
2587
+ setStatus(status) {
2588
+ if (this._status === status) return;
2589
+ this._status = status;
2590
+ for (const cb of this.statusCallbacks) {
2591
+ try {
2592
+ cb(status);
2593
+ } catch {
2594
+ }
2595
+ }
2596
+ }
2597
+ };
2598
+
2599
+ // src/services/video.ts
2600
+ var DEFAULT_CHUNK_SIZE = 5 * 1024 * 1024;
2601
+ var MIN_CHUNK_SIZE = 5 * 1024 * 1024;
2602
+ var VideoService = class extends ServiceModule {
2603
+ constructor() {
2604
+ super(...arguments);
2605
+ this.basePath = "/v1/videos";
2606
+ }
2607
+ // --------------------------------------------------------------------------
2608
+ // Upload (3-step chunked flow)
2609
+ // --------------------------------------------------------------------------
2610
+ /**
2611
+ * Upload a video file using chunked multipart upload.
2612
+ *
2613
+ * Internally:
2614
+ * 1. Starts a multipart upload session
2615
+ * 2. Uploads chunks sequentially with progress tracking
2616
+ * 3. Completes the upload, triggering transcoding
2617
+ *
2618
+ * @returns The video record with id, status, etc.
2619
+ */
2620
+ async upload(file, options, requestOptions) {
2621
+ if (options?.signal?.aborted) {
2622
+ return { data: null, error: { code: "aborted", message: "Upload aborted", status: 0 } };
2623
+ }
2624
+ const chunkSize = Math.max(options?.chunkSize ?? DEFAULT_CHUNK_SIZE, MIN_CHUNK_SIZE);
2625
+ const totalChunks = Math.ceil(file.size / chunkSize);
2626
+ const filename = options?.filename || file.name || "video";
2627
+ const startResult = await this.post("/upload-start", {
2628
+ filename,
2629
+ content_type: file.type || "video/mp4",
2630
+ size_bytes: file.size,
2631
+ title: options?.title,
2632
+ description: options?.description,
2633
+ metadata: options?.metadata
2634
+ }, requestOptions);
2635
+ if (startResult.error) return { data: null, error: startResult.error };
2636
+ const { video_id, upload_id } = startResult.data;
2637
+ const parts = [];
2638
+ for (let i = 0; i < totalChunks; i++) {
2639
+ if (options?.signal?.aborted) {
2640
+ return { data: null, error: { code: "aborted", message: "Upload aborted", status: 0 } };
2641
+ }
2642
+ const start = i * chunkSize;
2643
+ const end = Math.min(start + chunkSize, file.size);
2644
+ const chunk = file.slice(start, end);
2645
+ const partNumber = i + 1;
2646
+ const partResult = await this.uploadPart(
2647
+ video_id,
2648
+ upload_id,
2649
+ partNumber,
2650
+ chunk,
2651
+ options?.signal
2652
+ );
2653
+ if (partResult.error) return { data: null, error: partResult.error };
2654
+ parts.push({
2655
+ part_number: partNumber,
2656
+ etag: partResult.data.etag
2657
+ });
2658
+ if (options?.onProgress) {
2659
+ const progress = Math.round(partNumber / totalChunks * 100);
2660
+ options.onProgress(progress);
2661
+ }
2662
+ }
2663
+ const completeResult = await this.post(`/${video_id}/upload-complete`, {
2664
+ upload_id,
2665
+ parts
2666
+ }, requestOptions);
2667
+ return completeResult;
2668
+ }
2669
+ // --------------------------------------------------------------------------
2670
+ // Video Operations
2671
+ // --------------------------------------------------------------------------
2672
+ /** Get video metadata and status. */
2673
+ async get(videoId, options) {
2674
+ return super._get(`/${videoId}`, options);
2675
+ }
2676
+ /**
2677
+ * Get the HLS master playlist URL for streaming.
2678
+ * Returns the playlist URL that can be passed to a video player.
2679
+ */
2680
+ async getStreamUrl(videoId) {
2681
+ const baseUrl = this.client.getBaseUrl();
2682
+ return {
2683
+ data: { url: `${baseUrl}${this.basePath}/${videoId}/playlist.m3u8` },
2684
+ error: null
2685
+ };
2686
+ }
2687
+ /**
2688
+ * Track a playback event (view, play, pause, seek, complete, etc.).
2689
+ */
2690
+ async trackPlayback(videoId, event, options) {
2691
+ return this.post(`/${videoId}/track`, event, options);
2692
+ }
2693
+ /** Get video analytics (views, watch time, etc.). */
2694
+ async getAnalytics(videoId, options) {
2695
+ return super._get(`/${videoId}/analytics`, options);
2696
+ }
2697
+ /**
2698
+ * Update a video's access mode (public/private).
2699
+ * Public videos get 7-day signed URLs; private get 1-hour signed URLs.
2700
+ */
2701
+ async updateAccessMode(videoId, accessMode, options) {
2702
+ return this.patch(`/${videoId}`, { access_mode: accessMode }, options);
2703
+ }
2704
+ // --------------------------------------------------------------------------
2705
+ // Legacy methods (backward compat)
2706
+ // --------------------------------------------------------------------------
2707
+ /** @deprecated Use upload() instead */
2708
+ async uploadVideo(file, options) {
2709
+ return this.upload(file, {
2710
+ metadata: options?.metadata,
2711
+ onProgress: options?.onProgress,
2712
+ signal: options?.signal
2713
+ });
2714
+ }
2715
+ /** @deprecated Use get() instead */
2716
+ async getVideo(id) {
2717
+ return this.get(id);
2718
+ }
2719
+ // --------------------------------------------------------------------------
2720
+ // Private: Chunk upload
2721
+ // --------------------------------------------------------------------------
2722
+ async uploadPart(videoId, uploadId, partNumber, chunk, signal) {
2723
+ const formData = new FormData();
2724
+ formData.append("file", chunk);
2725
+ const path = `${this.basePath}/${videoId}/upload-part?upload_id=${encodeURIComponent(uploadId)}&part_number=${partNumber}`;
2726
+ const headers = {
2727
+ "x-api-key": this.client.getApiKey()
2728
+ };
2729
+ const token = this.client.getSessionToken();
2730
+ if (token) headers["Authorization"] = `Bearer ${token}`;
2731
+ try {
2732
+ const response = await fetch(`${this.client.getBaseUrl()}${path}`, {
2733
+ method: "POST",
2734
+ headers,
2735
+ body: formData,
2736
+ signal
2737
+ });
2738
+ const data = await response.json();
2739
+ if (!response.ok) {
2740
+ return {
2741
+ data: null,
2742
+ error: {
2743
+ code: data?.error?.code || "upload_error",
2744
+ message: data?.error?.message || data?.message || "Part upload failed",
2745
+ status: response.status
2746
+ }
2747
+ };
2748
+ }
2749
+ const result = data?.data !== void 0 ? data.data : data;
2750
+ return { data: result, error: null };
2751
+ } catch (err) {
2752
+ if (signal?.aborted) {
2753
+ return { data: null, error: { code: "aborted", message: "Upload aborted", status: 0 } };
2754
+ }
2755
+ return {
2756
+ data: null,
2757
+ error: {
2758
+ code: "upload_error",
2759
+ message: err instanceof Error ? err.message : "Part upload failed",
2760
+ status: 0
2761
+ }
2762
+ };
2763
+ }
2764
+ }
2765
+ };
2766
+
2767
+ // src/services/data.ts
2768
+ var DataService = class extends ServiceModule {
2769
+ constructor() {
2770
+ super(...arguments);
2771
+ this.basePath = "/v1/data";
2772
+ }
2773
+ // --------------------------------------------------------------------------
2774
+ // Collections
2775
+ // --------------------------------------------------------------------------
2776
+ async createCollection(name, schema, options) {
2777
+ return this.post("/collections", { name, schema }, options);
2778
+ }
2779
+ async listCollections(options) {
2780
+ return this._get("/collections", options);
2781
+ }
2782
+ async deleteCollection(name, options) {
2783
+ return this.del(`/collections/${name}`, options);
2784
+ }
2785
+ // --------------------------------------------------------------------------
2786
+ // Documents — CRUD
2787
+ // --------------------------------------------------------------------------
2788
+ async create(collection, data, options) {
2789
+ return this.post(`/${collection}/documents`, { data }, options);
2790
+ }
2791
+ async get(collection, docId, options) {
2792
+ return this._get(`/${collection}/documents/${docId}`, options);
2793
+ }
2794
+ async update(collection, docId, data, options) {
2795
+ return this.patch(`/${collection}/documents/${docId}`, { data }, options);
2796
+ }
2797
+ async delete(collection, docId, options) {
2798
+ return this.del(`/${collection}/documents/${docId}`, options);
2799
+ }
2800
+ // --------------------------------------------------------------------------
2801
+ // Documents — Query & Aggregate
2802
+ // --------------------------------------------------------------------------
2803
+ async query(collection, options, requestOptions) {
2804
+ const body = {
2805
+ filters: options?.filters ?? [],
2806
+ sort: options?.sort ?? [],
2807
+ page: options?.page ?? 1,
2808
+ per_page: options?.perPage ?? 20
2809
+ };
2810
+ const response = await this.post(`/${collection}/query`, body, requestOptions);
2811
+ if (response.error) {
2812
+ return {
2813
+ data: [],
2814
+ metadata: { total: 0, totalPages: 0, page: body.page, perPage: body.per_page },
2815
+ error: response.error
2816
+ };
2817
+ }
2818
+ const raw = response.data;
2819
+ const documents = raw?.documents ?? raw?.data ?? [];
2820
+ const total = raw?.total ?? documents.length;
2821
+ const totalPages = raw?.total_pages ?? (total > 0 ? Math.ceil(total / body.per_page) : 0);
2822
+ return {
2823
+ data: documents,
2824
+ metadata: {
2825
+ total,
2826
+ totalPages,
2827
+ page: raw?.page ?? body.page,
2828
+ perPage: raw?.per_page ?? body.per_page
2829
+ },
2830
+ error: null
2831
+ };
2832
+ }
2833
+ async aggregate(collection, options, requestOptions) {
2834
+ return this.post(`/${collection}/aggregate`, options, requestOptions);
2835
+ }
2836
+ async myDocuments(collection, options, requestOptions) {
2837
+ return this._list(`/${collection}/my-documents`, options, requestOptions);
2838
+ }
2839
+ // --------------------------------------------------------------------------
2840
+ // Legacy methods (backward compat)
2841
+ // --------------------------------------------------------------------------
2842
+ /** @deprecated Use create() instead */
2843
+ async createDocument(collection, data) {
2844
+ return this.create(collection, data);
2845
+ }
2846
+ /** @deprecated Use get() instead */
2847
+ async getDocument(collection, id) {
2848
+ return this.get(collection, id);
2849
+ }
2850
+ /** @deprecated Use update() instead */
2851
+ async updateDocument(collection, id, data) {
2852
+ return this.update(collection, id, data);
2853
+ }
2854
+ /** @deprecated Use delete() instead */
2855
+ async deleteDocument(collection, id) {
2856
+ return this.delete(collection, id);
2857
+ }
2858
+ /** @deprecated Use query() instead */
2859
+ async queryDocuments(collection, options) {
2860
+ return this.query(collection, options);
2861
+ }
2862
+ };
2863
+
2864
+ // src/services/chat.ts
2865
+ var ChatService = class extends ServiceModule {
2866
+ constructor() {
2867
+ super(...arguments);
2868
+ this.basePath = "/v1/chat";
2869
+ }
2870
+ // --------------------------------------------------------------------------
2871
+ // Conversations
2872
+ // --------------------------------------------------------------------------
2873
+ async createConversation(data, options) {
2874
+ return this.post("/conversations", data, options);
2875
+ }
2876
+ async listConversations(params, requestOptions) {
2877
+ return this._list("/conversations", params, requestOptions);
2878
+ }
2879
+ async getConversation(id, options) {
2880
+ return this._get(`/conversations/${id}`, options);
2881
+ }
2882
+ async addParticipant(conversationId, userId, options) {
2883
+ return this.post(`/conversations/${conversationId}/participants`, { user_id: userId }, options);
2884
+ }
2885
+ async removeParticipant(conversationId, userId, options) {
2886
+ return this.del(`/conversations/${conversationId}/participants/${userId}`, options);
2887
+ }
2888
+ // --------------------------------------------------------------------------
2889
+ // Messages
2890
+ // --------------------------------------------------------------------------
2891
+ async sendMessage(conversationId, data, options) {
2892
+ return this.post(`/conversations/${conversationId}/messages`, data, options);
2893
+ }
2894
+ async getMessages(conversationId, options, requestOptions) {
2895
+ return this._get(this.withQuery(`/conversations/${conversationId}/messages`, options), requestOptions);
2896
+ }
2897
+ async editMessage(messageId, data, options) {
2898
+ return this.patch(`/messages/${messageId}`, data, options);
2899
+ }
2900
+ async deleteMessage(messageId, options) {
2901
+ return this.del(`/messages/${messageId}`, options);
2902
+ }
2903
+ async addReaction(messageId, data, options) {
2904
+ return this.post(`/messages/${messageId}/reactions`, data, options);
2905
+ }
2906
+ // --------------------------------------------------------------------------
2907
+ // Typing & Read Receipts
2908
+ // --------------------------------------------------------------------------
2909
+ async sendTyping(conversationId, options) {
2910
+ return this.post(`/conversations/${conversationId}/typing`, void 0, options);
2911
+ }
2912
+ async markRead(conversationId, options) {
2913
+ return this.post(`/conversations/${conversationId}/read`, void 0, options);
2914
+ }
2915
+ async getReadStatus(conversationId, options) {
2916
+ return this._get(`/conversations/${conversationId}/read-status`, options);
2917
+ }
2918
+ // --------------------------------------------------------------------------
2919
+ // Legacy methods (backward compat)
2920
+ // --------------------------------------------------------------------------
2921
+ /** @deprecated Use createConversation() instead */
2922
+ async createChat(data) {
2923
+ return this.createConversation(data);
2924
+ }
2925
+ };
2926
+
2927
+ // src/services/social.ts
2928
+ var SocialService = class extends ServiceModule {
2929
+ constructor() {
2930
+ super(...arguments);
2931
+ this.basePath = "/v1/social";
2932
+ }
2933
+ // --------------------------------------------------------------------------
2934
+ // Follow / Unfollow
2935
+ // --------------------------------------------------------------------------
2936
+ async follow(userId, options) {
2937
+ return this.post(`/users/${userId}/follow`, void 0, options);
2938
+ }
2939
+ async unfollow(userId, options) {
2940
+ return this.del(`/users/${userId}/follow`, options);
2941
+ }
2942
+ async getFollowers(userId, params, requestOptions) {
2943
+ return this._list(`/users/${userId}/followers`, params, requestOptions);
2944
+ }
2945
+ async getFollowing(userId, params, requestOptions) {
2946
+ return this._list(`/users/${userId}/following`, params, requestOptions);
2947
+ }
2948
+ async getFollowStatus(userId, options) {
2949
+ return this._get(`/users/${userId}/follow-status`, options);
2950
+ }
2951
+ // --------------------------------------------------------------------------
2952
+ // Posts
2953
+ // --------------------------------------------------------------------------
2954
+ async createPost(data, options) {
2955
+ return this.post("/posts", data, options);
2956
+ }
2957
+ async getPost(postId, options) {
2958
+ return this._get(`/posts/${postId}`, options);
2959
+ }
2960
+ async deletePost(postId, options) {
2961
+ return this.del(`/posts/${postId}`, options);
2962
+ }
2963
+ async getUserPosts(userId, params, requestOptions) {
2964
+ return this._list(`/users/${userId}/posts`, params, requestOptions);
2965
+ }
2966
+ async getFeed(options, requestOptions) {
2967
+ return this._list("/feed", options, requestOptions);
2968
+ }
2969
+ // --------------------------------------------------------------------------
2970
+ // Likes
2971
+ // --------------------------------------------------------------------------
2972
+ async like(targetType, targetId, options) {
2973
+ return this.post(`/${targetType}/${targetId}/like`, void 0, options);
2974
+ }
2975
+ async unlike(targetType, targetId, options) {
2976
+ return this.del(`/${targetType}/${targetId}/like`, options);
2977
+ }
2978
+ async getLikes(targetType, targetId, params, requestOptions) {
2979
+ return this._list(`/${targetType}/${targetId}/likes`, params, requestOptions);
2980
+ }
2981
+ // --------------------------------------------------------------------------
2982
+ // Comments
2983
+ // --------------------------------------------------------------------------
2984
+ async comment(postId, data, options) {
2985
+ return this.post(`/posts/${postId}/comments`, data, options);
2986
+ }
2987
+ async getComments(postId, params, requestOptions) {
2988
+ return this._list(`/posts/${postId}/comments`, params, requestOptions);
2989
+ }
2990
+ // --------------------------------------------------------------------------
2991
+ // Activity Feed
2992
+ // --------------------------------------------------------------------------
2993
+ async getActivity(params, requestOptions) {
2994
+ return this._list("/activity", params, requestOptions);
2995
+ }
2996
+ async markActivityRead(activityId, options) {
2997
+ return this.patch(`/activity/${activityId}/read`, {}, options);
2998
+ }
2999
+ async markAllRead(options) {
3000
+ return this.patch("/activity/read-all", {}, options);
3001
+ }
3002
+ // --------------------------------------------------------------------------
3003
+ // Legacy methods (backward compat)
3004
+ // --------------------------------------------------------------------------
3005
+ /** @deprecated Use comment() instead */
3006
+ async addComment(postId, data) {
3007
+ return this.comment(postId, data);
3008
+ }
3009
+ };
3010
+
3011
+ // src/services/billing.ts
3012
+ var BillingService = class extends ServiceModule {
3013
+ constructor() {
3014
+ super(...arguments);
3015
+ this.basePath = "/v1/billing";
3016
+ }
3017
+ // --------------------------------------------------------------------------
3018
+ // Customers
3019
+ // --------------------------------------------------------------------------
3020
+ async createCustomer(data, options) {
3021
+ return this.post("/customers", data, options);
3022
+ }
3023
+ async addPaymentMethod(data, options) {
3024
+ return this.post("/payment-methods", data, options);
3025
+ }
3026
+ // --------------------------------------------------------------------------
3027
+ // Subscriptions
3028
+ // --------------------------------------------------------------------------
3029
+ async subscribe(data, options) {
3030
+ return this.post("/subscriptions", data, options);
3031
+ }
3032
+ async listSubscriptions(params, options) {
3033
+ return this._list("/subscriptions", params, options);
3034
+ }
3035
+ async cancelSubscription(id, options) {
3036
+ return this.post(`/subscriptions/${id}/cancel`, void 0, options);
3037
+ }
3038
+ async resumeSubscription(id, options) {
3039
+ return this.post(`/subscriptions/${id}/resume`, void 0, options);
3040
+ }
3041
+ async upgradeSubscription(id, data, options) {
3042
+ return this.patch(`/subscriptions/${id}/upgrade`, data, options);
3043
+ }
3044
+ // --------------------------------------------------------------------------
3045
+ // Usage
3046
+ // --------------------------------------------------------------------------
3047
+ async reportUsage(data, options) {
3048
+ return this.post("/usage", data, options);
3049
+ }
3050
+ async getUsageSummary(options) {
3051
+ return this._get("/usage/summary", options);
3052
+ }
3053
+ // --------------------------------------------------------------------------
3054
+ // Invoices
3055
+ // --------------------------------------------------------------------------
3056
+ async listInvoices(params, options) {
3057
+ return this._list("/invoices", params, options);
3058
+ }
3059
+ async getInvoice(id, options) {
3060
+ return this._get(`/invoices/${id}`, options);
3061
+ }
3062
+ async payInvoice(id, options) {
3063
+ return this.post(`/invoices/${id}/pay`, void 0, options);
3064
+ }
3065
+ async getInvoicePdf(id, options) {
3066
+ return this._get(`/invoices/${id}/pdf`, options);
3067
+ }
3068
+ // --------------------------------------------------------------------------
3069
+ // Connected Accounts (Marketplace)
3070
+ // --------------------------------------------------------------------------
3071
+ async createConnectedAccount(data, options) {
3072
+ return this.post("/connected-accounts", data, options);
3073
+ }
3074
+ async getConnectedAccount(id, options) {
3075
+ return this._get(`/connected-accounts/${id}`, options);
3076
+ }
3077
+ async getMyConnectedAccount(options) {
3078
+ return this._get("/connected-accounts/me", options);
3079
+ }
3080
+ async createOnboardingLink(id, data, options) {
3081
+ return this.post(`/connected-accounts/${id}/onboarding-link`, data, options);
3082
+ }
3083
+ async getAccountBalance(id, options) {
3084
+ return this._get(`/connected-accounts/${id}/balance`, options);
3085
+ }
3086
+ async createAccountSession(id, options) {
3087
+ return this.post(`/connected-accounts/${id}/account-session`, void 0, options);
3088
+ }
3089
+ // --------------------------------------------------------------------------
3090
+ // Config
3091
+ // --------------------------------------------------------------------------
3092
+ async getPublishableKey(options) {
3093
+ return this._get("/config/publishable-key", options);
3094
+ }
3095
+ // --------------------------------------------------------------------------
3096
+ // Payments (Marketplace)
3097
+ // --------------------------------------------------------------------------
3098
+ async createPayment(data, options) {
3099
+ return this.post("/payments", data, options);
3100
+ }
3101
+ async getPayment(id, options) {
3102
+ return this._get(`/payments/${id}`, options);
3103
+ }
3104
+ async listPayments(params, options) {
3105
+ return this._list("/payments", params, options);
3106
+ }
3107
+ // --------------------------------------------------------------------------
3108
+ // Refunds
3109
+ // --------------------------------------------------------------------------
3110
+ async refundPayment(id, data, options) {
3111
+ return this.post(`/payments/${id}/refund`, data, options);
3112
+ }
3113
+ // --------------------------------------------------------------------------
3114
+ // Payouts
3115
+ // --------------------------------------------------------------------------
3116
+ async getPayoutHistory(accountId, params, options) {
3117
+ return this._list(`/connected-accounts/${accountId}/payouts`, params, options);
3118
+ }
3119
+ async getPayoutSchedule(accountId, options) {
3120
+ return this._get(`/connected-accounts/${accountId}/payout-schedule`, options);
3121
+ }
3122
+ async setPayoutSchedule(accountId, data, options) {
3123
+ return this.put(`/connected-accounts/${accountId}/payout-schedule`, data, options);
3124
+ }
3125
+ // --------------------------------------------------------------------------
3126
+ // Ledger
3127
+ // --------------------------------------------------------------------------
3128
+ async getTransactions(params, options) {
3129
+ return this._list("/transactions", params, options);
3130
+ }
3131
+ async getTransactionSummary(params, options) {
3132
+ return this._get(this.withQuery("/transactions/summary", params), options);
3133
+ }
3134
+ // --------------------------------------------------------------------------
3135
+ // Setup Sessions
3136
+ // --------------------------------------------------------------------------
3137
+ async createSetupSession(data, options) {
3138
+ return this.post("/setup-sessions", data, options);
3139
+ }
3140
+ // --------------------------------------------------------------------------
3141
+ // Connected Account Operations: Products, Prices, Subscriptions, Transfers
3142
+ // --------------------------------------------------------------------------
3143
+ async createProduct(data, options) {
3144
+ return this.post("/products", data, options);
3145
+ }
3146
+ async createPrice(data, options) {
3147
+ return this.post("/prices", data, options);
3148
+ }
3149
+ async deactivatePrice(id, options) {
3150
+ return this.post(`/prices/${id}/deactivate`, void 0, options);
3151
+ }
3152
+ async createConnectedSubscription(data, options) {
3153
+ return this.post("/connected-subscriptions", data, options);
3154
+ }
3155
+ async cancelConnectedSubscription(id, data, options) {
3156
+ return this.post(`/connected-subscriptions/${id}/cancel`, data, options);
3157
+ }
3158
+ async listConnectedSubscriptions(params, options) {
3159
+ return this._list("/connected-subscriptions", params, options);
3160
+ }
3161
+ async createConnectedSetupIntent(data, options) {
3162
+ return this.post("/connected-setup-intents", data, options);
3163
+ }
3164
+ async createTransfer(data, options) {
3165
+ return this.post("/transfers", data, options);
3166
+ }
3167
+ async syncPaymentStatus(id, options) {
3168
+ return this.post(`/payments/${id}/sync`, void 0, options);
3169
+ }
3170
+ // --------------------------------------------------------------------------
3171
+ // Legacy methods (backward compat)
3172
+ // --------------------------------------------------------------------------
3173
+ /** @deprecated Use subscribe() instead */
3174
+ async createSubscription(data) {
3175
+ return this.subscribe(data);
3176
+ }
3177
+ /** @deprecated Use listInvoices() instead */
3178
+ async getInvoices(params) {
3179
+ return this.listInvoices(params);
3180
+ }
3181
+ };
3182
+
3183
+ // src/services/analytics.ts
3184
+ var AnalyticsService = class extends ServiceModule {
3185
+ constructor() {
3186
+ super(...arguments);
3187
+ this.basePath = "/v1/analytics";
3188
+ }
3189
+ // --------------------------------------------------------------------------
3190
+ // Event Tracking (v2 — JetStream buffered)
3191
+ // --------------------------------------------------------------------------
3192
+ async track(event, properties, userId, options) {
3193
+ return this.post("/v2/events", { event, properties, user_id: userId }, options);
3194
+ }
3195
+ async trackBatch(events, options) {
3196
+ return this.post("/v2/events/batch", { events }, options);
3197
+ }
3198
+ async trackPageView(data, options) {
3199
+ return this.post("/page-view", data, options);
3200
+ }
3201
+ // --------------------------------------------------------------------------
3202
+ // Identity
3203
+ // --------------------------------------------------------------------------
3204
+ async identify(userId, traits, anonymousId, options) {
3205
+ return this.post("/identify", { user_id: userId, traits, anonymous_id: anonymousId }, options);
3206
+ }
3207
+ async alias(userId, anonymousId, options) {
3208
+ return this.post("/alias", { user_id: userId, anonymous_id: anonymousId }, options);
3209
+ }
3210
+ // --------------------------------------------------------------------------
3211
+ // Query & Aggregations
3212
+ // --------------------------------------------------------------------------
3213
+ async queryEvents(filters) {
3214
+ return this._list("/events", filters);
3215
+ }
3216
+ async getAggregations(filters) {
3217
+ return this._get(this.withQuery("/aggregations", filters));
3218
+ }
3219
+ async getTopEvents(filters) {
3220
+ return this._get(this.withQuery("/top-events", filters));
3221
+ }
3222
+ async getActiveUsers() {
3223
+ return this._get("/users/active");
3224
+ }
3225
+ // --------------------------------------------------------------------------
3226
+ // Funnels
3227
+ // --------------------------------------------------------------------------
3228
+ async createFunnel(data) {
3229
+ return this.post("/funnels", data);
3230
+ }
3231
+ async listFunnels() {
3232
+ return this._get("/funnels");
3233
+ }
3234
+ async getFunnelConversions(id) {
3235
+ return this._get(`/funnels/${id}/conversions`);
3236
+ }
3237
+ // --------------------------------------------------------------------------
3238
+ // Custom Metrics
3239
+ // --------------------------------------------------------------------------
3240
+ async trackMetric(data, options) {
3241
+ return this.post("/metrics", data, options);
3242
+ }
3243
+ async queryMetrics(filters) {
3244
+ return this._get(this.withQuery("/metrics/query", filters));
3245
+ }
3246
+ // --------------------------------------------------------------------------
3247
+ // Legacy methods (backward compat)
3248
+ // --------------------------------------------------------------------------
3249
+ /** @deprecated Use queryEvents() instead */
3250
+ async query(filters) {
3251
+ return this._get(this.withQuery("/events", filters));
3252
+ }
3253
+ };
3254
+
3255
+ // src/services/communication.ts
3256
+ var CommunicationService = class extends ServiceModule {
3257
+ constructor() {
3258
+ super(...arguments);
3259
+ this.basePath = "/v1/communication";
3260
+ }
3261
+ // --------------------------------------------------------------------------
3262
+ // Email
3263
+ // --------------------------------------------------------------------------
3264
+ async sendEmail(data, options) {
3265
+ return this.post("/email/send", data, options);
3266
+ }
3267
+ async sendEmailTemplate(template, data, options) {
3268
+ return this.post(`/email/templates/${template}/send`, data, options);
3269
+ }
3270
+ // --------------------------------------------------------------------------
3271
+ // SMS
3272
+ // --------------------------------------------------------------------------
3273
+ async sendSms(data, options) {
3274
+ return this.post("/sms/send", data, options);
3275
+ }
3276
+ async sendSmsTemplate(template, data, options) {
3277
+ return this.post(`/sms/templates/${template}/send`, data, options);
3278
+ }
3279
+ // --------------------------------------------------------------------------
3280
+ // Push Notifications
3281
+ // --------------------------------------------------------------------------
3282
+ async sendPush(data, options) {
3283
+ return this.post("/push/send", data, options);
3284
+ }
3285
+ async registerPushToken(data, options) {
3286
+ return this.post("/push/register", data, options);
3287
+ }
3288
+ async unregisterPushToken(token, options) {
3289
+ return this.del(`/push/tokens/${token}`, options);
3290
+ }
3291
+ // --------------------------------------------------------------------------
3292
+ // Message Status
3293
+ // --------------------------------------------------------------------------
3294
+ async getMessageStatus(id, options) {
3295
+ return this._get(`/messages/${id}`, options);
3296
+ }
3297
+ // --------------------------------------------------------------------------
3298
+ // Legacy methods (backward compat)
3299
+ // --------------------------------------------------------------------------
3300
+ /** @deprecated Use sendSms() instead */
3301
+ async sendSMS(data) {
3302
+ return this.sendSms(data);
3303
+ }
3304
+ /** @deprecated Use sendPush() instead */
3305
+ async sendPushNotification(data) {
3306
+ return this.sendPush(data);
3307
+ }
3308
+ };
3309
+
3310
+ // src/services/scheduler.ts
3311
+ var SchedulerService = class extends ServiceModule {
3312
+ constructor() {
3313
+ super(...arguments);
3314
+ this.basePath = "/v1/scheduler";
3315
+ }
3316
+ // --------------------------------------------------------------------------
3317
+ // Job CRUD
3318
+ // --------------------------------------------------------------------------
3319
+ async createJob(data, options) {
3320
+ return this.post("/jobs", data, options);
3321
+ }
3322
+ async listJobs(params, requestOptions) {
3323
+ return this._list("/jobs", params, requestOptions);
3324
+ }
3325
+ async getJob(id, options) {
3326
+ return this._get(`/jobs/${id}`, options);
3327
+ }
3328
+ async updateJob(id, data, options) {
3329
+ return this.patch(`/jobs/${id}`, data, options);
3330
+ }
3331
+ async deleteJob(id, options) {
3332
+ return this.del(`/jobs/${id}`, options);
3333
+ }
3334
+ // --------------------------------------------------------------------------
3335
+ // Job Control
3336
+ // --------------------------------------------------------------------------
3337
+ async pauseJob(id, options) {
3338
+ return this.post(`/jobs/${id}/pause`, void 0, options);
3339
+ }
3340
+ async resumeJob(id, options) {
3341
+ return this.post(`/jobs/${id}/resume`, void 0, options);
3342
+ }
3343
+ async runNow(id, options) {
3344
+ return this.post(`/jobs/${id}/run-now`, void 0, options);
3345
+ }
3346
+ // --------------------------------------------------------------------------
3347
+ // Execution History & Stats
3348
+ // --------------------------------------------------------------------------
3349
+ async getExecutions(jobId, params, requestOptions) {
3350
+ return this._list(`/jobs/${jobId}/executions`, params, requestOptions);
3351
+ }
3352
+ async getStats(jobId, options) {
3353
+ return this._get(`/jobs/${jobId}/stats`, options);
3354
+ }
3355
+ };
3356
+
3357
+ // src/services/permissions.ts
3358
+ function canPerform(matrix, resource, action) {
3359
+ if (!matrix) return false;
3360
+ const resourcePerms = matrix.permissions[resource];
3361
+ if (!resourcePerms) return false;
3362
+ return resourcePerms[action] === "allow";
3363
+ }
3364
+ function hasMinRoleLevel(matrix, minLevel) {
3365
+ if (!matrix) return false;
3366
+ if (matrix.roleLevel === void 0) return false;
3367
+ return matrix.roleLevel >= minLevel;
3368
+ }
3369
+ var PermissionsService = class extends ServiceModule {
3370
+ constructor() {
3371
+ super(...arguments);
3372
+ this.basePath = "/v1/permissions";
3373
+ }
3374
+ // --------------------------------------------------------------------------
3375
+ // Roles
3376
+ // --------------------------------------------------------------------------
3377
+ async createRole(data, options) {
3378
+ return this.post("/roles", data, options);
3379
+ }
3380
+ async listRoles(options) {
3381
+ return this._get("/roles", options);
3382
+ }
3383
+ async assignPermissions(roleId, permissions, options) {
3384
+ return this.post(`/roles/${roleId}/permissions`, { permissions }, options);
3385
+ }
3386
+ async assignRole(userId, roleId, options) {
3387
+ return this.post(`/users/${userId}/roles`, { role_id: roleId }, options);
3388
+ }
3389
+ // --------------------------------------------------------------------------
3390
+ // Permission Checks (unified — supports both member and user identity types)
3391
+ // --------------------------------------------------------------------------
3392
+ /** Check a single permission. Supports identity_type for unified model. */
3393
+ async check(identityId, permission, options) {
3394
+ const { identityType, resourceType, resourceId, ...reqOptions } = options || {};
3395
+ return this.post("/check", {
3396
+ identity_id: identityId,
3397
+ identity_type: identityType || "user",
3398
+ permission,
3399
+ resource_type: resourceType,
3400
+ resource_id: resourceId
3401
+ }, reqOptions);
3402
+ }
3403
+ /** Batch check multiple permissions for an identity. */
3404
+ async batchCheck(identityId, permissions, options) {
3405
+ const { identityType, ...reqOptions } = options || {};
3406
+ return this.post("/batch-check", {
3407
+ identity_id: identityId,
3408
+ identity_type: identityType || "user",
3409
+ permissions
3410
+ }, reqOptions);
3411
+ }
3412
+ /** Fetch the full permission matrix for an identity (single request, no N+1). */
3413
+ async getMatrix(identityId, identityType = "user", options) {
3414
+ const params = new URLSearchParams({ identity_id: identityId, identity_type: identityType });
3415
+ return this._get(`/matrix?${params.toString()}`, options);
3416
+ }
3417
+ async getUserPermissions(userId, options) {
3418
+ return this._get(`/users/${userId}/permissions`, options);
3419
+ }
3420
+ // --------------------------------------------------------------------------
3421
+ // Policies
3422
+ // --------------------------------------------------------------------------
3423
+ async createPolicy(data, options) {
3424
+ return this.post("/policies", data, options);
3425
+ }
3426
+ async listPolicies(options) {
3427
+ return this._get("/policies", options);
3428
+ }
3429
+ async evaluate(data, options) {
3430
+ return this.post("/evaluate", data, options);
3431
+ }
3432
+ // --------------------------------------------------------------------------
3433
+ // Legacy methods (backward compat)
3434
+ // --------------------------------------------------------------------------
3435
+ /** @deprecated Use assignPermissions() instead */
3436
+ async assignPermission(roleId, permission) {
3437
+ return this.assignPermissions(roleId, [permission]);
3438
+ }
3439
+ /** @deprecated Use check() instead */
3440
+ async checkPermission(userId, permission) {
3441
+ return this.check(userId, permission);
3442
+ }
3443
+ };
3444
+
3445
+ // src/services/teams.ts
3446
+ var TeamsService = class extends ServiceModule {
3447
+ constructor() {
3448
+ super(...arguments);
3449
+ this.basePath = "/v1/teams";
3450
+ }
3451
+ // --------------------------------------------------------------------------
3452
+ // Team CRUD
3453
+ // --------------------------------------------------------------------------
3454
+ async create(data, options) {
3455
+ return this.post("", data, options);
3456
+ }
3457
+ async list(params, requestOptions) {
3458
+ return this._list("", params, requestOptions);
3459
+ }
3460
+ async get(id, options) {
3461
+ return this._get(`/${id}`, options);
3462
+ }
3463
+ async update(id, data, options) {
3464
+ return this.patch(`/${id}`, data, options);
3465
+ }
3466
+ async delete(id, options) {
3467
+ return this.del(`/${id}`, options);
3468
+ }
3469
+ // --------------------------------------------------------------------------
3470
+ // Members
3471
+ // --------------------------------------------------------------------------
3472
+ async listMembers(teamId, params, requestOptions) {
3473
+ return this._list(`/${teamId}/members`, params, requestOptions);
3474
+ }
3475
+ async addMember(teamId, data, options) {
3476
+ return this.post(`/${teamId}/members`, data, options);
3477
+ }
3478
+ async updateMember(teamId, userId, data, options) {
3479
+ return this.patch(`/${teamId}/members/${userId}`, data, options);
3480
+ }
3481
+ async removeMember(teamId, userId, options) {
3482
+ return this.del(`/${teamId}/members/${userId}`, options);
3483
+ }
3484
+ // --------------------------------------------------------------------------
3485
+ // Invitations
3486
+ // --------------------------------------------------------------------------
3487
+ async invite(teamId, data, options) {
3488
+ return this.post(`/${teamId}/invitations`, data, options);
3489
+ }
3490
+ async listInvitations(teamId, options) {
3491
+ return this._get(`/${teamId}/invitations`, options);
3492
+ }
3493
+ async acceptInvitation(token, options) {
3494
+ return this.post(`/invitations/${token}/accept`, void 0, options);
3495
+ }
3496
+ async cancelInvitation(id, options) {
3497
+ return this.del(`/invitations/${id}`, options);
3498
+ }
3499
+ // --------------------------------------------------------------------------
3500
+ // SSO
3501
+ // --------------------------------------------------------------------------
3502
+ async configureSso(teamId, data, options) {
3503
+ return this.post(`/${teamId}/sso/configure`, data, options);
3504
+ }
3505
+ async getSso(teamId, options) {
3506
+ return this._get(`/${teamId}/sso`, options);
3507
+ }
3508
+ // --------------------------------------------------------------------------
3509
+ // Legacy methods (backward compat)
3510
+ // --------------------------------------------------------------------------
3511
+ /** @deprecated Use create() instead */
3512
+ async createTeam(data) {
3513
+ return this.create(data);
3514
+ }
3515
+ /** @deprecated Use invite() instead */
3516
+ async inviteMember(teamId, email, role) {
3517
+ return this.invite(teamId, { email, role });
3518
+ }
3519
+ };
3520
+
3521
+ // src/services/graph.ts
3522
+ var GraphService = class extends ServiceModule {
3523
+ constructor() {
3524
+ super(...arguments);
3525
+ this.basePath = "/v1/graph";
3526
+ }
3527
+ async createNode(data, requestOptions) {
3528
+ return this.post("/nodes", data, requestOptions);
3529
+ }
3530
+ async updateNode(nodeId, data, requestOptions) {
3531
+ return this.patch(`/nodes/${nodeId}`, data, requestOptions);
3532
+ }
3533
+ async createEdge(data, requestOptions) {
3534
+ return this.post("/edges", data, requestOptions);
3535
+ }
3536
+ async getEdges(nodeId, options, requestOptions) {
3537
+ return this._get(this.withQuery(`/nodes/${nodeId}/edges`, options), requestOptions);
3538
+ }
3539
+ async traverse(nodeId, options, requestOptions) {
3540
+ return this._get(this.withQuery(`/nodes/${nodeId}/traverse`, options), requestOptions);
3541
+ }
3542
+ async shortestPath(options, requestOptions) {
3543
+ return this.post("/shortest-path", options, requestOptions);
3544
+ }
3545
+ async neighbors(nodeId, options, requestOptions) {
3546
+ return this._get(this.withQuery(`/nodes/${nodeId}/neighbors`, options), requestOptions);
3547
+ }
3548
+ async pageRank(options, requestOptions) {
3549
+ return this.post("/algorithms/pagerank", options, requestOptions);
3550
+ }
3551
+ async centrality(options, requestOptions) {
3552
+ return this.post("/algorithms/centrality", options, requestOptions);
3553
+ }
3554
+ async connectedComponents(options) {
3555
+ return this.post("/algorithms/connected-components", void 0, options);
3556
+ }
3557
+ };
3558
+
3559
+ // src/services/functions.ts
3560
+ var FunctionsService = class extends ServiceModule {
3561
+ constructor() {
3562
+ super(...arguments);
3563
+ this.basePath = "/v1/functions";
3564
+ }
3565
+ async deploy(data, options) {
3566
+ return this.post("", data, options);
3567
+ }
3568
+ async list(options) {
3569
+ return this._get("", options);
3570
+ }
3571
+ async get(name, options) {
3572
+ return this._get(`/${name}`, options);
3573
+ }
3574
+ async update(name, data, options) {
3575
+ return this.patch(`/${name}`, data, options);
3576
+ }
3577
+ async delete(name, options) {
3578
+ return this.del(`/${name}`, options);
3579
+ }
3580
+ async invoke(name, payload, options) {
3581
+ return this.post(`/${name}/invoke`, payload, options);
3582
+ }
3583
+ async invokeAsync(name, payload, options) {
3584
+ return this.post(`/${name}/invoke-async`, payload, options);
3585
+ }
3586
+ async getLogs(name, options) {
3587
+ return this._get(`/${name}/logs`, options);
3588
+ }
3589
+ async getExecutions(name, params, requestOptions) {
3590
+ return this._list(`/${name}/executions`, params, requestOptions);
3591
+ }
3592
+ async getMetrics(name, options) {
3593
+ return this._get(`/${name}/metrics`, options);
3594
+ }
3595
+ /** @deprecated Use deploy() instead */
3596
+ async deployFunction(data) {
3597
+ return this.deploy(data);
3598
+ }
3599
+ /** @deprecated Use invoke() instead */
3600
+ async invokeFunction(name, payload) {
3601
+ return this.invoke(name, payload);
3602
+ }
3603
+ };
3604
+
3605
+ // src/services/listings.ts
3606
+ var ListingsService = class extends ServiceModule {
3607
+ constructor() {
3608
+ super(...arguments);
3609
+ this.basePath = "/v1/listings";
3610
+ }
3611
+ async create(data, options) {
3612
+ return this.post("", data, options);
3613
+ }
3614
+ async get(id, options) {
3615
+ return this._get(`/${id}`, options);
3616
+ }
3617
+ async update(id, data, options) {
3618
+ return this.patch(`/${id}`, data, options);
3619
+ }
3620
+ async delete(id, options) {
3621
+ return this.del(`/${id}`, options);
3622
+ }
3623
+ async search(query, filters, options) {
3624
+ return this._get(this.withQuery("/search", { query, ...filters }), options);
3625
+ }
3626
+ async nearby(nearbyOptions, options) {
3627
+ return this._get(this.withQuery("/nearby", nearbyOptions), options);
3628
+ }
3629
+ async getByCategory(category, params, requestOptions) {
3630
+ return this._list(`/categories/${category}`, params, requestOptions);
3631
+ }
3632
+ async favorite(listingId, options) {
3633
+ return this.post(`/${listingId}/favorite`, void 0, options);
3634
+ }
3635
+ async unfavorite(listingId, options) {
3636
+ return this.del(`/${listingId}/favorite`, options);
3637
+ }
3638
+ async getFavorites(params, requestOptions) {
3639
+ return this._list("/favorites", params, requestOptions);
3640
+ }
3641
+ async trackView(listingId, options) {
3642
+ return this.post(`/${listingId}/view`, void 0, options);
3643
+ }
3644
+ /** @deprecated Use create() instead */
3645
+ async createListing(data) {
3646
+ return this.create(data);
3647
+ }
3648
+ /** @deprecated Use search() instead */
3649
+ async searchListings(query, filters) {
3650
+ return this.search(query, filters);
3651
+ }
3652
+ /** @deprecated Use get() instead */
3653
+ async getListing(id) {
3654
+ return this.get(id);
3655
+ }
3656
+ };
3657
+
3658
+ // src/services/events.ts
3659
+ var EventsService = class extends ServiceModule {
3660
+ constructor() {
3661
+ super(...arguments);
3662
+ this.basePath = "/v1/events";
3663
+ }
3664
+ async create(data, options) {
3665
+ return this.post("", data, options);
3666
+ }
3667
+ async get(eventId, options) {
3668
+ return this._get(`/${eventId}`, options);
3669
+ }
3670
+ async update(eventId, data, options) {
3671
+ return this.patch(`/${eventId}`, data, options);
3672
+ }
3673
+ async delete(eventId, options) {
3674
+ return this.del(`/${eventId}`, options);
3675
+ }
3676
+ async list(filters, requestOptions) {
3677
+ return this._list("", filters, requestOptions);
3678
+ }
3679
+ async register(eventId, options) {
3680
+ return this.post(`/${eventId}/register`, void 0, options);
3681
+ }
3682
+ async unregister(eventId, options) {
3683
+ return this.del(`/${eventId}/register`, options);
3684
+ }
3685
+ async getAttendees(eventId, params, requestOptions) {
3686
+ return this._list(`/${eventId}/attendees`, params, requestOptions);
3687
+ }
3688
+ async checkIn(eventId, options) {
3689
+ return this.post(`/${eventId}/check-in`, void 0, options);
3690
+ }
3691
+ /** @deprecated Use create() instead */
3692
+ async createEvent(data) {
3693
+ return this.create(data);
3694
+ }
3695
+ /** @deprecated Use list() instead */
3696
+ async listEvents(filters) {
3697
+ return this.list(filters);
3698
+ }
3699
+ };
3700
+
3701
+ // src/services/leaderboard.ts
3702
+ var LeaderboardService = class extends ServiceModule {
3703
+ constructor() {
3704
+ super(...arguments);
3705
+ this.basePath = "/v1/leaderboard";
3706
+ }
3707
+ async create(data, options) {
3708
+ return this.post("", data, options);
3709
+ }
3710
+ async submitScore(boardId, data, options) {
3711
+ return this.post(`/${boardId}/scores`, data, options);
3712
+ }
3713
+ async getRankings(boardId, rankingOptions, requestOptions) {
3714
+ return this._get(this.withQuery(`/${boardId}/rankings`, rankingOptions), requestOptions);
3715
+ }
3716
+ async getUserRank(boardId, userId, options) {
3717
+ return this._get(`/${boardId}/users/${userId}/rank`, options);
3718
+ }
3719
+ async getUserHistory(boardId, userId, options) {
3720
+ return this._get(`/${boardId}/users/${userId}/history`, options);
3721
+ }
3722
+ async updateScore(boardId, userId, data, options) {
3723
+ return this.patch(`/${boardId}/users/${userId}/score`, data, options);
3724
+ }
3725
+ async deleteScore(boardId, userId, options) {
3726
+ return this.del(`/${boardId}/users/${userId}/score`, options);
3727
+ }
3728
+ };
3729
+
3730
+ // src/services/webhooks.ts
3731
+ var WebhooksService = class extends ServiceModule {
3732
+ constructor() {
3733
+ super(...arguments);
3734
+ this.basePath = "/v1/webhooks";
3735
+ }
3736
+ async create(data, options) {
3737
+ return this.post("", data, options);
3738
+ }
3739
+ async list(options) {
3740
+ return this._get("", options);
3741
+ }
3742
+ async get(id, options) {
3743
+ return this._get(`/${id}`, options);
3744
+ }
3745
+ async update(id, data, options) {
3746
+ return this.patch(`/${id}`, data, options);
3747
+ }
3748
+ async delete(id, options) {
3749
+ return this.del(`/${id}`, options);
3750
+ }
3751
+ async listEvents(options) {
3752
+ return this._get("/events", options);
3753
+ }
3754
+ /** @deprecated Use create() instead */
3755
+ async createWebhook(data) {
3756
+ return this.create(data);
3757
+ }
3758
+ /** @deprecated Use list() instead */
3759
+ async listWebhooks() {
3760
+ return this.list();
3761
+ }
3762
+ /** @deprecated Use delete() instead */
3763
+ async deleteWebhook(id) {
3764
+ return this.delete(id);
3765
+ }
3766
+ };
3767
+
3768
+ // src/services/search.ts
3769
+ var SearchService = class extends ServiceModule {
3770
+ constructor() {
3771
+ super(...arguments);
3772
+ this.basePath = "/v1/search";
3773
+ }
3774
+ async query(queryStr, queryOptions, requestOptions) {
3775
+ return this.post("", { query: queryStr, ...queryOptions }, requestOptions);
3776
+ }
3777
+ async index(indexName, document, options) {
3778
+ return this.post("/documents", { index: indexName, ...document }, options);
3779
+ }
3780
+ async removeDocument(indexName, docId, options) {
3781
+ return this.del(`/documents/${indexName}/${docId}`, options);
3782
+ }
3783
+ /** @deprecated Use query() instead */
3784
+ async search(queryStr, options) {
3785
+ return this.query(queryStr, options);
3786
+ }
3787
+ /** @deprecated Use index() instead */
3788
+ async indexDocument(data) {
3789
+ return this.post("/documents", data);
3790
+ }
3791
+ };
3792
+
3793
+ // src/services/photo.ts
3794
+ var PHOTO_BREAKPOINTS = [150, 320, 640, 1080];
3795
+ var PhotoService = class extends ServiceModule {
3796
+ constructor() {
3797
+ super(...arguments);
3798
+ this.basePath = "/v1/photos";
3799
+ }
3800
+ async upload(file, uploadOptions, requestOptions) {
3801
+ const fields = {};
3802
+ if (uploadOptions?.metadata) fields["metadata"] = JSON.stringify(uploadOptions.metadata);
3803
+ return this._upload("", file, fields, {
3804
+ ...requestOptions,
3805
+ onProgress: uploadOptions?.onProgress,
3806
+ signal: uploadOptions?.signal
3807
+ });
3808
+ }
3809
+ async transform(photoId, transformations, options) {
3810
+ return this.post(`/${photoId}/transform`, transformations, options);
3811
+ }
3812
+ async get(id, options) {
3813
+ return this._get(`/${id}`, options);
3814
+ }
3815
+ async delete(id, options) {
3816
+ return this.del(`/${id}`, options);
3817
+ }
3818
+ /**
3819
+ * Build an absolute URL for the on-demand transform endpoint.
3820
+ *
3821
+ * Use in `<img src>` or `srcset` — the server negotiates the best format
3822
+ * (AVIF > WebP > JPEG) from the browser's Accept header automatically.
3823
+ * Transformed images are cached server-side on first request.
3824
+ */
3825
+ getTransformUrl(photoId, options) {
3826
+ const params = new URLSearchParams();
3827
+ if (options?.width) params.set("width", String(options.width));
3828
+ if (options?.height) params.set("height", String(options.height));
3829
+ if (options?.fit) params.set("fit", options.fit);
3830
+ if (options?.format) params.set("format", options.format);
3831
+ if (options?.quality) params.set("quality", String(options.quality));
3832
+ const qs = params.toString();
3833
+ return `${this.client.getBaseUrl()}${this.basePath}/${photoId}/transform${qs ? `?${qs}` : ""}`;
3834
+ }
3835
+ /**
3836
+ * Get a transform URL optimized for the given display area.
3837
+ *
3838
+ * Snaps UP to the nearest pre-generated square breakpoint (150, 320, 640, 1080)
3839
+ * so the response is served instantly from cache. Format is auto-negotiated
3840
+ * by the browser's Accept header (WebP in modern browsers, JPEG fallback).
3841
+ *
3842
+ * @param photoId Photo ID
3843
+ * @param displayWidth CSS pixel width of the display area
3844
+ * @param options.dpr Device pixel ratio (default: 1). Pass `window.devicePixelRatio` in browsers.
3845
+ *
3846
+ * @example
3847
+ * ```typescript
3848
+ * // 280px card on 2x Retina -> snaps to 640px (280×2=560, next breakpoint up)
3849
+ * const url = sm.photo.getOptimalUrl(photoId, 280, { dpr: 2 })
3850
+ *
3851
+ * // Profile avatar at 48px -> snaps to 150px
3852
+ * const url = sm.photo.getOptimalUrl(photoId, 48)
3853
+ * ```
3854
+ */
3855
+ getOptimalUrl(photoId, displayWidth, options) {
3856
+ const requestedDpr = options?.dpr ?? 1;
3857
+ const dpr = Number.isFinite(requestedDpr) && requestedDpr > 0 ? requestedDpr : 1;
3858
+ const cssWidth = Number.isFinite(displayWidth) && displayWidth > 0 ? displayWidth : PHOTO_BREAKPOINTS[0];
3859
+ const physicalWidth = Math.ceil(cssWidth * dpr);
3860
+ const size = PHOTO_BREAKPOINTS.find((bp) => bp >= physicalWidth) ?? PHOTO_BREAKPOINTS[PHOTO_BREAKPOINTS.length - 1];
3861
+ return this.getTransformUrl(photoId, { width: size, height: size, fit: "cover" });
3862
+ }
3863
+ /**
3864
+ * Generate an HTML srcset string for responsive square photo display.
3865
+ *
3866
+ * Returns all pre-generated breakpoints as width descriptors. Pair with
3867
+ * the `sizes` attribute so the browser picks the optimal variant automatically.
3868
+ *
3869
+ * @example
3870
+ * ```tsx
3871
+ * const srcset = sm.photo.getSrcSet(photoId)
3872
+ * // -> ".../transform?width=150&height=150&fit=cover 150w, .../transform?width=320..."
3873
+ *
3874
+ * <img
3875
+ * src={sm.photo.getOptimalUrl(photoId, 320)}
3876
+ * srcSet={srcset}
3877
+ * sizes="(max-width: 640px) 100vw, 640px"
3878
+ * alt="Photo"
3879
+ * />
3880
+ * ```
3881
+ */
3882
+ getSrcSet(photoId) {
3883
+ return PHOTO_BREAKPOINTS.map((size) => `${this.getTransformUrl(photoId, { width: size, height: size, fit: "cover" })} ${size}w`).join(", ");
3884
+ }
3885
+ /**
3886
+ * Register a photo from an already-uploaded storage file.
3887
+ *
3888
+ * Creates a photo record so the optimization pipeline can process it.
3889
+ * Use this when files are uploaded via the storage service (presigned URL)
3890
+ * instead of the photo service's upload endpoint.
3891
+ *
3892
+ * Returns the photo record with `id` that can be used with `getTransformUrl()`.
3893
+ */
3894
+ async register(registerOptions, requestOptions) {
3895
+ return this.post("/register", {
3896
+ file_id: registerOptions.fileId,
3897
+ sm_user_id: registerOptions.userId
3898
+ }, requestOptions);
3899
+ }
3900
+ /** @deprecated Use upload() instead */
3901
+ async uploadPhoto(file, options) {
3902
+ return this.upload(file, options);
3903
+ }
3904
+ /** @deprecated Use transform() instead */
3905
+ async transformPhoto(photoId, transformations) {
3906
+ return this.transform(photoId, transformations);
3907
+ }
3908
+ /** @deprecated Use get() instead */
3909
+ async getPhoto(id) {
3910
+ return this.get(id);
3911
+ }
3912
+ };
3913
+
3914
+ // src/services/queue.ts
3915
+ var DeadLetterApi = class extends ServiceModule {
3916
+ constructor() {
3917
+ super(...arguments);
3918
+ this.basePath = "/v1/queue/dead-letter";
3919
+ }
3920
+ async list(options) {
3921
+ return this._get("", options);
3922
+ }
3923
+ async get(id, options) {
3924
+ return this._get(`/${id}`, options);
3925
+ }
3926
+ async retry(id, options) {
3927
+ return this.post(`/${id}/retry`, void 0, options);
3928
+ }
3929
+ async delete(id, options) {
3930
+ return this.del(`/${id}`, options);
3931
+ }
3932
+ };
3933
+ var QueueService = class extends ServiceModule {
3934
+ constructor(client) {
3935
+ super(client);
3936
+ this.basePath = "/v1/queue";
3937
+ this.deadLetter = new DeadLetterApi(client);
3938
+ }
3939
+ async enqueue(data, options) {
3940
+ return this.post("/jobs", data, options);
3941
+ }
3942
+ async getJob(id, options) {
3943
+ return this._get(`/jobs/${id}`, options);
3944
+ }
3945
+ };
3946
+
3947
+ // src/services/cache.ts
3948
+ var CacheService = class extends ServiceModule {
3949
+ constructor() {
3950
+ super(...arguments);
3951
+ this.basePath = "/v1/cache";
3952
+ }
3953
+ async get(key, options) {
3954
+ return this._get(`/${key}`, options);
3955
+ }
3956
+ async set(key, value, ttl, options) {
3957
+ return this.post("", { key, value, ttl }, options);
3958
+ }
3959
+ async delete(key, options) {
3960
+ return this.del(`/${key}`, options);
3961
+ }
3962
+ async flush(options) {
3963
+ return this.del("", options);
3964
+ }
3965
+ };
3966
+
3967
+ // src/services/compliance.ts
3968
+ var ComplianceService = class extends ServiceModule {
3969
+ constructor() {
3970
+ super(...arguments);
3971
+ this.basePath = "/v1/compliance";
3972
+ }
3973
+ /** Build query string from params object */
3974
+ qs(params) {
3975
+ if (!params) return "";
3976
+ const entries = Object.entries(params).filter(([, v]) => v !== void 0 && v !== null);
3977
+ if (entries.length === 0) return "";
3978
+ return "?" + entries.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`).join("&");
3979
+ }
3980
+ // --- Audit Logs ---
3981
+ async log(data, options) {
3982
+ return this.post("/audit-logs", data, options);
3983
+ }
3984
+ async queryAuditLogs(params, requestOptions) {
3985
+ return this._get(`/audit-logs${this.qs(params)}`, requestOptions);
3986
+ }
3987
+ // --- Legacy GDPR (deprecated) ---
3988
+ /** @deprecated Use createDataSubjectRequest({ request_type: 'access', ... }) instead */
3989
+ async requestDataExport(userId) {
3990
+ return this.post("/gdpr/access-request", { user_id: userId });
3991
+ }
3992
+ /** @deprecated Use createDataSubjectRequest({ request_type: 'deletion', ... }) instead */
3993
+ async requestDataDeletion(userId) {
3994
+ return this.post("/gdpr/deletion-request", { user_id: userId });
3995
+ }
3996
+ /** @deprecated Use log() instead */
3997
+ async createAuditLog(data) {
3998
+ return this.log(data);
3999
+ }
4000
+ // --- Consent Purposes ---
4001
+ async listConsentPurposes(options) {
4002
+ return this._get("/consent-purposes", options);
4003
+ }
4004
+ async createConsentPurpose(data, options) {
4005
+ return this.post("/consent-purposes", data, options);
4006
+ }
4007
+ // --- Consent v2 ---
4008
+ async recordConsent(data, options) {
4009
+ return this.post("/consent/v2", data, options);
4010
+ }
4011
+ async getUserConsents(userId, options) {
4012
+ return this._get(`/consent/v2/${userId}`, options);
4013
+ }
4014
+ async withdrawConsent(consentId, data, options) {
4015
+ return this.put(`/consent/v2/${consentId}/withdraw`, data || {}, options);
4016
+ }
4017
+ // --- Data Subject Requests ---
4018
+ async createDataSubjectRequest(data, options) {
4019
+ return this.post("/dsr", data, options);
4020
+ }
4021
+ async listDataSubjectRequests(params, requestOptions) {
4022
+ return this._get(`/dsr${this.qs(params)}`, requestOptions);
4023
+ }
4024
+ async getDataSubjectRequest(id, options) {
4025
+ return this._get(`/dsr/${id}`, options);
4026
+ }
4027
+ async updateDsrStatus(id, data, options) {
4028
+ return this.put(`/dsr/${id}/status`, data, options);
4029
+ }
4030
+ async createDsrAction(dsrId, data, options) {
4031
+ return this.post(`/dsr/${dsrId}/actions`, data, options);
4032
+ }
4033
+ async listDsrActions(dsrId, options) {
4034
+ return this._get(`/dsr/${dsrId}/actions`, options);
4035
+ }
4036
+ // --- Data Breaches ---
4037
+ async reportBreach(data, options) {
4038
+ return this.post("/breaches", data, options);
4039
+ }
4040
+ async listBreaches(params, requestOptions) {
4041
+ return this._get(`/breaches${this.qs(params)}`, requestOptions);
4042
+ }
4043
+ async getBreach(id, options) {
4044
+ return this._get(`/breaches/${id}`, options);
4045
+ }
4046
+ async updateBreach(id, data, options) {
4047
+ return this.put(`/breaches/${id}`, data, options);
4048
+ }
4049
+ // --- Retention Policies ---
4050
+ async listRetentionPolicies(options) {
4051
+ return this._get("/retention/policies", options);
4052
+ }
4053
+ async createRetentionPolicy(data, options) {
4054
+ return this.post("/retention/policies", data, options);
4055
+ }
4056
+ // --- Processing Activities ---
4057
+ async createProcessingActivity(data, options) {
4058
+ return this.post("/processing-activities", data, options);
4059
+ }
4060
+ async listProcessingActivities(params, requestOptions) {
4061
+ return this._get(`/processing-activities${this.qs(params)}`, requestOptions);
4062
+ }
4063
+ async getProcessingActivity(id, options) {
4064
+ return this._get(`/processing-activities/${id}`, options);
4065
+ }
4066
+ async updateProcessingActivity(id, data, options) {
4067
+ return this.put(`/processing-activities/${id}`, data, options);
4068
+ }
4069
+ };
4070
+
4071
+ // src/services/orchestrator.ts
4072
+ var OrchestratorService = class extends ServiceModule {
4073
+ constructor() {
4074
+ super(...arguments);
4075
+ this.basePath = "/v1/orchestrator";
4076
+ }
4077
+ async createWorkflow(data, options) {
4078
+ return this.post("/workflows", data, options);
4079
+ }
4080
+ async execute(workflowId, input, options) {
4081
+ return this.post(`/workflows/${workflowId}/execute`, input, options);
4082
+ }
4083
+ async getExecution(executionId, options) {
4084
+ return this._get(`/executions/${executionId}`, options);
4085
+ }
4086
+ /** @deprecated Use execute() instead */
4087
+ async executeWorkflow(workflowId, input) {
4088
+ return this.execute(workflowId, input);
4089
+ }
4090
+ };
4091
+
4092
+ // src/services/accounts.ts
4093
+ var AccountsService = class extends ServiceModule {
4094
+ constructor() {
4095
+ super(...arguments);
4096
+ this.basePath = "/v1/accounts";
4097
+ }
4098
+ async createClient(data, options) {
4099
+ return this.post("/clients", data, options);
4100
+ }
4101
+ async getClients(options) {
4102
+ return this._get("/clients", options);
4103
+ }
4104
+ async createApplication(data, options) {
4105
+ return this.post("/applications", data, options);
4106
+ }
4107
+ async getApplications(options) {
4108
+ return this._get("/applications", options);
4109
+ }
4110
+ };
4111
+
4112
+ // src/services/identity.ts
4113
+ var IdentityService = class extends ServiceModule {
4114
+ constructor() {
4115
+ super(...arguments);
4116
+ this.basePath = "/v1/identity";
4117
+ }
4118
+ async createApiKey(data, options) {
4119
+ return this.post("/api-keys", data, options);
4120
+ }
4121
+ async listApiKeys(options) {
4122
+ return this._get("/api-keys", options);
4123
+ }
4124
+ async revokeApiKey(id, options) {
4125
+ return this.del(`/api-keys/${id}`, options);
4126
+ }
4127
+ };
4128
+
4129
+ // src/services/catalog.ts
4130
+ var CatalogService = class extends ServiceModule {
4131
+ constructor() {
4132
+ super(...arguments);
4133
+ this.basePath = "/v1/catalog";
4134
+ }
4135
+ async listServices(options) {
4136
+ return this._get("/services", options);
4137
+ }
4138
+ async getServiceHealth(name, options) {
4139
+ return this._get(`/services/${name}/health`, options);
4140
+ }
4141
+ };
4142
+
4143
+ // src/services/flagcontent.ts
4144
+ var FlagContentService = class extends ServiceModule {
4145
+ constructor() {
4146
+ super(...arguments);
4147
+ this.basePath = "/v1/flagcontent";
4148
+ }
4149
+ async createFlag(data, options) {
4150
+ return this.post("/flags", data, options);
4151
+ }
4152
+ async checkFlag(params, requestOptions) {
4153
+ return this._get(this.withQuery("/flags/check", params), requestOptions);
4154
+ }
4155
+ async getFlag(id, options) {
4156
+ return this._get(`/flags/${id}`, options);
4157
+ }
4158
+ async submitAppeal(data, options) {
4159
+ return this.post("/appeals", data, options);
4160
+ }
4161
+ async getAppeal(id, options) {
4162
+ return this._get(`/appeals/${id}`, options);
4163
+ }
4164
+ };
4165
+
4166
+ // src/index.ts
4167
+ var ScaleMule = class {
4168
+ constructor(config) {
4169
+ this._client = new ScaleMuleClient(config);
4170
+ this.auth = new AuthService(this._client);
4171
+ this.storage = new StorageService(this._client);
4172
+ this.realtime = new RealtimeService(this._client);
4173
+ this.video = new VideoService(this._client);
4174
+ this.data = new DataService(this._client);
4175
+ this.chat = new ChatService(this._client);
4176
+ this.social = new SocialService(this._client);
4177
+ this.billing = new BillingService(this._client);
4178
+ this.analytics = new AnalyticsService(this._client);
4179
+ this.communication = new CommunicationService(this._client);
4180
+ this.scheduler = new SchedulerService(this._client);
4181
+ this.permissions = new PermissionsService(this._client);
4182
+ this.teams = new TeamsService(this._client);
4183
+ this.accounts = new AccountsService(this._client);
4184
+ this.identity = new IdentityService(this._client);
4185
+ this.catalog = new CatalogService(this._client);
4186
+ this.cache = new CacheService(this._client);
4187
+ this.queue = new QueueService(this._client);
4188
+ this.search = new SearchService(this._client);
4189
+ this.logger = new LoggerService(this._client);
4190
+ this.webhooks = new WebhooksService(this._client);
4191
+ this.leaderboard = new LeaderboardService(this._client);
4192
+ this.listings = new ListingsService(this._client);
4193
+ this.events = new EventsService(this._client);
4194
+ this.graph = new GraphService(this._client);
4195
+ this.functions = new FunctionsService(this._client);
4196
+ this.photo = new PhotoService(this._client);
4197
+ this.flagContent = new FlagContentService(this._client);
4198
+ this.compliance = new ComplianceService(this._client);
4199
+ this.orchestrator = new OrchestratorService(this._client);
4200
+ }
4201
+ /**
4202
+ * Initialize the client — loads persisted session from storage.
4203
+ * Call this once after construction, before making authenticated requests.
4204
+ */
4205
+ async initialize() {
4206
+ return this._client.initialize();
4207
+ }
4208
+ /**
4209
+ * Set authentication session (token + userId).
4210
+ * Persisted to storage for cross-session continuity.
4211
+ */
4212
+ async setSession(token, userId) {
4213
+ return this._client.setSession(token, userId);
4214
+ }
4215
+ /** Clear the current session and remove from storage. */
4216
+ async clearSession() {
4217
+ return this._client.clearSession();
4218
+ }
4219
+ /** Set access token (in-memory only, not persisted). */
4220
+ setAccessToken(token) {
4221
+ this._client.setAccessToken(token);
4222
+ }
4223
+ /** Clear access token. */
4224
+ clearAccessToken() {
4225
+ this._client.clearAccessToken();
4226
+ }
4227
+ /** Current session token, or null. */
4228
+ getSessionToken() {
4229
+ return this._client.getSessionToken();
4230
+ }
4231
+ /** Current user ID, or null. */
4232
+ getUserId() {
4233
+ return this._client.getUserId();
4234
+ }
4235
+ /** Whether a session token is set. */
4236
+ isAuthenticated() {
4237
+ return this._client.isAuthenticated();
4238
+ }
4239
+ /** The base URL being used for API requests. */
4240
+ getBaseUrl() {
4241
+ return this._client.getBaseUrl();
4242
+ }
4243
+ /** Access the underlying ScaleMuleClient for advanced usage. */
4244
+ getClient() {
4245
+ return this._client;
4246
+ }
4247
+ };
4248
+ var index_default = ScaleMule;
4249
+ export {
4250
+ AccountsService,
4251
+ AnalyticsService,
4252
+ AuthService,
4253
+ BillingService,
4254
+ CacheService,
4255
+ CatalogService,
4256
+ ChatService,
4257
+ CommunicationService,
4258
+ ComplianceService,
4259
+ DataService,
4260
+ ErrorCodes,
4261
+ EventsService,
4262
+ FlagContentService,
4263
+ FunctionsService,
4264
+ GraphService,
4265
+ IdentityService,
4266
+ LeaderboardService,
4267
+ ListingsService,
4268
+ LoggerService,
4269
+ OrchestratorService,
4270
+ PHONE_COUNTRIES,
4271
+ PHOTO_BREAKPOINTS,
4272
+ PermissionsService,
4273
+ PhotoService,
4274
+ QueueService,
4275
+ RealtimeService,
4276
+ ScaleMule,
4277
+ ScaleMuleClient,
4278
+ SchedulerService,
4279
+ SearchService,
4280
+ ServiceModule,
4281
+ SocialService,
4282
+ StorageService,
4283
+ TeamsService,
4284
+ UploadResumeStore,
4285
+ UploadTelemetry,
4286
+ VideoService,
4287
+ WebhooksService,
4288
+ buildClientContextHeaders,
4289
+ calculateTotalParts,
4290
+ canPerform,
4291
+ composePhoneNumber,
4292
+ countryFlag,
4293
+ createUploadPlan,
4294
+ index_default as default,
4295
+ detectCountryFromE164,
4296
+ detectNetworkClass,
4297
+ extractClientContext,
4298
+ findPhoneCountryByCode,
4299
+ findPhoneCountryByDialCode,
4300
+ generateUploadSessionId,
4301
+ getMeasuredBandwidthMbps,
4302
+ getPartRange,
4303
+ hasMinRoleLevel,
4304
+ isValidE164Phone,
4305
+ normalizeAndValidatePhone,
4306
+ normalizePhoneNumber,
4307
+ resolveStrategy,
4308
+ validateIP
4309
+ };