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/README.md +262 -10
- package/dist/client.d.ts +58 -0
- package/dist/client.js +177 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.js +25 -0
- package/dist/idempotentStrategy.d.ts +9 -0
- package/dist/idempotentStrategy.js +13 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +33 -0
- package/dist/step.d.ts +125 -0
- package/dist/step.js +411 -0
- package/dist/stepType.d.ts +9 -0
- package/dist/stepType.js +13 -0
- package/dist/types.d.ts +110 -0
- package/dist/types.js +2 -0
- package/package.json +20 -5
- package/examples/basic.js +0 -43
- package/src/client.js +0 -136
- package/src/errors.js +0 -27
- package/src/index.js +0 -11
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
|
+
}
|
package/dist/stepType.js
ADDED
|
@@ -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 = {}));
|
package/dist/types.d.ts
ADDED
|
@@ -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