@vigneshreddy/cms-sdk 1.0.13 → 1.0.15
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/README.md +122 -37
- package/dist/src/circuit-breaker.d.ts +97 -0
- package/dist/src/circuit-breaker.js +162 -0
- package/dist/src/client.d.ts +3 -3
- package/dist/src/client.js +37 -12
- package/dist/src/constants/constants.d.ts +128 -0
- package/dist/src/constants/constants.js +182 -0
- package/dist/src/constants.d.ts +128 -0
- package/dist/src/constants.js +182 -0
- package/dist/src/errors.d.ts +1 -0
- package/dist/src/errors.js +34 -8
- package/dist/src/funcs/api.d.ts +192 -0
- package/dist/src/funcs/api.js +276 -0
- package/dist/src/funcs/base.d.ts +46 -0
- package/dist/src/funcs/base.js +40 -0
- package/dist/src/funcs/common.d.ts +20 -0
- package/dist/src/funcs/common.js +103 -0
- package/dist/src/funcs/configuration.d.ts +87 -0
- package/dist/src/funcs/configuration.js +37 -0
- package/dist/src/funcs/index.d.ts +4 -0
- package/dist/src/funcs/index.js +20 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.js +28 -1
- package/dist/src/rate-limiter.d.ts +74 -0
- package/dist/src/rate-limiter.js +109 -0
- package/dist/src/validation.d.ts +119 -0
- package/dist/src/validation.js +196 -0
- package/dist/src/validations/validation.d.ts +119 -0
- package/dist/src/validations/validation.js +196 -0
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
# @vigneshreddy/cms-sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@vigneshreddy/cms-sdk)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://nodejs.org/)
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
Official TypeScript/JavaScript SDK for the CutMeShort CMS API. Track leads and sales with reliable retry logic, comprehensive validation, and security best practices.
|
|
8
|
+
|
|
9
|
+
**Key Features:**
|
|
10
|
+
- 🚀 Full TypeScript support with strict types
|
|
11
|
+
- ✅ Input validation with Zod
|
|
12
|
+
- 🔄 Automatic retry with exponential backoff
|
|
13
|
+
- 🛡️ Security hardened (API key validation, sensitive data redaction)
|
|
14
|
+
- ⚡ Sub-second tracking events
|
|
15
|
+
- 📊 Comprehensive error handling with specific error types
|
|
16
|
+
|
|
17
|
+
---
|
|
8
18
|
|
|
9
19
|
## Install
|
|
10
20
|
|
|
@@ -41,7 +51,7 @@ const cms = new CMS({
|
|
|
41
51
|
const response = await cms.trackLead({
|
|
42
52
|
clickId: "id_123",
|
|
43
53
|
eventName: "signup_started",
|
|
44
|
-
|
|
54
|
+
customerExternalId: "user_42",
|
|
45
55
|
});
|
|
46
56
|
|
|
47
57
|
console.log(response);
|
|
@@ -59,16 +69,15 @@ const cms = new CMS({ apiKey: "sk_live_xxx" });
|
|
|
59
69
|
await cms.trackLead({
|
|
60
70
|
clickId: "id_123",
|
|
61
71
|
eventName: "signup_started",
|
|
62
|
-
|
|
72
|
+
customerExternalId: "user_42",
|
|
63
73
|
});
|
|
64
74
|
```
|
|
65
75
|
|
|
66
76
|
Lead payload fields:
|
|
67
77
|
- `clickId: string` (optional in **deferred** follow-up calls)
|
|
68
78
|
- `eventName: string`
|
|
69
|
-
- `
|
|
79
|
+
- `customerExternalId: string`
|
|
70
80
|
- `timestamp?: string` (ISO 8601, e.g. `new Date().toISOString()`)
|
|
71
|
-
- `customerExternalId?: string`
|
|
72
81
|
- `customerName?: string`
|
|
73
82
|
- `customerEmail?: string`
|
|
74
83
|
- `customerAvatar?: string`
|
|
@@ -82,18 +91,18 @@ import { CMS } from "@vigneshreddy/cms-sdk";
|
|
|
82
91
|
|
|
83
92
|
const cms = new CMS({ apiKey: "sk_live_xxx" });
|
|
84
93
|
|
|
85
|
-
// Step 1: store the clickId <->
|
|
94
|
+
// Step 1: store the clickId <-> customerExternalId association
|
|
86
95
|
await cms.trackLead({
|
|
87
96
|
clickId: "id_123",
|
|
88
97
|
eventName: "signup_started",
|
|
89
|
-
|
|
98
|
+
customerExternalId: "user_42",
|
|
90
99
|
mode: "deferred",
|
|
91
100
|
});
|
|
92
101
|
|
|
93
|
-
// Step 2: later, track using just
|
|
102
|
+
// Step 2: later, track using just customerExternalId (no clickId)
|
|
94
103
|
await cms.trackLead({
|
|
95
104
|
eventName: "email_verified",
|
|
96
|
-
|
|
105
|
+
customerExternalId: "user_42",
|
|
97
106
|
mode: "deferred",
|
|
98
107
|
});
|
|
99
108
|
```
|
|
@@ -103,10 +112,11 @@ await cms.trackLead({
|
|
|
103
112
|
```ts
|
|
104
113
|
import { CMS } from "@vigneshreddy/cms-sdk";
|
|
105
114
|
|
|
106
|
-
const cms = new CMS({ apiKey: "
|
|
115
|
+
const cms = new CMS({ apiKey: "xxx" });
|
|
107
116
|
|
|
108
117
|
await cms.trackSale({
|
|
109
118
|
clickId: "id_123",
|
|
119
|
+
customerExternalId: "user_123",
|
|
110
120
|
eventName: "purchase_completed",
|
|
111
121
|
invoiceId: "inv_987",
|
|
112
122
|
amount: 4999,
|
|
@@ -118,7 +128,7 @@ Sale payload fields:
|
|
|
118
128
|
- `clickId: string`
|
|
119
129
|
- `eventName: string`
|
|
120
130
|
- `timestamp?: string` (ISO 8601, e.g. `new Date().toISOString()`)
|
|
121
|
-
- `customerExternalId
|
|
131
|
+
- `customerExternalId: string`
|
|
122
132
|
- `customerName?: string`
|
|
123
133
|
- `customerEmail?: string`
|
|
124
134
|
- `customerAvatar?: string`
|
|
@@ -144,7 +154,7 @@ type CMSConfig = {
|
|
|
144
154
|
```
|
|
145
155
|
|
|
146
156
|
Defaults:
|
|
147
|
-
- `baseUrl`: `https://www.cutmeshort.com
|
|
157
|
+
- `baseUrl`: `https://www.cutmeshort.com`
|
|
148
158
|
- `timeout`: `10000`
|
|
149
159
|
- `maxRetries`: `2`
|
|
150
160
|
- `retryDelayMs`: `500`
|
|
@@ -165,7 +175,7 @@ await cms.trackLead(
|
|
|
165
175
|
{
|
|
166
176
|
clickId: "id_123",
|
|
167
177
|
eventName: "signup_started",
|
|
168
|
-
|
|
178
|
+
customerExternalId: "user_42",
|
|
169
179
|
},
|
|
170
180
|
{
|
|
171
181
|
timeout: 5000,
|
|
@@ -177,42 +187,117 @@ await cms.trackLead(
|
|
|
177
187
|
|
|
178
188
|
## Error Handling
|
|
179
189
|
|
|
180
|
-
|
|
190
|
+
The SDK provides specific error classes for different failure scenarios. Catch and handle them appropriately:
|
|
181
191
|
|
|
182
192
|
```ts
|
|
183
|
-
import {
|
|
193
|
+
import {
|
|
194
|
+
CMS,
|
|
195
|
+
CMSAPIError,
|
|
196
|
+
UnauthorizedError,
|
|
197
|
+
RateLimitError,
|
|
198
|
+
ValidationError
|
|
199
|
+
} from "@vigneshreddy/cms-sdk";
|
|
184
200
|
|
|
185
|
-
const cms = new CMS({ apiKey:
|
|
201
|
+
const cms = new CMS({ apiKey: process.env.CMS_API_KEY });
|
|
186
202
|
|
|
187
203
|
try {
|
|
188
|
-
await cms.
|
|
204
|
+
await cms.trackLead({
|
|
189
205
|
clickId: "id_123",
|
|
190
|
-
eventName: "
|
|
191
|
-
|
|
192
|
-
amount: 4999,
|
|
193
|
-
currency: "USD",
|
|
206
|
+
eventName: "signup_started",
|
|
207
|
+
customerExternalId: "user_42",
|
|
194
208
|
});
|
|
195
209
|
} catch (error) {
|
|
196
|
-
if (error instanceof
|
|
197
|
-
console.error("
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
});
|
|
210
|
+
if (error instanceof UnauthorizedError) {
|
|
211
|
+
console.error("Invalid API key - check your credentials");
|
|
212
|
+
} else if (error instanceof RateLimitError) {
|
|
213
|
+
console.error("Rate limited - wait before retrying");
|
|
214
|
+
} else if (error instanceof CMSAPIError) {
|
|
215
|
+
console.error(`CMS API error [${error.statusCode}]:`, error.message);
|
|
216
|
+
console.debug("Type:", error.type);
|
|
202
217
|
} else {
|
|
203
|
-
console.error("Unexpected error", error);
|
|
218
|
+
console.error("Unexpected error:", error);
|
|
204
219
|
}
|
|
205
220
|
}
|
|
206
221
|
```
|
|
207
222
|
|
|
223
|
+
### Available Error Types
|
|
224
|
+
|
|
225
|
+
| Error Type | HTTP Code | When It Happens |
|
|
226
|
+
|---|---|---|
|
|
227
|
+
| `BadRequestError` | 400 | Invalid payload format |
|
|
228
|
+
| `UnauthorizedError` | 401 | Invalid/missing API key |
|
|
229
|
+
| `ForbiddenError` | 403 | Insufficient permissions |
|
|
230
|
+
| `NotFoundError` | 404 | Resource not found |
|
|
231
|
+
| `ConflictError` | 409 | Resource already exists |
|
|
232
|
+
| `UnprocessableEntityError` | 422 | Validation failed |
|
|
233
|
+
| `RateLimitError` | 429 | Too many requests |
|
|
234
|
+
| `InternalServerError` | 500 | Server error |
|
|
235
|
+
| `BadGatewayError` | 502 | Gateway error (retried) |
|
|
236
|
+
| `ServiceUnavailableError` | 503 | Service down (retried) |
|
|
237
|
+
| `GatewayTimeoutError` | 504 | Timeout (retried) |
|
|
238
|
+
|
|
239
|
+
**Note:** Errors with HTTP codes 429, 500, 502, 503, 504 are automatically retried (configurable).
|
|
240
|
+
|
|
241
|
+
### Retry Behavior
|
|
242
|
+
|
|
243
|
+
The SDK automatically retries transient failures with exponential backoff:
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
const cms = new CMS({
|
|
247
|
+
apiKey: process.env.CMS_API_KEY,
|
|
248
|
+
maxRetries: 2, // default: 2
|
|
249
|
+
retryDelayMs: 500, // initial delay: 500ms
|
|
250
|
+
retryMaxDelayMs: 10000, // max delay cap: 10s
|
|
251
|
+
retryOnNetworkError: true,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Override for a specific request
|
|
255
|
+
await cms.trackLead(
|
|
256
|
+
{ clickId: "id_123", eventName: "event", customerExternalId: "user_1" },
|
|
257
|
+
{ maxRetries: 5, retryDelayMs: 1000 }
|
|
258
|
+
);
|
|
259
|
+
```
|
|
260
|
+
## TypeScript Usage
|
|
261
|
+
|
|
262
|
+
The SDK is fully typed. Utilize TypeScript for better DX:
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
import {
|
|
266
|
+
CMS,
|
|
267
|
+
type CMSConfig,
|
|
268
|
+
type LeadPayload,
|
|
269
|
+
type SalePayload,
|
|
270
|
+
type RequestOptions
|
|
271
|
+
} from "@vigneshreddy/cms-sdk";
|
|
272
|
+
|
|
273
|
+
// Config is validated at initialization
|
|
274
|
+
const config: CMSConfig = {
|
|
275
|
+
apiKey: process.env.CMS_API_KEY!,
|
|
276
|
+
timeout: 5000,
|
|
277
|
+
maxRetries: 3,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const cms = new CMS(config);
|
|
281
|
+
|
|
282
|
+
// Payloads have full type hints
|
|
283
|
+
const leadPayload: LeadPayload = {
|
|
284
|
+
clickId: "id_123",
|
|
285
|
+
eventName: "signup_started",
|
|
286
|
+
customerExteranlId: "user_42",
|
|
287
|
+
customerEmail: "user@example.com",
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
// RequestOptions for per-call configuration
|
|
291
|
+
const options: RequestOptions = {
|
|
292
|
+
timeout: 3000,
|
|
293
|
+
maxRetries: 1,
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
await cms.trackLead(leadPayload, options);
|
|
297
|
+
```
|
|
298
|
+
|
|
208
299
|
## Public API
|
|
209
300
|
|
|
210
301
|
This package intentionally exposes only:
|
|
211
302
|
- `CMS` (use `cms.trackLead` and `cms.trackSale`)
|
|
212
303
|
- `CMSAPIError` (for `instanceof` checks)
|
|
213
|
-
|
|
214
|
-
Deep imports (example: `@vigneshreddy/cms-sdk/client`) are intentionally blocked.
|
|
215
|
-
|
|
216
|
-
## Security Best Practice
|
|
217
|
-
|
|
218
|
-
Do not expose private API keys in public frontend code. Use this SDK from a trusted backend/server environment when using secret keys.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Circuit breaker to prevent cascading failures
|
|
3
|
+
* Fails fast when backend is unhealthy
|
|
4
|
+
*/
|
|
5
|
+
export declare enum CircuitBreakerState {
|
|
6
|
+
CLOSED = "CLOSED",// Normal operation
|
|
7
|
+
OPEN = "OPEN",// Failing fast
|
|
8
|
+
HALF_OPEN = "HALF_OPEN"
|
|
9
|
+
}
|
|
10
|
+
export interface CircuitBreakerOptions {
|
|
11
|
+
/** Number of failures before opening circuit */
|
|
12
|
+
failureThreshold: number;
|
|
13
|
+
/** Time window to count failures (milliseconds) */
|
|
14
|
+
failureWindowMs: number;
|
|
15
|
+
/** Time to wait before attempting to recover (milliseconds) */
|
|
16
|
+
resetTimeoutMs: number;
|
|
17
|
+
/** Callback when state changes */
|
|
18
|
+
onStateChange?: (newState: CircuitBreakerState, reason?: string) => void;
|
|
19
|
+
}
|
|
20
|
+
export declare class CircuitBreaker {
|
|
21
|
+
private state;
|
|
22
|
+
private failureCount;
|
|
23
|
+
private failureTimestamps;
|
|
24
|
+
private lastFailureTime;
|
|
25
|
+
private nextAttemptTime;
|
|
26
|
+
private failureThreshold;
|
|
27
|
+
private failureWindowMs;
|
|
28
|
+
private resetTimeoutMs;
|
|
29
|
+
private onStateChange?;
|
|
30
|
+
constructor(options: CircuitBreakerOptions);
|
|
31
|
+
/**
|
|
32
|
+
* Check if a request should be allowed
|
|
33
|
+
*/
|
|
34
|
+
canAttempt(): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Record a successful request
|
|
37
|
+
*/
|
|
38
|
+
recordSuccess(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Record a failed request
|
|
41
|
+
*/
|
|
42
|
+
recordFailure(): void;
|
|
43
|
+
/**
|
|
44
|
+
* Get current state
|
|
45
|
+
*/
|
|
46
|
+
getState(): CircuitBreakerState;
|
|
47
|
+
/**
|
|
48
|
+
* Get number of recent failures
|
|
49
|
+
*/
|
|
50
|
+
getFailureCount(): number;
|
|
51
|
+
/**
|
|
52
|
+
* Get milliseconds until circuit attempts to recover
|
|
53
|
+
*/
|
|
54
|
+
getRecoveryTime(): number;
|
|
55
|
+
/**
|
|
56
|
+
* Manually reset the circuit breaker
|
|
57
|
+
*/
|
|
58
|
+
reset(): void;
|
|
59
|
+
private setState;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Pre-configured circuit breaker settings for common scenarios
|
|
63
|
+
*/
|
|
64
|
+
export declare const CIRCUIT_BREAKER_PRESETS: {
|
|
65
|
+
/**
|
|
66
|
+
* Strict: Open after 2 failures in 10 seconds, wait 30 seconds before retry
|
|
67
|
+
*/
|
|
68
|
+
readonly STRICT: {
|
|
69
|
+
readonly failureThreshold: 2;
|
|
70
|
+
readonly failureWindowMs: 10000;
|
|
71
|
+
readonly resetTimeoutMs: 30000;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Standard: Open after 5 failures in 30 seconds, wait 60 seconds before retry
|
|
75
|
+
*/
|
|
76
|
+
readonly STANDARD: {
|
|
77
|
+
readonly failureThreshold: 5;
|
|
78
|
+
readonly failureWindowMs: 30000;
|
|
79
|
+
readonly resetTimeoutMs: 60000;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Lenient: Open after 10 failures in 60 seconds, wait 120 seconds before retry
|
|
83
|
+
*/
|
|
84
|
+
readonly LENIENT: {
|
|
85
|
+
readonly failureThreshold: 10;
|
|
86
|
+
readonly failureWindowMs: 60000;
|
|
87
|
+
readonly resetTimeoutMs: 120000;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Disabled: Circuit breaker always allows attempts
|
|
91
|
+
*/
|
|
92
|
+
readonly DISABLED: {
|
|
93
|
+
readonly failureThreshold: number;
|
|
94
|
+
readonly failureWindowMs: number;
|
|
95
|
+
readonly resetTimeoutMs: 0;
|
|
96
|
+
};
|
|
97
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Circuit breaker to prevent cascading failures
|
|
4
|
+
* Fails fast when backend is unhealthy
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.CIRCUIT_BREAKER_PRESETS = exports.CircuitBreaker = exports.CircuitBreakerState = void 0;
|
|
8
|
+
var CircuitBreakerState;
|
|
9
|
+
(function (CircuitBreakerState) {
|
|
10
|
+
CircuitBreakerState["CLOSED"] = "CLOSED";
|
|
11
|
+
CircuitBreakerState["OPEN"] = "OPEN";
|
|
12
|
+
CircuitBreakerState["HALF_OPEN"] = "HALF_OPEN";
|
|
13
|
+
})(CircuitBreakerState || (exports.CircuitBreakerState = CircuitBreakerState = {}));
|
|
14
|
+
class CircuitBreaker {
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.state = CircuitBreakerState.CLOSED;
|
|
17
|
+
this.failureCount = 0;
|
|
18
|
+
this.failureTimestamps = [];
|
|
19
|
+
this.lastFailureTime = 0;
|
|
20
|
+
this.nextAttemptTime = 0;
|
|
21
|
+
this.failureThreshold = options.failureThreshold;
|
|
22
|
+
this.failureWindowMs = options.failureWindowMs;
|
|
23
|
+
this.resetTimeoutMs = options.resetTimeoutMs;
|
|
24
|
+
this.onStateChange = options.onStateChange;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Check if a request should be allowed
|
|
28
|
+
*/
|
|
29
|
+
canAttempt() {
|
|
30
|
+
if (this.state === CircuitBreakerState.CLOSED) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
if (this.state === CircuitBreakerState.OPEN) {
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
if (now >= this.nextAttemptTime) {
|
|
36
|
+
// Try to recover
|
|
37
|
+
this.setState(CircuitBreakerState.HALF_OPEN, "Attempting recovery");
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
// HALF_OPEN - allow attempt
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Record a successful request
|
|
47
|
+
*/
|
|
48
|
+
recordSuccess() {
|
|
49
|
+
if (this.state === CircuitBreakerState.HALF_OPEN) {
|
|
50
|
+
this.setState(CircuitBreakerState.CLOSED, "Recovered successfully");
|
|
51
|
+
}
|
|
52
|
+
// Clear failure history on success
|
|
53
|
+
if (this.state === CircuitBreakerState.CLOSED) {
|
|
54
|
+
this.failureCount = 0;
|
|
55
|
+
this.failureTimestamps = [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Record a failed request
|
|
60
|
+
*/
|
|
61
|
+
recordFailure() {
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
this.lastFailureTime = now;
|
|
64
|
+
// Remove old failures outside the window
|
|
65
|
+
this.failureTimestamps = this.failureTimestamps.filter((timestamp) => now - timestamp < this.failureWindowMs);
|
|
66
|
+
// Add new failure
|
|
67
|
+
this.failureTimestamps.push(now);
|
|
68
|
+
this.failureCount = this.failureTimestamps.length;
|
|
69
|
+
if (this.state === CircuitBreakerState.HALF_OPEN) {
|
|
70
|
+
// Failed during recovery attempt
|
|
71
|
+
this.setState(CircuitBreakerState.OPEN, "Recovery attempt failed");
|
|
72
|
+
this.nextAttemptTime = now + this.resetTimeoutMs;
|
|
73
|
+
}
|
|
74
|
+
else if (this.state === CircuitBreakerState.CLOSED) {
|
|
75
|
+
// Check if threshold exceeded
|
|
76
|
+
if (this.failureCount >= this.failureThreshold) {
|
|
77
|
+
this.setState(CircuitBreakerState.OPEN, `${this.failureCount} failures in ${this.failureWindowMs}ms`);
|
|
78
|
+
this.nextAttemptTime = now + this.resetTimeoutMs;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get current state
|
|
84
|
+
*/
|
|
85
|
+
getState() {
|
|
86
|
+
return this.state;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get number of recent failures
|
|
90
|
+
*/
|
|
91
|
+
getFailureCount() {
|
|
92
|
+
// Clean up old failures
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
this.failureTimestamps = this.failureTimestamps.filter((timestamp) => now - timestamp < this.failureWindowMs);
|
|
95
|
+
return this.failureTimestamps.length;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get milliseconds until circuit attempts to recover
|
|
99
|
+
*/
|
|
100
|
+
getRecoveryTime() {
|
|
101
|
+
if (this.state !== CircuitBreakerState.OPEN) {
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
return Math.max(0, this.nextAttemptTime - Date.now());
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Manually reset the circuit breaker
|
|
108
|
+
*/
|
|
109
|
+
reset() {
|
|
110
|
+
this.setState(CircuitBreakerState.CLOSED, "Manual reset");
|
|
111
|
+
this.failureCount = 0;
|
|
112
|
+
this.failureTimestamps = [];
|
|
113
|
+
this.lastFailureTime = 0;
|
|
114
|
+
this.nextAttemptTime = 0;
|
|
115
|
+
}
|
|
116
|
+
setState(newState, reason) {
|
|
117
|
+
if (this.state !== newState) {
|
|
118
|
+
this.state = newState;
|
|
119
|
+
if (this.onStateChange) {
|
|
120
|
+
this.onStateChange(newState, reason);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
exports.CircuitBreaker = CircuitBreaker;
|
|
126
|
+
/**
|
|
127
|
+
* Pre-configured circuit breaker settings for common scenarios
|
|
128
|
+
*/
|
|
129
|
+
exports.CIRCUIT_BREAKER_PRESETS = {
|
|
130
|
+
/**
|
|
131
|
+
* Strict: Open after 2 failures in 10 seconds, wait 30 seconds before retry
|
|
132
|
+
*/
|
|
133
|
+
STRICT: {
|
|
134
|
+
failureThreshold: 2,
|
|
135
|
+
failureWindowMs: 10000,
|
|
136
|
+
resetTimeoutMs: 30000,
|
|
137
|
+
},
|
|
138
|
+
/**
|
|
139
|
+
* Standard: Open after 5 failures in 30 seconds, wait 60 seconds before retry
|
|
140
|
+
*/
|
|
141
|
+
STANDARD: {
|
|
142
|
+
failureThreshold: 5,
|
|
143
|
+
failureWindowMs: 30000,
|
|
144
|
+
resetTimeoutMs: 60000,
|
|
145
|
+
},
|
|
146
|
+
/**
|
|
147
|
+
* Lenient: Open after 10 failures in 60 seconds, wait 120 seconds before retry
|
|
148
|
+
*/
|
|
149
|
+
LENIENT: {
|
|
150
|
+
failureThreshold: 10,
|
|
151
|
+
failureWindowMs: 60000,
|
|
152
|
+
resetTimeoutMs: 120000,
|
|
153
|
+
},
|
|
154
|
+
/**
|
|
155
|
+
* Disabled: Circuit breaker always allows attempts
|
|
156
|
+
*/
|
|
157
|
+
DISABLED: {
|
|
158
|
+
failureThreshold: Infinity,
|
|
159
|
+
failureWindowMs: Infinity,
|
|
160
|
+
resetTimeoutMs: 0,
|
|
161
|
+
},
|
|
162
|
+
};
|
package/dist/src/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EventsApi, TrackLeadRequest, TrackSaleRequest } from "./
|
|
1
|
+
import { EventsApi, TrackLeadRequest, TrackSaleRequest } from "./funcs";
|
|
2
2
|
export type LeadPayload = TrackLeadRequest;
|
|
3
3
|
export type SalePayload = TrackSaleRequest;
|
|
4
4
|
export interface CMSConfig {
|
|
@@ -57,6 +57,6 @@ export declare class CMS {
|
|
|
57
57
|
private toGeneratedRequestOptions;
|
|
58
58
|
private withRetry;
|
|
59
59
|
private isRetryableErrorWithOptions;
|
|
60
|
-
trackLead(leadData: LeadPayload, options?: RequestOptions): Promise<import("./
|
|
61
|
-
trackSale(saleData: SalePayload, options?: RequestOptions): Promise<import("./
|
|
60
|
+
trackLead(leadData: LeadPayload, options?: RequestOptions): Promise<import("./funcs").TrackResponse>;
|
|
61
|
+
trackSale(saleData: SalePayload, options?: RequestOptions): Promise<import("./funcs").TrackResponse>;
|
|
62
62
|
}
|
package/dist/src/client.js
CHANGED
|
@@ -2,19 +2,26 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CMS = void 0;
|
|
4
4
|
// src/client.ts
|
|
5
|
-
const
|
|
5
|
+
const funcs_1 = require("./funcs");
|
|
6
6
|
const errors_1 = require("./errors");
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
7
|
+
const validation_1 = require("./validations/validation");
|
|
8
|
+
const constants_1 = require("./constants/constants");
|
|
9
|
+
const DEFAULT_BASE_URL = constants_1.API_CONFIG.BASE_URL;
|
|
10
|
+
const DEFAULT_TIMEOUT_MS = constants_1.API_CONFIG.TIMEOUT_MS;
|
|
11
|
+
const DEFAULT_MAX_RETRIES = constants_1.RETRY_CONFIG.MAX_RETRIES;
|
|
12
|
+
const DEFAULT_RETRY_DELAY_MS = constants_1.RETRY_CONFIG.DELAY_MS;
|
|
13
|
+
const DEFAULT_RETRY_MAX_DELAY_MS = constants_1.RETRY_CONFIG.MAX_DELAY_MS;
|
|
14
|
+
const DEFAULT_RETRY_STATUSES = [...constants_1.RETRY_CONFIG.ON_STATUSES];
|
|
13
15
|
class CMS {
|
|
14
16
|
constructor(config) {
|
|
15
17
|
var _a, _b, _c, _d;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
// Validate configuration with strict schema
|
|
19
|
+
try {
|
|
20
|
+
(0, validation_1.validateCMSConfig)(config);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
24
|
+
throw new Error(`CMS SDK: Invalid configuration - ${message}`);
|
|
18
25
|
}
|
|
19
26
|
const basePath = this.normalizeBaseUrl((_a = config.baseUrl) !== null && _a !== void 0 ? _a : DEFAULT_BASE_URL);
|
|
20
27
|
// Retry configuration with sensible defaults
|
|
@@ -24,20 +31,22 @@ class CMS {
|
|
|
24
31
|
this.retryOnStatuses = this.resolveRetryStatuses((_b = config.retryOnStatuses) !== null && _b !== void 0 ? _b : DEFAULT_RETRY_STATUSES);
|
|
25
32
|
this.retryOnNetworkError = (_c = config.retryOnNetworkError) !== null && _c !== void 0 ? _c : true;
|
|
26
33
|
// Create the Configuration object
|
|
27
|
-
const apiConfig = new
|
|
34
|
+
const apiConfig = new funcs_1.Configuration({
|
|
28
35
|
basePath,
|
|
29
36
|
accessToken: config.apiKey,
|
|
30
37
|
baseOptions: {
|
|
31
38
|
// Ensure generated client uses the same timeout & headers
|
|
32
39
|
timeout: (_d = config.timeout) !== null && _d !== void 0 ? _d : DEFAULT_TIMEOUT_MS,
|
|
33
40
|
headers: {
|
|
34
|
-
'Content-Type': 'application/json'
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
'X-CMS-SDK-Version': constants_1.SDK_VERSION,
|
|
43
|
+
'X-CMS-SDK-Runtime': typeof globalThis !== 'undefined' ? constants_1.HTTP_HEADERS.RUNTIME_NODEJS : constants_1.HTTP_HEADERS.RUNTIME_BROWSER,
|
|
35
44
|
},
|
|
36
45
|
},
|
|
37
46
|
});
|
|
38
47
|
// Instantiate the EventsApi using configuration and basePath.
|
|
39
48
|
// The underlying implementation uses the global `fetch` API.
|
|
40
|
-
this.events = new
|
|
49
|
+
this.events = new funcs_1.EventsApi(apiConfig, basePath);
|
|
41
50
|
}
|
|
42
51
|
async sleep(ms) {
|
|
43
52
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -182,12 +191,28 @@ class CMS {
|
|
|
182
191
|
return false;
|
|
183
192
|
}
|
|
184
193
|
async trackLead(leadData, options) {
|
|
194
|
+
// Validate lead data
|
|
195
|
+
try {
|
|
196
|
+
(0, validation_1.validateLeadPayload)(leadData);
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
200
|
+
throw new Error(`CMS SDK: Invalid lead data - ${message}`);
|
|
201
|
+
}
|
|
185
202
|
const requestOptions = this.toGeneratedRequestOptions(options);
|
|
186
203
|
return this.withRetry(async () => {
|
|
187
204
|
return this.events.trackLead(leadData, requestOptions);
|
|
188
205
|
}, options);
|
|
189
206
|
}
|
|
190
207
|
async trackSale(saleData, options) {
|
|
208
|
+
// Validate sale data
|
|
209
|
+
try {
|
|
210
|
+
(0, validation_1.validateSalePayload)(saleData);
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
214
|
+
throw new Error(`CMS SDK: Invalid sale data - ${message}`);
|
|
215
|
+
}
|
|
191
216
|
const requestOptions = this.toGeneratedRequestOptions(options);
|
|
192
217
|
return this.withRetry(async () => {
|
|
193
218
|
return this.events.trackSale(saleData, requestOptions);
|