ezthrottle 1.0.0 → 1.1.1

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/dist/step.d.ts ADDED
@@ -0,0 +1,125 @@
1
+ import { StepType } from './stepType';
2
+ import { IdempotentStrategy } from './idempotentStrategy';
3
+ import { JobPayload, WebhookConfig, RetryPolicy, IStep } from './types';
4
+ import { EZThrottle } from './client';
5
+ /**
6
+ * Fluent builder for EZThrottle job steps
7
+ *
8
+ * Usage:
9
+ * const step = new Step(client)
10
+ * .url('https://api.example.com')
11
+ * .method('POST')
12
+ * .type(StepType.FRUGAL)
13
+ * .fallbackOnError([429, 500])
14
+ * .onSuccess(successStep)
15
+ * .execute();
16
+ */
17
+ export declare class Step implements IStep {
18
+ private client;
19
+ private _stepType;
20
+ private _url;
21
+ private _method;
22
+ private _headers;
23
+ private _body;
24
+ private _metadata;
25
+ private _webhooks;
26
+ private _webhookQuorum;
27
+ private _regions;
28
+ private _regionPolicy;
29
+ private _executionMode;
30
+ private _retryPolicy;
31
+ private _retryAt;
32
+ private _idempotentKey;
33
+ private _idempotentStrategy;
34
+ private _fallbackOnError;
35
+ private _localTimeout;
36
+ private _fallbackSteps;
37
+ private _onSuccessStep;
38
+ private _onFailureStep;
39
+ private _onFailureTimeoutMs;
40
+ constructor(client?: EZThrottle | null);
41
+ type(stepType: StepType): this;
42
+ url(url: string): this;
43
+ method(method: string): this;
44
+ headers(headers: Record<string, string>): this;
45
+ body(body: string): this;
46
+ metadata(metadata: Record<string, any>): this;
47
+ webhooks(webhooks: WebhookConfig[]): this;
48
+ webhookQuorum(quorum: number): this;
49
+ regions(regions: string[]): this;
50
+ regionPolicy(policy: 'fallback' | 'strict'): this;
51
+ executionMode(mode: 'race' | 'fanout'): this;
52
+ retryPolicy(policy: RetryPolicy): this;
53
+ retryAt(timestampMs: number): this;
54
+ idempotentKey(key: string): this;
55
+ idempotentStrategy(strategy: IdempotentStrategy): this;
56
+ /**
57
+ * (FRUGAL only) Set error codes that trigger EZThrottle forwarding
58
+ * Default: [429, 500, 502, 503, 504]
59
+ */
60
+ fallbackOnError(codes: number[]): this;
61
+ /**
62
+ * (FRUGAL only) Set timeout for local execution
63
+ * Default: 30000ms (30 seconds)
64
+ */
65
+ timeout(timeout: number): this;
66
+ /**
67
+ * Alias for timeout() to match IStep interface
68
+ * @deprecated Use timeout() instead
69
+ */
70
+ localTimeout(timeout: number): this;
71
+ /**
72
+ * Add fallback step with trigger conditions
73
+ */
74
+ fallback(step: Step, options?: {
75
+ triggerOnError?: number[] | null;
76
+ triggerOnTimeout?: number | null;
77
+ }): this;
78
+ /**
79
+ * Chain step to execute on success
80
+ */
81
+ onSuccess(step: Step): this;
82
+ /**
83
+ * Chain step to execute on failure
84
+ */
85
+ onFailure(step: Step, timeoutMs?: number | null): this;
86
+ /**
87
+ * Set timeout for on_failure workflow
88
+ */
89
+ onFailureTimeout(ms: number): this;
90
+ /**
91
+ * Build EZThrottle job payload from step configuration
92
+ */
93
+ _buildJobPayload(): JobPayload;
94
+ /**
95
+ * Build recursive fallback chain
96
+ */
97
+ _buildFallbackChain(): JobPayload | null;
98
+ /**
99
+ * Execute HTTP request locally (FRUGAL mode)
100
+ */
101
+ private _executeLocal;
102
+ /**
103
+ * Try all fallback steps locally
104
+ */
105
+ private _tryLocalFallbacks;
106
+ /**
107
+ * Execute the step workflow
108
+ *
109
+ * For FRUGAL: Executes locally first, forwards to EZThrottle on error
110
+ * For PERFORMANCE: Submits to EZThrottle immediately
111
+ */
112
+ execute(client?: EZThrottle | null): Promise<any>;
113
+ /**
114
+ * Execute FRUGAL workflow (local first, try fallbacks, then queue on error)
115
+ */
116
+ private _executeFrugal;
117
+ /**
118
+ * Forward job to EZThrottle
119
+ */
120
+ private _forwardToEZThrottle;
121
+ /**
122
+ * Execute PERFORMANCE workflow (submit to EZThrottle immediately)
123
+ */
124
+ private _executePerformance;
125
+ }
package/dist/step.js ADDED
@@ -0,0 +1,411 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Step = void 0;
7
+ const node_fetch_1 = __importDefault(require("node-fetch"));
8
+ const uuid_1 = require("uuid");
9
+ const stepType_1 = require("./stepType");
10
+ const idempotentStrategy_1 = require("./idempotentStrategy");
11
+ /**
12
+ * Fluent builder for EZThrottle job steps
13
+ *
14
+ * Usage:
15
+ * const step = new Step(client)
16
+ * .url('https://api.example.com')
17
+ * .method('POST')
18
+ * .type(StepType.FRUGAL)
19
+ * .fallbackOnError([429, 500])
20
+ * .onSuccess(successStep)
21
+ * .execute();
22
+ */
23
+ class Step {
24
+ constructor(client = null) {
25
+ this.client = client;
26
+ this._stepType = stepType_1.StepType.PERFORMANCE; // Default
27
+ // Request configuration
28
+ this._url = null;
29
+ this._method = 'GET';
30
+ this._headers = {};
31
+ this._body = null;
32
+ this._metadata = {};
33
+ // Webhooks configuration
34
+ this._webhooks = [];
35
+ this._webhookQuorum = 1;
36
+ // Multi-region configuration
37
+ this._regions = null;
38
+ this._regionPolicy = 'fallback';
39
+ this._executionMode = 'race';
40
+ // Retry configuration
41
+ this._retryPolicy = null;
42
+ this._retryAt = null;
43
+ // Deduplication
44
+ this._idempotentKey = null;
45
+ this._idempotentStrategy = idempotentStrategy_1.IdempotentStrategy.HASH; // Default
46
+ // Frugal-specific: error codes that trigger EZThrottle forwarding
47
+ this._fallbackOnError = [429, 500, 502, 503, 504];
48
+ this._localTimeout = 30000; // milliseconds
49
+ // Workflow chaining
50
+ this._fallbackSteps = [];
51
+ this._onSuccessStep = null;
52
+ this._onFailureStep = null;
53
+ this._onFailureTimeoutMs = null;
54
+ }
55
+ type(stepType) {
56
+ this._stepType = stepType;
57
+ return this;
58
+ }
59
+ url(url) {
60
+ this._url = url;
61
+ return this;
62
+ }
63
+ method(method) {
64
+ this._method = method.toUpperCase();
65
+ return this;
66
+ }
67
+ headers(headers) {
68
+ this._headers = headers;
69
+ return this;
70
+ }
71
+ body(body) {
72
+ this._body = body;
73
+ return this;
74
+ }
75
+ metadata(metadata) {
76
+ this._metadata = metadata;
77
+ return this;
78
+ }
79
+ webhooks(webhooks) {
80
+ this._webhooks = webhooks;
81
+ return this;
82
+ }
83
+ webhookQuorum(quorum) {
84
+ this._webhookQuorum = quorum;
85
+ return this;
86
+ }
87
+ regions(regions) {
88
+ this._regions = regions;
89
+ return this;
90
+ }
91
+ regionPolicy(policy) {
92
+ this._regionPolicy = policy;
93
+ return this;
94
+ }
95
+ executionMode(mode) {
96
+ this._executionMode = mode;
97
+ return this;
98
+ }
99
+ retryPolicy(policy) {
100
+ this._retryPolicy = policy;
101
+ return this;
102
+ }
103
+ retryAt(timestampMs) {
104
+ this._retryAt = timestampMs;
105
+ return this;
106
+ }
107
+ idempotentKey(key) {
108
+ this._idempotentKey = key;
109
+ return this;
110
+ }
111
+ idempotentStrategy(strategy) {
112
+ this._idempotentStrategy = strategy;
113
+ return this;
114
+ }
115
+ /**
116
+ * (FRUGAL only) Set error codes that trigger EZThrottle forwarding
117
+ * Default: [429, 500, 502, 503, 504]
118
+ */
119
+ fallbackOnError(codes) {
120
+ this._fallbackOnError = codes;
121
+ return this;
122
+ }
123
+ /**
124
+ * (FRUGAL only) Set timeout for local execution
125
+ * Default: 30000ms (30 seconds)
126
+ */
127
+ timeout(timeout) {
128
+ this._localTimeout = timeout;
129
+ return this;
130
+ }
131
+ /**
132
+ * Alias for timeout() to match IStep interface
133
+ * @deprecated Use timeout() instead
134
+ */
135
+ localTimeout(timeout) {
136
+ return this.timeout(timeout);
137
+ }
138
+ /**
139
+ * Add fallback step with trigger conditions
140
+ */
141
+ fallback(step, options = {}) {
142
+ const { triggerOnError = null, triggerOnTimeout = null } = options;
143
+ let trigger = {};
144
+ if (triggerOnError) {
145
+ trigger = { type: 'on_error', codes: triggerOnError };
146
+ }
147
+ else if (triggerOnTimeout) {
148
+ trigger = { type: 'on_timeout', timeout_ms: triggerOnTimeout };
149
+ }
150
+ this._fallbackSteps.push({ step, trigger });
151
+ return this;
152
+ }
153
+ /**
154
+ * Chain step to execute on success
155
+ */
156
+ onSuccess(step) {
157
+ this._onSuccessStep = step;
158
+ return this;
159
+ }
160
+ /**
161
+ * Chain step to execute on failure
162
+ */
163
+ onFailure(step, timeoutMs = null) {
164
+ this._onFailureStep = step;
165
+ if (timeoutMs) {
166
+ this._onFailureTimeoutMs = timeoutMs;
167
+ }
168
+ return this;
169
+ }
170
+ /**
171
+ * Set timeout for on_failure workflow
172
+ */
173
+ onFailureTimeout(ms) {
174
+ this._onFailureTimeoutMs = ms;
175
+ return this;
176
+ }
177
+ /**
178
+ * Build EZThrottle job payload from step configuration
179
+ */
180
+ _buildJobPayload() {
181
+ if (!this._url) {
182
+ throw new Error('URL is required');
183
+ }
184
+ const payload = {
185
+ url: this._url,
186
+ method: this._method,
187
+ };
188
+ // Add optional fields
189
+ if (Object.keys(this._headers).length > 0)
190
+ payload.headers = this._headers;
191
+ if (this._body)
192
+ payload.body = this._body;
193
+ if (Object.keys(this._metadata).length > 0)
194
+ payload.metadata = this._metadata;
195
+ if (this._webhooks.length > 0)
196
+ payload.webhooks = this._webhooks;
197
+ if (this._webhookQuorum !== 1)
198
+ payload.webhookQuorum = this._webhookQuorum;
199
+ if (this._regions)
200
+ payload.regions = this._regions;
201
+ if (this._regionPolicy !== 'fallback')
202
+ payload.regionPolicy = this._regionPolicy;
203
+ if (this._executionMode !== 'race')
204
+ payload.executionMode = this._executionMode;
205
+ if (this._retryPolicy)
206
+ payload.retryPolicy = this._retryPolicy;
207
+ if (this._retryAt !== null)
208
+ payload.retryAt = this._retryAt;
209
+ // Handle idempotent key based on strategy
210
+ if (this._idempotentKey) {
211
+ payload.idempotentKey = this._idempotentKey;
212
+ }
213
+ else if (this._idempotentStrategy === idempotentStrategy_1.IdempotentStrategy.UNIQUE) {
214
+ payload.idempotentKey = (0, uuid_1.v4)();
215
+ }
216
+ // else: HASH strategy - let backend generate deterministic hash
217
+ // Add fallback chain
218
+ if (this._fallbackSteps.length > 0) {
219
+ const fallbackJob = this._buildFallbackChain();
220
+ if (fallbackJob) {
221
+ payload.fallbackJob = fallbackJob;
222
+ }
223
+ }
224
+ // Add workflow chaining
225
+ if (this._onSuccessStep) {
226
+ payload.onSuccess = this._onSuccessStep._buildJobPayload();
227
+ }
228
+ if (this._onFailureStep) {
229
+ payload.onFailure = this._onFailureStep._buildJobPayload();
230
+ }
231
+ if (this._onFailureTimeoutMs !== null) {
232
+ payload.onFailureTimeoutMs = this._onFailureTimeoutMs;
233
+ }
234
+ return payload;
235
+ }
236
+ /**
237
+ * Build recursive fallback chain
238
+ */
239
+ _buildFallbackChain() {
240
+ if (this._fallbackSteps.length === 0) {
241
+ return null;
242
+ }
243
+ // Build chain recursively (first fallback → second fallback → ...)
244
+ let fallbackJob = null;
245
+ for (let i = this._fallbackSteps.length - 1; i >= 0; i--) {
246
+ const { step, trigger } = this._fallbackSteps[i];
247
+ const currentFallback = step._buildJobPayload();
248
+ currentFallback.trigger = trigger;
249
+ // Attach nested fallback
250
+ if (fallbackJob) {
251
+ currentFallback.fallbackJob = fallbackJob;
252
+ }
253
+ fallbackJob = currentFallback;
254
+ }
255
+ return fallbackJob;
256
+ }
257
+ /**
258
+ * Execute HTTP request locally (FRUGAL mode)
259
+ */
260
+ async _executeLocal() {
261
+ if (!this._url) {
262
+ throw new Error('URL is required');
263
+ }
264
+ const controller = new AbortController();
265
+ const timeout = setTimeout(() => controller.abort(), this._localTimeout);
266
+ try {
267
+ const response = await (0, node_fetch_1.default)(this._url, {
268
+ method: this._method,
269
+ headers: this._headers,
270
+ body: this._body || undefined,
271
+ signal: controller.signal,
272
+ });
273
+ clearTimeout(timeout);
274
+ return response;
275
+ }
276
+ catch (error) {
277
+ clearTimeout(timeout);
278
+ throw error;
279
+ }
280
+ }
281
+ /**
282
+ * Try all fallback steps locally
283
+ */
284
+ async _tryLocalFallbacks(client, errorCode = null) {
285
+ for (const { step, trigger } of this._fallbackSteps) {
286
+ // Check if this fallback should be triggered
287
+ let shouldTrigger = false;
288
+ if (trigger && trigger.type) {
289
+ const triggerType = trigger.type;
290
+ if (triggerType === 'on_error') {
291
+ const triggerCodes = trigger.codes || [];
292
+ if (errorCode && triggerCodes.includes(errorCode)) {
293
+ shouldTrigger = true;
294
+ }
295
+ }
296
+ else if (triggerType === 'on_timeout') {
297
+ // For timeout trigger, always try
298
+ shouldTrigger = true;
299
+ }
300
+ }
301
+ else {
302
+ // No trigger specified, always try
303
+ shouldTrigger = true;
304
+ }
305
+ if (!shouldTrigger) {
306
+ continue;
307
+ }
308
+ // Try this fallback locally
309
+ try {
310
+ // Only execute fallback if it's FRUGAL type
311
+ if (step._stepType === stepType_1.StepType.FRUGAL) {
312
+ const result = await step.execute(client);
313
+ // If fallback succeeded, return immediately
314
+ if (result.status === 'success') {
315
+ return result;
316
+ }
317
+ }
318
+ else {
319
+ // PERFORMANCE fallback - can't execute locally, skip
320
+ continue;
321
+ }
322
+ }
323
+ catch (error) {
324
+ // Fallback failed, try next one
325
+ continue;
326
+ }
327
+ }
328
+ // All fallbacks failed
329
+ return null;
330
+ }
331
+ /**
332
+ * Execute the step workflow
333
+ *
334
+ * For FRUGAL: Executes locally first, forwards to EZThrottle on error
335
+ * For PERFORMANCE: Submits to EZThrottle immediately
336
+ */
337
+ async execute(client = null) {
338
+ const _client = client || this.client;
339
+ if (!_client) {
340
+ throw new Error('Client is required. Pass client to execute() or Step(client)');
341
+ }
342
+ if (this._stepType === stepType_1.StepType.FRUGAL) {
343
+ return this._executeFrugal(_client);
344
+ }
345
+ else {
346
+ return this._executePerformance(_client);
347
+ }
348
+ }
349
+ /**
350
+ * Execute FRUGAL workflow (local first, try fallbacks, then queue on error)
351
+ */
352
+ async _executeFrugal(client) {
353
+ // Try primary step locally
354
+ try {
355
+ const response = await this._executeLocal();
356
+ // Success! Execute on_success and return
357
+ if (response.status >= 200 && response.status < 300) {
358
+ // Execute on_success workflow if present (async, don't wait)
359
+ if (this._onSuccessStep) {
360
+ setImmediate(() => this._onSuccessStep.execute(client));
361
+ }
362
+ const body = await response.text();
363
+ return {
364
+ status: 'success',
365
+ executed_locally: true,
366
+ status_code: response.status,
367
+ response: body,
368
+ };
369
+ }
370
+ // Error - try fallback chain locally
371
+ if (this._fallbackOnError.includes(response.status)) {
372
+ const fallbackResult = await this._tryLocalFallbacks(client, response.status);
373
+ if (fallbackResult) {
374
+ return fallbackResult;
375
+ }
376
+ // All fallbacks failed → forward to EZThrottle
377
+ return this._forwardToEZThrottle(client);
378
+ }
379
+ // Non-trigger error - don't forward, just return error
380
+ return {
381
+ status: 'failed',
382
+ executed_locally: true,
383
+ status_code: response.status,
384
+ error: `Request failed: ${response.status}`,
385
+ };
386
+ }
387
+ catch (error) {
388
+ // Network error or timeout → try fallbacks, then forward to EZThrottle
389
+ const fallbackResult = await this._tryLocalFallbacks(client, null);
390
+ if (fallbackResult) {
391
+ return fallbackResult;
392
+ }
393
+ return this._forwardToEZThrottle(client);
394
+ }
395
+ }
396
+ /**
397
+ * Forward job to EZThrottle
398
+ */
399
+ async _forwardToEZThrottle(client) {
400
+ const payload = this._buildJobPayload();
401
+ return client.submitJob(payload);
402
+ }
403
+ /**
404
+ * Execute PERFORMANCE workflow (submit to EZThrottle immediately)
405
+ */
406
+ async _executePerformance(client) {
407
+ const payload = this._buildJobPayload();
408
+ return client.submitJob(payload);
409
+ }
410
+ }
411
+ exports.Step = Step;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Step execution strategy
3
+ */
4
+ export declare enum StepType {
5
+ /** Client executes first, queue to EZThrottle on error (cost optimization) */
6
+ FRUGAL = "frugal",
7
+ /** Server executes immediately via EZThrottle (speed optimization) */
8
+ PERFORMANCE = "performance"
9
+ }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StepType = void 0;
4
+ /**
5
+ * Step execution strategy
6
+ */
7
+ var StepType;
8
+ (function (StepType) {
9
+ /** Client executes first, queue to EZThrottle on error (cost optimization) */
10
+ StepType["FRUGAL"] = "frugal";
11
+ /** Server executes immediately via EZThrottle (speed optimization) */
12
+ StepType["PERFORMANCE"] = "performance";
13
+ })(StepType || (exports.StepType = StepType = {}));
@@ -0,0 +1,110 @@
1
+ import { StepType } from './stepType';
2
+ import { IdempotentStrategy } from './idempotentStrategy';
3
+ import type { Step } from './step';
4
+ /**
5
+ * Webhook configuration
6
+ */
7
+ export interface WebhookConfig {
8
+ url: string;
9
+ regions?: string[];
10
+ has_quorum_vote?: boolean;
11
+ }
12
+ /**
13
+ * Retry policy configuration
14
+ */
15
+ export interface RetryPolicy {
16
+ max_retries?: number;
17
+ max_reroutes?: number;
18
+ retry_codes?: number[];
19
+ reroute_codes?: number[];
20
+ }
21
+ /**
22
+ * Fallback trigger configuration
23
+ */
24
+ export interface FallbackTrigger {
25
+ type: 'on_error' | 'on_timeout';
26
+ codes?: number[];
27
+ timeout_ms?: number;
28
+ }
29
+ /**
30
+ * Job payload sent to EZThrottle API
31
+ */
32
+ export interface JobPayload {
33
+ url: string;
34
+ method: string;
35
+ headers?: Record<string, string>;
36
+ body?: string;
37
+ metadata?: Record<string, any>;
38
+ webhooks?: WebhookConfig[];
39
+ webhookQuorum?: number;
40
+ regions?: string[];
41
+ regionPolicy?: 'fallback' | 'strict';
42
+ executionMode?: 'race' | 'fanout';
43
+ retryPolicy?: RetryPolicy;
44
+ fallbackJob?: JobPayload;
45
+ onSuccess?: JobPayload;
46
+ onFailure?: JobPayload;
47
+ onFailureTimeoutMs?: number;
48
+ idempotentKey?: string;
49
+ retryAt?: number;
50
+ }
51
+ /**
52
+ * Submit job parameters
53
+ */
54
+ export interface SubmitJobParams {
55
+ url: string;
56
+ method?: string;
57
+ headers?: Record<string, string>;
58
+ body?: string;
59
+ metadata?: Record<string, any>;
60
+ webhooks?: WebhookConfig[];
61
+ webhookQuorum?: number;
62
+ regions?: string[];
63
+ regionPolicy?: 'fallback' | 'strict';
64
+ executionMode?: 'race' | 'fanout';
65
+ retryPolicy?: RetryPolicy;
66
+ fallbackJob?: JobPayload;
67
+ onSuccess?: JobPayload;
68
+ onFailure?: JobPayload;
69
+ onFailureTimeoutMs?: number;
70
+ idempotentKey?: string;
71
+ retryAt?: number;
72
+ }
73
+ /**
74
+ * EZThrottle client configuration
75
+ */
76
+ export interface EZThrottleConfig {
77
+ apiKey: string;
78
+ tracktagsUrl?: string;
79
+ ezthrottleUrl?: string;
80
+ }
81
+ /**
82
+ * Step class builder methods return type
83
+ */
84
+ export interface IStep {
85
+ url(url: string): this;
86
+ method(method: string): this;
87
+ headers(headers: Record<string, string>): this;
88
+ body(body: string): this;
89
+ metadata(metadata: Record<string, any>): this;
90
+ type(type: StepType): this;
91
+ webhooks(webhooks: WebhookConfig[]): this;
92
+ webhookQuorum(quorum: number): this;
93
+ regions(regions: string[]): this;
94
+ regionPolicy(policy: 'fallback' | 'strict'): this;
95
+ executionMode(mode: 'race' | 'fanout'): this;
96
+ retryPolicy(policy: RetryPolicy): this;
97
+ retryAt(timestamp: number): this;
98
+ idempotentKey(key: string): this;
99
+ idempotentStrategy(strategy: IdempotentStrategy): this;
100
+ fallbackOnError(codes: number[]): this;
101
+ timeout(ms: number): this;
102
+ fallback(step: Step, options?: {
103
+ triggerOnError?: number[] | null;
104
+ triggerOnTimeout?: number | null;
105
+ }): this;
106
+ onSuccess(step: any): this;
107
+ onFailure(step: any): this;
108
+ onFailureTimeout(ms: number): this;
109
+ execute(client?: any): Promise<any>;
110
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });