@ductape/sdk 0.0.4-v55 → 0.0.4-v57

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.
@@ -3,11 +3,17 @@
3
3
  * Quota Service
4
4
  *
5
5
  * Code-first API for defining and managing quotas with weighted distribution.
6
+ * Uses ProductBuilder for CRUD operations and ProcessorService for execution.
7
+ * Implements health-aware weighted selection with minimal latency.
6
8
  */
9
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
7
12
  Object.defineProperty(exports, "__esModule", { value: true });
8
13
  exports.QuotaService = exports.QuotaError = void 0;
9
14
  const types_1 = require("./types");
10
- const resilienceApi_service_1 = require("../api/services/resilienceApi.service");
15
+ const products_service_1 = __importDefault(require("../products/services/products.service"));
16
+ const processor_service_1 = __importDefault(require("../processor/services/processor.service"));
11
17
  /**
12
18
  * Error class for quota operations
13
19
  */
@@ -20,6 +26,97 @@ class QuotaError extends Error {
20
26
  }
21
27
  }
22
28
  exports.QuotaError = QuotaError;
29
+ // ============================================================================
30
+ // Recording Builders (for code-first API)
31
+ // ============================================================================
32
+ class RecordingAppProviderBuilder {
33
+ constructor(parent, appTag) {
34
+ this.parent = parent;
35
+ this.appTag = appTag;
36
+ }
37
+ action(event) {
38
+ this.parent.setAppAction(this.appTag, event);
39
+ return this.parent;
40
+ }
41
+ weight(value) { return this.parent.weight(value); }
42
+ healthcheck(tag) { return this.parent.healthcheck(tag); }
43
+ app(tag) { return this.parent.app(tag); }
44
+ feature(tag) { return this.parent.feature(tag); }
45
+ database(tag) { return this.parent.database(tag); }
46
+ workflow(tag) { return this.parent.workflow(tag); }
47
+ notification(tag) { return this.parent.notification(tag); }
48
+ mapInput(mapper) { return this.parent.mapInput(mapper); }
49
+ mapOutput(mapper) { return this.parent.mapOutput(mapper); }
50
+ retries(count) { return this.parent.retries(count); }
51
+ checkInterval(ms) { return this.parent.checkInterval(ms); }
52
+ }
53
+ class RecordingDatabaseProviderBuilder {
54
+ constructor(parent, databaseTag) {
55
+ this.parent = parent;
56
+ this.databaseTag = databaseTag;
57
+ }
58
+ action(event) {
59
+ this.parent.setDatabaseAction(this.databaseTag, event);
60
+ return this.parent;
61
+ }
62
+ weight(value) { return this.parent.weight(value); }
63
+ healthcheck(tag) { return this.parent.healthcheck(tag); }
64
+ app(tag) { return this.parent.app(tag); }
65
+ feature(tag) { return this.parent.feature(tag); }
66
+ database(tag) { return this.parent.database(tag); }
67
+ workflow(tag) { return this.parent.workflow(tag); }
68
+ notification(tag) { return this.parent.notification(tag); }
69
+ mapInput(mapper) { return this.parent.mapInput(mapper); }
70
+ mapOutput(mapper) { return this.parent.mapOutput(mapper); }
71
+ retries(count) { return this.parent.retries(count); }
72
+ checkInterval(ms) { return this.parent.checkInterval(ms); }
73
+ }
74
+ class RecordingWorkflowProviderBuilder {
75
+ constructor(parent, workflowTag) {
76
+ this.parent = parent;
77
+ this.parent.setWorkflow(workflowTag);
78
+ }
79
+ input(data) {
80
+ this.parent.setWorkflowInput(data);
81
+ return this.parent;
82
+ }
83
+ weight(value) { return this.parent.weight(value); }
84
+ healthcheck(tag) { return this.parent.healthcheck(tag); }
85
+ app(tag) { return this.parent.app(tag); }
86
+ feature(tag) { return this.parent.feature(tag); }
87
+ database(tag) { return this.parent.database(tag); }
88
+ workflow(tag) { return this.parent.workflow(tag); }
89
+ notification(tag) { return this.parent.notification(tag); }
90
+ mapInput(mapper) { return this.parent.mapInput(mapper); }
91
+ mapOutput(mapper) { return this.parent.mapOutput(mapper); }
92
+ retries(count) { return this.parent.retries(count); }
93
+ checkInterval(ms) { return this.parent.checkInterval(ms); }
94
+ }
95
+ class RecordingNotificationProviderBuilder {
96
+ constructor(parent, notificationTag) {
97
+ this.parent = parent;
98
+ this.parent.setNotification(notificationTag);
99
+ }
100
+ message(messageTag) {
101
+ this.parent.setNotificationMessage(messageTag);
102
+ return this.parent;
103
+ }
104
+ event(eventTag) {
105
+ this.parent.setNotificationEvent(eventTag);
106
+ return this.parent;
107
+ }
108
+ weight(value) { return this.parent.weight(value); }
109
+ healthcheck(tag) { return this.parent.healthcheck(tag); }
110
+ app(tag) { return this.parent.app(tag); }
111
+ feature(tag) { return this.parent.feature(tag); }
112
+ database(tag) { return this.parent.database(tag); }
113
+ workflow(tag) { return this.parent.workflow(tag); }
114
+ notification(tag) { return this.parent.notification(tag); }
115
+ mapInput(mapper) { return this.parent.mapInput(mapper); }
116
+ mapOutput(mapper) { return this.parent.mapOutput(mapper); }
117
+ retries(count) { return this.parent.retries(count); }
118
+ checkInterval(ms) { return this.parent.checkInterval(ms); }
119
+ }
23
120
  /**
24
121
  * Provider builder that records provider configuration
25
122
  */
@@ -39,310 +136,75 @@ class RecordingProviderBuilder {
39
136
  return this;
40
137
  }
41
138
  app(tag) {
42
- this._type = types_1.FeatureEventTypes.ACTION;
43
139
  this._app = tag;
44
- return new RecordingAppProviderBuilder(this, this._app);
140
+ return new RecordingAppProviderBuilder(this, tag);
45
141
  }
46
- /**
47
- * @deprecated Features are deprecated - use workflow() instead
48
- */
49
142
  feature(tag) {
50
143
  console.warn('feature() is deprecated. Use workflow() instead.');
51
- this._type = types_1.FeatureEventTypes.ACTION;
52
144
  this._event = tag;
53
145
  this.finalize();
54
146
  return this;
55
147
  }
56
148
  database(tag) {
57
- this._type = types_1.FeatureEventTypes.ACTION;
58
- this._database = tag;
59
- return new RecordingDatabaseProviderBuilder(this, this._database);
149
+ return new RecordingDatabaseProviderBuilder(this, tag);
60
150
  }
61
151
  workflow(tag) {
62
- this._type = types_1.FeatureEventTypes.ACTION;
63
- this._workflow = tag;
64
- return new RecordingWorkflowProviderBuilder(this, this._workflow);
152
+ return new RecordingWorkflowProviderBuilder(this, tag);
65
153
  }
66
154
  notification(tag) {
67
- this._type = types_1.FeatureEventTypes.ACTION;
68
- this._notification = tag;
69
- return new RecordingNotificationProviderBuilder(this, this._notification);
155
+ return new RecordingNotificationProviderBuilder(this, tag);
70
156
  }
71
- mapInput(mapper) {
72
- this._inputMapper = mapper;
157
+ mapInput(_mapper) {
73
158
  return this;
74
159
  }
75
- mapOutput(mapper) {
76
- this._outputMapper = mapper;
160
+ mapOutput(_mapper) {
77
161
  return this;
78
162
  }
79
163
  retries(count) {
80
164
  this._retries = count;
165
+ this.finalize();
81
166
  return this;
82
167
  }
83
168
  checkInterval(ms) {
84
169
  this._checkInterval = ms;
85
170
  return this;
86
171
  }
87
- /**
88
- * Set event/action name (used by sub-builders)
89
- */
90
- setEvent(event) {
172
+ setAppAction(app, event) {
173
+ this._app = app;
91
174
  this._event = event;
92
- this.finalize();
93
175
  }
94
- /**
95
- * Set workflow input (used by workflow builder)
96
- */
97
- setWorkflowInput(input) {
98
- this._workflowInput = input;
99
- this.finalize();
176
+ setDatabaseAction(_database, event) {
177
+ this._event = event;
178
+ }
179
+ setWorkflow(workflow) {
180
+ this._event = workflow;
181
+ }
182
+ setWorkflowInput(_input) { }
183
+ setNotification(_notification) { }
184
+ setNotificationMessage(_message) { }
185
+ setNotificationEvent(event) {
186
+ this._event = event;
100
187
  }
101
- /**
102
- * Finalize and add provider to options
103
- */
104
188
  finalize() {
105
- // Generate input mapping from function
106
- const inputSchema = this.generateInputSchema();
107
- const outputSchema = this.generateOutputSchema();
108
189
  const option = {
109
190
  provider: this.providerName,
110
191
  quota: this._weight,
111
- healthcheck: this._healthcheck,
112
- type: this._type || types_1.FeatureEventTypes.ACTION,
113
- app: this._app,
192
+ type: types_1.FeatureEventTypes.ACTION,
114
193
  event: this._event || '',
115
- input: this._workflowInput || inputSchema,
116
- output: outputSchema,
194
+ input: {},
195
+ output: {},
117
196
  retries: this._retries,
197
+ healthcheck: this._healthcheck,
118
198
  check_interval: this._checkInterval,
119
199
  };
120
- this.options.push(option);
121
- }
122
- /**
123
- * Generate input schema from mapper function
124
- */
125
- generateInputSchema() {
126
- if (!this._inputMapper) {
127
- return {};
200
+ if (this._app) {
201
+ option.app = this._app;
128
202
  }
129
- // Create a proxy to capture property access patterns
130
- const proxyHandler = {
131
- get: (_target, prop) => {
132
- if (typeof prop === 'string') {
133
- return `$Input{${prop}}`;
134
- }
135
- return undefined;
136
- },
137
- };
138
- try {
139
- const proxy = new Proxy({}, proxyHandler);
140
- return this._inputMapper(proxy);
141
- }
142
- catch (_a) {
143
- // If proxy fails, return empty schema
144
- return {};
145
- }
146
- }
147
- /**
148
- * Generate output schema from mapper function
149
- */
150
- generateOutputSchema() {
151
- if (!this._outputMapper) {
152
- return {};
153
- }
154
- // Create a proxy to capture property access patterns
155
- const proxyHandler = {
156
- get: (_target, prop) => {
157
- if (typeof prop === 'string') {
158
- return `$Response{${prop}}`;
159
- }
160
- return undefined;
161
- },
162
- };
163
- try {
164
- const proxy = new Proxy({}, proxyHandler);
165
- return this._outputMapper(proxy);
166
- }
167
- catch (_a) {
168
- return {};
169
- }
170
- }
171
- }
172
- class RecordingAppProviderBuilder {
173
- constructor(parent, appTag) {
174
- this.parent = parent;
175
- this.appTag = appTag;
176
- }
177
- action(event) {
178
- this.parent.setEvent(event);
179
- return this.parent;
180
- }
181
- weight(value) {
182
- return this.parent.weight(value);
183
- }
184
- healthcheck(tag) {
185
- return this.parent.healthcheck(tag);
186
- }
187
- app(tag) {
188
- return this.parent.app(tag);
189
- }
190
- feature(tag) {
191
- return this.parent.feature(tag);
192
- }
193
- database(tag) {
194
- return this.parent.database(tag);
195
- }
196
- workflow(tag) {
197
- return this.parent.workflow(tag);
198
- }
199
- notification(tag) {
200
- return this.parent.notification(tag);
201
- }
202
- mapInput(mapper) {
203
- return this.parent.mapInput(mapper);
204
- }
205
- mapOutput(mapper) {
206
- return this.parent.mapOutput(mapper);
207
- }
208
- retries(count) {
209
- return this.parent.retries(count);
210
- }
211
- checkInterval(ms) {
212
- return this.parent.checkInterval(ms);
213
- }
214
- }
215
- class RecordingDatabaseProviderBuilder {
216
- constructor(parent, dbTag) {
217
- this.parent = parent;
218
- this.dbTag = dbTag;
219
- }
220
- action(event) {
221
- this.parent.setEvent(event);
222
- return this.parent;
223
- }
224
- weight(value) {
225
- return this.parent.weight(value);
226
- }
227
- healthcheck(tag) {
228
- return this.parent.healthcheck(tag);
229
- }
230
- app(tag) {
231
- return this.parent.app(tag);
232
- }
233
- feature(tag) {
234
- return this.parent.feature(tag);
235
- }
236
- database(tag) {
237
- return this.parent.database(tag);
238
- }
239
- workflow(tag) {
240
- return this.parent.workflow(tag);
241
- }
242
- notification(tag) {
243
- return this.parent.notification(tag);
244
- }
245
- mapInput(mapper) {
246
- return this.parent.mapInput(mapper);
247
- }
248
- mapOutput(mapper) {
249
- return this.parent.mapOutput(mapper);
250
- }
251
- retries(count) {
252
- return this.parent.retries(count);
253
- }
254
- checkInterval(ms) {
255
- return this.parent.checkInterval(ms);
256
- }
257
- }
258
- class RecordingWorkflowProviderBuilder {
259
- constructor(parent, workflowTag) {
260
- this.parent = parent;
261
- this.workflowTag = workflowTag;
262
- }
263
- input(data) {
264
- this.parent.setWorkflowInput(data);
265
- return this.parent;
266
- }
267
- weight(value) {
268
- return this.parent.weight(value);
269
- }
270
- healthcheck(tag) {
271
- return this.parent.healthcheck(tag);
272
- }
273
- app(tag) {
274
- return this.parent.app(tag);
275
- }
276
- feature(tag) {
277
- return this.parent.feature(tag);
278
- }
279
- database(tag) {
280
- return this.parent.database(tag);
281
- }
282
- workflow(tag) {
283
- return this.parent.workflow(tag);
284
- }
285
- notification(tag) {
286
- return this.parent.notification(tag);
287
- }
288
- mapInput(mapper) {
289
- return this.parent.mapInput(mapper);
290
- }
291
- mapOutput(mapper) {
292
- return this.parent.mapOutput(mapper);
293
- }
294
- retries(count) {
295
- return this.parent.retries(count);
296
- }
297
- checkInterval(ms) {
298
- return this.parent.checkInterval(ms);
299
- }
300
- }
301
- class RecordingNotificationProviderBuilder {
302
- constructor(parent, notificationTag) {
303
- this.parent = parent;
304
- this.notificationTag = notificationTag;
305
- }
306
- event(event) {
307
- this.parent.setEvent(event);
308
- return this.parent;
309
- }
310
- weight(value) {
311
- return this.parent.weight(value);
312
- }
313
- healthcheck(tag) {
314
- return this.parent.healthcheck(tag);
315
- }
316
- app(tag) {
317
- return this.parent.app(tag);
318
- }
319
- feature(tag) {
320
- return this.parent.feature(tag);
321
- }
322
- database(tag) {
323
- return this.parent.database(tag);
324
- }
325
- workflow(tag) {
326
- return this.parent.workflow(tag);
327
- }
328
- notification(tag) {
329
- return this.parent.notification(tag);
330
- }
331
- mapInput(mapper) {
332
- return this.parent.mapInput(mapper);
333
- }
334
- mapOutput(mapper) {
335
- return this.parent.mapOutput(mapper);
336
- }
337
- retries(count) {
338
- return this.parent.retries(count);
339
- }
340
- checkInterval(ms) {
341
- return this.parent.checkInterval(ms);
203
+ this.options.push(option);
342
204
  }
343
205
  }
344
206
  /**
345
- * Recording context that captures quota configuration during handler execution
207
+ * Recording context for quota configuration
346
208
  */
347
209
  class RecordingQuotaContext {
348
210
  constructor(inputSchema) {
@@ -350,106 +212,84 @@ class RecordingQuotaContext {
350
212
  this._input = inputSchema;
351
213
  }
352
214
  get input() {
353
- // Create a proxy that returns data references
354
- const proxyHandler = {
355
- get: (target, prop) => {
356
- if (typeof prop === 'string') {
357
- return `$Input{${prop}}`;
358
- }
359
- return undefined;
360
- },
361
- };
362
- return new Proxy({}, proxyHandler);
215
+ return new Proxy({}, {
216
+ get: (_, prop) => typeof prop === 'string' ? `$Input{${prop}}` : undefined,
217
+ });
363
218
  }
364
219
  provider(name) {
365
220
  return new RecordingProviderBuilder(name, this._options);
366
221
  }
367
- auth(field) {
368
- return `$Auth{${field}}`;
369
- }
370
- token(key) {
371
- return `$Secret{${key}}`;
372
- }
373
- variable(app, key) {
374
- return `$Variable{${app}}{${key}}`;
375
- }
376
- constant(app, key) {
377
- return `$Constant{${app}}{${key}}`;
378
- }
379
- /**
380
- * Build the quota schema from recorded configuration
381
- */
222
+ auth(field) { return `$Auth{${field}}`; }
223
+ token(key) { return `$Secret{${key}}`; }
224
+ variable(app, key) { return `$Variable{${app}}{${key}}`; }
225
+ constant(app, key) { return `$Constant{${app}}{${key}}`; }
382
226
  build(tag, name, description) {
383
227
  if (this._options.length === 0) {
384
228
  throw new QuotaError('At least one provider must be configured', 'NO_PROVIDERS_CONFIGURED');
385
229
  }
386
- const total_quota = this._options.reduce((sum, opt) => sum + opt.quota, 0);
387
230
  return {
388
231
  tag,
389
232
  name,
390
233
  description,
391
234
  input: this._input,
392
- total_quota,
235
+ total_quota: this._options.reduce((sum, opt) => sum + opt.quota, 0),
393
236
  options: this._options,
394
237
  };
395
238
  }
396
- get options() {
397
- return this._options;
398
- }
239
+ get options() { return this._options; }
399
240
  }
400
- /**
401
- * Quota Service
402
- *
403
- * Provides code-first API for defining, creating, and managing quotas.
404
- */
241
+ // ============================================================================
242
+ // Quota Service - Optimized for Minimal Latency
243
+ // ============================================================================
405
244
  class QuotaService {
406
245
  constructor(config) {
407
- this.apiService = new resilienceApi_service_1.ResilienceApiService({
408
- token: config.token,
409
- env_type: config.env_type,
410
- base_url: config.base_url,
411
- });
246
+ this.productBuilders = new Map();
247
+ this.processorCache = new Map();
248
+ this.config = config;
412
249
  }
413
250
  /**
414
- * Define a quota using code-first API
415
- *
416
- * @example
417
- * ```ts
418
- * const quota = await quotaService.define({
419
- * product: 'my-product',
420
- * tag: 'sms-quota',
421
- * name: 'SMS Provider Quota',
422
- * input: {
423
- * phone: { type: 'string', required: true },
424
- * message: { type: 'string', required: true },
425
- * },
426
- * handler: async (ctx) => {
427
- * ctx.provider('twilio')
428
- * .weight(70)
429
- * .healthcheck('twilio-health')
430
- * .app('twilio-app')
431
- * .action('send-sms')
432
- * .mapInput((input) => ({ body: { to: input.phone, body: input.message } }))
433
- * .mapOutput((res) => ({ messageId: res.sid }))
434
- * .retries(2);
435
- *
436
- * ctx.provider('nexmo')
437
- * .weight(30)
438
- * .healthcheck('nexmo-health')
439
- * .app('nexmo-app')
440
- * .action('send-sms')
441
- * .mapInput((input) => ({ body: { to: input.phone, text: input.message } }))
442
- * .mapOutput((res) => ({ messageId: res['message-id'] }))
443
- * .retries(2);
444
- * },
445
- * });
446
- * ```
251
+ * Gets or creates a cached ProductBuilder
252
+ */
253
+ async getProductBuilder(productTag) {
254
+ let builder = this.productBuilders.get(productTag);
255
+ if (!builder) {
256
+ builder = new products_service_1.default({
257
+ workspace_id: this.config.workspace_id,
258
+ public_key: this.config.public_key,
259
+ user_id: this.config.user_id,
260
+ token: this.config.token,
261
+ env_type: this.config.env_type,
262
+ redis_client: this.config.redis_client,
263
+ queues: this.config.queues,
264
+ });
265
+ await builder.initializeProductByTag(productTag);
266
+ this.productBuilders.set(productTag, builder);
267
+ }
268
+ return builder;
269
+ }
270
+ /**
271
+ * Gets or creates a cached ProcessorService for minimal latency
447
272
  */
273
+ getProcessor(productTag) {
274
+ let processor = this.processorCache.get(productTag);
275
+ if (!processor) {
276
+ processor = new processor_service_1.default({
277
+ workspace_id: this.config.workspace_id,
278
+ public_key: this.config.public_key,
279
+ user_id: this.config.user_id,
280
+ token: this.config.token,
281
+ env_type: this.config.env_type,
282
+ redis_client: this.config.redis_client,
283
+ queues: this.config.queues,
284
+ });
285
+ this.processorCache.set(productTag, processor);
286
+ }
287
+ return processor;
288
+ }
289
+ // ==================== Code-First API ====================
448
290
  async define(options) {
449
291
  const ctx = new RecordingQuotaContext(options.input);
450
- // Execute the handler to record the configuration
451
292
  await options.handler(ctx);
452
- // Build the schema
453
293
  const schema = ctx.build(options.tag, options.name, options.description);
454
294
  const definedQuota = {
455
295
  tag: options.tag,
@@ -458,60 +298,215 @@ class QuotaService {
458
298
  input: options.input,
459
299
  compile: () => schema,
460
300
  };
461
- // If product is specified, register immediately
462
301
  if (options.product) {
463
302
  await this.create(options.product, definedQuota);
464
303
  }
465
304
  return definedQuota;
466
305
  }
467
- /**
468
- * Create a quota from a defined quota or schema
469
- */
306
+ // ==================== CRUD via ProductBuilder ====================
470
307
  async create(product, quota) {
471
308
  const schema = 'compile' in quota ? quota.compile() : quota;
472
- return await this.apiService.createQuota(product, schema);
309
+ const builder = await this.getProductBuilder(product);
310
+ const productQuota = {
311
+ tag: schema.tag,
312
+ name: schema.name || schema.tag,
313
+ description: schema.description || '',
314
+ input: schema.input,
315
+ total_quota: schema.total_quota,
316
+ options: schema.options.map(opt => ({
317
+ app: opt.app,
318
+ type: types_1.FeatureEventTypes.ACTION,
319
+ event: opt.event,
320
+ input: opt.input || {},
321
+ output: opt.output || {},
322
+ retries: opt.retries || 2,
323
+ quota: opt.quota,
324
+ healthcheck: opt.healthcheck,
325
+ check_interval: opt.check_interval,
326
+ })),
327
+ };
328
+ await builder.createQuota(productQuota);
329
+ return await builder.fetchQuota(schema.tag);
473
330
  }
474
- /**
475
- * Update an existing quota
476
- */
477
- async update(product, tag, quota) {
478
- if ('handler' in quota && quota.handler && quota.input) {
479
- const ctx = new RecordingQuotaContext(quota.input);
480
- await quota.handler(ctx);
481
- const schema = ctx.build(tag, quota.name, quota.description);
482
- return await this.apiService.updateQuota(product, tag, schema);
483
- }
484
- return await this.apiService.updateQuota(product, tag, quota);
331
+ async fetch(product, tag) {
332
+ const builder = await this.getProductBuilder(product);
333
+ return builder.fetchQuota(tag);
334
+ }
335
+ async fetchAll(product) {
336
+ const builder = await this.getProductBuilder(product);
337
+ return builder.fetchQuotas();
485
338
  }
339
+ async update(product, tag, data) {
340
+ const builder = await this.getProductBuilder(product);
341
+ await builder.updateQuota(tag, data);
342
+ }
343
+ async delete(product, tag) {
344
+ const builder = await this.getProductBuilder(product);
345
+ await builder.deleteQuota(tag);
346
+ }
347
+ // ==================== Execution - Optimized for Minimal Latency ====================
486
348
  /**
487
- * Fetch a quota by tag
349
+ * Run a quota with health-aware weighted provider selection
350
+ *
351
+ * Optimization strategies:
352
+ * 1. Parallel health status lookups from Redis (O(1) per provider)
353
+ * 2. Cached processor instances to avoid re-initialization
354
+ * 3. Pre-computed weighted selection for O(n) provider selection
355
+ * 4. Exponential backoff only on failures
488
356
  */
489
- async fetch(product, tag) {
490
- return await this.apiService.getQuota(product, tag);
357
+ async run(options) {
358
+ var _a;
359
+ const { product, env, tag, input } = options;
360
+ const startTime = Date.now();
361
+ // Fetch quota config
362
+ const quota = await this.fetch(product, tag);
363
+ if (!quota) {
364
+ throw new QuotaError(`Quota '${tag}' not found in product '${product}'`, 'QUOTA_NOT_FOUND');
365
+ }
366
+ if (!((_a = quota.options) === null || _a === void 0 ? void 0 : _a.length)) {
367
+ throw new QuotaError(`Quota '${tag}' has no providers configured`, 'NO_PROVIDERS');
368
+ }
369
+ const processor = this.getProcessor(product);
370
+ // Parallel health status lookup - minimal latency
371
+ const healthStatuses = await this.getHealthStatusesParallel(processor, product, env, quota.options);
372
+ // Fast weighted selection with health awareness
373
+ const { provider, wasHealthy } = this.selectHealthyProvider(quota.options, healthStatuses);
374
+ // Execute with retries
375
+ const result = await this.executeWithRetries(processor, provider, env, product, input);
376
+ return {
377
+ data: result.data,
378
+ provider: provider.app || provider.event,
379
+ latency: Date.now() - startTime,
380
+ wasHealthy,
381
+ retriesUsed: result.retriesUsed,
382
+ };
491
383
  }
492
384
  /**
493
- * Fetch all quotas for a product
385
+ * Parallel health status lookup from Redis - O(n) with parallel I/O
494
386
  */
495
- async fetchAll(product) {
496
- return await this.apiService.listQuotas(product);
387
+ async getHealthStatusesParallel(processor, product, env, options) {
388
+ const statuses = new Map();
389
+ // Collect healthcheck tags
390
+ const healthcheckTags = options
391
+ .filter(opt => opt.healthcheck)
392
+ .map(opt => opt.healthcheck);
393
+ if (healthcheckTags.length === 0) {
394
+ return statuses;
395
+ }
396
+ // Parallel Redis lookups
397
+ const results = await Promise.allSettled(healthcheckTags.map(async (tag) => {
398
+ try {
399
+ const cached = await processor.getHealthcheckStatusFromCache(product, `${tag}:${env}`);
400
+ return { tag, status: (cached === null || cached === void 0 ? void 0 : cached.status) || 'unknown', latency: cached === null || cached === void 0 ? void 0 : cached.latency };
401
+ }
402
+ catch (_a) {
403
+ return { tag, status: 'unknown' };
404
+ }
405
+ }));
406
+ for (const result of results) {
407
+ if (result.status === 'fulfilled') {
408
+ statuses.set(result.value.tag, {
409
+ status: result.value.status,
410
+ lastLatency: result.value.latency,
411
+ });
412
+ }
413
+ }
414
+ return statuses;
497
415
  }
498
416
  /**
499
- * Delete a quota
417
+ * Fast weighted selection with health-aware prioritization
500
418
  */
501
- async delete(product, tag) {
502
- await this.apiService.deleteQuota(product, tag);
419
+ selectHealthyProvider(options, healthStatuses) {
420
+ // Categorize by health in single pass
421
+ const healthy = [];
422
+ const unknown = [];
423
+ const unhealthy = [];
424
+ for (const opt of options) {
425
+ if (!opt.healthcheck) {
426
+ healthy.push(opt); // No healthcheck = assume healthy
427
+ }
428
+ else {
429
+ const status = healthStatuses.get(opt.healthcheck);
430
+ if (!status || status.status === 'unknown') {
431
+ unknown.push(opt);
432
+ }
433
+ else if (status.status === 'available') {
434
+ healthy.push(opt);
435
+ }
436
+ else {
437
+ unhealthy.push(opt);
438
+ }
439
+ }
440
+ }
441
+ // Priority: healthy > unknown > unhealthy
442
+ let candidates = healthy.length > 0 ? healthy : unknown.length > 0 ? unknown : unhealthy;
443
+ const wasHealthy = healthy.length > 0;
444
+ if (candidates.length === 0) {
445
+ throw new QuotaError('No providers available', 'NO_PROVIDERS_AVAILABLE');
446
+ }
447
+ // Weighted random selection - O(n)
448
+ const totalWeight = candidates.reduce((sum, opt) => sum + (opt.quota || 1), 0);
449
+ const random = Math.random() * totalWeight;
450
+ let cumulative = 0;
451
+ for (const opt of candidates) {
452
+ cumulative += opt.quota || 1;
453
+ if (random <= cumulative) {
454
+ return { provider: opt, wasHealthy };
455
+ }
456
+ }
457
+ return { provider: candidates[candidates.length - 1], wasHealthy };
503
458
  }
504
459
  /**
505
- * Run a quota
460
+ * Execute provider action with exponential backoff retries
506
461
  */
507
- async run(options) {
508
- return await this.apiService.runQuota(options);
462
+ async executeWithRetries(processor, provider, env, product, input) {
463
+ const maxRetries = provider.retries || 2;
464
+ let lastError = null;
465
+ let retriesUsed = 0;
466
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
467
+ try {
468
+ // Execute app action via processor
469
+ const result = await processor.processAction({
470
+ env,
471
+ product,
472
+ app: provider.app,
473
+ action: provider.event,
474
+ input,
475
+ retries: 0, // We handle retries
476
+ });
477
+ return { data: result, retriesUsed };
478
+ }
479
+ catch (error) {
480
+ lastError = error;
481
+ retriesUsed++;
482
+ if (attempt < maxRetries) {
483
+ // Exponential backoff: 50ms, 100ms, 200ms (capped at 2s for low latency)
484
+ await new Promise(r => setTimeout(r, Math.min(50 * Math.pow(2, attempt), 2000)));
485
+ }
486
+ }
487
+ }
488
+ throw new QuotaError(`All ${maxRetries + 1} attempts failed. Last error: ${lastError === null || lastError === void 0 ? void 0 : lastError.message}`, 'ALL_RETRIES_FAILED', { lastError, retriesUsed });
509
489
  }
510
490
  /**
511
- * Dispatch/schedule a quota for later execution
491
+ * Dispatch quota for deferred execution via job queue
492
+ *
493
+ * Note: For immediate execution, use run() instead.
494
+ * Dispatch schedules the quota for background processing.
512
495
  */
513
496
  async dispatch(options) {
514
- return await this.apiService.dispatchQuota(options);
497
+ const { product, tag } = options;
498
+ const jobId = `quota_${tag}_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
499
+ // For deferred execution, we run the quota asynchronously
500
+ // The job queue integration would be implemented based on your queue system
501
+ setImmediate(async () => {
502
+ try {
503
+ await this.run(options);
504
+ }
505
+ catch (error) {
506
+ console.error(`Deferred quota execution failed for ${product}:${tag}:`, error);
507
+ }
508
+ });
509
+ return { jobId };
515
510
  }
516
511
  }
517
512
  exports.QuotaService = QuotaService;