salesflare-mcp-server 1.0.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 (73) hide show
  1. package/API.md +691 -0
  2. package/CHANGELOG.md +49 -0
  3. package/CLAUDE.md +117 -0
  4. package/CONTRIBUTING.md +399 -0
  5. package/FIX_PLAN.md +70 -0
  6. package/INSPECTOR.md +191 -0
  7. package/LICENSE +21 -0
  8. package/PUBLISH.md +73 -0
  9. package/README.md +383 -0
  10. package/dist/auth/api-key-auth.d.ts +75 -0
  11. package/dist/auth/api-key-auth.d.ts.map +1 -0
  12. package/dist/auth/api-key-auth.js +103 -0
  13. package/dist/auth/oauth-auth.d.ts +81 -0
  14. package/dist/auth/oauth-auth.d.ts.map +1 -0
  15. package/dist/auth/oauth-auth.js +123 -0
  16. package/dist/auth/token-manager.d.ts +105 -0
  17. package/dist/auth/token-manager.d.ts.map +1 -0
  18. package/dist/auth/token-manager.js +87 -0
  19. package/dist/client/salesflare-client.d.ts +219 -0
  20. package/dist/client/salesflare-client.d.ts.map +1 -0
  21. package/dist/client/salesflare-client.js +484 -0
  22. package/dist/index.d.ts +15 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +82 -0
  25. package/dist/server.d.ts +39 -0
  26. package/dist/server.d.ts.map +1 -0
  27. package/dist/server.js +140 -0
  28. package/dist/tools/companies.d.ts +45 -0
  29. package/dist/tools/companies.d.ts.map +1 -0
  30. package/dist/tools/companies.js +392 -0
  31. package/dist/tools/contacts.d.ts +45 -0
  32. package/dist/tools/contacts.d.ts.map +1 -0
  33. package/dist/tools/contacts.js +290 -0
  34. package/dist/tools/deals.d.ts +46 -0
  35. package/dist/tools/deals.d.ts.map +1 -0
  36. package/dist/tools/deals.js +442 -0
  37. package/dist/tools/pipeline.d.ts +43 -0
  38. package/dist/tools/pipeline.d.ts.map +1 -0
  39. package/dist/tools/pipeline.js +328 -0
  40. package/dist/tools/tasks.d.ts +44 -0
  41. package/dist/tools/tasks.d.ts.map +1 -0
  42. package/dist/tools/tasks.js +406 -0
  43. package/dist/transport/http-transport.d.ts +36 -0
  44. package/dist/transport/http-transport.d.ts.map +1 -0
  45. package/dist/transport/http-transport.js +173 -0
  46. package/dist/transport/stdio-transport.d.ts +37 -0
  47. package/dist/transport/stdio-transport.d.ts.map +1 -0
  48. package/dist/transport/stdio-transport.js +129 -0
  49. package/dist/types/company.d.ts +223 -0
  50. package/dist/types/company.d.ts.map +1 -0
  51. package/dist/types/company.js +8 -0
  52. package/dist/types/contact.d.ts +166 -0
  53. package/dist/types/contact.d.ts.map +1 -0
  54. package/dist/types/contact.js +8 -0
  55. package/dist/types/deal.d.ts +203 -0
  56. package/dist/types/deal.d.ts.map +1 -0
  57. package/dist/types/deal.js +8 -0
  58. package/dist/types/pipeline.d.ts +116 -0
  59. package/dist/types/pipeline.d.ts.map +1 -0
  60. package/dist/types/pipeline.js +8 -0
  61. package/dist/types/task.d.ts +154 -0
  62. package/dist/types/task.d.ts.map +1 -0
  63. package/dist/types/task.js +8 -0
  64. package/dist/utils/errors.d.ts +128 -0
  65. package/dist/utils/errors.d.ts.map +1 -0
  66. package/dist/utils/errors.js +205 -0
  67. package/dist/utils/validation.d.ts +354 -0
  68. package/dist/utils/validation.d.ts.map +1 -0
  69. package/dist/utils/validation.js +716 -0
  70. package/package.json +49 -0
  71. package/test-tasks-debug.js +21 -0
  72. package/test-tasks-params.js +52 -0
  73. package/test-tools.js +171 -0
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Salesflare API Client
3
+ *
4
+ * Provides a robust HTTP client for communicating with the Salesflare API.
5
+ * Features:
6
+ * - Automatic authentication via AuthProvider
7
+ * - Exponential backoff retry for rate limiting (429 responses)
8
+ * - Comprehensive error translation to SalesflareError
9
+ * - Type-safe HTTP methods with generics
10
+ *
11
+ * Per D-10, D-11: Verbose error responses with specific error codes.
12
+ * Per D-12: Includes retryable flag for LLM error handling.
13
+ *
14
+ * @module client/salesflare-client
15
+ */
16
+ import { AxiosRequestConfig } from 'axios';
17
+ import { AuthProvider } from '../auth/token-manager.js';
18
+ /**
19
+ * Configuration options for SalesflareClient
20
+ */
21
+ export interface SalesflareClientConfig {
22
+ /** Authentication provider for API requests */
23
+ authProvider: AuthProvider;
24
+ /** Base URL for Salesflare API (default: https://api.salesflare.com) */
25
+ baseURL?: string;
26
+ /** Request timeout in milliseconds (default: 30000) */
27
+ timeout?: number;
28
+ /** Maximum number of retries for rate-limited requests (default: 3) */
29
+ maxRetries?: number;
30
+ }
31
+ /**
32
+ * Salesflare API client with automatic retry and error handling
33
+ *
34
+ * Provides type-safe HTTP methods for communicating with the Salesflare API.
35
+ * Automatically handles authentication, rate limiting with exponential backoff,
36
+ * and error translation.
37
+ *
38
+ * @class
39
+ * @example
40
+ * ```typescript
41
+ * const client = new SalesflareClient({
42
+ * authProvider: apiKeyAuth,
43
+ * baseURL: 'https://api.salesflare.com',
44
+ * timeout: 30000,
45
+ * maxRetries: 3,
46
+ * });
47
+ *
48
+ * const contacts = await client.get<Contact[]>('/contacts');
49
+ * ```
50
+ */
51
+ export declare class SalesflareClient {
52
+ /** Axios instance for HTTP requests */
53
+ private axios;
54
+ /** Client configuration */
55
+ private config;
56
+ /** Retry attempt counter for exponential backoff */
57
+ private retryCount;
58
+ /**
59
+ * Create a new SalesflareClient instance
60
+ *
61
+ * @param config - Client configuration including auth provider
62
+ */
63
+ constructor(config: SalesflareClientConfig);
64
+ /**
65
+ * Setup request interceptor to add authentication headers
66
+ *
67
+ * Automatically calls AuthProvider.getAuthHeaders() for each request
68
+ * and merges the headers into the request config.
69
+ *
70
+ * @private
71
+ */
72
+ private setupRequestInterceptor;
73
+ /**
74
+ * Setup response interceptor for error handling and retry logic
75
+ *
76
+ * Handles various HTTP errors and implements exponential backoff
77
+ * for rate-limited requests (429 responses).
78
+ *
79
+ * @private
80
+ */
81
+ private setupResponseInterceptor;
82
+ /**
83
+ * Handle rate limit errors with exponential backoff retry
84
+ *
85
+ * Implements exponential backoff with jitter for 429 responses.
86
+ * Maximum delay capped at 30 seconds.
87
+ *
88
+ * @param error - The Axios error for the rate-limited request
89
+ * @returns Promise that resolves after retry or rejects with error
90
+ * @private
91
+ */
92
+ private handleRateLimitError;
93
+ /**
94
+ * Delay execution for specified milliseconds
95
+ *
96
+ * @param ms - Milliseconds to delay
97
+ * @returns Promise that resolves after delay
98
+ * @private
99
+ */
100
+ private delay;
101
+ /**
102
+ * Extract error message from API response data
103
+ *
104
+ * @param data - Response data from API error
105
+ * @returns Extracted error message or undefined
106
+ * @private
107
+ */
108
+ private extractErrorMessage;
109
+ /**
110
+ * Make a GET request to the Salesflare API
111
+ *
112
+ * @template T - Expected response type
113
+ * @param path - API endpoint path (e.g., '/contacts')
114
+ * @param config - Optional Axios request configuration
115
+ * @returns Promise resolving to typed response data
116
+ * @throws {SalesflareError} On authentication, validation, or API errors
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * const contacts = await client.get<Contact[]>('/contacts', {
121
+ * params: { limit: 10 }
122
+ * });
123
+ * ```
124
+ */
125
+ get<T>(path: string, config?: AxiosRequestConfig): Promise<T>;
126
+ /**
127
+ * Make a POST request to the Salesflare API
128
+ *
129
+ * @template T - Expected response type
130
+ * @param path - API endpoint path (e.g., '/contacts')
131
+ * @param data - Request body data
132
+ * @param config - Optional Axios request configuration
133
+ * @returns Promise resolving to typed response data
134
+ * @throws {SalesflareError} On authentication, validation, or API errors
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * const newContact = await client.post<Contact>('/contacts', {
139
+ * name: 'John Doe',
140
+ * email: 'john@example.com'
141
+ * });
142
+ * ```
143
+ */
144
+ post<T>(path: string, data: unknown, config?: AxiosRequestConfig): Promise<T>;
145
+ /**
146
+ * Make a PATCH request to the Salesflare API
147
+ *
148
+ * @template T - Expected response type
149
+ * @param path - API endpoint path (e.g., '/contacts/123')
150
+ * @param data - Request body data with fields to update
151
+ * @param config - Optional Axios request configuration
152
+ * @returns Promise resolving to typed response data
153
+ * @throws {SalesflareError} On authentication, validation, or API errors
154
+ *
155
+ * @example
156
+ * ```typescript
157
+ * const updatedContact = await client.patch<Contact>('/contacts/123', {
158
+ * name: 'Jane Doe'
159
+ * });
160
+ * ```
161
+ */
162
+ patch<T>(path: string, data: unknown, config?: AxiosRequestConfig): Promise<T>;
163
+ /**
164
+ * Make a DELETE request to the Salesflare API
165
+ *
166
+ * @template T - Expected response type (often void or empty object)
167
+ * @param path - API endpoint path (e.g., '/contacts/123')
168
+ * @param config - Optional Axios request configuration
169
+ * @returns Promise resolving to typed response data
170
+ * @throws {SalesflareError} On authentication or API errors
171
+ *
172
+ * @example
173
+ * ```typescript
174
+ * await client.delete('/contacts/123');
175
+ * ```
176
+ */
177
+ delete<T>(path: string, config?: AxiosRequestConfig): Promise<T>;
178
+ /**
179
+ * Wrap an unknown error in a SalesflareError
180
+ *
181
+ * @param error - The error to wrap
182
+ * @returns SalesflareError with appropriate code and message
183
+ * @private
184
+ */
185
+ private wrapError;
186
+ /**
187
+ * Get the current authentication provider
188
+ *
189
+ * @returns The AuthProvider instance
190
+ */
191
+ getAuthProvider(): AuthProvider;
192
+ /**
193
+ * Get the base URL for API requests
194
+ *
195
+ * @returns The configured base URL
196
+ */
197
+ getBaseURL(): string;
198
+ }
199
+ /**
200
+ * Factory function to create a configured SalesflareClient
201
+ *
202
+ * Convenience function for creating a new client instance with the given
203
+ * configuration. Validates required options and provides sensible defaults.
204
+ *
205
+ * @param config - Client configuration
206
+ * @returns Configured SalesflareClient instance
207
+ * @throws {SalesflareError} If required configuration is missing
208
+ *
209
+ * @example
210
+ * ```typescript
211
+ * const client = createSalesflareClient({
212
+ * authProvider: apiKeyAuth,
213
+ * timeout: 30000,
214
+ * maxRetries: 3,
215
+ * });
216
+ * ```
217
+ */
218
+ export declare function createSalesflareClient(config: SalesflareClientConfig): SalesflareClient;
219
+ //# sourceMappingURL=salesflare-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"salesflare-client.d.ts","sourceRoot":"","sources":["../../src/client/salesflare-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAc,EAEZ,kBAAkB,EAGnB,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAGxD;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,+CAA+C;IAC/C,YAAY,EAAE,YAAY,CAAC;IAC3B,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,gBAAgB;IAC3B,uCAAuC;IACvC,OAAO,CAAC,KAAK,CAAgB;IAC7B,2BAA2B;IAC3B,OAAO,CAAC,MAAM,CAAyB;IACvC,oDAAoD;IACpD,OAAO,CAAC,UAAU,CAAa;IAE/B;;;;OAIG;gBACS,MAAM,EAAE,sBAAsB;IAmC1C;;;;;;;OAOG;IACH,OAAO,CAAC,uBAAuB;IA8B/B;;;;;;;OAOG;IACH,OAAO,CAAC,wBAAwB;IA+FhC;;;;;;;;;OASG;YACW,oBAAoB;IAmElC;;;;;;OAMG;IACH,OAAO,CAAC,KAAK;IAIb;;;;;;OAMG;IACH,OAAO,CAAC,mBAAmB;IAc3B;;;;;;;;;;;;;;;OAeG;IACG,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,CAAC,CAAC;IAYnE;;;;;;;;;;;;;;;;;OAiBG;IACG,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,CAAC,CAAC;IAYnF;;;;;;;;;;;;;;;;OAgBG;IACG,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,CAAC,CAAC;IAYpF;;;;;;;;;;;;;OAaG;IACG,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,CAAC,CAAC;IAYtE;;;;;;OAMG;IACH,OAAO,CAAC,SAAS;IAsBjB;;;;OAIG;IACH,eAAe,IAAI,YAAY;IAI/B;;;;OAIG;IACH,UAAU,IAAI,MAAM;CAGrB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB,GAAG,gBAAgB,CAEvF"}
@@ -0,0 +1,484 @@
1
+ /**
2
+ * Salesflare API Client
3
+ *
4
+ * Provides a robust HTTP client for communicating with the Salesflare API.
5
+ * Features:
6
+ * - Automatic authentication via AuthProvider
7
+ * - Exponential backoff retry for rate limiting (429 responses)
8
+ * - Comprehensive error translation to SalesflareError
9
+ * - Type-safe HTTP methods with generics
10
+ *
11
+ * Per D-10, D-11: Verbose error responses with specific error codes.
12
+ * Per D-12: Includes retryable flag for LLM error handling.
13
+ *
14
+ * @module client/salesflare-client
15
+ */
16
+ import axios from 'axios';
17
+ import { SalesflareError, ErrorCode } from '../utils/errors.js';
18
+ /**
19
+ * Salesflare API client with automatic retry and error handling
20
+ *
21
+ * Provides type-safe HTTP methods for communicating with the Salesflare API.
22
+ * Automatically handles authentication, rate limiting with exponential backoff,
23
+ * and error translation.
24
+ *
25
+ * @class
26
+ * @example
27
+ * ```typescript
28
+ * const client = new SalesflareClient({
29
+ * authProvider: apiKeyAuth,
30
+ * baseURL: 'https://api.salesflare.com',
31
+ * timeout: 30000,
32
+ * maxRetries: 3,
33
+ * });
34
+ *
35
+ * const contacts = await client.get<Contact[]>('/contacts');
36
+ * ```
37
+ */
38
+ export class SalesflareClient {
39
+ /** Axios instance for HTTP requests */
40
+ axios;
41
+ /** Client configuration */
42
+ config;
43
+ /** Retry attempt counter for exponential backoff */
44
+ retryCount = 0;
45
+ /**
46
+ * Create a new SalesflareClient instance
47
+ *
48
+ * @param config - Client configuration including auth provider
49
+ */
50
+ constructor(config) {
51
+ this.config = {
52
+ baseURL: 'https://api.salesflare.com',
53
+ timeout: 30000,
54
+ maxRetries: 3,
55
+ ...config,
56
+ };
57
+ // Validate required auth provider
58
+ if (!this.config.authProvider) {
59
+ throw new SalesflareError({
60
+ code: ErrorCode.AUTH_REQUIRED,
61
+ message: 'AuthProvider is required for SalesflareClient',
62
+ fix: 'Provide an AuthProvider instance (ApiKeyAuth or OAuthAuth)',
63
+ retryable: false,
64
+ });
65
+ }
66
+ // Create axios instance with base configuration
67
+ this.axios = axios.create({
68
+ baseURL: this.config.baseURL,
69
+ timeout: this.config.timeout,
70
+ headers: {
71
+ 'Content-Type': 'application/json',
72
+ 'Accept': 'application/json',
73
+ },
74
+ });
75
+ // Setup request interceptor for authentication
76
+ this.setupRequestInterceptor();
77
+ // Setup response interceptor for error handling
78
+ this.setupResponseInterceptor();
79
+ }
80
+ /**
81
+ * Setup request interceptor to add authentication headers
82
+ *
83
+ * Automatically calls AuthProvider.getAuthHeaders() for each request
84
+ * and merges the headers into the request config.
85
+ *
86
+ * @private
87
+ */
88
+ setupRequestInterceptor() {
89
+ this.axios.interceptors.request.use(async (config) => {
90
+ try {
91
+ const authHeaders = await this.config.authProvider.getAuthHeaders();
92
+ // Merge auth headers into request config
93
+ config.headers = config.headers || {};
94
+ Object.entries(authHeaders).forEach(([key, value]) => {
95
+ config.headers[key] = value;
96
+ });
97
+ return config;
98
+ }
99
+ catch (error) {
100
+ // Handle auth header retrieval errors
101
+ if (error instanceof SalesflareError) {
102
+ throw error;
103
+ }
104
+ throw new SalesflareError({
105
+ code: ErrorCode.AUTH_INVALID,
106
+ message: 'Failed to get authentication headers',
107
+ fix: 'Check your authentication configuration',
108
+ retryable: false,
109
+ });
110
+ }
111
+ }, (error) => Promise.reject(error));
112
+ }
113
+ /**
114
+ * Setup response interceptor for error handling and retry logic
115
+ *
116
+ * Handles various HTTP errors and implements exponential backoff
117
+ * for rate-limited requests (429 responses).
118
+ *
119
+ * @private
120
+ */
121
+ setupResponseInterceptor() {
122
+ this.axios.interceptors.response.use((response) => {
123
+ // Reset retry count on successful response
124
+ this.retryCount = 0;
125
+ return response;
126
+ }, async (error) => {
127
+ // Handle network errors (no response received)
128
+ if (!error.response) {
129
+ throw new SalesflareError({
130
+ code: ErrorCode.NETWORK_ERROR,
131
+ message: 'Network error: Unable to connect to Salesflare API',
132
+ fix: 'Check your internet connection and try again',
133
+ retryable: true,
134
+ });
135
+ }
136
+ const { status, data } = error.response;
137
+ const errorData = data;
138
+ // Handle specific HTTP status codes
139
+ switch (status) {
140
+ case 401:
141
+ throw new SalesflareError({
142
+ code: ErrorCode.AUTH_EXPIRED,
143
+ message: 'Authentication expired or invalid',
144
+ fix: 'Check your SALESFLARE_API_KEY environment variable or re-authenticate',
145
+ retryable: false,
146
+ statusCode: 401,
147
+ details: errorData,
148
+ });
149
+ case 403:
150
+ throw new SalesflareError({
151
+ code: ErrorCode.AUTH_INVALID,
152
+ message: 'Access forbidden: insufficient permissions',
153
+ fix: 'Verify your API key has the required permissions',
154
+ retryable: false,
155
+ statusCode: 403,
156
+ details: errorData,
157
+ });
158
+ case 404:
159
+ throw new SalesflareError({
160
+ code: ErrorCode.NOT_FOUND,
161
+ message: `Resource not found: ${error.config?.url || 'unknown path'}`,
162
+ fix: 'Verify the resource ID is correct and the resource exists',
163
+ retryable: false,
164
+ statusCode: 404,
165
+ details: errorData,
166
+ });
167
+ case 429:
168
+ // Rate limit - implement exponential backoff retry
169
+ return this.handleRateLimitError(error);
170
+ case 400:
171
+ case 422:
172
+ throw new SalesflareError({
173
+ code: ErrorCode.VALIDATION_ERROR,
174
+ message: `Validation error: ${this.extractErrorMessage(errorData) || 'Invalid input'}`,
175
+ fix: 'Check the input parameters against the API schema',
176
+ retryable: false,
177
+ statusCode: status,
178
+ details: errorData,
179
+ });
180
+ case 500:
181
+ case 502:
182
+ case 503:
183
+ case 504:
184
+ throw new SalesflareError({
185
+ code: ErrorCode.SERVER_ERROR,
186
+ message: `Salesflare API server error (${status})`,
187
+ fix: 'The Salesflare API is experiencing issues. Try again later',
188
+ retryable: true,
189
+ statusCode: status,
190
+ details: errorData,
191
+ });
192
+ default:
193
+ throw new SalesflareError({
194
+ code: ErrorCode.API_ERROR,
195
+ message: `API error (${status}): ${this.extractErrorMessage(errorData) || 'Unknown error'}`,
196
+ fix: 'Check the API documentation or try again later',
197
+ retryable: status >= 500, // Server errors are retryable
198
+ statusCode: status,
199
+ details: errorData,
200
+ });
201
+ }
202
+ });
203
+ }
204
+ /**
205
+ * Handle rate limit errors with exponential backoff retry
206
+ *
207
+ * Implements exponential backoff with jitter for 429 responses.
208
+ * Maximum delay capped at 30 seconds.
209
+ *
210
+ * @param error - The Axios error for the rate-limited request
211
+ * @returns Promise that resolves after retry or rejects with error
212
+ * @private
213
+ */
214
+ async handleRateLimitError(error) {
215
+ const maxRetries = this.config.maxRetries || 3;
216
+ if (this.retryCount >= maxRetries) {
217
+ this.retryCount = 0;
218
+ throw new SalesflareError({
219
+ code: ErrorCode.RATE_LIMIT,
220
+ message: 'Rate limit exceeded. Maximum retries reached.',
221
+ fix: 'Wait before retrying; consider reducing request frequency',
222
+ retryable: true,
223
+ statusCode: 429,
224
+ });
225
+ }
226
+ // Check for Retry-After header
227
+ const retryAfter = error.response?.headers['retry-after'];
228
+ let delayMs;
229
+ if (retryAfter) {
230
+ // Parse Retry-After header (can be seconds or HTTP date)
231
+ const retryAfterSeconds = parseInt(retryAfter, 10);
232
+ if (!isNaN(retryAfterSeconds)) {
233
+ delayMs = retryAfterSeconds * 1000;
234
+ }
235
+ else {
236
+ // Parse as HTTP date
237
+ const retryDate = new Date(retryAfter);
238
+ delayMs = retryDate.getTime() - Date.now();
239
+ }
240
+ }
241
+ else {
242
+ // Exponential backoff: 2^attempt * 1000ms + jitter
243
+ const baseDelay = Math.pow(2, this.retryCount) * 1000;
244
+ // Add random jitter (0-1000ms) to prevent thundering herd
245
+ const jitter = Math.floor(Math.random() * 1000);
246
+ delayMs = baseDelay + jitter;
247
+ }
248
+ // Cap delay at 30 seconds
249
+ const maxDelayMs = 30000;
250
+ delayMs = Math.min(delayMs, maxDelayMs);
251
+ this.retryCount++;
252
+ // Wait and retry
253
+ await this.delay(delayMs);
254
+ // Retry the request
255
+ const config = error.config;
256
+ if (!config) {
257
+ throw new SalesflareError({
258
+ code: ErrorCode.RATE_LIMIT,
259
+ message: 'Rate limit error: unable to retry request',
260
+ fix: 'Try the request again manually',
261
+ retryable: true,
262
+ statusCode: 429,
263
+ });
264
+ }
265
+ try {
266
+ const response = await this.axios.request(config);
267
+ this.retryCount = 0;
268
+ return response.data;
269
+ }
270
+ catch (retryError) {
271
+ // If retry also fails, the interceptor will handle it again
272
+ throw retryError;
273
+ }
274
+ }
275
+ /**
276
+ * Delay execution for specified milliseconds
277
+ *
278
+ * @param ms - Milliseconds to delay
279
+ * @returns Promise that resolves after delay
280
+ * @private
281
+ */
282
+ delay(ms) {
283
+ return new Promise((resolve) => setTimeout(resolve, ms));
284
+ }
285
+ /**
286
+ * Extract error message from API response data
287
+ *
288
+ * @param data - Response data from API error
289
+ * @returns Extracted error message or undefined
290
+ * @private
291
+ */
292
+ extractErrorMessage(data) {
293
+ if (!data)
294
+ return undefined;
295
+ // Common error message fields in API responses
296
+ if (typeof data.message === 'string')
297
+ return data.message;
298
+ if (typeof data.error === 'string')
299
+ return data.error;
300
+ if (typeof data.error_message === 'string')
301
+ return data.error_message;
302
+ if (Array.isArray(data.errors) && data.errors.length > 0) {
303
+ return data.errors.join(', ');
304
+ }
305
+ return undefined;
306
+ }
307
+ /**
308
+ * Make a GET request to the Salesflare API
309
+ *
310
+ * @template T - Expected response type
311
+ * @param path - API endpoint path (e.g., '/contacts')
312
+ * @param config - Optional Axios request configuration
313
+ * @returns Promise resolving to typed response data
314
+ * @throws {SalesflareError} On authentication, validation, or API errors
315
+ *
316
+ * @example
317
+ * ```typescript
318
+ * const contacts = await client.get<Contact[]>('/contacts', {
319
+ * params: { limit: 10 }
320
+ * });
321
+ * ```
322
+ */
323
+ async get(path, config) {
324
+ try {
325
+ const response = await this.axios.get(path, config);
326
+ return response.data;
327
+ }
328
+ catch (error) {
329
+ if (error instanceof SalesflareError) {
330
+ throw error;
331
+ }
332
+ throw this.wrapError(error);
333
+ }
334
+ }
335
+ /**
336
+ * Make a POST request to the Salesflare API
337
+ *
338
+ * @template T - Expected response type
339
+ * @param path - API endpoint path (e.g., '/contacts')
340
+ * @param data - Request body data
341
+ * @param config - Optional Axios request configuration
342
+ * @returns Promise resolving to typed response data
343
+ * @throws {SalesflareError} On authentication, validation, or API errors
344
+ *
345
+ * @example
346
+ * ```typescript
347
+ * const newContact = await client.post<Contact>('/contacts', {
348
+ * name: 'John Doe',
349
+ * email: 'john@example.com'
350
+ * });
351
+ * ```
352
+ */
353
+ async post(path, data, config) {
354
+ try {
355
+ const response = await this.axios.post(path, data, config);
356
+ return response.data;
357
+ }
358
+ catch (error) {
359
+ if (error instanceof SalesflareError) {
360
+ throw error;
361
+ }
362
+ throw this.wrapError(error);
363
+ }
364
+ }
365
+ /**
366
+ * Make a PATCH request to the Salesflare API
367
+ *
368
+ * @template T - Expected response type
369
+ * @param path - API endpoint path (e.g., '/contacts/123')
370
+ * @param data - Request body data with fields to update
371
+ * @param config - Optional Axios request configuration
372
+ * @returns Promise resolving to typed response data
373
+ * @throws {SalesflareError} On authentication, validation, or API errors
374
+ *
375
+ * @example
376
+ * ```typescript
377
+ * const updatedContact = await client.patch<Contact>('/contacts/123', {
378
+ * name: 'Jane Doe'
379
+ * });
380
+ * ```
381
+ */
382
+ async patch(path, data, config) {
383
+ try {
384
+ const response = await this.axios.patch(path, data, config);
385
+ return response.data;
386
+ }
387
+ catch (error) {
388
+ if (error instanceof SalesflareError) {
389
+ throw error;
390
+ }
391
+ throw this.wrapError(error);
392
+ }
393
+ }
394
+ /**
395
+ * Make a DELETE request to the Salesflare API
396
+ *
397
+ * @template T - Expected response type (often void or empty object)
398
+ * @param path - API endpoint path (e.g., '/contacts/123')
399
+ * @param config - Optional Axios request configuration
400
+ * @returns Promise resolving to typed response data
401
+ * @throws {SalesflareError} On authentication or API errors
402
+ *
403
+ * @example
404
+ * ```typescript
405
+ * await client.delete('/contacts/123');
406
+ * ```
407
+ */
408
+ async delete(path, config) {
409
+ try {
410
+ const response = await this.axios.delete(path, config);
411
+ return response.data;
412
+ }
413
+ catch (error) {
414
+ if (error instanceof SalesflareError) {
415
+ throw error;
416
+ }
417
+ throw this.wrapError(error);
418
+ }
419
+ }
420
+ /**
421
+ * Wrap an unknown error in a SalesflareError
422
+ *
423
+ * @param error - The error to wrap
424
+ * @returns SalesflareError with appropriate code and message
425
+ * @private
426
+ */
427
+ wrapError(error) {
428
+ if (error instanceof SalesflareError) {
429
+ return error;
430
+ }
431
+ if (error instanceof Error) {
432
+ return new SalesflareError({
433
+ code: ErrorCode.API_ERROR,
434
+ message: error.message,
435
+ fix: 'Check the API documentation or try again later',
436
+ retryable: false,
437
+ });
438
+ }
439
+ return new SalesflareError({
440
+ code: ErrorCode.API_ERROR,
441
+ message: 'An unknown error occurred',
442
+ fix: 'Try again or contact support',
443
+ retryable: false,
444
+ });
445
+ }
446
+ /**
447
+ * Get the current authentication provider
448
+ *
449
+ * @returns The AuthProvider instance
450
+ */
451
+ getAuthProvider() {
452
+ return this.config.authProvider;
453
+ }
454
+ /**
455
+ * Get the base URL for API requests
456
+ *
457
+ * @returns The configured base URL
458
+ */
459
+ getBaseURL() {
460
+ return this.config.baseURL || 'https://api.salesflare.com';
461
+ }
462
+ }
463
+ /**
464
+ * Factory function to create a configured SalesflareClient
465
+ *
466
+ * Convenience function for creating a new client instance with the given
467
+ * configuration. Validates required options and provides sensible defaults.
468
+ *
469
+ * @param config - Client configuration
470
+ * @returns Configured SalesflareClient instance
471
+ * @throws {SalesflareError} If required configuration is missing
472
+ *
473
+ * @example
474
+ * ```typescript
475
+ * const client = createSalesflareClient({
476
+ * authProvider: apiKeyAuth,
477
+ * timeout: 30000,
478
+ * maxRetries: 3,
479
+ * });
480
+ * ```
481
+ */
482
+ export function createSalesflareClient(config) {
483
+ return new SalesflareClient(config);
484
+ }