ezthrottle 1.1.1 → 1.4.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/README.md +824 -0
- package/dist/client.d.ts +108 -0
- package/dist/client.js +251 -0
- package/dist/forward.d.ts +75 -0
- package/dist/forward.js +101 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +9 -1
- package/dist/webhookUtils.d.ts +116 -0
- package/dist/webhookUtils.js +224 -0
- package/package.json +3 -3
package/dist/client.d.ts
CHANGED
|
@@ -54,5 +54,113 @@ export declare class EZThrottle {
|
|
|
54
54
|
queueRequest({ url, webhookUrl, method, headers, body, metadata, retryAt }: QueueRequestParams): Promise<any>;
|
|
55
55
|
request({ url, method, headers, body }: RequestParams): Promise<any>;
|
|
56
56
|
queueAndWait({ url, webhookUrl, method, headers, body, metadata, retryAt, timeout, pollInterval, }: QueueAndWaitParams): Promise<any>;
|
|
57
|
+
/**
|
|
58
|
+
* Create or update webhook HMAC secrets for signature verification.
|
|
59
|
+
*
|
|
60
|
+
* @param primarySecret - Primary webhook secret (min 16 characters)
|
|
61
|
+
* @param secondarySecret - Optional secondary secret for rotation (min 16 characters)
|
|
62
|
+
* @returns Response with status and message
|
|
63
|
+
* @throws {Error} If secret creation fails
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* // Create primary secret
|
|
68
|
+
* await client.createWebhookSecret('your_secure_secret_here_min_16_chars');
|
|
69
|
+
*
|
|
70
|
+
* // Create with rotation support (primary + secondary)
|
|
71
|
+
* await client.createWebhookSecret(
|
|
72
|
+
* 'new_secret_after_rotation',
|
|
73
|
+
* 'old_secret_before_rotation'
|
|
74
|
+
* );
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
createWebhookSecret(primarySecret: string, secondarySecret?: string): Promise<any>;
|
|
78
|
+
/**
|
|
79
|
+
* Get webhook secrets (masked for security).
|
|
80
|
+
*
|
|
81
|
+
* @returns Object with masked secrets
|
|
82
|
+
* @throws {Error} If secrets not configured (404) or request fails
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* const secrets = await client.getWebhookSecret();
|
|
87
|
+
* console.log(secrets);
|
|
88
|
+
* // {
|
|
89
|
+
* // customer_id: 'cust_XXX',
|
|
90
|
+
* // primary_secret: 'your****ars',
|
|
91
|
+
* // secondary_secret: 'opti****ars',
|
|
92
|
+
* // has_secondary: true
|
|
93
|
+
* // }
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
getWebhookSecret(): Promise<any>;
|
|
97
|
+
/**
|
|
98
|
+
* Delete webhook secrets.
|
|
99
|
+
*
|
|
100
|
+
* @returns Response with status and message
|
|
101
|
+
* @throws {Error} If deletion fails
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* const result = await client.deleteWebhookSecret();
|
|
106
|
+
* console.log(result); // { status: 'ok', message: 'Webhook secrets deleted' }
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
deleteWebhookSecret(): Promise<any>;
|
|
110
|
+
/**
|
|
111
|
+
* Rotate webhook secret safely by promoting secondary to primary.
|
|
112
|
+
*
|
|
113
|
+
* @param newSecret - New webhook secret to set as primary
|
|
114
|
+
* @returns Response with status and message
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* // Step 1: Rotate (keeps old secret as backup)
|
|
119
|
+
* await client.rotateWebhookSecret('new_secret_min_16_chars');
|
|
120
|
+
*
|
|
121
|
+
* // Step 2: After verifying webhooks work with new secret
|
|
122
|
+
* // Remove old secret by setting only new one
|
|
123
|
+
* await client.createWebhookSecret('new_secret_min_16_chars');
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
/**
|
|
127
|
+
* Try to forward request to EZThrottle, fall back to callback if EZThrottle is unreachable.
|
|
128
|
+
*
|
|
129
|
+
* This makes EZThrottle a reliability LAYER, not a single point of failure.
|
|
130
|
+
* If EZThrottle is down, you just lose the extra reliability features and
|
|
131
|
+
* fall back to your existing solution.
|
|
132
|
+
*
|
|
133
|
+
* @param fallback - Callback function to execute if EZThrottle is unreachable
|
|
134
|
+
* @param options - Job options (same as submitJob)
|
|
135
|
+
* @returns Either EZThrottle job response or the fallback function's return value
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```typescript
|
|
139
|
+
* // If EZThrottle is down, fall back to direct HTTP call
|
|
140
|
+
* const result = await client.forwardOrFallback(
|
|
141
|
+
* async () => {
|
|
142
|
+
* const response = await fetch('https://api.stripe.com/charges', {
|
|
143
|
+
* method: 'POST',
|
|
144
|
+
* headers: { 'Authorization': 'Bearer sk_live_...' },
|
|
145
|
+
* body: JSON.stringify({ amount: 1000 })
|
|
146
|
+
* });
|
|
147
|
+
* return response.json();
|
|
148
|
+
* },
|
|
149
|
+
* {
|
|
150
|
+
* url: 'https://api.stripe.com/charges',
|
|
151
|
+
* method: 'POST',
|
|
152
|
+
* headers: { 'Authorization': 'Bearer sk_live_...' },
|
|
153
|
+
* body: JSON.stringify({ amount: 1000 }),
|
|
154
|
+
* webhooks: [{ url: 'https://your-app.com/webhook' }]
|
|
155
|
+
* }
|
|
156
|
+
* );
|
|
157
|
+
* ```
|
|
158
|
+
*
|
|
159
|
+
* Note: The fallback is ONLY called when EZThrottle itself is unreachable
|
|
160
|
+
* (connection errors, timeouts). It is NOT called for rate limiting or
|
|
161
|
+
* other EZThrottle errors - those indicate EZThrottle is working.
|
|
162
|
+
*/
|
|
163
|
+
forwardOrFallback<T>(fallback: () => Promise<T>, options: SubmitJobParams): Promise<any | T>;
|
|
164
|
+
rotateWebhookSecret(newSecret: string): Promise<any>;
|
|
57
165
|
}
|
|
58
166
|
export {};
|
package/dist/client.js
CHANGED
|
@@ -173,5 +173,256 @@ class EZThrottle {
|
|
|
173
173
|
checkResult();
|
|
174
174
|
});
|
|
175
175
|
}
|
|
176
|
+
// ============================================================================
|
|
177
|
+
// WEBHOOK SECRETS MANAGEMENT
|
|
178
|
+
// ============================================================================
|
|
179
|
+
/**
|
|
180
|
+
* Create or update webhook HMAC secrets for signature verification.
|
|
181
|
+
*
|
|
182
|
+
* @param primarySecret - Primary webhook secret (min 16 characters)
|
|
183
|
+
* @param secondarySecret - Optional secondary secret for rotation (min 16 characters)
|
|
184
|
+
* @returns Response with status and message
|
|
185
|
+
* @throws {Error} If secret creation fails
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```typescript
|
|
189
|
+
* // Create primary secret
|
|
190
|
+
* await client.createWebhookSecret('your_secure_secret_here_min_16_chars');
|
|
191
|
+
*
|
|
192
|
+
* // Create with rotation support (primary + secondary)
|
|
193
|
+
* await client.createWebhookSecret(
|
|
194
|
+
* 'new_secret_after_rotation',
|
|
195
|
+
* 'old_secret_before_rotation'
|
|
196
|
+
* );
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
async createWebhookSecret(primarySecret, secondarySecret) {
|
|
200
|
+
if (primarySecret.length < 16) {
|
|
201
|
+
throw new Error('primarySecret must be at least 16 characters');
|
|
202
|
+
}
|
|
203
|
+
if (secondarySecret && secondarySecret.length < 16) {
|
|
204
|
+
throw new Error('secondarySecret must be at least 16 characters');
|
|
205
|
+
}
|
|
206
|
+
const payload = { primary_secret: primarySecret };
|
|
207
|
+
if (secondarySecret) {
|
|
208
|
+
payload.secondary_secret = secondarySecret;
|
|
209
|
+
}
|
|
210
|
+
const proxyPayload = {
|
|
211
|
+
scope: 'customer',
|
|
212
|
+
metric_name: '',
|
|
213
|
+
target_url: `${this.ezthrottleUrl}/api/v1/webhook-secrets`,
|
|
214
|
+
method: 'POST',
|
|
215
|
+
headers: { 'Content-Type': 'application/json' },
|
|
216
|
+
body: JSON.stringify(payload),
|
|
217
|
+
};
|
|
218
|
+
const response = await (0, node_fetch_1.default)(`${this.tracktagsUrl}/api/v1/proxy`, {
|
|
219
|
+
method: 'POST',
|
|
220
|
+
headers: {
|
|
221
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
222
|
+
'Content-Type': 'application/json',
|
|
223
|
+
},
|
|
224
|
+
body: JSON.stringify(proxyPayload),
|
|
225
|
+
});
|
|
226
|
+
if (response.status !== 200) {
|
|
227
|
+
const text = await response.text();
|
|
228
|
+
throw new errors_1.EZThrottleError(`Failed to create webhook secret: ${text}`);
|
|
229
|
+
}
|
|
230
|
+
const proxyResponse = await response.json();
|
|
231
|
+
if (proxyResponse.status !== 'allowed') {
|
|
232
|
+
throw new errors_1.EZThrottleError(`Request denied: ${proxyResponse.error || 'Unknown error'}`);
|
|
233
|
+
}
|
|
234
|
+
const forwarded = proxyResponse.forwarded_response || {};
|
|
235
|
+
return JSON.parse(forwarded.body || '{}');
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get webhook secrets (masked for security).
|
|
239
|
+
*
|
|
240
|
+
* @returns Object with masked secrets
|
|
241
|
+
* @throws {Error} If secrets not configured (404) or request fails
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* ```typescript
|
|
245
|
+
* const secrets = await client.getWebhookSecret();
|
|
246
|
+
* console.log(secrets);
|
|
247
|
+
* // {
|
|
248
|
+
* // customer_id: 'cust_XXX',
|
|
249
|
+
* // primary_secret: 'your****ars',
|
|
250
|
+
* // secondary_secret: 'opti****ars',
|
|
251
|
+
* // has_secondary: true
|
|
252
|
+
* // }
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
async getWebhookSecret() {
|
|
256
|
+
const proxyPayload = {
|
|
257
|
+
scope: 'customer',
|
|
258
|
+
metric_name: '',
|
|
259
|
+
target_url: `${this.ezthrottleUrl}/api/v1/webhook-secrets`,
|
|
260
|
+
method: 'GET',
|
|
261
|
+
headers: {},
|
|
262
|
+
body: '',
|
|
263
|
+
};
|
|
264
|
+
const response = await (0, node_fetch_1.default)(`${this.tracktagsUrl}/api/v1/proxy`, {
|
|
265
|
+
method: 'POST',
|
|
266
|
+
headers: {
|
|
267
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
268
|
+
'Content-Type': 'application/json',
|
|
269
|
+
},
|
|
270
|
+
body: JSON.stringify(proxyPayload),
|
|
271
|
+
});
|
|
272
|
+
if (response.status !== 200) {
|
|
273
|
+
const text = await response.text();
|
|
274
|
+
throw new errors_1.EZThrottleError(`Failed to get webhook secret: ${text}`);
|
|
275
|
+
}
|
|
276
|
+
const proxyResponse = await response.json();
|
|
277
|
+
if (proxyResponse.status !== 'allowed') {
|
|
278
|
+
throw new errors_1.EZThrottleError(`Request denied: ${proxyResponse.error || 'Unknown error'}`);
|
|
279
|
+
}
|
|
280
|
+
const forwarded = proxyResponse.forwarded_response || {};
|
|
281
|
+
const statusCode = forwarded.status_code || 0;
|
|
282
|
+
if (statusCode === 404) {
|
|
283
|
+
throw new errors_1.EZThrottleError('No webhook secrets configured. Call createWebhookSecret() first.');
|
|
284
|
+
}
|
|
285
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
286
|
+
throw new errors_1.EZThrottleError(`Failed to get webhook secrets: ${forwarded.body}`);
|
|
287
|
+
}
|
|
288
|
+
return JSON.parse(forwarded.body || '{}');
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Delete webhook secrets.
|
|
292
|
+
*
|
|
293
|
+
* @returns Response with status and message
|
|
294
|
+
* @throws {Error} If deletion fails
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* ```typescript
|
|
298
|
+
* const result = await client.deleteWebhookSecret();
|
|
299
|
+
* console.log(result); // { status: 'ok', message: 'Webhook secrets deleted' }
|
|
300
|
+
* ```
|
|
301
|
+
*/
|
|
302
|
+
async deleteWebhookSecret() {
|
|
303
|
+
const proxyPayload = {
|
|
304
|
+
scope: 'customer',
|
|
305
|
+
metric_name: '',
|
|
306
|
+
target_url: `${this.ezthrottleUrl}/api/v1/webhook-secrets`,
|
|
307
|
+
method: 'DELETE',
|
|
308
|
+
headers: {},
|
|
309
|
+
body: '',
|
|
310
|
+
};
|
|
311
|
+
const response = await (0, node_fetch_1.default)(`${this.tracktagsUrl}/api/v1/proxy`, {
|
|
312
|
+
method: 'POST',
|
|
313
|
+
headers: {
|
|
314
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
315
|
+
'Content-Type': 'application/json',
|
|
316
|
+
},
|
|
317
|
+
body: JSON.stringify(proxyPayload),
|
|
318
|
+
});
|
|
319
|
+
if (response.status !== 200) {
|
|
320
|
+
const text = await response.text();
|
|
321
|
+
throw new errors_1.EZThrottleError(`Failed to delete webhook secret: ${text}`);
|
|
322
|
+
}
|
|
323
|
+
const proxyResponse = await response.json();
|
|
324
|
+
if (proxyResponse.status !== 'allowed') {
|
|
325
|
+
throw new errors_1.EZThrottleError(`Request denied: ${proxyResponse.error || 'Unknown error'}`);
|
|
326
|
+
}
|
|
327
|
+
const forwarded = proxyResponse.forwarded_response || {};
|
|
328
|
+
return JSON.parse(forwarded.body || '{}');
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Rotate webhook secret safely by promoting secondary to primary.
|
|
332
|
+
*
|
|
333
|
+
* @param newSecret - New webhook secret to set as primary
|
|
334
|
+
* @returns Response with status and message
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* ```typescript
|
|
338
|
+
* // Step 1: Rotate (keeps old secret as backup)
|
|
339
|
+
* await client.rotateWebhookSecret('new_secret_min_16_chars');
|
|
340
|
+
*
|
|
341
|
+
* // Step 2: After verifying webhooks work with new secret
|
|
342
|
+
* // Remove old secret by setting only new one
|
|
343
|
+
* await client.createWebhookSecret('new_secret_min_16_chars');
|
|
344
|
+
* ```
|
|
345
|
+
*/
|
|
346
|
+
/**
|
|
347
|
+
* Try to forward request to EZThrottle, fall back to callback if EZThrottle is unreachable.
|
|
348
|
+
*
|
|
349
|
+
* This makes EZThrottle a reliability LAYER, not a single point of failure.
|
|
350
|
+
* If EZThrottle is down, you just lose the extra reliability features and
|
|
351
|
+
* fall back to your existing solution.
|
|
352
|
+
*
|
|
353
|
+
* @param fallback - Callback function to execute if EZThrottle is unreachable
|
|
354
|
+
* @param options - Job options (same as submitJob)
|
|
355
|
+
* @returns Either EZThrottle job response or the fallback function's return value
|
|
356
|
+
*
|
|
357
|
+
* @example
|
|
358
|
+
* ```typescript
|
|
359
|
+
* // If EZThrottle is down, fall back to direct HTTP call
|
|
360
|
+
* const result = await client.forwardOrFallback(
|
|
361
|
+
* async () => {
|
|
362
|
+
* const response = await fetch('https://api.stripe.com/charges', {
|
|
363
|
+
* method: 'POST',
|
|
364
|
+
* headers: { 'Authorization': 'Bearer sk_live_...' },
|
|
365
|
+
* body: JSON.stringify({ amount: 1000 })
|
|
366
|
+
* });
|
|
367
|
+
* return response.json();
|
|
368
|
+
* },
|
|
369
|
+
* {
|
|
370
|
+
* url: 'https://api.stripe.com/charges',
|
|
371
|
+
* method: 'POST',
|
|
372
|
+
* headers: { 'Authorization': 'Bearer sk_live_...' },
|
|
373
|
+
* body: JSON.stringify({ amount: 1000 }),
|
|
374
|
+
* webhooks: [{ url: 'https://your-app.com/webhook' }]
|
|
375
|
+
* }
|
|
376
|
+
* );
|
|
377
|
+
* ```
|
|
378
|
+
*
|
|
379
|
+
* Note: The fallback is ONLY called when EZThrottle itself is unreachable
|
|
380
|
+
* (connection errors, timeouts). It is NOT called for rate limiting or
|
|
381
|
+
* other EZThrottle errors - those indicate EZThrottle is working.
|
|
382
|
+
*/
|
|
383
|
+
async forwardOrFallback(fallback, options) {
|
|
384
|
+
try {
|
|
385
|
+
return await this.submitJob(options);
|
|
386
|
+
}
|
|
387
|
+
catch (error) {
|
|
388
|
+
// Check if it's a network/connection error
|
|
389
|
+
if (error.code === 'ECONNREFUSED' ||
|
|
390
|
+
error.code === 'ENOTFOUND' ||
|
|
391
|
+
error.code === 'ETIMEDOUT' ||
|
|
392
|
+
error.code === 'ECONNRESET' ||
|
|
393
|
+
error.name === 'AbortError' ||
|
|
394
|
+
error.type === 'system') {
|
|
395
|
+
// EZThrottle is unreachable - use fallback
|
|
396
|
+
return fallback();
|
|
397
|
+
}
|
|
398
|
+
// Re-throw other errors (rate limiting, validation, etc.)
|
|
399
|
+
throw error;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
async rotateWebhookSecret(newSecret) {
|
|
403
|
+
if (newSecret.length < 16) {
|
|
404
|
+
throw new Error('newSecret must be at least 16 characters');
|
|
405
|
+
}
|
|
406
|
+
try {
|
|
407
|
+
// Get current secret to use as secondary
|
|
408
|
+
const current = await this.getWebhookSecret();
|
|
409
|
+
const oldPrimary = current.primary_secret || '';
|
|
410
|
+
// If we have a masked secret, we can't use it as secondary
|
|
411
|
+
// In this case, just set the new secret without secondary
|
|
412
|
+
if (oldPrimary.includes('****')) {
|
|
413
|
+
return this.createWebhookSecret(newSecret);
|
|
414
|
+
}
|
|
415
|
+
// Set new as primary, old as secondary
|
|
416
|
+
return this.createWebhookSecret(newSecret, oldPrimary);
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
if (error instanceof errors_1.EZThrottleError &&
|
|
420
|
+
error.message.includes('No webhook secrets configured')) {
|
|
421
|
+
// No existing secret, just create new one
|
|
422
|
+
return this.createWebhookSecret(newSecret);
|
|
423
|
+
}
|
|
424
|
+
throw error;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
176
427
|
}
|
|
177
428
|
exports.EZThrottle = EZThrottle;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { EZThrottle } from './client';
|
|
2
|
+
import { StepType } from './stepType';
|
|
3
|
+
import { WebhookConfig } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* ForwardRequest - Represents a request that should be forwarded to EZThrottle
|
|
6
|
+
*
|
|
7
|
+
* Use this with executeWithForwarding() to integrate EZThrottle into legacy code
|
|
8
|
+
* without rewriting error handling.
|
|
9
|
+
*/
|
|
10
|
+
export interface ForwardRequest {
|
|
11
|
+
url: string;
|
|
12
|
+
method?: string;
|
|
13
|
+
headers?: Record<string, string>;
|
|
14
|
+
body?: string;
|
|
15
|
+
idempotentKey?: string;
|
|
16
|
+
metadata?: Record<string, any>;
|
|
17
|
+
webhooks?: WebhookConfig[];
|
|
18
|
+
regions?: string[];
|
|
19
|
+
fallbackOnError?: number[];
|
|
20
|
+
stepType?: StepType;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* executeWithForwarding - Higher-order function that wraps legacy code to auto-forward errors
|
|
24
|
+
*
|
|
25
|
+
* This is the Node.js equivalent of Python's @auto_forward decorator.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* // Legacy function that may throw ForwardRequest on errors
|
|
30
|
+
* async function processPayment(orderId: string): Promise<any> {
|
|
31
|
+
* try {
|
|
32
|
+
* const response = await fetch('https://api.stripe.com/charges', {...});
|
|
33
|
+
* if (response.status === 429) {
|
|
34
|
+
* // Return ForwardRequest to trigger auto-forwarding
|
|
35
|
+
* return {
|
|
36
|
+
* forward: {
|
|
37
|
+
* url: 'https://api.stripe.com/charges',
|
|
38
|
+
* method: 'POST',
|
|
39
|
+
* idempotentKey: `order_${orderId}`,
|
|
40
|
+
* webhooks: [{url: 'https://app.com/webhook', hasQuorumVote: true}]
|
|
41
|
+
* }
|
|
42
|
+
* };
|
|
43
|
+
* }
|
|
44
|
+
* return await response.json();
|
|
45
|
+
* } catch (error) {
|
|
46
|
+
* // Network error - forward to EZThrottle
|
|
47
|
+
* return {
|
|
48
|
+
* forward: {
|
|
49
|
+
* url: 'https://api.stripe.com/charges',
|
|
50
|
+
* method: 'POST',
|
|
51
|
+
* idempotentKey: `order_${orderId}`
|
|
52
|
+
* }
|
|
53
|
+
* };
|
|
54
|
+
* }
|
|
55
|
+
* }
|
|
56
|
+
*
|
|
57
|
+
* // Wrap with auto-forwarding
|
|
58
|
+
* const result = await executeWithForwarding(client, () => processPayment('order_123'));
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare function executeWithForwarding<T>(client: EZThrottle, fn: () => Promise<T | {
|
|
62
|
+
forward: ForwardRequest;
|
|
63
|
+
}>): Promise<T | any>;
|
|
64
|
+
/**
|
|
65
|
+
* withAutoForward - Decorator-style wrapper for async functions
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* const processPaymentWithForwarding = withAutoForward(client, processPayment);
|
|
70
|
+
* const result = await processPaymentWithForwarding('order_123');
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export declare function withAutoForward<TArgs extends any[], TReturn>(client: EZThrottle, fn: (...args: TArgs) => Promise<TReturn | {
|
|
74
|
+
forward: ForwardRequest;
|
|
75
|
+
}>): (...args: TArgs) => Promise<TReturn | any>;
|
package/dist/forward.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executeWithForwarding = executeWithForwarding;
|
|
4
|
+
exports.withAutoForward = withAutoForward;
|
|
5
|
+
const step_1 = require("./step");
|
|
6
|
+
const stepType_1 = require("./stepType");
|
|
7
|
+
/**
|
|
8
|
+
* executeWithForwarding - Higher-order function that wraps legacy code to auto-forward errors
|
|
9
|
+
*
|
|
10
|
+
* This is the Node.js equivalent of Python's @auto_forward decorator.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // Legacy function that may throw ForwardRequest on errors
|
|
15
|
+
* async function processPayment(orderId: string): Promise<any> {
|
|
16
|
+
* try {
|
|
17
|
+
* const response = await fetch('https://api.stripe.com/charges', {...});
|
|
18
|
+
* if (response.status === 429) {
|
|
19
|
+
* // Return ForwardRequest to trigger auto-forwarding
|
|
20
|
+
* return {
|
|
21
|
+
* forward: {
|
|
22
|
+
* url: 'https://api.stripe.com/charges',
|
|
23
|
+
* method: 'POST',
|
|
24
|
+
* idempotentKey: `order_${orderId}`,
|
|
25
|
+
* webhooks: [{url: 'https://app.com/webhook', hasQuorumVote: true}]
|
|
26
|
+
* }
|
|
27
|
+
* };
|
|
28
|
+
* }
|
|
29
|
+
* return await response.json();
|
|
30
|
+
* } catch (error) {
|
|
31
|
+
* // Network error - forward to EZThrottle
|
|
32
|
+
* return {
|
|
33
|
+
* forward: {
|
|
34
|
+
* url: 'https://api.stripe.com/charges',
|
|
35
|
+
* method: 'POST',
|
|
36
|
+
* idempotentKey: `order_${orderId}`
|
|
37
|
+
* }
|
|
38
|
+
* };
|
|
39
|
+
* }
|
|
40
|
+
* }
|
|
41
|
+
*
|
|
42
|
+
* // Wrap with auto-forwarding
|
|
43
|
+
* const result = await executeWithForwarding(client, () => processPayment('order_123'));
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
async function executeWithForwarding(client, fn) {
|
|
47
|
+
const result = await fn();
|
|
48
|
+
// Check if result contains a ForwardRequest
|
|
49
|
+
if (result && typeof result === 'object' && 'forward' in result) {
|
|
50
|
+
const forwardReq = result.forward;
|
|
51
|
+
// Auto-forward to EZThrottle
|
|
52
|
+
const step = new step_1.Step(client)
|
|
53
|
+
.url(forwardReq.url)
|
|
54
|
+
.method(forwardReq.method || 'GET');
|
|
55
|
+
if (forwardReq.headers) {
|
|
56
|
+
step.headers(forwardReq.headers);
|
|
57
|
+
}
|
|
58
|
+
if (forwardReq.body) {
|
|
59
|
+
step.body(forwardReq.body);
|
|
60
|
+
}
|
|
61
|
+
if (forwardReq.idempotentKey) {
|
|
62
|
+
step.idempotentKey(forwardReq.idempotentKey);
|
|
63
|
+
}
|
|
64
|
+
if (forwardReq.metadata) {
|
|
65
|
+
step.metadata(forwardReq.metadata);
|
|
66
|
+
}
|
|
67
|
+
if (forwardReq.webhooks) {
|
|
68
|
+
step.webhooks(forwardReq.webhooks);
|
|
69
|
+
}
|
|
70
|
+
if (forwardReq.regions) {
|
|
71
|
+
step.regions(forwardReq.regions);
|
|
72
|
+
}
|
|
73
|
+
// Set step type (default to FRUGAL if not specified)
|
|
74
|
+
if (forwardReq.stepType) {
|
|
75
|
+
step.type(forwardReq.stepType);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
step.type(stepType_1.StepType.FRUGAL);
|
|
79
|
+
}
|
|
80
|
+
// Set fallback on error codes if specified
|
|
81
|
+
if (forwardReq.fallbackOnError && forwardReq.fallbackOnError.length > 0) {
|
|
82
|
+
step.fallbackOnError(forwardReq.fallbackOnError);
|
|
83
|
+
}
|
|
84
|
+
return await step.execute();
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* withAutoForward - Decorator-style wrapper for async functions
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* const processPaymentWithForwarding = withAutoForward(client, processPayment);
|
|
94
|
+
* const result = await processPaymentWithForwarding('order_123');
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
function withAutoForward(client, fn) {
|
|
98
|
+
return async (...args) => {
|
|
99
|
+
return executeWithForwarding(client, () => fn(...args));
|
|
100
|
+
};
|
|
101
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,5 +4,8 @@ export { Step } from './step';
|
|
|
4
4
|
export { StepType } from './stepType';
|
|
5
5
|
export { IdempotentStrategy } from './idempotentStrategy';
|
|
6
6
|
export * from './types';
|
|
7
|
+
export { executeWithForwarding, withAutoForward } from './forward';
|
|
8
|
+
export type { ForwardRequest } from './forward';
|
|
9
|
+
export { verifyWebhookSignature, verifyWebhookSignatureStrict, tryVerifyWithSecrets, WebhookVerificationError, VerificationResult } from './webhookUtils';
|
|
7
10
|
import { EZThrottle } from './client';
|
|
8
11
|
export default EZThrottle;
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.IdempotentStrategy = exports.StepType = exports.Step = exports.RateLimitError = exports.TimeoutError = exports.EZThrottleError = exports.EZThrottle = void 0;
|
|
17
|
+
exports.WebhookVerificationError = exports.tryVerifyWithSecrets = exports.verifyWebhookSignatureStrict = exports.verifyWebhookSignature = exports.withAutoForward = exports.executeWithForwarding = exports.IdempotentStrategy = exports.StepType = exports.Step = exports.RateLimitError = exports.TimeoutError = exports.EZThrottleError = exports.EZThrottle = void 0;
|
|
18
18
|
var client_1 = require("./client");
|
|
19
19
|
Object.defineProperty(exports, "EZThrottle", { enumerable: true, get: function () { return client_1.EZThrottle; } });
|
|
20
20
|
var errors_1 = require("./errors");
|
|
@@ -28,6 +28,14 @@ Object.defineProperty(exports, "StepType", { enumerable: true, get: function ()
|
|
|
28
28
|
var idempotentStrategy_1 = require("./idempotentStrategy");
|
|
29
29
|
Object.defineProperty(exports, "IdempotentStrategy", { enumerable: true, get: function () { return idempotentStrategy_1.IdempotentStrategy; } });
|
|
30
30
|
__exportStar(require("./types"), exports);
|
|
31
|
+
var forward_1 = require("./forward");
|
|
32
|
+
Object.defineProperty(exports, "executeWithForwarding", { enumerable: true, get: function () { return forward_1.executeWithForwarding; } });
|
|
33
|
+
Object.defineProperty(exports, "withAutoForward", { enumerable: true, get: function () { return forward_1.withAutoForward; } });
|
|
34
|
+
var webhookUtils_1 = require("./webhookUtils");
|
|
35
|
+
Object.defineProperty(exports, "verifyWebhookSignature", { enumerable: true, get: function () { return webhookUtils_1.verifyWebhookSignature; } });
|
|
36
|
+
Object.defineProperty(exports, "verifyWebhookSignatureStrict", { enumerable: true, get: function () { return webhookUtils_1.verifyWebhookSignatureStrict; } });
|
|
37
|
+
Object.defineProperty(exports, "tryVerifyWithSecrets", { enumerable: true, get: function () { return webhookUtils_1.tryVerifyWithSecrets; } });
|
|
38
|
+
Object.defineProperty(exports, "WebhookVerificationError", { enumerable: true, get: function () { return webhookUtils_1.WebhookVerificationError; } });
|
|
31
39
|
// Default export for CommonJS compatibility
|
|
32
40
|
const client_2 = require("./client");
|
|
33
41
|
exports.default = client_2.EZThrottle;
|