@vaiftechnologies/vaif-client 0.1.2 → 0.2.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.cjs CHANGED
@@ -25,21 +25,142 @@ __export(index_exports, {
25
25
  VaifAdmin: () => VaifAdmin,
26
26
  VaifApiKeys: () => VaifApiKeys,
27
27
  VaifAuth: () => VaifAuth,
28
+ VaifAuthError: () => VaifAuthError,
28
29
  VaifClient: () => VaifClient,
30
+ VaifConflictError: () => VaifConflictError,
29
31
  VaifDatabase: () => VaifDatabase,
32
+ VaifError: () => VaifError,
30
33
  VaifFunctions: () => VaifFunctions,
31
34
  VaifIntegrations: () => VaifIntegrations,
35
+ VaifNetworkError: () => VaifNetworkError,
36
+ VaifNotFoundError: () => VaifNotFoundError,
32
37
  VaifProjects: () => VaifProjects,
38
+ VaifRateLimitError: () => VaifRateLimitError,
33
39
  VaifRealtime: () => VaifRealtime,
34
40
  VaifSchema: () => VaifSchema,
35
41
  VaifStorage: () => VaifStorage,
42
+ VaifTimeoutError: () => VaifTimeoutError,
36
43
  VaifTypeGen: () => VaifTypeGen,
44
+ VaifValidationError: () => VaifValidationError,
37
45
  createClient: () => createClient,
38
46
  createTypeGen: () => createTypeGen,
39
- createVaifClient: () => createClient
47
+ createVaifClient: () => createClient,
48
+ isVaifAuthError: () => isVaifAuthError,
49
+ isVaifConflictError: () => isVaifConflictError,
50
+ isVaifError: () => isVaifError,
51
+ isVaifNetworkError: () => isVaifNetworkError,
52
+ isVaifNotFoundError: () => isVaifNotFoundError,
53
+ isVaifRateLimitError: () => isVaifRateLimitError,
54
+ isVaifTimeoutError: () => isVaifTimeoutError,
55
+ isVaifValidationError: () => isVaifValidationError
40
56
  });
41
57
  module.exports = __toCommonJS(index_exports);
42
58
 
59
+ // src/errors.ts
60
+ var VaifError = class _VaifError extends Error {
61
+ constructor(message, options) {
62
+ super(message);
63
+ this.name = "VaifError";
64
+ this.code = options.code;
65
+ this.statusCode = options.statusCode;
66
+ this.requestId = options.requestId;
67
+ this.details = options.details;
68
+ const ErrorWithCapture = Error;
69
+ if (ErrorWithCapture.captureStackTrace) {
70
+ ErrorWithCapture.captureStackTrace(this, _VaifError);
71
+ }
72
+ }
73
+ toJSON() {
74
+ return {
75
+ name: this.name,
76
+ message: this.message,
77
+ code: this.code,
78
+ statusCode: this.statusCode,
79
+ requestId: this.requestId,
80
+ details: this.details
81
+ };
82
+ }
83
+ };
84
+ var VaifAuthError = class extends VaifError {
85
+ constructor(message, options) {
86
+ super(message, {
87
+ code: options?.code ?? "AUTH_ERROR",
88
+ statusCode: options?.statusCode ?? 401,
89
+ requestId: options?.requestId
90
+ });
91
+ this.name = "VaifAuthError";
92
+ }
93
+ };
94
+ var VaifValidationError = class extends VaifError {
95
+ constructor(message, options) {
96
+ super(message, {
97
+ code: "VALIDATION_ERROR",
98
+ statusCode: 400,
99
+ requestId: options?.requestId,
100
+ details: options?.details ?? options?.fieldErrors
101
+ });
102
+ this.name = "VaifValidationError";
103
+ this.fieldErrors = options?.fieldErrors;
104
+ }
105
+ };
106
+ var VaifNetworkError = class extends VaifError {
107
+ constructor(message, cause) {
108
+ super(message, { code: "NETWORK_ERROR" });
109
+ this.name = "VaifNetworkError";
110
+ this.cause = cause;
111
+ }
112
+ };
113
+ var VaifRateLimitError = class extends VaifError {
114
+ constructor(message, retryAfter) {
115
+ super(message, { code: "RATE_LIMITED", statusCode: 429 });
116
+ this.name = "VaifRateLimitError";
117
+ this.retryAfter = retryAfter;
118
+ }
119
+ };
120
+ var VaifNotFoundError = class extends VaifError {
121
+ constructor(message, requestId) {
122
+ super(message, { code: "NOT_FOUND", statusCode: 404, requestId });
123
+ this.name = "VaifNotFoundError";
124
+ }
125
+ };
126
+ var VaifConflictError = class extends VaifError {
127
+ constructor(message, requestId) {
128
+ super(message, { code: "CONFLICT", statusCode: 409, requestId });
129
+ this.name = "VaifConflictError";
130
+ }
131
+ };
132
+ var VaifTimeoutError = class extends VaifNetworkError {
133
+ constructor(message, timeoutMs) {
134
+ super(message);
135
+ this.name = "VaifTimeoutError";
136
+ this.timeoutMs = timeoutMs;
137
+ }
138
+ };
139
+ function isVaifError(error) {
140
+ return error instanceof VaifError;
141
+ }
142
+ function isVaifAuthError(error) {
143
+ return error instanceof VaifAuthError;
144
+ }
145
+ function isVaifValidationError(error) {
146
+ return error instanceof VaifValidationError;
147
+ }
148
+ function isVaifNetworkError(error) {
149
+ return error instanceof VaifNetworkError;
150
+ }
151
+ function isVaifRateLimitError(error) {
152
+ return error instanceof VaifRateLimitError;
153
+ }
154
+ function isVaifNotFoundError(error) {
155
+ return error instanceof VaifNotFoundError;
156
+ }
157
+ function isVaifConflictError(error) {
158
+ return error instanceof VaifConflictError;
159
+ }
160
+ function isVaifTimeoutError(error) {
161
+ return error instanceof VaifTimeoutError;
162
+ }
163
+
43
164
  // src/lib/database.ts
44
165
  var VaifDatabase = class {
45
166
  constructor(client) {
@@ -48,6 +169,9 @@ var VaifDatabase = class {
48
169
  /**
49
170
  * Start a query on a table
50
171
  *
172
+ * @param table - Table name to query
173
+ * @returns A query builder for chaining operations
174
+ *
51
175
  * @example
52
176
  * ```typescript
53
177
  * const users = await vaif.db.from('users').select('*').execute();
@@ -59,6 +183,10 @@ var VaifDatabase = class {
59
183
  /**
60
184
  * Execute raw SQL query (admin only)
61
185
  *
186
+ * @param sql - SQL query string with $1, $2, etc. parameter placeholders
187
+ * @param params - Parameter values for the query
188
+ * @returns Query result with typed rows
189
+ *
62
190
  * @example
63
191
  * ```typescript
64
192
  * const result = await vaif.db.raw('SELECT * FROM users WHERE id = $1', [userId]);
@@ -86,7 +214,16 @@ var VaifDatabase = class {
86
214
  };
87
215
  }
88
216
  /**
89
- * Execute a function (stored procedure)
217
+ * Execute a stored procedure (RPC call)
218
+ *
219
+ * @param functionName - Name of the database function to call
220
+ * @param params - Parameters to pass to the function
221
+ * @returns Query result with the function's return value
222
+ *
223
+ * @example
224
+ * ```typescript
225
+ * const result = await vaif.db.rpc('get_user_stats', { userId: '123' });
226
+ * ```
90
227
  */
91
228
  async rpc(functionName, params) {
92
229
  const response = await this.client.request(`/db/rpc/${functionName}`, {
@@ -175,72 +312,85 @@ var QueryBuilder = class {
175
312
  this.onConflictUpdate = data;
176
313
  return this;
177
314
  }
178
- // Filter methods
315
+ /** Filter by equality (column = value) */
179
316
  eq(column, value) {
180
317
  this.addFilter(column, this.negateNext ? "neq" : "eq", value);
181
318
  this.negateNext = false;
182
319
  return this;
183
320
  }
321
+ /** Filter by inequality (column != value) */
184
322
  neq(column, value) {
185
323
  this.addFilter(column, this.negateNext ? "eq" : "neq", value);
186
324
  this.negateNext = false;
187
325
  return this;
188
326
  }
327
+ /** Filter by greater than (column > value) */
189
328
  gt(column, value) {
190
329
  this.addFilter(column, this.negateNext ? "lte" : "gt", value);
191
330
  this.negateNext = false;
192
331
  return this;
193
332
  }
333
+ /** Filter by greater than or equal (column >= value) */
194
334
  gte(column, value) {
195
335
  this.addFilter(column, this.negateNext ? "lt" : "gte", value);
196
336
  this.negateNext = false;
197
337
  return this;
198
338
  }
339
+ /** Filter by less than (column < value) */
199
340
  lt(column, value) {
200
341
  this.addFilter(column, this.negateNext ? "gte" : "lt", value);
201
342
  this.negateNext = false;
202
343
  return this;
203
344
  }
345
+ /** Filter by less than or equal (column <= value) */
204
346
  lte(column, value) {
205
347
  this.addFilter(column, this.negateNext ? "gt" : "lte", value);
206
348
  this.negateNext = false;
207
349
  return this;
208
350
  }
351
+ /** Filter by pattern matching (case-sensitive LIKE) */
209
352
  like(column, pattern) {
210
353
  this.addFilter(column, this.negateNext ? "nlike" : "like", pattern);
211
354
  this.negateNext = false;
212
355
  return this;
213
356
  }
357
+ /** Filter by pattern matching (case-insensitive ILIKE) */
214
358
  ilike(column, pattern) {
215
359
  this.addFilter(column, this.negateNext ? "nilike" : "ilike", pattern);
216
360
  this.negateNext = false;
217
361
  return this;
218
362
  }
363
+ /** Filter by IS (null/boolean checks) */
219
364
  is(column, value) {
220
365
  this.addFilter(column, this.negateNext ? "isnot" : "is", value);
221
366
  this.negateNext = false;
222
367
  return this;
223
368
  }
369
+ /** Filter by inclusion in an array of values */
224
370
  in(column, values) {
225
371
  this.addFilter(column, this.negateNext ? "nin" : "in", values);
226
372
  this.negateNext = false;
227
373
  return this;
228
374
  }
375
+ /** Filter by array containment (@>) */
229
376
  contains(column, value) {
230
377
  this.addFilter(column, "cs", value);
231
378
  this.negateNext = false;
232
379
  return this;
233
380
  }
381
+ /** Filter by array being contained by (<@) */
234
382
  containedBy(column, value) {
235
383
  this.addFilter(column, "cd", value);
236
384
  this.negateNext = false;
237
385
  return this;
238
386
  }
387
+ /** Filter by array overlap (&&) */
239
388
  overlaps(column, value) {
240
389
  this.addFilter(column, "ov", value);
241
390
  this.negateNext = false;
242
391
  return this;
243
392
  }
393
+ /** Full-text search filter */
244
394
  textSearch(column, query, options) {
245
395
  this.addFilter(column, "fts", query, options?.config);
246
396
  this.negateNext = false;
@@ -455,17 +605,19 @@ var VaifRealtime = class {
455
605
  return;
456
606
  }
457
607
  const config = this.client.getConfig();
458
- const url = new URL(config.realtimeUrl);
459
- url.searchParams.set("apikey", config.apiKey);
460
- url.searchParams.set("project", config.projectId);
461
- const token = this.client.getAccessToken();
462
- if (token) {
463
- url.searchParams.set("token", token);
464
- }
465
- this.socket = new WebSocket(url.toString());
608
+ this.socket = new WebSocket(config.realtimeUrl);
466
609
  this.socket.onopen = () => {
467
610
  this.client.debug("Realtime connected");
468
611
  this.reconnectAttempts = 0;
612
+ this.send({
613
+ topic: "phoenix",
614
+ event: "auth",
615
+ payload: {
616
+ apikey: config.apiKey,
617
+ project: config.projectId,
618
+ token: this.client.getAccessToken() ?? void 0
619
+ }
620
+ });
469
621
  if (options.heartbeat !== false) {
470
622
  this.startHeartbeat(options.heartbeatInterval ?? 3e4);
471
623
  }
@@ -1916,6 +2068,63 @@ var VaifAI = class {
1916
2068
  }
1917
2069
  return { data: response.data, error: null };
1918
2070
  }
2071
+ /**
2072
+ * Get Copilot observability metrics for the current project
2073
+ *
2074
+ * @example
2075
+ * ```typescript
2076
+ * const { data, error } = await vaif.ai.getCopilotMetrics({ range: '24h' });
2077
+ * console.log(`Success rate: ${data.summary.successRate}%`);
2078
+ * console.log(`Total tokens: ${data.summary.totalTokens}`);
2079
+ * ```
2080
+ */
2081
+ async getCopilotMetrics(options = {}) {
2082
+ const config = this.client.getConfig();
2083
+ const range = options.range ?? "24h";
2084
+ const response = await this.client.request(
2085
+ `/copilot/metrics/${config.projectId}?range=${range}`,
2086
+ { method: "GET" }
2087
+ );
2088
+ if (response.error) {
2089
+ return {
2090
+ data: null,
2091
+ error: {
2092
+ message: response.error.message,
2093
+ code: response.error.code,
2094
+ status: response.status
2095
+ }
2096
+ };
2097
+ }
2098
+ return { data: response.data, error: null };
2099
+ }
2100
+ /**
2101
+ * Get Copilot observability metrics for an organization
2102
+ *
2103
+ * @example
2104
+ * ```typescript
2105
+ * const { data, error } = await vaif.ai.getCopilotOrgMetrics(orgId, { range: '7d' });
2106
+ * console.log(`Total requests: ${data.summary.totalRequests}`);
2107
+ * console.log(`Projects: ${data.summary.uniqueProjects}`);
2108
+ * ```
2109
+ */
2110
+ async getCopilotOrgMetrics(orgId, options = {}) {
2111
+ const range = options.range ?? "24h";
2112
+ const response = await this.client.request(
2113
+ `/copilot/metrics/org/${orgId}?range=${range}`,
2114
+ { method: "GET" }
2115
+ );
2116
+ if (response.error) {
2117
+ return {
2118
+ data: null,
2119
+ error: {
2120
+ message: response.error.message,
2121
+ code: response.error.code,
2122
+ status: response.status
2123
+ }
2124
+ };
2125
+ }
2126
+ return { data: response.data, error: null };
2127
+ }
1919
2128
  };
1920
2129
 
1921
2130
  // src/lib/schema.ts
@@ -2482,7 +2691,7 @@ var VaifAdmin = class {
2482
2691
  const response = await fetch(url, {
2483
2692
  ...options,
2484
2693
  headers,
2485
- credentials: "include"
2694
+ credentials: "same-origin"
2486
2695
  });
2487
2696
  if (!response.ok) {
2488
2697
  const error = await response.json().catch(() => ({ message: "Request failed" }));
@@ -3271,6 +3480,14 @@ var VaifIntegrations = class {
3271
3480
  var DEFAULT_API_URL = "https://api.vaif.studio";
3272
3481
  var DEFAULT_REALTIME_URL = "wss://realtime.vaif.studio";
3273
3482
  var DEFAULT_TIMEOUT = 3e4;
3483
+ var DEFAULT_RETRY = {
3484
+ maxRetries: 3,
3485
+ retryDelay: 1e3,
3486
+ maxRetryDelay: 3e4,
3487
+ backoffMultiplier: 2,
3488
+ retryOn: [429, 500, 502, 503, 504],
3489
+ retryOnNetworkError: true
3490
+ };
3274
3491
  var memoryStorage = {
3275
3492
  data: /* @__PURE__ */ new Map(),
3276
3493
  getItem(key) {
@@ -3288,6 +3505,18 @@ var browserStorage = typeof window !== "undefined" && window.localStorage ? {
3288
3505
  setItem: (key, value) => window.localStorage.setItem(key, value),
3289
3506
  removeItem: (key) => window.localStorage.removeItem(key)
3290
3507
  } : null;
3508
+ function calculateRetryDelay(attempt, config) {
3509
+ const exponentialDelay = config.retryDelay * Math.pow(config.backoffMultiplier, attempt);
3510
+ const cappedDelay = Math.min(exponentialDelay, config.maxRetryDelay);
3511
+ const jitter = 0.5 + Math.random();
3512
+ return Math.floor(cappedDelay * jitter);
3513
+ }
3514
+ function isRetryableStatus(status, config) {
3515
+ return config.retryOn.includes(status);
3516
+ }
3517
+ function sleep(ms) {
3518
+ return new Promise((resolve) => setTimeout(resolve, ms));
3519
+ }
3291
3520
  function createClient(options) {
3292
3521
  return new VaifClient(options);
3293
3522
  }
@@ -3297,10 +3526,10 @@ var VaifClient = class {
3297
3526
  const isAdminClient = options.baseUrl || options.accessToken;
3298
3527
  if (!isAdminClient) {
3299
3528
  if (!options.projectId) {
3300
- throw new Error("projectId is required");
3529
+ throw new VaifError("projectId is required", { code: "INVALID_CONFIG" });
3301
3530
  }
3302
3531
  if (!options.apiKey) {
3303
- throw new Error("apiKey is required");
3532
+ throw new VaifError("apiKey is required", { code: "INVALID_CONFIG" });
3304
3533
  }
3305
3534
  }
3306
3535
  this.config = {
@@ -3314,7 +3543,13 @@ var VaifClient = class {
3314
3543
  debug: options.debug ?? false,
3315
3544
  autoRefreshToken: options.autoRefreshToken ?? true,
3316
3545
  persistSession: options.persistSession ?? true,
3317
- storage: options.storage ?? browserStorage ?? memoryStorage
3546
+ storage: options.storage ?? browserStorage ?? memoryStorage,
3547
+ retry: {
3548
+ ...DEFAULT_RETRY,
3549
+ ...options.retry,
3550
+ retryOn: options.retry?.retryOn ?? DEFAULT_RETRY.retryOn
3551
+ },
3552
+ interceptors: options.interceptors ?? {}
3318
3553
  };
3319
3554
  if (options.accessToken) {
3320
3555
  this.accessToken = options.accessToken;
@@ -3333,120 +3568,228 @@ var VaifClient = class {
3333
3568
  this.integrations = new VaifIntegrations(this);
3334
3569
  this.restoreSession();
3335
3570
  }
3336
- /**
3337
- * Get current configuration
3338
- */
3571
+ /** Get current configuration */
3339
3572
  getConfig() {
3340
3573
  return this.config;
3341
3574
  }
3342
- /**
3343
- * Set access token for authenticated requests
3344
- */
3575
+ /** Set access token for authenticated requests */
3345
3576
  setAccessToken(token) {
3346
3577
  this.accessToken = token;
3347
3578
  }
3348
- /**
3349
- * Get current access token
3350
- */
3579
+ /** Get current access token */
3351
3580
  getAccessToken() {
3352
3581
  return this.accessToken;
3353
3582
  }
3354
3583
  /**
3355
- * Make an authenticated API request
3584
+ * Make an authenticated API request with automatic retry and interceptors
3585
+ *
3586
+ * @param path - API path (relative to base URL)
3587
+ * @param options - Request options (method, body, params, etc.)
3588
+ * @returns Typed API response with data, error, status, and headers
3356
3589
  */
3357
3590
  async request(path, options = {}) {
3358
- const url = new URL(path, this.config.apiUrl);
3359
- if (options.params) {
3360
- Object.entries(options.params).forEach(([key, value]) => {
3361
- if (value !== void 0) {
3362
- url.searchParams.set(key, String(value));
3591
+ const { retry, interceptors } = this.config;
3592
+ for (let attempt = 0; attempt <= retry.maxRetries; attempt++) {
3593
+ const startTime = Date.now();
3594
+ const url = new URL(path, this.config.apiUrl);
3595
+ if (options.params) {
3596
+ Object.entries(options.params).forEach(([key, value]) => {
3597
+ if (value !== void 0) {
3598
+ url.searchParams.set(key, String(value));
3599
+ }
3600
+ });
3601
+ }
3602
+ const headers = {
3603
+ "Content-Type": "application/json",
3604
+ ...this.config.headers,
3605
+ ...options.headers
3606
+ };
3607
+ if (this.config.projectId) {
3608
+ headers["X-Project-ID"] = this.config.projectId;
3609
+ }
3610
+ if (this.config.apiKey) {
3611
+ headers[this.config.apiKeyHeader] = this.config.apiKey;
3612
+ }
3613
+ if (this.accessToken) {
3614
+ headers.Authorization = `Bearer ${this.accessToken}`;
3615
+ }
3616
+ let requestContext = {
3617
+ url: url.toString(),
3618
+ path,
3619
+ method: options.method ?? "GET",
3620
+ headers: { ...headers },
3621
+ body: options.body && options.method !== "GET" ? JSON.stringify(options.body) : void 0,
3622
+ attempt
3623
+ };
3624
+ if (interceptors.onRequest) {
3625
+ try {
3626
+ const modified = await interceptors.onRequest(requestContext);
3627
+ if (modified) {
3628
+ requestContext = modified;
3629
+ }
3630
+ } catch {
3363
3631
  }
3364
- });
3365
- }
3366
- const headers = {
3367
- "Content-Type": "application/json",
3368
- ...this.config.headers,
3369
- ...options.headers
3370
- };
3371
- if (this.config.projectId) {
3372
- headers["X-Project-ID"] = this.config.projectId;
3373
- }
3374
- if (this.config.apiKey) {
3375
- headers[this.config.apiKeyHeader] = this.config.apiKey;
3376
- }
3377
- if (this.accessToken) {
3378
- headers.Authorization = `Bearer ${this.accessToken}`;
3379
- }
3380
- const fetchOptions = {
3381
- method: options.method ?? "GET",
3382
- headers,
3383
- signal: options.signal
3384
- };
3385
- if (options.body && options.method !== "GET") {
3386
- fetchOptions.body = JSON.stringify(options.body);
3387
- }
3388
- const timeoutMs = options.timeout ?? this.config.timeout;
3389
- const timeoutController = new AbortController();
3390
- const timeoutId = setTimeout(() => timeoutController.abort(), timeoutMs);
3391
- if (options.signal) {
3392
- options.signal.addEventListener("abort", () => timeoutController.abort());
3393
- }
3394
- fetchOptions.signal = timeoutController.signal;
3395
- try {
3396
- if (this.config.debug) {
3397
- console.log(`[VAIF] ${options.method ?? "GET"} ${url.toString()}`);
3398
3632
  }
3399
- const response = await fetch(url.toString(), fetchOptions);
3400
- clearTimeout(timeoutId);
3401
- const contentType = response.headers.get("content-type");
3402
- let data = null;
3403
- let error = null;
3404
- if (contentType?.includes("application/json")) {
3405
- const json = await response.json();
3406
- if (response.ok) {
3407
- data = json;
3408
- } else {
3633
+ const fetchOptions = {
3634
+ method: requestContext.method,
3635
+ headers: requestContext.headers,
3636
+ signal: options.signal
3637
+ };
3638
+ if (requestContext.body) {
3639
+ fetchOptions.body = requestContext.body;
3640
+ }
3641
+ const timeoutMs = options.timeout ?? this.config.timeout;
3642
+ const timeoutController = new AbortController();
3643
+ const timeoutId = setTimeout(() => timeoutController.abort(), timeoutMs);
3644
+ if (options.signal) {
3645
+ options.signal.addEventListener("abort", () => timeoutController.abort());
3646
+ }
3647
+ fetchOptions.signal = timeoutController.signal;
3648
+ try {
3649
+ if (this.config.debug) {
3650
+ console.log(`[VAIF] ${requestContext.method} ${requestContext.url} (attempt ${attempt + 1})`);
3651
+ }
3652
+ const response = await fetch(requestContext.url, fetchOptions);
3653
+ clearTimeout(timeoutId);
3654
+ const durationMs = Date.now() - startTime;
3655
+ const contentType = response.headers.get("content-type");
3656
+ let data = null;
3657
+ let error = null;
3658
+ if (contentType?.includes("application/json")) {
3659
+ const json = await response.json();
3660
+ if (response.ok) {
3661
+ data = json;
3662
+ } else {
3663
+ error = {
3664
+ message: json.message ?? json.error ?? "Request failed",
3665
+ code: json.code,
3666
+ status: response.status,
3667
+ details: json.details,
3668
+ requestId: response.headers.get("x-request-id") ?? void 0
3669
+ };
3670
+ }
3671
+ } else if (!response.ok) {
3409
3672
  error = {
3410
- message: json.message ?? json.error ?? "Request failed",
3411
- code: json.code,
3412
- status: response.status,
3413
- details: json.details,
3414
- requestId: response.headers.get("x-request-id") ?? void 0
3673
+ message: `Request failed with status ${response.status}`,
3674
+ status: response.status
3415
3675
  };
3416
3676
  }
3417
- } else if (!response.ok) {
3418
- error = {
3419
- message: `Request failed with status ${response.status}`,
3420
- status: response.status
3677
+ const responseHeaders = {};
3678
+ response.headers.forEach((value, key) => {
3679
+ responseHeaders[key] = value;
3680
+ });
3681
+ if (error && attempt < retry.maxRetries && isRetryableStatus(response.status, retry)) {
3682
+ if (interceptors.onError) {
3683
+ try {
3684
+ await interceptors.onError({
3685
+ request: requestContext,
3686
+ error: new VaifError(error.message, {
3687
+ code: error.code ?? "REQUEST_ERROR",
3688
+ statusCode: response.status
3689
+ }),
3690
+ durationMs,
3691
+ willRetry: true
3692
+ });
3693
+ } catch {
3694
+ }
3695
+ }
3696
+ const delay = calculateRetryDelay(attempt, retry);
3697
+ if (this.config.debug) {
3698
+ console.log(`[VAIF] Retrying in ${delay}ms (attempt ${attempt + 2}/${retry.maxRetries + 1})`);
3699
+ }
3700
+ await sleep(delay);
3701
+ continue;
3702
+ }
3703
+ if (!error && interceptors.onResponse) {
3704
+ try {
3705
+ await interceptors.onResponse({
3706
+ request: requestContext,
3707
+ data,
3708
+ status: response.status,
3709
+ headers: responseHeaders,
3710
+ durationMs
3711
+ });
3712
+ } catch {
3713
+ }
3714
+ }
3715
+ if (error && interceptors.onError) {
3716
+ try {
3717
+ await interceptors.onError({
3718
+ request: requestContext,
3719
+ error: new VaifError(error.message, {
3720
+ code: error.code ?? "REQUEST_ERROR",
3721
+ statusCode: response.status
3722
+ }),
3723
+ durationMs,
3724
+ willRetry: false
3725
+ });
3726
+ } catch {
3727
+ }
3728
+ }
3729
+ return {
3730
+ data,
3731
+ error,
3732
+ status: response.status,
3733
+ headers: responseHeaders
3734
+ };
3735
+ } catch (err) {
3736
+ clearTimeout(timeoutId);
3737
+ const durationMs = Date.now() - startTime;
3738
+ const isTimeout = err instanceof Error && err.name === "AbortError";
3739
+ const isNetworkError = !isTimeout;
3740
+ if (isNetworkError && retry.retryOnNetworkError && attempt < retry.maxRetries) {
3741
+ if (interceptors.onError) {
3742
+ try {
3743
+ await interceptors.onError({
3744
+ request: requestContext,
3745
+ error: err instanceof Error ? err : new Error(String(err)),
3746
+ durationMs,
3747
+ willRetry: true
3748
+ });
3749
+ } catch {
3750
+ }
3751
+ }
3752
+ const delay = calculateRetryDelay(attempt, retry);
3753
+ if (this.config.debug) {
3754
+ console.log(`[VAIF] Network error, retrying in ${delay}ms`);
3755
+ }
3756
+ await sleep(delay);
3757
+ continue;
3758
+ }
3759
+ const error = {
3760
+ message: err instanceof Error ? isTimeout ? "Request timed out" : err.message : "Unknown error",
3761
+ code: isTimeout ? "TIMEOUT" : "NETWORK_ERROR"
3762
+ };
3763
+ if (interceptors.onError) {
3764
+ try {
3765
+ await interceptors.onError({
3766
+ request: requestContext,
3767
+ error: isTimeout ? new VaifTimeoutError("Request timed out", timeoutMs) : new VaifNetworkError(
3768
+ err instanceof Error ? err.message : "Network error",
3769
+ err instanceof Error ? err : void 0
3770
+ ),
3771
+ durationMs,
3772
+ willRetry: false
3773
+ });
3774
+ } catch {
3775
+ }
3776
+ }
3777
+ return {
3778
+ data: null,
3779
+ error,
3780
+ status: 0,
3781
+ headers: {}
3421
3782
  };
3422
3783
  }
3423
- const responseHeaders = {};
3424
- response.headers.forEach((value, key) => {
3425
- responseHeaders[key] = value;
3426
- });
3427
- return {
3428
- data,
3429
- error,
3430
- status: response.status,
3431
- headers: responseHeaders
3432
- };
3433
- } catch (err) {
3434
- clearTimeout(timeoutId);
3435
- const error = {
3436
- message: err instanceof Error ? err.name === "AbortError" ? "Request timed out" : err.message : "Unknown error",
3437
- code: err instanceof Error && err.name === "AbortError" ? "TIMEOUT" : "NETWORK_ERROR"
3438
- };
3439
- return {
3440
- data: null,
3441
- error,
3442
- status: 0,
3443
- headers: {}
3444
- };
3445
3784
  }
3785
+ return {
3786
+ data: null,
3787
+ error: { message: "Max retries exceeded", code: "MAX_RETRIES" },
3788
+ status: 0,
3789
+ headers: {}
3790
+ };
3446
3791
  }
3447
- /**
3448
- * Restore session from storage
3449
- */
3792
+ /** Restore session from storage */
3450
3793
  async restoreSession() {
3451
3794
  if (!this.config.persistSession) return;
3452
3795
  try {
@@ -3462,12 +3805,16 @@ var VaifClient = class {
3462
3805
  } catch {
3463
3806
  }
3464
3807
  }
3465
- /**
3466
- * Log debug messages
3467
- */
3808
+ /** Log debug messages (sanitised to avoid leaking tokens) */
3468
3809
  debug(...args) {
3469
3810
  if (this.config.debug) {
3470
- console.log("[VAIF]", ...args);
3811
+ const safe = args.map((arg) => {
3812
+ if (arg instanceof Error) return arg.message;
3813
+ if (typeof arg === "string") return arg;
3814
+ if (typeof arg === "number" || typeof arg === "boolean") return arg;
3815
+ return "[object]";
3816
+ });
3817
+ console.log("[VAIF]", ...safe);
3471
3818
  }
3472
3819
  }
3473
3820
  };
@@ -3478,16 +3825,32 @@ var VaifClient = class {
3478
3825
  VaifAdmin,
3479
3826
  VaifApiKeys,
3480
3827
  VaifAuth,
3828
+ VaifAuthError,
3481
3829
  VaifClient,
3830
+ VaifConflictError,
3482
3831
  VaifDatabase,
3832
+ VaifError,
3483
3833
  VaifFunctions,
3484
3834
  VaifIntegrations,
3835
+ VaifNetworkError,
3836
+ VaifNotFoundError,
3485
3837
  VaifProjects,
3838
+ VaifRateLimitError,
3486
3839
  VaifRealtime,
3487
3840
  VaifSchema,
3488
3841
  VaifStorage,
3842
+ VaifTimeoutError,
3489
3843
  VaifTypeGen,
3844
+ VaifValidationError,
3490
3845
  createClient,
3491
3846
  createTypeGen,
3492
- createVaifClient
3847
+ createVaifClient,
3848
+ isVaifAuthError,
3849
+ isVaifConflictError,
3850
+ isVaifError,
3851
+ isVaifNetworkError,
3852
+ isVaifNotFoundError,
3853
+ isVaifRateLimitError,
3854
+ isVaifTimeoutError,
3855
+ isVaifValidationError
3493
3856
  });