proofio-sdk 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/cjs/client.js +83 -0
  2. package/dist/cjs/error.js +30 -0
  3. package/dist/cjs/index.js +13 -64
  4. package/dist/cjs/resources/aggregations.js +13 -0
  5. package/dist/cjs/resources/clusters.js +14 -0
  6. package/dist/cjs/resources/insights.js +7 -88
  7. package/dist/cjs/resources/reviews.js +10 -60
  8. package/dist/cjs/resources/widget.js +5 -50
  9. package/dist/cjs/types.js +3 -0
  10. package/dist/client.d.ts +12 -0
  11. package/dist/error.d.ts +17 -0
  12. package/dist/esm/client.js +79 -0
  13. package/dist/esm/error.js +26 -0
  14. package/dist/esm/index.js +13 -63
  15. package/dist/esm/resources/aggregations.js +9 -0
  16. package/dist/esm/resources/clusters.js +10 -0
  17. package/dist/esm/resources/insights.js +7 -88
  18. package/dist/esm/resources/reviews.js +8 -58
  19. package/dist/esm/resources/widget.js +3 -48
  20. package/dist/esm/types.js +2 -0
  21. package/dist/index.d.ts +11 -61
  22. package/dist/resources/aggregations.d.ts +8 -0
  23. package/dist/resources/clusters.d.ts +8 -0
  24. package/dist/resources/insights.d.ts +9 -42
  25. package/dist/resources/reviews.d.ts +6 -27
  26. package/dist/resources/widget.d.ts +6 -42
  27. package/dist/types.d.ts +147 -0
  28. package/package.json +12 -11
  29. package/README.md +0 -319
  30. package/dist/cjs/client/api-client.js +0 -284
  31. package/dist/cjs/resources/competitors.js +0 -45
  32. package/dist/cjs/types/index.js +0 -7
  33. package/dist/cjs/utils/errors.js +0 -50
  34. package/dist/client/api-client.d.ts +0 -68
  35. package/dist/client/api-client.d.ts.map +0 -1
  36. package/dist/esm/client/api-client.js +0 -280
  37. package/dist/esm/resources/competitors.js +0 -41
  38. package/dist/esm/types/index.js +0 -6
  39. package/dist/esm/utils/errors.js +0 -46
  40. package/dist/index.d.ts.map +0 -1
  41. package/dist/resources/competitors.d.ts +0 -33
  42. package/dist/resources/competitors.d.ts.map +0 -1
  43. package/dist/resources/insights.d.ts.map +0 -1
  44. package/dist/resources/reviews.d.ts.map +0 -1
  45. package/dist/resources/widget.d.ts.map +0 -1
  46. package/dist/types/index.d.ts +0 -217
  47. package/dist/types/index.d.ts.map +0 -1
  48. package/dist/utils/errors.d.ts +0 -32
  49. package/dist/utils/errors.d.ts.map +0 -1
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HttpClient = void 0;
4
+ const error_1 = require("./error");
5
+ const DEFAULT_BASE_URL = 'https://api.proofio.app';
6
+ const DEFAULT_TIMEOUT = 30000;
7
+ const DEFAULT_MAX_RETRIES = 3;
8
+ const DEFAULT_RETRY_DELAY = 1000;
9
+ class HttpClient {
10
+ constructor(config) {
11
+ if (!config.apiKey) {
12
+ throw new Error('proofio-sdk: apiKey is required');
13
+ }
14
+ this.apiKey = config.apiKey;
15
+ this.baseURL = (config.baseURL ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
16
+ this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
17
+ this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
18
+ this.retryDelay = config.retryDelay ?? DEFAULT_RETRY_DELAY;
19
+ }
20
+ async get(path, params) {
21
+ const url = this.buildURL(path, params);
22
+ return this.request(url);
23
+ }
24
+ buildURL(path, params) {
25
+ const url = new URL(`${this.baseURL}${path}`);
26
+ if (params) {
27
+ for (const [key, value] of Object.entries(params)) {
28
+ if (value !== undefined && value !== null) {
29
+ url.searchParams.set(key, String(value));
30
+ }
31
+ }
32
+ }
33
+ return url.toString();
34
+ }
35
+ async request(url, attempt = 0) {
36
+ const controller = new AbortController();
37
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
38
+ try {
39
+ const response = await fetch(url, {
40
+ method: 'GET',
41
+ headers: {
42
+ 'x-api-key': this.apiKey,
43
+ 'Content-Type': 'application/json',
44
+ },
45
+ signal: controller.signal,
46
+ });
47
+ if (!response.ok) {
48
+ const body = await response.json().catch(() => ({ error: response.statusText }));
49
+ const error = new error_1.ProofioError(body.message ?? body.error ?? `HTTP ${response.status}`, response.status, body.error ?? 'unknown_error', response.headers);
50
+ // Retry on 5xx or 429 (with backoff)
51
+ if (error.isRetryable() && attempt < this.maxRetries) {
52
+ const delay = error.status === 429 && error.retryAfter
53
+ ? error.retryAfter * 1000
54
+ : this.retryDelay * Math.pow(2, attempt);
55
+ await sleep(delay);
56
+ return this.request(url, attempt + 1);
57
+ }
58
+ throw error;
59
+ }
60
+ return (await response.json());
61
+ }
62
+ catch (err) {
63
+ if (err instanceof error_1.ProofioError)
64
+ throw err;
65
+ // Network / timeout errors are retryable
66
+ if (attempt < this.maxRetries) {
67
+ await sleep(this.retryDelay * Math.pow(2, attempt));
68
+ return this.request(url, attempt + 1);
69
+ }
70
+ if (err instanceof DOMException && err.name === 'AbortError') {
71
+ throw new error_1.ProofioError(`Request timed out after ${this.timeout}ms`, 0, 'timeout');
72
+ }
73
+ throw new error_1.ProofioError(err instanceof Error ? err.message : 'Network error', 0, 'network_error');
74
+ }
75
+ finally {
76
+ clearTimeout(timeoutId);
77
+ }
78
+ }
79
+ }
80
+ exports.HttpClient = HttpClient;
81
+ function sleep(ms) {
82
+ return new Promise((resolve) => setTimeout(resolve, ms));
83
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProofioError = void 0;
4
+ class ProofioError extends Error {
5
+ constructor(message, status, code, headers) {
6
+ super(message);
7
+ this.name = 'ProofioError';
8
+ this.status = status;
9
+ this.code = code;
10
+ const retryAfterHeader = headers?.get('retry-after');
11
+ this.retryAfter = retryAfterHeader ? parseInt(retryAfterHeader, 10) : null;
12
+ this.rateLimit = {
13
+ limit: parseHeaderInt(headers, 'x-ratelimit-limit'),
14
+ remaining: parseHeaderInt(headers, 'x-ratelimit-remaining'),
15
+ reset: parseHeaderInt(headers, 'x-ratelimit-reset'),
16
+ };
17
+ }
18
+ /** Whether this error is retryable (5xx or 429) */
19
+ isRetryable() {
20
+ return this.status === 429 || this.status >= 500;
21
+ }
22
+ }
23
+ exports.ProofioError = ProofioError;
24
+ function parseHeaderInt(headers, name) {
25
+ const value = headers?.get(name);
26
+ if (!value)
27
+ return null;
28
+ const parsed = parseInt(value, 10);
29
+ return isNaN(parsed) ? null : parsed;
30
+ }
package/dist/cjs/index.js CHANGED
@@ -1,74 +1,23 @@
1
1
  "use strict";
2
- /**
3
- * Proofio SDK
4
- *
5
- * Official JavaScript/TypeScript SDK for Proofio API
6
- *
7
- * @example
8
- * ```typescript
9
- * import { Proofio } from 'proofio-sdk';
10
- *
11
- * const proofio = new Proofio({ apiKey: 'your-api-key' });
12
- *
13
- * const reviews = await proofio.reviews.list();
14
- * const summary = await proofio.insights.summary();
15
- * ```
16
- */
17
2
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.Proofio = exports.ApiClient = exports.ProofioError = void 0;
19
- const api_client_1 = require("./client/api-client");
3
+ exports.ProofioError = exports.Proofio = void 0;
4
+ const client_1 = require("./client");
20
5
  const reviews_1 = require("./resources/reviews");
6
+ const aggregations_1 = require("./resources/aggregations");
7
+ const clusters_1 = require("./resources/clusters");
21
8
  const insights_1 = require("./resources/insights");
22
- const competitors_1 = require("./resources/competitors");
23
9
  const widget_1 = require("./resources/widget");
24
- var errors_1 = require("./utils/errors");
25
- Object.defineProperty(exports, "ProofioError", { enumerable: true, get: function () { return errors_1.ProofioError; } });
26
- var api_client_2 = require("./client/api-client");
27
- Object.defineProperty(exports, "ApiClient", { enumerable: true, get: function () { return api_client_2.ApiClient; } });
28
- /**
29
- * Main Proofio SDK Client
30
- *
31
- * Provides access to all Proofio API resources:
32
- * - reviews: List and get reviews
33
- * - insights: Get summaries and trends
34
- * - competitors: Compare with competitors
35
- * - widget: Get widget data and configuration
36
- */
37
10
  class Proofio {
38
- /**
39
- * Create a new Proofio SDK instance
40
- *
41
- * @param config - SDK configuration
42
- * @param config.apiKey - Your Proofio API key (required)
43
- * @param config.baseURL - Base URL for API (default: https://proofio.app)
44
- * @param config.timeout - Request timeout in milliseconds (default: 30000)
45
- * @param config.maxRetries - Maximum number of retries (default: 3)
46
- * @param config.retryDelay - Delay between retries in milliseconds (default: 1000)
47
- *
48
- * @example
49
- * ```typescript
50
- * const proofio = new Proofio({
51
- * apiKey: 'your-api-key-here',
52
- * baseURL: 'https://proofio.app', // optional
53
- * timeout: 30000, // optional
54
- * });
55
- * ```
56
- */
57
11
  constructor(config) {
58
- this.client = new api_client_1.ApiClient(config);
59
- // Initialize resources
60
- this.reviews = new reviews_1.ReviewsResource(this.client);
61
- this.insights = new insights_1.InsightsResource(this.client);
62
- this.competitors = new competitors_1.CompetitorsResource(this.client);
63
- this.widget = new widget_1.WidgetResource(this.client);
64
- }
65
- /**
66
- * Get the underlying API client (for advanced usage)
67
- */
68
- getClient() {
69
- return this.client;
12
+ const client = new client_1.HttpClient(config);
13
+ this.reviews = new reviews_1.Reviews(client);
14
+ this.aggregations = new aggregations_1.AggregationsResource(client);
15
+ this.clusters = new clusters_1.Clusters(client);
16
+ this.insights = new insights_1.InsightsResource(client);
17
+ this.widget = new widget_1.Widget(client);
70
18
  }
71
19
  }
72
20
  exports.Proofio = Proofio;
73
- // Default export
74
- exports.default = Proofio;
21
+ // Re-export everything
22
+ var error_1 = require("./error");
23
+ Object.defineProperty(exports, "ProofioError", { enumerable: true, get: function () { return error_1.ProofioError; } });
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AggregationsResource = void 0;
4
+ class AggregationsResource {
5
+ constructor(client) {
6
+ this.client = client;
7
+ }
8
+ /** Get aggregated review statistics */
9
+ async get() {
10
+ return this.client.get('/api/v1/public/aggregations');
11
+ }
12
+ }
13
+ exports.AggregationsResource = AggregationsResource;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Clusters = void 0;
4
+ class Clusters {
5
+ constructor(client) {
6
+ this.client = client;
7
+ }
8
+ /** Get review clusters with keyword groupings */
9
+ async list() {
10
+ const response = await this.client.get('/api/v1/public/clusters');
11
+ return response.clusters;
12
+ }
13
+ }
14
+ exports.Clusters = Clusters;
@@ -1,101 +1,20 @@
1
1
  "use strict";
2
- /**
3
- * Insights Resource
4
- *
5
- * Handles insights, summaries, and trend analysis
6
- */
7
2
  Object.defineProperty(exports, "__esModule", { value: true });
8
3
  exports.InsightsResource = void 0;
9
4
  class InsightsResource {
10
5
  constructor(client) {
11
6
  this.client = client;
12
7
  }
13
- /**
14
- * Get insight summary
15
- *
16
- * Returns aggregated statistics including:
17
- * - Total reviews
18
- * - Average rating
19
- * - Rating distribution
20
- * - Sentiment distribution
21
- * - Source breakdown
22
- * - AI summary (if available for paid plans)
23
- *
24
- * @returns Promise with insight summary
25
- */
26
- async summary() {
27
- const response = await this.client.get('/api/v1/public/aggregations');
28
- return response.data;
8
+ /** Get AI-powered review intelligence (trust score, risk, emotions, topics) */
9
+ async get() {
10
+ return this.client.get('/api/v1/public/insights');
29
11
  }
30
12
  /**
31
- * Get trend analysis
32
- *
33
- * Returns detailed trend data including:
34
- * - Trend over time (last 30 days)
35
- * - Review volume (7, 30, 90 days)
36
- * - Top topics
37
- * - Key takeaways
38
- * - Recent changes
39
- *
40
- * Note: The public API provides limited trend data. For full trend analysis
41
- * including trendOverTime, topTopics, and recentChanges, you need access
42
- * to the dashboard API (requires authentication).
43
- *
44
- * This method returns available data from the public aggregations endpoint
45
- * and sets default/empty values for fields not available in the public API.
46
- *
47
- * @returns Promise with trend data
13
+ * Get aggregated summary (totalReviews, averageRating, etc.)
14
+ * Alias for aggregations endpoint - provided for convenience.
48
15
  */
49
- async trends() {
50
- // Get data from public aggregations endpoint
51
- const response = await this.client.get('/api/v1/public/aggregations');
52
- const data = response.data;
53
- // Calculate positive percentage
54
- const positivePercentage = data.totalReviews > 0
55
- ? (data.sentimentDistribution.positive / data.totalReviews) * 100
56
- : 0;
57
- // Transform to InsightTrends format
58
- // Note: The public API doesn't provide all trend fields,
59
- // so we provide what's available and set defaults for missing fields
60
- return {
61
- totalReviews: data.totalReviews,
62
- averageRating: data.averageRating,
63
- ratingDistribution: data.ratingDistribution,
64
- sentimentDistribution: data.sentimentDistribution,
65
- thisWeekCount: 0, // Not available in public API
66
- thisWeekChange: 0, // Not available in public API
67
- positivePercentage: Math.round(positivePercentage * 10) / 10,
68
- reviewVolume: {
69
- last7Days: 0, // Not available in public API
70
- last30Days: 0, // Not available in public API
71
- last90Days: 0, // Not available in public API
72
- total: data.totalReviews,
73
- },
74
- sourceBreakdown: data.sources.reduce((acc, source) => {
75
- acc[source.id] = {
76
- count: source.total,
77
- avgRating: source.averageRating,
78
- sentiment: {
79
- positive: 0, // Not available in public API
80
- neutral: 0,
81
- negative: 0,
82
- },
83
- };
84
- return acc;
85
- }, {}),
86
- sourcesMap: data.sources.reduce((acc, source) => {
87
- acc[source.id] = {
88
- name: source.name || source.type || 'Unknown',
89
- type: source.type || 'UNKNOWN',
90
- };
91
- return acc;
92
- }, {}),
93
- trendOverTime: [], // Not available in public API - requires dashboard API
94
- topTopics: [], // Not available in public API - requires dashboard API
95
- keyTakeaways: [], // Not available in public API - requires dashboard API
96
- recentChanges: [], // Not available in public API - requires dashboard API
97
- lastSync: null, // Not available in public API
98
- };
16
+ async summary() {
17
+ return this.client.get('/api/v1/public/aggregations');
99
18
  }
100
19
  }
101
20
  exports.InsightsResource = InsightsResource;
@@ -1,69 +1,19 @@
1
1
  "use strict";
2
- /**
3
- * Reviews Resource
4
- *
5
- * Handles all review-related API calls
6
- */
7
2
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.ReviewsResource = void 0;
9
- class ReviewsResource {
3
+ exports.Reviews = void 0;
4
+ class Reviews {
10
5
  constructor(client) {
11
6
  this.client = client;
12
7
  }
13
- /**
14
- * List reviews
15
- *
16
- * @param options - Filter and pagination options
17
- * @returns Promise with array of reviews
18
- */
8
+ /** List reviews with optional filters */
19
9
  async list(options) {
20
- const queryParams = {};
21
- if (options?.limit !== undefined) {
22
- queryParams.limit = Math.min(options.limit, 100); // Max 100 per API
23
- }
24
- else {
25
- queryParams.limit = 10; // Default
26
- }
27
- if (options?.offset !== undefined) {
28
- queryParams.offset = options.offset;
29
- }
30
- if (options?.minRating !== undefined) {
31
- queryParams.minRating = options.minRating;
32
- }
33
- if (options?.maxRating !== undefined) {
34
- queryParams.maxRating = options.maxRating;
35
- }
36
- if (options?.sentiment) {
37
- queryParams.sentiment = options.sentiment;
38
- }
39
- if (options?.language) {
40
- queryParams.language = options.language;
41
- }
42
- if (options?.sourceId) {
43
- queryParams.sourceId = options.sourceId;
44
- }
45
- if (options?.since) {
46
- queryParams.since = options.since;
47
- }
48
- const response = await this.client.get('/api/v1/public/reviews', {
49
- queryParams,
10
+ return this.client.get('/api/v1/public/reviews', {
11
+ limit: options?.limit,
12
+ minRating: options?.minRating,
13
+ sentiment: options?.sentiment,
14
+ language: options?.language,
15
+ sourceId: options?.sourceId,
50
16
  });
51
- return response.data;
52
- }
53
- /**
54
- * Get a single review by ID
55
- *
56
- * Note: The API doesn't have a direct GET /reviews/:id endpoint,
57
- * so we fetch the list and filter. This is a convenience method.
58
- *
59
- * @param id - Review ID
60
- * @returns Promise with review or null if not found
61
- */
62
- async get(id) {
63
- // Since there's no direct GET endpoint, we need to fetch and filter
64
- // In a real implementation, you might want to cache reviews or use a different approach
65
- const reviews = await this.list({ limit: 100 });
66
- return reviews.find((review) => review.id === id) || null;
67
17
  }
68
18
  }
69
- exports.ReviewsResource = ReviewsResource;
19
+ exports.Reviews = Reviews;
@@ -1,58 +1,13 @@
1
1
  "use strict";
2
- /**
3
- * Widget Resource
4
- *
5
- * Handles widget-related API calls
6
- */
7
2
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.WidgetResource = void 0;
9
- class WidgetResource {
3
+ exports.Widget = void 0;
4
+ class Widget {
10
5
  constructor(client) {
11
6
  this.client = client;
12
7
  }
13
- /**
14
- * Get widget data
15
- *
16
- * Returns widget statistics and settings including:
17
- * - Total reviews
18
- * - Average rating
19
- * - Number of platforms
20
- * - Widget configuration (language, theme, badges, branding)
21
- *
22
- * @returns Promise with widget data
23
- */
8
+ /** Get widget data (stats + display settings) */
24
9
  async get() {
25
- const response = await this.client.get('/api/v1/public/widget');
26
- return response.data;
27
- }
28
- /**
29
- * Get widget configuration
30
- *
31
- * Returns only the widget settings/configuration.
32
- * This is a convenience method that extracts settings from get().
33
- *
34
- * @returns Promise with widget settings
35
- */
36
- async config() {
37
- const data = await this.get();
38
- return data.settings;
39
- }
40
- /**
41
- * Update widget configuration
42
- *
43
- * Note: Widget configuration updates typically require dashboard API access.
44
- * This method is provided for API consistency but may require
45
- * additional authentication setup.
46
- *
47
- * @param options - Widget configuration options
48
- * @returns Promise with updated widget settings
49
- */
50
- async updateConfig(options) {
51
- // Note: This endpoint typically requires authentication
52
- // The public API doesn't support widget config updates
53
- // This is a placeholder that matches the expected API structure
54
- const response = await this.client.post('/api/dashboard/widget-settings', options);
55
- return response.data;
10
+ return this.client.get('/api/v1/public/widget');
56
11
  }
57
12
  }
58
- exports.WidgetResource = WidgetResource;
13
+ exports.Widget = Widget;
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // ─── Client Configuration ────────────────────────────────────────────────────
3
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,12 @@
1
+ import { ProofioConfig } from './types';
2
+ export declare class HttpClient {
3
+ private readonly apiKey;
4
+ private readonly baseURL;
5
+ private readonly timeout;
6
+ private readonly maxRetries;
7
+ private readonly retryDelay;
8
+ constructor(config: ProofioConfig);
9
+ get<T>(path: string, params?: Record<string, string | number | undefined>): Promise<T>;
10
+ private buildURL;
11
+ private request;
12
+ }
@@ -0,0 +1,17 @@
1
+ export declare class ProofioError extends Error {
2
+ /** HTTP status code */
3
+ readonly status: number;
4
+ /** Error code from API response */
5
+ readonly code: string;
6
+ /** Retry-After header value in seconds (present on 429) */
7
+ readonly retryAfter: number | null;
8
+ /** Rate limit info from response headers */
9
+ readonly rateLimit: {
10
+ limit: number | null;
11
+ remaining: number | null;
12
+ reset: number | null;
13
+ };
14
+ constructor(message: string, status: number, code: string, headers?: Headers);
15
+ /** Whether this error is retryable (5xx or 429) */
16
+ isRetryable(): boolean;
17
+ }
@@ -0,0 +1,79 @@
1
+ import { ProofioError } from './error';
2
+ const DEFAULT_BASE_URL = 'https://api.proofio.app';
3
+ const DEFAULT_TIMEOUT = 30000;
4
+ const DEFAULT_MAX_RETRIES = 3;
5
+ const DEFAULT_RETRY_DELAY = 1000;
6
+ export class HttpClient {
7
+ constructor(config) {
8
+ if (!config.apiKey) {
9
+ throw new Error('proofio-sdk: apiKey is required');
10
+ }
11
+ this.apiKey = config.apiKey;
12
+ this.baseURL = (config.baseURL ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
13
+ this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
14
+ this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
15
+ this.retryDelay = config.retryDelay ?? DEFAULT_RETRY_DELAY;
16
+ }
17
+ async get(path, params) {
18
+ const url = this.buildURL(path, params);
19
+ return this.request(url);
20
+ }
21
+ buildURL(path, params) {
22
+ const url = new URL(`${this.baseURL}${path}`);
23
+ if (params) {
24
+ for (const [key, value] of Object.entries(params)) {
25
+ if (value !== undefined && value !== null) {
26
+ url.searchParams.set(key, String(value));
27
+ }
28
+ }
29
+ }
30
+ return url.toString();
31
+ }
32
+ async request(url, attempt = 0) {
33
+ const controller = new AbortController();
34
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
35
+ try {
36
+ const response = await fetch(url, {
37
+ method: 'GET',
38
+ headers: {
39
+ 'x-api-key': this.apiKey,
40
+ 'Content-Type': 'application/json',
41
+ },
42
+ signal: controller.signal,
43
+ });
44
+ if (!response.ok) {
45
+ const body = await response.json().catch(() => ({ error: response.statusText }));
46
+ const error = new ProofioError(body.message ?? body.error ?? `HTTP ${response.status}`, response.status, body.error ?? 'unknown_error', response.headers);
47
+ // Retry on 5xx or 429 (with backoff)
48
+ if (error.isRetryable() && attempt < this.maxRetries) {
49
+ const delay = error.status === 429 && error.retryAfter
50
+ ? error.retryAfter * 1000
51
+ : this.retryDelay * Math.pow(2, attempt);
52
+ await sleep(delay);
53
+ return this.request(url, attempt + 1);
54
+ }
55
+ throw error;
56
+ }
57
+ return (await response.json());
58
+ }
59
+ catch (err) {
60
+ if (err instanceof ProofioError)
61
+ throw err;
62
+ // Network / timeout errors are retryable
63
+ if (attempt < this.maxRetries) {
64
+ await sleep(this.retryDelay * Math.pow(2, attempt));
65
+ return this.request(url, attempt + 1);
66
+ }
67
+ if (err instanceof DOMException && err.name === 'AbortError') {
68
+ throw new ProofioError(`Request timed out after ${this.timeout}ms`, 0, 'timeout');
69
+ }
70
+ throw new ProofioError(err instanceof Error ? err.message : 'Network error', 0, 'network_error');
71
+ }
72
+ finally {
73
+ clearTimeout(timeoutId);
74
+ }
75
+ }
76
+ }
77
+ function sleep(ms) {
78
+ return new Promise((resolve) => setTimeout(resolve, ms));
79
+ }
@@ -0,0 +1,26 @@
1
+ export class ProofioError extends Error {
2
+ constructor(message, status, code, headers) {
3
+ super(message);
4
+ this.name = 'ProofioError';
5
+ this.status = status;
6
+ this.code = code;
7
+ const retryAfterHeader = headers?.get('retry-after');
8
+ this.retryAfter = retryAfterHeader ? parseInt(retryAfterHeader, 10) : null;
9
+ this.rateLimit = {
10
+ limit: parseHeaderInt(headers, 'x-ratelimit-limit'),
11
+ remaining: parseHeaderInt(headers, 'x-ratelimit-remaining'),
12
+ reset: parseHeaderInt(headers, 'x-ratelimit-reset'),
13
+ };
14
+ }
15
+ /** Whether this error is retryable (5xx or 429) */
16
+ isRetryable() {
17
+ return this.status === 429 || this.status >= 500;
18
+ }
19
+ }
20
+ function parseHeaderInt(headers, name) {
21
+ const value = headers?.get(name);
22
+ if (!value)
23
+ return null;
24
+ const parsed = parseInt(value, 10);
25
+ return isNaN(parsed) ? null : parsed;
26
+ }