@vigneshreddy/cms-sdk 1.0.14 → 1.0.16

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 CHANGED
@@ -1,10 +1,20 @@
1
1
  # @vigneshreddy/cms-sdk
2
2
 
3
- Official TypeScript/JavaScript SDK for the CutMeShort CMS API.
3
+ [![npm version](https://img.shields.io/npm/v/@vigneshreddy/cms-sdk)](https://www.npmjs.com/package/@vigneshreddy/cms-sdk)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Node.js 18+](https://img.shields.io/badge/Node.js-18%2B-green)](https://nodejs.org/)
4
6
 
5
- Use this package to send:
6
- - lead tracking events (`cms.trackLead`)
7
- - sale tracking events (`cms.trackSale`)
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
- customerId: "user_42",
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
- customerId: "user_42",
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
- - `customerId: string`
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 <-> customerId association
94
+ // Step 1: store the clickId <-> customerExternalId association
86
95
  await cms.trackLead({
87
96
  clickId: "id_123",
88
97
  eventName: "signup_started",
89
- customerId: "user_42",
98
+ customerExternalId: "user_42",
90
99
  mode: "deferred",
91
100
  });
92
101
 
93
- // Step 2: later, track using just customerId (no clickId)
102
+ // Step 2: later, track using just customerExternalId (no clickId)
94
103
  await cms.trackLead({
95
104
  eventName: "email_verified",
96
- customerId: "user_42",
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: "sk_live_xxx" });
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?: string`
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/sdk`
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
- customerId: "user_42",
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
- Class-based methods (`cms.trackLead`, `cms.trackSale`) throw `CMSAPIError` on failure.
190
+ The SDK provides specific error classes for different failure scenarios. Catch and handle them appropriately:
181
191
 
182
192
  ```ts
183
- import { CMS, CMSAPIError } from "@vigneshreddy/cms-sdk";
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: "sk_live_xxx" });
201
+ const cms = new CMS({ apiKey: process.env.CMS_API_KEY });
186
202
 
187
203
  try {
188
- await cms.trackSale({
204
+ await cms.trackLead({
189
205
  clickId: "id_123",
190
- eventName: "purchase_completed",
191
- invoiceId: "inv_987",
192
- amount: 4999,
193
- currency: "USD",
206
+ eventName: "signup_started",
207
+ customerExternalId: "user_42",
194
208
  });
195
209
  } catch (error) {
196
- if (error instanceof CMSAPIError) {
197
- console.error("CMS API error", {
198
- statusCode: error.statusCode,
199
- type: error.type,
200
- message: error.message,
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.
@@ -1,4 +1,4 @@
1
- import { EventsApi, TrackLeadRequest, TrackSaleRequest } from "./generated";
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("./generated").TrackResponse>;
61
- trackSale(saleData: SalePayload, options?: RequestOptions): Promise<import("./generated").TrackResponse>;
60
+ trackLead(leadData: LeadPayload, options?: RequestOptions): Promise<import("./funcs").TrackResponse>;
61
+ trackSale(saleData: SalePayload, options?: RequestOptions): Promise<import("./funcs").TrackResponse>;
62
62
  }
@@ -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 generated_1 = require("./generated");
5
+ const funcs_1 = require("./funcs");
6
6
  const errors_1 = require("./errors");
7
- const DEFAULT_BASE_URL = "https://www.cutmeshort.com";
8
- const DEFAULT_TIMEOUT_MS = 10000;
9
- const DEFAULT_MAX_RETRIES = 2;
10
- const DEFAULT_RETRY_DELAY_MS = 500;
11
- const DEFAULT_RETRY_MAX_DELAY_MS = 10000;
12
- const DEFAULT_RETRY_STATUSES = [429, 500, 502, 503, 504];
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
- if (!config.apiKey) {
17
- throw new Error("CMS SDK: apiKey is required.");
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 generated_1.Configuration({
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 generated_1.EventsApi(apiConfig, basePath);
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);
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Centralized constants for the CMS SDK
3
+ * This single source of truth makes it easy to adjust configuration without updating multiple files
4
+ */
5
+ export declare const SDK_VERSION = "1.0.14";
6
+ export declare const API_CONFIG: {
7
+ /** Default base URL for the CMS API */
8
+ readonly BASE_URL: "https://www.cutmeshort.com";
9
+ /** Default timeout in milliseconds for HTTP requests */
10
+ readonly TIMEOUT_MS: 10000;
11
+ };
12
+ export declare const RETRY_CONFIG: {
13
+ /** Default maximum number of retry attempts for transient failures */
14
+ readonly MAX_RETRIES: 2;
15
+ /** Default base delay (in ms) between retries for transient failures */
16
+ readonly DELAY_MS: 500;
17
+ /** Default maximum delay cap (in ms) for retry backoff */
18
+ readonly MAX_DELAY_MS: 10000;
19
+ /** HTTP status codes that should be retried by default */
20
+ readonly ON_STATUSES: readonly [429, 500, 502, 503, 504];
21
+ /** Whether to retry on network errors (no response / timeout) by default */
22
+ readonly ON_NETWORK_ERROR: true;
23
+ };
24
+ export declare const VALIDATION_CONSTRAINTS: {
25
+ readonly API_KEY: {
26
+ /** Regex pattern for valid API keys: must be 20+ alphanumeric chars */
27
+ readonly PATTERN: RegExp;
28
+ };
29
+ readonly TIMEOUT: {
30
+ readonly MIN_MS: 1000;
31
+ readonly MAX_MS: 60000;
32
+ };
33
+ readonly MAX_RETRIES: {
34
+ readonly MIN: 0;
35
+ readonly MAX: 10;
36
+ };
37
+ readonly RETRY_DELAY: {
38
+ readonly MIN_MS: 100;
39
+ readonly MAX_MS: 30000;
40
+ };
41
+ readonly RETRY_MAX_DELAY: {
42
+ readonly MIN_MS: 1000;
43
+ readonly MAX_MS: 120000;
44
+ };
45
+ readonly STRING_FIELDS: {
46
+ readonly CLICK_ID: {
47
+ readonly MIN: 1;
48
+ readonly MAX: 255;
49
+ };
50
+ readonly CUSTOMER_ID: {
51
+ readonly MIN: 1;
52
+ readonly MAX: 255;
53
+ };
54
+ readonly CUSTOMER_EXTERNAL_ID: {
55
+ readonly MIN: 1;
56
+ readonly MAX: 255;
57
+ };
58
+ readonly CUSTOMER_NAME: {
59
+ readonly MAX: 255;
60
+ };
61
+ readonly CUSTOMER_EMAIL: {
62
+ readonly MAX: 255;
63
+ };
64
+ readonly EVENT_NAME: {
65
+ readonly MIN: 1;
66
+ readonly MAX: 255;
67
+ };
68
+ readonly INVOICE_ID: {
69
+ readonly MIN: 1;
70
+ readonly MAX: 255;
71
+ };
72
+ };
73
+ readonly CURRENCY: {
74
+ readonly LENGTH: 3;
75
+ readonly PATTERN: RegExp;
76
+ };
77
+ readonly AMOUNT: {
78
+ readonly MIN: 0;
79
+ };
80
+ };
81
+ export declare const ERROR_MESSAGES: {
82
+ readonly API_KEY_REQUIRED: "apiKey is required";
83
+ readonly API_KEY_INVALID_FORMAT: "apiKey must be a valid API key format";
84
+ readonly BASE_URL_INVALID: "baseUrl must be a valid absolute URL";
85
+ readonly TIMEOUT_TOO_LOW: "timeout must be at least 1000ms";
86
+ readonly TIMEOUT_TOO_HIGH: "timeout must not exceed 60000ms";
87
+ readonly MAX_RETRIES_NEGATIVE: "maxRetries must be non-negative";
88
+ readonly MAX_RETRIES_TOO_HIGH: "maxRetries must not exceed 10";
89
+ readonly RETRY_DELAY_TOO_LOW: "retryDelayMs must be at least 100ms";
90
+ readonly RETRY_DELAY_TOO_HIGH: "retryDelayMs must not exceed 30000ms";
91
+ readonly RETRY_MAX_DELAY_TOO_LOW: "retryMaxDelayMs must be at least 1000ms";
92
+ readonly RETRY_MAX_DELAY_TOO_HIGH: "retryMaxDelayMs must not exceed 120000ms";
93
+ readonly EVENT_NAME_REQUIRED: "eventName is required";
94
+ readonly EVENT_NAME_TOO_LONG: "eventName must not exceed 255 characters";
95
+ readonly CUSTOMER_ID_REQUIRED: "customerId is required";
96
+ readonly CUSTOMER_ID_TOO_LONG: "customerId must not exceed 255 characters";
97
+ readonly CUSTOMER_EXTERNAL_ID_REQUIRED: "customerExternalId is required";
98
+ readonly CUSTOMER_EXTERNAL_ID_TOO_LONG: "customerExternalId must not exceed 255 characters";
99
+ readonly CUSTOMER_EMAIL_INVALID: "customerEmail must be a valid email";
100
+ readonly CUSTOMER_EMAIL_TOO_LONG: "customerEmail must not exceed 255 characters";
101
+ readonly CUSTOMER_AVATAR_INVALID: "customerAvatar must be a valid URL";
102
+ readonly INVOICE_ID_REQUIRED: "invoiceId is required";
103
+ readonly INVOICE_ID_TOO_LONG: "invoiceId must not exceed 255 characters";
104
+ readonly AMOUNT_NEGATIVE: "amount must be non-negative";
105
+ readonly CURRENCY_INVALID_LENGTH: "currency must be 3-letter code (e.g., USD)";
106
+ readonly CURRENCY_INVALID_FORMAT: "currency must be uppercase ISO 4217 code";
107
+ readonly CURRENCY_OPTIONAL_IN_SALE: "currency is optional in sale tracking";
108
+ };
109
+ export declare const HTTP_HEADERS: {
110
+ /** Content type for all API requests */
111
+ readonly CONTENT_TYPE: "application/json";
112
+ /** SDK version header key */
113
+ readonly SDK_VERSION_HEADER: "X-CMS-SDK-Version";
114
+ /** SDK runtime detection header key */
115
+ readonly SDK_RUNTIME_HEADER: "X-CMS-SDK-Runtime";
116
+ /** SDK runtime value for Node.js */
117
+ readonly RUNTIME_NODEJS: "nodejs";
118
+ /** SDK runtime value for browser */
119
+ readonly RUNTIME_BROWSER: "browser";
120
+ };
121
+ export declare const SENSITIVE_FIELDS: {
122
+ /** List of field names that should be redacted in logs */
123
+ readonly PATTERNS: readonly ["apiKey", "accessToken", "token", "password", "secret", "authorization", "x-api-key"];
124
+ /** Redaction placeholder */
125
+ readonly REDACTION_PLACEHOLDER: "[REDACTED]";
126
+ /** API key redaction placeholder */
127
+ readonly API_KEY_REDACTION: "[REDACTED_API_KEY]";
128
+ };
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ /**
3
+ * Centralized constants for the CMS SDK
4
+ * This single source of truth makes it easy to adjust configuration without updating multiple files
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.SENSITIVE_FIELDS = exports.HTTP_HEADERS = exports.ERROR_MESSAGES = exports.VALIDATION_CONSTRAINTS = exports.RETRY_CONFIG = exports.API_CONFIG = exports.SDK_VERSION = void 0;
8
+ // ============================================================================
9
+ // SDK VERSION
10
+ // ============================================================================
11
+ exports.SDK_VERSION = "1.0.14";
12
+ // ============================================================================
13
+ // API CONFIGURATION
14
+ // ============================================================================
15
+ exports.API_CONFIG = {
16
+ /** Default base URL for the CMS API */
17
+ BASE_URL: "https://www.cutmeshort.com",
18
+ /** Default timeout in milliseconds for HTTP requests */
19
+ TIMEOUT_MS: 10000,
20
+ };
21
+ // ============================================================================
22
+ // RETRY CONFIGURATION
23
+ // ============================================================================
24
+ exports.RETRY_CONFIG = {
25
+ /** Default maximum number of retry attempts for transient failures */
26
+ MAX_RETRIES: 2,
27
+ /** Default base delay (in ms) between retries for transient failures */
28
+ DELAY_MS: 500,
29
+ /** Default maximum delay cap (in ms) for retry backoff */
30
+ MAX_DELAY_MS: 10000,
31
+ /** HTTP status codes that should be retried by default */
32
+ ON_STATUSES: [429, 500, 502, 503, 504],
33
+ /** Whether to retry on network errors (no response / timeout) by default */
34
+ ON_NETWORK_ERROR: true,
35
+ };
36
+ // ============================================================================
37
+ // VALIDATION CONSTRAINTS
38
+ // ============================================================================
39
+ exports.VALIDATION_CONSTRAINTS = {
40
+ // API Key validation
41
+ API_KEY: {
42
+ /** Regex pattern for valid API keys: must be 20+ alphanumeric chars */
43
+ PATTERN: /^[a-zA-Z0-9_]{20,}$/,
44
+ },
45
+ // Timeout constraints
46
+ TIMEOUT: {
47
+ MIN_MS: 1000,
48
+ MAX_MS: 60000,
49
+ },
50
+ // Max retries constraints
51
+ MAX_RETRIES: {
52
+ MIN: 0,
53
+ MAX: 10,
54
+ },
55
+ // Retry delay constraints
56
+ RETRY_DELAY: {
57
+ MIN_MS: 100,
58
+ MAX_MS: 30000,
59
+ },
60
+ // Retry max delay constraints
61
+ RETRY_MAX_DELAY: {
62
+ MIN_MS: 1000,
63
+ MAX_MS: 120000,
64
+ },
65
+ // String field constraints
66
+ STRING_FIELDS: {
67
+ CLICK_ID: {
68
+ MIN: 1,
69
+ MAX: 255,
70
+ },
71
+ CUSTOMER_ID: {
72
+ MIN: 1,
73
+ MAX: 255,
74
+ },
75
+ CUSTOMER_EXTERNAL_ID: {
76
+ MIN: 1,
77
+ MAX: 255,
78
+ },
79
+ CUSTOMER_NAME: {
80
+ MAX: 255,
81
+ },
82
+ CUSTOMER_EMAIL: {
83
+ MAX: 255,
84
+ },
85
+ EVENT_NAME: {
86
+ MIN: 1,
87
+ MAX: 255,
88
+ },
89
+ INVOICE_ID: {
90
+ MIN: 1,
91
+ MAX: 255,
92
+ },
93
+ },
94
+ // Currency code constraints (ISO 4217)
95
+ CURRENCY: {
96
+ LENGTH: 3,
97
+ PATTERN: /^[A-Z]{3}$/,
98
+ },
99
+ // Amount constraints
100
+ AMOUNT: {
101
+ MIN: 0,
102
+ },
103
+ };
104
+ // ============================================================================
105
+ // ERROR HANDLING
106
+ // ============================================================================
107
+ exports.ERROR_MESSAGES = {
108
+ // API Key errors
109
+ API_KEY_REQUIRED: "apiKey is required",
110
+ API_KEY_INVALID_FORMAT: "apiKey must be a valid API key format",
111
+ // Base URL errors
112
+ BASE_URL_INVALID: "baseUrl must be a valid absolute URL",
113
+ // Timeout errors
114
+ TIMEOUT_TOO_LOW: `timeout must be at least ${exports.VALIDATION_CONSTRAINTS.TIMEOUT.MIN_MS}ms`,
115
+ TIMEOUT_TOO_HIGH: `timeout must not exceed ${exports.VALIDATION_CONSTRAINTS.TIMEOUT.MAX_MS}ms`,
116
+ // Max retries errors
117
+ MAX_RETRIES_NEGATIVE: "maxRetries must be non-negative",
118
+ MAX_RETRIES_TOO_HIGH: `maxRetries must not exceed ${exports.VALIDATION_CONSTRAINTS.MAX_RETRIES.MAX}`,
119
+ // Retry delay errors
120
+ RETRY_DELAY_TOO_LOW: `retryDelayMs must be at least ${exports.VALIDATION_CONSTRAINTS.RETRY_DELAY.MIN_MS}ms`,
121
+ RETRY_DELAY_TOO_HIGH: `retryDelayMs must not exceed ${exports.VALIDATION_CONSTRAINTS.RETRY_DELAY.MAX_MS}ms`,
122
+ // Retry max delay errors
123
+ RETRY_MAX_DELAY_TOO_LOW: `retryMaxDelayMs must be at least ${exports.VALIDATION_CONSTRAINTS.RETRY_MAX_DELAY.MIN_MS}ms`,
124
+ RETRY_MAX_DELAY_TOO_HIGH: `retryMaxDelayMs must not exceed ${exports.VALIDATION_CONSTRAINTS.RETRY_MAX_DELAY.MAX_MS}ms`,
125
+ // Event name errors
126
+ EVENT_NAME_REQUIRED: "eventName is required",
127
+ EVENT_NAME_TOO_LONG: `eventName must not exceed ${exports.VALIDATION_CONSTRAINTS.STRING_FIELDS.EVENT_NAME.MAX} characters`,
128
+ // Customer ID errors
129
+ CUSTOMER_ID_REQUIRED: "customerId is required",
130
+ CUSTOMER_ID_TOO_LONG: `customerId must not exceed ${exports.VALIDATION_CONSTRAINTS.STRING_FIELDS.CUSTOMER_ID.MAX} characters`,
131
+ // Customer external ID errors
132
+ CUSTOMER_EXTERNAL_ID_REQUIRED: "customerExternalId is required",
133
+ CUSTOMER_EXTERNAL_ID_TOO_LONG: `customerExternalId must not exceed ${exports.VALIDATION_CONSTRAINTS.STRING_FIELDS.CUSTOMER_EXTERNAL_ID.MAX} characters`,
134
+ // Email errors
135
+ CUSTOMER_EMAIL_INVALID: "customerEmail must be a valid email",
136
+ CUSTOMER_EMAIL_TOO_LONG: `customerEmail must not exceed ${exports.VALIDATION_CONSTRAINTS.STRING_FIELDS.CUSTOMER_EMAIL.MAX} characters`,
137
+ // Avatar errors
138
+ CUSTOMER_AVATAR_INVALID: "customerAvatar must be a valid URL",
139
+ // Invoice ID errors
140
+ INVOICE_ID_REQUIRED: "invoiceId is required",
141
+ INVOICE_ID_TOO_LONG: `invoiceId must not exceed ${exports.VALIDATION_CONSTRAINTS.STRING_FIELDS.INVOICE_ID.MAX} characters`,
142
+ // Amount errors
143
+ AMOUNT_NEGATIVE: "amount must be non-negative",
144
+ // Currency errors
145
+ CURRENCY_INVALID_LENGTH: `currency must be ${exports.VALIDATION_CONSTRAINTS.CURRENCY.LENGTH}-letter code (e.g., USD)`,
146
+ CURRENCY_INVALID_FORMAT: "currency must be uppercase ISO 4217 code",
147
+ CURRENCY_OPTIONAL_IN_SALE: "currency is optional in sale tracking",
148
+ };
149
+ // ============================================================================
150
+ // HTTP HEADERS
151
+ // ============================================================================
152
+ exports.HTTP_HEADERS = {
153
+ /** Content type for all API requests */
154
+ CONTENT_TYPE: "application/json",
155
+ /** SDK version header key */
156
+ SDK_VERSION_HEADER: "X-CMS-SDK-Version",
157
+ /** SDK runtime detection header key */
158
+ SDK_RUNTIME_HEADER: "X-CMS-SDK-Runtime",
159
+ /** SDK runtime value for Node.js */
160
+ RUNTIME_NODEJS: "nodejs",
161
+ /** SDK runtime value for browser */
162
+ RUNTIME_BROWSER: "browser",
163
+ };
164
+ // ============================================================================
165
+ // SENSITIVE FIELD PATTERNS
166
+ // ============================================================================
167
+ exports.SENSITIVE_FIELDS = {
168
+ /** List of field names that should be redacted in logs */
169
+ PATTERNS: [
170
+ "apiKey",
171
+ "accessToken",
172
+ "token",
173
+ "password",
174
+ "secret",
175
+ "authorization",
176
+ "x-api-key",
177
+ ],
178
+ /** Redaction placeholder */
179
+ REDACTION_PLACEHOLDER: "[REDACTED]",
180
+ /** API key redaction placeholder */
181
+ API_KEY_REDACTION: "[REDACTED_API_KEY]",
182
+ };
@@ -4,5 +4,6 @@ export { BadRequestError, UnauthorizedError, ForbiddenError, NotFoundError, Conf
4
4
  /**
5
5
  * Parses fetch/network errors into a clean CMSAPIError (or specific subclass)
6
6
  * Uses specific error types when possible for better type safety
7
+ * Sanitizes sensitive information before storing
7
8
  */
8
9
  export declare function handleApiError(error: unknown, request?: unknown, response?: unknown): never;
@@ -23,6 +23,7 @@ Object.defineProperty(exports, "createSpecificError", { enumerable: true, get: f
23
23
  /**
24
24
  * Parses fetch/network errors into a clean CMSAPIError (or specific subclass)
25
25
  * Uses specific error types when possible for better type safety
26
+ * Sanitizes sensitive information before storing
26
27
  */
27
28
  function handleApiError(error, request, response) {
28
29
  var _a, _b, _c, _d, _e, _f;
@@ -71,14 +72,29 @@ function handleApiError(error, request, response) {
71
72
  if (!req || typeof req !== "object")
72
73
  return undefined;
73
74
  const r = req;
74
- return {
75
+ const sanitized = {
75
76
  url: typeof r.url === "string" ? r.url : undefined,
76
77
  method: typeof r.method === "string" ? r.method : undefined,
77
78
  timeout: typeof r.timeout === "number" ? r.timeout : undefined,
78
- headers: typeof r.headers === "object" && r.headers !== null
79
- ? r.headers
80
- : undefined,
81
79
  };
80
+ // Only include headers if they don't contain sensitive data
81
+ if (typeof r.headers === "object" && r.headers !== null) {
82
+ const headersObj = r.headers;
83
+ const safeHeaders = {};
84
+ for (const [key, value] of Object.entries(headersObj)) {
85
+ const lowerKey = key.toLowerCase();
86
+ // Exclude authorization and api-key headers
87
+ if (!lowerKey.includes("authorization") &&
88
+ !lowerKey.includes("api-key") &&
89
+ !lowerKey.includes("token")) {
90
+ safeHeaders[key] = value;
91
+ }
92
+ }
93
+ if (Object.keys(safeHeaders).length > 0) {
94
+ sanitized.headers = safeHeaders;
95
+ }
96
+ }
97
+ return sanitized;
82
98
  };
83
99
  // Error with response from server
84
100
  const errorWithResponse = (_a = error === null || error === void 0 ? void 0 : error.response) !== null && _a !== void 0 ? _a : response;
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.operationServerMap = exports.RequiredError = exports.BaseAPI = exports.COLLECTION_FORMATS = exports.BASE_PATH = void 0;
4
- exports.BASE_PATH = "https://www.cutmeshort.com".replace(/\/+$/, "");
4
+ const constants_1 = require("../constants/constants");
5
+ exports.BASE_PATH = constants_1.API_CONFIG.BASE_URL.replace(/\/+$/, "");
5
6
  exports.COLLECTION_FORMATS = {
6
7
  csv: ",",
7
8
  ssv: " ",
@@ -1,2 +1,7 @@
1
1
  export { CMS } from "./client";
2
+ export type { CMSConfig, RequestOptions, LeadPayload, SalePayload } from "./client";
2
3
  export { CMSAPIError } from "./errors";
4
+ export { BadRequestError, UnauthorizedError, ForbiddenError, NotFoundError, ConflictError, UnprocessableEntityError, RateLimitError, InternalServerError, BadGatewayError, ServiceUnavailableError, GatewayTimeoutError, createSpecificError, } from "./errors/specific";
5
+ export { validateLeadPayload, validateSalePayload, validateCMSConfig, sanitizeForLogging } from "./validations/validation";
6
+ export type { LeadPayloadInput, SalePayloadInput, CMSConfigInput } from "./validations/validation";
7
+ export { SDK_VERSION, API_CONFIG, RETRY_CONFIG, VALIDATION_CONSTRAINTS, ERROR_MESSAGES, HTTP_HEADERS, SENSITIVE_FIELDS, } from "./constants/constants";
package/dist/src/index.js CHANGED
@@ -1,7 +1,34 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CMSAPIError = exports.CMS = void 0;
3
+ exports.SENSITIVE_FIELDS = exports.HTTP_HEADERS = exports.ERROR_MESSAGES = exports.VALIDATION_CONSTRAINTS = exports.RETRY_CONFIG = exports.API_CONFIG = exports.SDK_VERSION = exports.sanitizeForLogging = exports.validateCMSConfig = exports.validateSalePayload = exports.validateLeadPayload = exports.createSpecificError = exports.GatewayTimeoutError = exports.ServiceUnavailableError = exports.BadGatewayError = exports.InternalServerError = exports.RateLimitError = exports.UnprocessableEntityError = exports.ConflictError = exports.NotFoundError = exports.ForbiddenError = exports.UnauthorizedError = exports.BadRequestError = exports.CMSAPIError = exports.CMS = void 0;
4
4
  var client_1 = require("./client");
5
5
  Object.defineProperty(exports, "CMS", { enumerable: true, get: function () { return client_1.CMS; } });
6
6
  var errors_1 = require("./errors");
7
7
  Object.defineProperty(exports, "CMSAPIError", { enumerable: true, get: function () { return errors_1.CMSAPIError; } });
8
+ var specific_1 = require("./errors/specific");
9
+ Object.defineProperty(exports, "BadRequestError", { enumerable: true, get: function () { return specific_1.BadRequestError; } });
10
+ Object.defineProperty(exports, "UnauthorizedError", { enumerable: true, get: function () { return specific_1.UnauthorizedError; } });
11
+ Object.defineProperty(exports, "ForbiddenError", { enumerable: true, get: function () { return specific_1.ForbiddenError; } });
12
+ Object.defineProperty(exports, "NotFoundError", { enumerable: true, get: function () { return specific_1.NotFoundError; } });
13
+ Object.defineProperty(exports, "ConflictError", { enumerable: true, get: function () { return specific_1.ConflictError; } });
14
+ Object.defineProperty(exports, "UnprocessableEntityError", { enumerable: true, get: function () { return specific_1.UnprocessableEntityError; } });
15
+ Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return specific_1.RateLimitError; } });
16
+ Object.defineProperty(exports, "InternalServerError", { enumerable: true, get: function () { return specific_1.InternalServerError; } });
17
+ Object.defineProperty(exports, "BadGatewayError", { enumerable: true, get: function () { return specific_1.BadGatewayError; } });
18
+ Object.defineProperty(exports, "ServiceUnavailableError", { enumerable: true, get: function () { return specific_1.ServiceUnavailableError; } });
19
+ Object.defineProperty(exports, "GatewayTimeoutError", { enumerable: true, get: function () { return specific_1.GatewayTimeoutError; } });
20
+ Object.defineProperty(exports, "createSpecificError", { enumerable: true, get: function () { return specific_1.createSpecificError; } });
21
+ var validation_1 = require("./validations/validation");
22
+ Object.defineProperty(exports, "validateLeadPayload", { enumerable: true, get: function () { return validation_1.validateLeadPayload; } });
23
+ Object.defineProperty(exports, "validateSalePayload", { enumerable: true, get: function () { return validation_1.validateSalePayload; } });
24
+ Object.defineProperty(exports, "validateCMSConfig", { enumerable: true, get: function () { return validation_1.validateCMSConfig; } });
25
+ Object.defineProperty(exports, "sanitizeForLogging", { enumerable: true, get: function () { return validation_1.sanitizeForLogging; } });
26
+ // Export constants for advanced configuration
27
+ var constants_1 = require("./constants/constants");
28
+ Object.defineProperty(exports, "SDK_VERSION", { enumerable: true, get: function () { return constants_1.SDK_VERSION; } });
29
+ Object.defineProperty(exports, "API_CONFIG", { enumerable: true, get: function () { return constants_1.API_CONFIG; } });
30
+ Object.defineProperty(exports, "RETRY_CONFIG", { enumerable: true, get: function () { return constants_1.RETRY_CONFIG; } });
31
+ Object.defineProperty(exports, "VALIDATION_CONSTRAINTS", { enumerable: true, get: function () { return constants_1.VALIDATION_CONSTRAINTS; } });
32
+ Object.defineProperty(exports, "ERROR_MESSAGES", { enumerable: true, get: function () { return constants_1.ERROR_MESSAGES; } });
33
+ Object.defineProperty(exports, "HTTP_HEADERS", { enumerable: true, get: function () { return constants_1.HTTP_HEADERS; } });
34
+ Object.defineProperty(exports, "SENSITIVE_FIELDS", { enumerable: true, get: function () { return constants_1.SENSITIVE_FIELDS; } });
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Input validation schemas using Zod
3
+ * Ensures all incoming data is sanitized and valid
4
+ */
5
+ import { z } from "zod";
6
+ export declare const CMSConfigSchema: z.ZodObject<{
7
+ apiKey: z.ZodEffects<z.ZodString, string, string>;
8
+ baseUrl: z.ZodOptional<z.ZodString>;
9
+ timeout: z.ZodOptional<z.ZodNumber>;
10
+ maxRetries: z.ZodOptional<z.ZodNumber>;
11
+ retryDelayMs: z.ZodOptional<z.ZodNumber>;
12
+ retryMaxDelayMs: z.ZodOptional<z.ZodNumber>;
13
+ retryOnStatuses: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
14
+ retryOnNetworkError: z.ZodOptional<z.ZodBoolean>;
15
+ }, "strip", z.ZodTypeAny, {
16
+ apiKey: string;
17
+ timeout?: number | undefined;
18
+ baseUrl?: string | undefined;
19
+ maxRetries?: number | undefined;
20
+ retryDelayMs?: number | undefined;
21
+ retryMaxDelayMs?: number | undefined;
22
+ retryOnStatuses?: number[] | undefined;
23
+ retryOnNetworkError?: boolean | undefined;
24
+ }, {
25
+ apiKey: string;
26
+ timeout?: number | undefined;
27
+ baseUrl?: string | undefined;
28
+ maxRetries?: number | undefined;
29
+ retryDelayMs?: number | undefined;
30
+ retryMaxDelayMs?: number | undefined;
31
+ retryOnStatuses?: number[] | undefined;
32
+ retryOnNetworkError?: boolean | undefined;
33
+ }>;
34
+ export type CMSConfigInput = z.infer<typeof CMSConfigSchema>;
35
+ export declare const LeadPayloadSchema: z.ZodObject<{
36
+ clickId: z.ZodOptional<z.ZodString>;
37
+ eventName: z.ZodString;
38
+ customerExternalId: z.ZodString;
39
+ timestamp: z.ZodOptional<z.ZodString>;
40
+ customerId: z.ZodOptional<z.ZodString>;
41
+ customerName: z.ZodOptional<z.ZodString>;
42
+ customerEmail: z.ZodOptional<z.ZodString>;
43
+ customerAvatar: z.ZodOptional<z.ZodString>;
44
+ mode: z.ZodOptional<z.ZodEnum<["deferred"]>>;
45
+ }, "strip", z.ZodTypeAny, {
46
+ eventName: string;
47
+ customerExternalId: string;
48
+ clickId?: string | undefined;
49
+ timestamp?: string | undefined;
50
+ customerId?: string | undefined;
51
+ customerName?: string | undefined;
52
+ customerEmail?: string | undefined;
53
+ customerAvatar?: string | undefined;
54
+ mode?: "deferred" | undefined;
55
+ }, {
56
+ eventName: string;
57
+ customerExternalId: string;
58
+ clickId?: string | undefined;
59
+ timestamp?: string | undefined;
60
+ customerId?: string | undefined;
61
+ customerName?: string | undefined;
62
+ customerEmail?: string | undefined;
63
+ customerAvatar?: string | undefined;
64
+ mode?: "deferred" | undefined;
65
+ }>;
66
+ export type LeadPayloadInput = z.infer<typeof LeadPayloadSchema>;
67
+ export declare const SalePayloadSchema: z.ZodObject<{
68
+ clickId: z.ZodOptional<z.ZodString>;
69
+ eventName: z.ZodOptional<z.ZodString>;
70
+ invoiceId: z.ZodString;
71
+ amount: z.ZodNumber;
72
+ currency: z.ZodOptional<z.ZodString>;
73
+ timestamp: z.ZodOptional<z.ZodString>;
74
+ customerId: z.ZodOptional<z.ZodString>;
75
+ customerExternalId: z.ZodOptional<z.ZodString>;
76
+ customerName: z.ZodOptional<z.ZodString>;
77
+ customerEmail: z.ZodOptional<z.ZodString>;
78
+ mode: z.ZodOptional<z.ZodEnum<["deferred"]>>;
79
+ }, "strip", z.ZodTypeAny, {
80
+ invoiceId: string;
81
+ amount: number;
82
+ clickId?: string | undefined;
83
+ eventName?: string | undefined;
84
+ customerExternalId?: string | undefined;
85
+ timestamp?: string | undefined;
86
+ customerId?: string | undefined;
87
+ customerName?: string | undefined;
88
+ customerEmail?: string | undefined;
89
+ mode?: "deferred" | undefined;
90
+ currency?: string | undefined;
91
+ }, {
92
+ invoiceId: string;
93
+ amount: number;
94
+ clickId?: string | undefined;
95
+ eventName?: string | undefined;
96
+ customerExternalId?: string | undefined;
97
+ timestamp?: string | undefined;
98
+ customerId?: string | undefined;
99
+ customerName?: string | undefined;
100
+ customerEmail?: string | undefined;
101
+ mode?: "deferred" | undefined;
102
+ currency?: string | undefined;
103
+ }>;
104
+ export type SalePayloadInput = z.infer<typeof SalePayloadSchema>;
105
+ /**
106
+ * Validates input data and throws detailed error if invalid
107
+ */
108
+ export declare function validateLeadPayload(data: unknown): LeadPayloadInput;
109
+ export declare function validateSalePayload(data: unknown): SalePayloadInput;
110
+ export declare function validateCMSConfig(data: unknown): CMSConfigInput;
111
+ /**
112
+ * Sanitizes sensitive data for logging/errors
113
+ * Removes API keys, tokens, and other credentials
114
+ */
115
+ export declare function sanitizeForLogging(obj: any): any;
116
+ /**
117
+ * Redacts API key from error messages
118
+ */
119
+ export declare function redactApiKey(message: string, apiKey?: string): string;
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SalePayloadSchema = exports.LeadPayloadSchema = exports.CMSConfigSchema = void 0;
4
+ exports.validateLeadPayload = validateLeadPayload;
5
+ exports.validateSalePayload = validateSalePayload;
6
+ exports.validateCMSConfig = validateCMSConfig;
7
+ exports.sanitizeForLogging = sanitizeForLogging;
8
+ exports.redactApiKey = redactApiKey;
9
+ /**
10
+ * Input validation schemas using Zod
11
+ * Ensures all incoming data is sanitized and valid
12
+ */
13
+ const zod_1 = require("zod");
14
+ const constants_1 = require("../constants/constants");
15
+ exports.CMSConfigSchema = zod_1.z.object({
16
+ apiKey: zod_1.z
17
+ .string()
18
+ .min(1, constants_1.ERROR_MESSAGES.API_KEY_REQUIRED)
19
+ .refine((key) => constants_1.VALIDATION_CONSTRAINTS.API_KEY.PATTERN.test(key), constants_1.ERROR_MESSAGES.API_KEY_INVALID_FORMAT),
20
+ baseUrl: zod_1.z
21
+ .string()
22
+ .url(constants_1.ERROR_MESSAGES.BASE_URL_INVALID)
23
+ .optional(),
24
+ timeout: zod_1.z
25
+ .number()
26
+ .int()
27
+ .min(constants_1.VALIDATION_CONSTRAINTS.TIMEOUT.MIN_MS, constants_1.ERROR_MESSAGES.TIMEOUT_TOO_LOW)
28
+ .max(constants_1.VALIDATION_CONSTRAINTS.TIMEOUT.MAX_MS, constants_1.ERROR_MESSAGES.TIMEOUT_TOO_HIGH)
29
+ .optional(),
30
+ maxRetries: zod_1.z
31
+ .number()
32
+ .int()
33
+ .min(constants_1.VALIDATION_CONSTRAINTS.MAX_RETRIES.MIN, constants_1.ERROR_MESSAGES.MAX_RETRIES_NEGATIVE)
34
+ .max(constants_1.VALIDATION_CONSTRAINTS.MAX_RETRIES.MAX, constants_1.ERROR_MESSAGES.MAX_RETRIES_TOO_HIGH)
35
+ .optional(),
36
+ retryDelayMs: zod_1.z
37
+ .number()
38
+ .int()
39
+ .min(constants_1.VALIDATION_CONSTRAINTS.RETRY_DELAY.MIN_MS, constants_1.ERROR_MESSAGES.RETRY_DELAY_TOO_LOW)
40
+ .max(constants_1.VALIDATION_CONSTRAINTS.RETRY_DELAY.MAX_MS, constants_1.ERROR_MESSAGES.RETRY_DELAY_TOO_HIGH)
41
+ .optional(),
42
+ retryMaxDelayMs: zod_1.z
43
+ .number()
44
+ .int()
45
+ .min(constants_1.VALIDATION_CONSTRAINTS.RETRY_MAX_DELAY.MIN_MS, constants_1.ERROR_MESSAGES.RETRY_MAX_DELAY_TOO_LOW)
46
+ .max(constants_1.VALIDATION_CONSTRAINTS.RETRY_MAX_DELAY.MAX_MS, constants_1.ERROR_MESSAGES.RETRY_MAX_DELAY_TOO_HIGH)
47
+ .optional(),
48
+ retryOnStatuses: zod_1.z
49
+ .array(zod_1.z.number().int().min(100).max(599))
50
+ .optional(),
51
+ retryOnNetworkError: zod_1.z.boolean().optional(),
52
+ });
53
+ // Lead payload validation
54
+ exports.LeadPayloadSchema = zod_1.z.object({
55
+ clickId: zod_1.z
56
+ .string()
57
+ .min(constants_1.VALIDATION_CONSTRAINTS.STRING_FIELDS.CLICK_ID.MIN)
58
+ .max(constants_1.VALIDATION_CONSTRAINTS.STRING_FIELDS.CLICK_ID.MAX)
59
+ .optional(),
60
+ eventName: zod_1.z
61
+ .string()
62
+ .min(constants_1.VALIDATION_CONSTRAINTS.STRING_FIELDS.EVENT_NAME.MIN, constants_1.ERROR_MESSAGES.EVENT_NAME_REQUIRED)
63
+ .max(constants_1.VALIDATION_CONSTRAINTS.STRING_FIELDS.EVENT_NAME.MAX, constants_1.ERROR_MESSAGES.EVENT_NAME_TOO_LONG),
64
+ customerExternalId: zod_1.z
65
+ .string()
66
+ .min(constants_1.VALIDATION_CONSTRAINTS.STRING_FIELDS.CUSTOMER_EXTERNAL_ID.MIN, constants_1.ERROR_MESSAGES.CUSTOMER_EXTERNAL_ID_REQUIRED)
67
+ .max(constants_1.VALIDATION_CONSTRAINTS.STRING_FIELDS.CUSTOMER_EXTERNAL_ID.MAX, constants_1.ERROR_MESSAGES.CUSTOMER_EXTERNAL_ID_TOO_LONG),
68
+ timestamp: zod_1.z
69
+ .string()
70
+ .datetime()
71
+ .optional(),
72
+ customerId: zod_1.z
73
+ .string()
74
+ .max(constants_1.VALIDATION_CONSTRAINTS.STRING_FIELDS.CUSTOMER_ID.MAX)
75
+ .optional(),
76
+ customerName: zod_1.z
77
+ .string()
78
+ .max(constants_1.VALIDATION_CONSTRAINTS.STRING_FIELDS.CUSTOMER_NAME.MAX)
79
+ .optional(),
80
+ customerEmail: zod_1.z
81
+ .string()
82
+ .email(constants_1.ERROR_MESSAGES.CUSTOMER_EMAIL_INVALID)
83
+ .max(constants_1.VALIDATION_CONSTRAINTS.STRING_FIELDS.CUSTOMER_EMAIL.MAX, constants_1.ERROR_MESSAGES.CUSTOMER_EMAIL_TOO_LONG)
84
+ .optional(),
85
+ customerAvatar: zod_1.z
86
+ .string()
87
+ .url(constants_1.ERROR_MESSAGES.CUSTOMER_AVATAR_INVALID)
88
+ .optional(),
89
+ mode: zod_1.z
90
+ .enum(["deferred"])
91
+ .optional(),
92
+ });
93
+ // Sale payload validation
94
+ exports.SalePayloadSchema = zod_1.z.object({
95
+ clickId: zod_1.z
96
+ .string()
97
+ .min(constants_1.VALIDATION_CONSTRAINTS.STRING_FIELDS.CLICK_ID.MIN)
98
+ .max(constants_1.VALIDATION_CONSTRAINTS.STRING_FIELDS.CLICK_ID.MAX)
99
+ .optional(),
100
+ eventName: zod_1.z
101
+ .string()
102
+ .optional(),
103
+ invoiceId: zod_1.z
104
+ .string()
105
+ .min(constants_1.VALIDATION_CONSTRAINTS.STRING_FIELDS.INVOICE_ID.MIN, constants_1.ERROR_MESSAGES.INVOICE_ID_REQUIRED)
106
+ .max(constants_1.VALIDATION_CONSTRAINTS.STRING_FIELDS.INVOICE_ID.MAX, constants_1.ERROR_MESSAGES.INVOICE_ID_TOO_LONG),
107
+ amount: zod_1.z
108
+ .number()
109
+ .int()
110
+ .min(constants_1.VALIDATION_CONSTRAINTS.AMOUNT.MIN, constants_1.ERROR_MESSAGES.AMOUNT_NEGATIVE),
111
+ currency: zod_1.z
112
+ .string()
113
+ .length(constants_1.VALIDATION_CONSTRAINTS.CURRENCY.LENGTH, constants_1.ERROR_MESSAGES.CURRENCY_INVALID_LENGTH)
114
+ .regex(constants_1.VALIDATION_CONSTRAINTS.CURRENCY.PATTERN, constants_1.ERROR_MESSAGES.CURRENCY_INVALID_FORMAT)
115
+ .optional(),
116
+ timestamp: zod_1.z
117
+ .string()
118
+ .datetime()
119
+ .optional(),
120
+ customerId: zod_1.z
121
+ .string()
122
+ .max(constants_1.VALIDATION_CONSTRAINTS.STRING_FIELDS.CUSTOMER_ID.MAX)
123
+ .optional(),
124
+ customerExternalId: zod_1.z
125
+ .string()
126
+ .max(constants_1.VALIDATION_CONSTRAINTS.STRING_FIELDS.CUSTOMER_EXTERNAL_ID.MAX)
127
+ .optional(),
128
+ customerName: zod_1.z
129
+ .string()
130
+ .max(constants_1.VALIDATION_CONSTRAINTS.STRING_FIELDS.CUSTOMER_NAME.MAX)
131
+ .optional(),
132
+ customerEmail: zod_1.z
133
+ .string()
134
+ .email(constants_1.ERROR_MESSAGES.CUSTOMER_EMAIL_INVALID)
135
+ .max(constants_1.VALIDATION_CONSTRAINTS.STRING_FIELDS.CUSTOMER_EMAIL.MAX, constants_1.ERROR_MESSAGES.CUSTOMER_EMAIL_TOO_LONG)
136
+ .optional(),
137
+ mode: zod_1.z
138
+ .enum(["deferred"])
139
+ .optional(),
140
+ });
141
+ /**
142
+ * Validates input data and throws detailed error if invalid
143
+ */
144
+ function validateLeadPayload(data) {
145
+ return exports.LeadPayloadSchema.parse(data);
146
+ }
147
+ function validateSalePayload(data) {
148
+ return exports.SalePayloadSchema.parse(data);
149
+ }
150
+ function validateCMSConfig(data) {
151
+ return exports.CMSConfigSchema.parse(data);
152
+ }
153
+ /**
154
+ * Sanitizes sensitive data for logging/errors
155
+ * Removes API keys, tokens, and other credentials
156
+ */
157
+ function sanitizeForLogging(obj) {
158
+ if (!obj || typeof obj !== "object") {
159
+ return obj;
160
+ }
161
+ if (Array.isArray(obj)) {
162
+ return obj.map((item) => sanitizeForLogging(item));
163
+ }
164
+ const sanitized = {};
165
+ for (const [key, value] of Object.entries(obj)) {
166
+ const lowerKey = key.toLowerCase();
167
+ // Mask sensitive field names
168
+ if (constants_1.SENSITIVE_FIELDS.PATTERNS.some((field) => lowerKey.includes(field))) {
169
+ sanitized[key] = typeof value === "string" ? constants_1.SENSITIVE_FIELDS.REDACTION_PLACEHOLDER : value;
170
+ }
171
+ else if (typeof value === "object" && value !== null) {
172
+ sanitized[key] = sanitizeForLogging(value);
173
+ }
174
+ else {
175
+ sanitized[key] = value;
176
+ }
177
+ }
178
+ return sanitized;
179
+ }
180
+ /**
181
+ * Redacts API key from error messages
182
+ */
183
+ function redactApiKey(message, apiKey) {
184
+ if (!apiKey)
185
+ return message;
186
+ // Replace the full key and common patterns
187
+ const patterns = [
188
+ apiKey,
189
+ apiKey.substring(0, 3) + "***" + apiKey.substring(apiKey.length - 3),
190
+ ];
191
+ let result = message;
192
+ for (const pattern of patterns) {
193
+ result = result.replace(new RegExp(pattern, "g"), constants_1.SENSITIVE_FIELDS.API_KEY_REDACTION);
194
+ }
195
+ return result;
196
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vigneshreddy/cms-sdk",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "Official TypeScript SDK for CutMeShort CMS API",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -17,7 +17,9 @@
17
17
  ],
18
18
  "scripts": {
19
19
  "build": "tsc -p tsconfig.json",
20
- "test": "vitest"
20
+ "test": "vitest",
21
+ "audit": "npm audit --audit-level=moderate",
22
+ "security-check": "npm audit && npm ls"
21
23
  },
22
24
  "keywords": [
23
25
  "cutmeshort",
@@ -42,6 +44,9 @@
42
44
  },
43
45
  "author": "Vignesh Reddy",
44
46
  "license": "MIT",
47
+ "dependencies": {
48
+ "zod": "^3.22.0"
49
+ },
45
50
  "devDependencies": {
46
51
  "typescript": "^5.7.0",
47
52
  "vitest": "^4.0.18"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes