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.
- package/API.md +691 -0
- package/CHANGELOG.md +49 -0
- package/CLAUDE.md +117 -0
- package/CONTRIBUTING.md +399 -0
- package/FIX_PLAN.md +70 -0
- package/INSPECTOR.md +191 -0
- package/LICENSE +21 -0
- package/PUBLISH.md +73 -0
- package/README.md +383 -0
- package/dist/auth/api-key-auth.d.ts +75 -0
- package/dist/auth/api-key-auth.d.ts.map +1 -0
- package/dist/auth/api-key-auth.js +103 -0
- package/dist/auth/oauth-auth.d.ts +81 -0
- package/dist/auth/oauth-auth.d.ts.map +1 -0
- package/dist/auth/oauth-auth.js +123 -0
- package/dist/auth/token-manager.d.ts +105 -0
- package/dist/auth/token-manager.d.ts.map +1 -0
- package/dist/auth/token-manager.js +87 -0
- package/dist/client/salesflare-client.d.ts +219 -0
- package/dist/client/salesflare-client.d.ts.map +1 -0
- package/dist/client/salesflare-client.js +484 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +82 -0
- package/dist/server.d.ts +39 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +140 -0
- package/dist/tools/companies.d.ts +45 -0
- package/dist/tools/companies.d.ts.map +1 -0
- package/dist/tools/companies.js +392 -0
- package/dist/tools/contacts.d.ts +45 -0
- package/dist/tools/contacts.d.ts.map +1 -0
- package/dist/tools/contacts.js +290 -0
- package/dist/tools/deals.d.ts +46 -0
- package/dist/tools/deals.d.ts.map +1 -0
- package/dist/tools/deals.js +442 -0
- package/dist/tools/pipeline.d.ts +43 -0
- package/dist/tools/pipeline.d.ts.map +1 -0
- package/dist/tools/pipeline.js +328 -0
- package/dist/tools/tasks.d.ts +44 -0
- package/dist/tools/tasks.d.ts.map +1 -0
- package/dist/tools/tasks.js +406 -0
- package/dist/transport/http-transport.d.ts +36 -0
- package/dist/transport/http-transport.d.ts.map +1 -0
- package/dist/transport/http-transport.js +173 -0
- package/dist/transport/stdio-transport.d.ts +37 -0
- package/dist/transport/stdio-transport.d.ts.map +1 -0
- package/dist/transport/stdio-transport.js +129 -0
- package/dist/types/company.d.ts +223 -0
- package/dist/types/company.d.ts.map +1 -0
- package/dist/types/company.js +8 -0
- package/dist/types/contact.d.ts +166 -0
- package/dist/types/contact.d.ts.map +1 -0
- package/dist/types/contact.js +8 -0
- package/dist/types/deal.d.ts +203 -0
- package/dist/types/deal.d.ts.map +1 -0
- package/dist/types/deal.js +8 -0
- package/dist/types/pipeline.d.ts +116 -0
- package/dist/types/pipeline.d.ts.map +1 -0
- package/dist/types/pipeline.js +8 -0
- package/dist/types/task.d.ts +154 -0
- package/dist/types/task.d.ts.map +1 -0
- package/dist/types/task.js +8 -0
- package/dist/utils/errors.d.ts +128 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +205 -0
- package/dist/utils/validation.d.ts +354 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +716 -0
- package/package.json +49 -0
- package/test-tasks-debug.js +21 -0
- package/test-tasks-params.js +52 -0
- 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
|
+
}
|