effect 4.0.0-beta.87 → 4.0.0-beta.89
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/Effect.d.ts +5 -2
- package/dist/Effect.d.ts.map +1 -1
- package/dist/Effect.js +4 -1
- package/dist/Effect.js.map +1 -1
- package/dist/Latch.d.ts +19 -0
- package/dist/Latch.d.ts.map +1 -1
- package/dist/Latch.js +11 -0
- package/dist/Latch.js.map +1 -1
- package/dist/Schema.d.ts +14 -1
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +14 -1
- package/dist/Schema.js.map +1 -1
- package/dist/SchemaAST.d.ts +17 -5
- package/dist/SchemaAST.d.ts.map +1 -1
- package/dist/SchemaAST.js +22 -7
- package/dist/SchemaAST.js.map +1 -1
- package/dist/internal/effect.js +17 -15
- package/dist/internal/effect.js.map +1 -1
- package/dist/unstable/http/HttpClient.d.ts +4 -0
- package/dist/unstable/http/HttpClient.d.ts.map +1 -1
- package/dist/unstable/http/HttpClient.js +56 -20
- package/dist/unstable/http/HttpClient.js.map +1 -1
- package/dist/unstable/observability/OtlpTracer.d.ts.map +1 -1
- package/dist/unstable/observability/OtlpTracer.js +2 -1
- package/dist/unstable/observability/OtlpTracer.js.map +1 -1
- package/dist/unstable/persistence/RateLimiter.d.ts +106 -3
- package/dist/unstable/persistence/RateLimiter.d.ts.map +1 -1
- package/dist/unstable/persistence/RateLimiter.js +397 -5
- package/dist/unstable/persistence/RateLimiter.js.map +1 -1
- package/dist/unstable/rpc/RpcServer.js +4 -4
- package/dist/unstable/rpc/RpcServer.js.map +1 -1
- package/package.json +1 -1
- package/src/Effect.ts +10 -4
- package/src/Latch.ts +21 -0
- package/src/Schema.ts +14 -1
- package/src/SchemaAST.ts +23 -7
- package/src/internal/effect.ts +30 -18
- package/src/unstable/http/HttpClient.ts +115 -24
- package/src/unstable/observability/OtlpTracer.ts +2 -1
- package/src/unstable/persistence/RateLimiter.ts +599 -5
- package/src/unstable/rpc/RpcServer.ts +4 -4
|
@@ -48,6 +48,8 @@ export interface RateLimiter {
|
|
|
48
48
|
readonly key: string;
|
|
49
49
|
readonly tokens?: number | undefined;
|
|
50
50
|
}) => Effect.Effect<ConsumeResult, RateLimiterError>;
|
|
51
|
+
readonly adaptiveConsume: (options: AdaptiveConsumeOptions) => Effect.Effect<AdaptiveConsumeResult, RateLimiterError>;
|
|
52
|
+
readonly adaptiveFeedback: (options: AdaptiveFeedbackOptions) => Effect.Effect<void, RateLimiterError>;
|
|
51
53
|
}
|
|
52
54
|
/**
|
|
53
55
|
* Service tag for persistent token-consumption services.
|
|
@@ -269,6 +271,85 @@ export interface ConsumeResult {
|
|
|
269
271
|
*/
|
|
270
272
|
readonly resetAfter: Duration.Duration;
|
|
271
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* Phase of adaptive rate limiting driven by server feedback.
|
|
276
|
+
*
|
|
277
|
+
* @category models
|
|
278
|
+
* @since 4.0.0
|
|
279
|
+
*/
|
|
280
|
+
export type AdaptivePhase = "inactive" | "cooldown" | "learning" | "learned";
|
|
281
|
+
/**
|
|
282
|
+
* Options for consuming tokens from the adaptive rate limiter store.
|
|
283
|
+
*
|
|
284
|
+
* @category models
|
|
285
|
+
* @since 4.0.0
|
|
286
|
+
*/
|
|
287
|
+
export interface AdaptiveConsumeOptions {
|
|
288
|
+
/**
|
|
289
|
+
* The rate-limit key.
|
|
290
|
+
*/
|
|
291
|
+
readonly key: string;
|
|
292
|
+
/**
|
|
293
|
+
* The number of tokens to consume.
|
|
294
|
+
*/
|
|
295
|
+
readonly tokens: number;
|
|
296
|
+
/**
|
|
297
|
+
* The fallback limit configured for the regular rate limiter.
|
|
298
|
+
*/
|
|
299
|
+
readonly fallbackLimit: number;
|
|
300
|
+
/**
|
|
301
|
+
* The fallback window configured for the regular rate limiter.
|
|
302
|
+
*/
|
|
303
|
+
readonly fallbackWindow: Duration.Duration;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Metadata returned after consuming tokens from the adaptive rate limiter store.
|
|
307
|
+
*
|
|
308
|
+
* @category models
|
|
309
|
+
* @since 4.0.0
|
|
310
|
+
*/
|
|
311
|
+
export interface AdaptiveConsumeResult {
|
|
312
|
+
/**
|
|
313
|
+
* The amount of delay to wait before making the request.
|
|
314
|
+
*/
|
|
315
|
+
readonly delay: Duration.Duration;
|
|
316
|
+
/**
|
|
317
|
+
* The adaptive state epoch used to correlate later response feedback.
|
|
318
|
+
*/
|
|
319
|
+
readonly epoch: number;
|
|
320
|
+
/**
|
|
321
|
+
* The adaptive phase observed by this consume operation.
|
|
322
|
+
*/
|
|
323
|
+
readonly phase: AdaptivePhase;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Options for reporting response feedback to the adaptive rate limiter store.
|
|
327
|
+
*
|
|
328
|
+
* @category models
|
|
329
|
+
* @since 4.0.0
|
|
330
|
+
*/
|
|
331
|
+
export interface AdaptiveFeedbackOptions {
|
|
332
|
+
/**
|
|
333
|
+
* The rate-limit key.
|
|
334
|
+
*/
|
|
335
|
+
readonly key: string;
|
|
336
|
+
/**
|
|
337
|
+
* The adaptive state epoch returned by `adaptiveConsume`.
|
|
338
|
+
*/
|
|
339
|
+
readonly epoch: number;
|
|
340
|
+
/**
|
|
341
|
+
* The number of tokens consumed by the request.
|
|
342
|
+
*/
|
|
343
|
+
readonly tokens: number;
|
|
344
|
+
/**
|
|
345
|
+
* The HTTP response status code.
|
|
346
|
+
*/
|
|
347
|
+
readonly status: number;
|
|
348
|
+
/**
|
|
349
|
+
* The parsed `Retry-After` delay, when present.
|
|
350
|
+
*/
|
|
351
|
+
readonly retryAfter: Duration.Duration | undefined;
|
|
352
|
+
}
|
|
272
353
|
declare const RateLimiterStore_base: Context.ServiceClass<RateLimiterStore, "effect/persistence/RateLimiter/RateLimiterStore", {
|
|
273
354
|
/**
|
|
274
355
|
* Returns the token count *after* taking the specified `tokens` and time to
|
|
@@ -303,14 +384,25 @@ declare const RateLimiterStore_base: Context.ServiceClass<RateLimiterStore, "eff
|
|
|
303
384
|
readonly refillRate: Duration.Duration;
|
|
304
385
|
readonly allowOverflow: boolean;
|
|
305
386
|
}) => Effect.Effect<number, RateLimiterError>;
|
|
387
|
+
/**
|
|
388
|
+
* Consumes tokens from the adaptive rate-limit state for the `key`.
|
|
389
|
+
*
|
|
390
|
+
* When the store has no adaptive state for the `key`, implementations
|
|
391
|
+
* should return a zero delay with the inactive phase.
|
|
392
|
+
*/
|
|
393
|
+
readonly adaptiveConsume: (options: AdaptiveConsumeOptions) => Effect.Effect<AdaptiveConsumeResult, RateLimiterError>;
|
|
394
|
+
/**
|
|
395
|
+
* Records response feedback for the adaptive rate-limit state.
|
|
396
|
+
*/
|
|
397
|
+
readonly adaptiveFeedback: (options: AdaptiveFeedbackOptions) => Effect.Effect<void, RateLimiterError>;
|
|
306
398
|
}>;
|
|
307
399
|
/**
|
|
308
|
-
* Defines the low-level backing store for
|
|
400
|
+
* Defines the low-level backing store for rate-limit state.
|
|
309
401
|
*
|
|
310
402
|
* **When to use**
|
|
311
403
|
*
|
|
312
|
-
* Use to provide the shared counter storage used by
|
|
313
|
-
* checks.
|
|
404
|
+
* Use to provide the shared counter storage and adaptive feedback state used by
|
|
405
|
+
* persistent rate-limit checks.
|
|
314
406
|
*
|
|
315
407
|
* @category store
|
|
316
408
|
* @since 4.0.0
|
|
@@ -367,6 +459,17 @@ export declare const makeStoreRedis: (options?: {
|
|
|
367
459
|
readonly refillRate: Duration.Duration;
|
|
368
460
|
readonly allowOverflow: boolean;
|
|
369
461
|
}) => Effect.Effect<number, RateLimiterError>;
|
|
462
|
+
/**
|
|
463
|
+
* Consumes tokens from the adaptive rate-limit state for the `key`.
|
|
464
|
+
*
|
|
465
|
+
* When the store has no adaptive state for the `key`, implementations
|
|
466
|
+
* should return a zero delay with the inactive phase.
|
|
467
|
+
*/
|
|
468
|
+
readonly adaptiveConsume: (options: AdaptiveConsumeOptions) => Effect.Effect<AdaptiveConsumeResult, RateLimiterError>;
|
|
469
|
+
/**
|
|
470
|
+
* Records response feedback for the adaptive rate-limit state.
|
|
471
|
+
*/
|
|
472
|
+
readonly adaptiveFeedback: (options: AdaptiveFeedbackOptions) => Effect.Effect<void, RateLimiterError>;
|
|
370
473
|
}, never, Redis.Redis>;
|
|
371
474
|
/**
|
|
372
475
|
* Provides a Redis-backed `RateLimiterStore` using `makeStoreRedis`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RateLimiter.d.ts","sourceRoot":"","sources":["../../../src/unstable/persistence/RateLimiter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAA;AACzC,OAAO,KAAK,OAAO,MAAM,kBAAkB,CAAA;AAC3C,OAAO,KAAK,QAAQ,MAAM,mBAAmB,CAAA;AAC7C,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAA;AAEzC,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAA;AACzC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AAEnC;;;;;GAKG;AACH,eAAO,MAAM,MAAM,EAAE,MAA0C,CAAA;AAE/D;;;;;GAKG;AACH,MAAM,MAAM,MAAM,GAAG,iCAAiC,CAAA;AAEtD;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IAEzB,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE;QAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,cAAc,GAAG,cAAc,GAAG,SAAS,CAAA;QAChE,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;QAClD,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAA;QAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;QACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;QACpB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KACrC,KAAK,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"RateLimiter.d.ts","sourceRoot":"","sources":["../../../src/unstable/persistence/RateLimiter.ts"],"names":[],"mappings":"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"}
|
|
@@ -52,6 +52,8 @@ export const make = /*#__PURE__*/Effect.gen(function* () {
|
|
|
52
52
|
const store = yield* RateLimiterStore;
|
|
53
53
|
return identity({
|
|
54
54
|
[TypeId]: TypeId,
|
|
55
|
+
adaptiveConsume: store.adaptiveConsume,
|
|
56
|
+
adaptiveFeedback: store.adaptiveFeedback,
|
|
55
57
|
consume(options) {
|
|
56
58
|
const tokens = options.tokens ?? 1;
|
|
57
59
|
const onExceeded = options.onExceeded ?? "fail";
|
|
@@ -317,17 +319,23 @@ export class RateLimiterError extends /*#__PURE__*/Schema.ErrorClass(ErrorTypeId
|
|
|
317
319
|
}
|
|
318
320
|
}
|
|
319
321
|
/**
|
|
320
|
-
* Defines the low-level backing store for
|
|
322
|
+
* Defines the low-level backing store for rate-limit state.
|
|
321
323
|
*
|
|
322
324
|
* **When to use**
|
|
323
325
|
*
|
|
324
|
-
* Use to provide the shared counter storage used by
|
|
325
|
-
* checks.
|
|
326
|
+
* Use to provide the shared counter storage and adaptive feedback state used by
|
|
327
|
+
* persistent rate-limit checks.
|
|
326
328
|
*
|
|
327
329
|
* @category store
|
|
328
330
|
* @since 4.0.0
|
|
329
331
|
*/
|
|
330
332
|
export class RateLimiterStore extends /*#__PURE__*/Context.Service()("effect/persistence/RateLimiter/RateLimiterStore") {}
|
|
333
|
+
const adaptiveStateTtlGraceMillis = 60_000;
|
|
334
|
+
const adaptiveStateMaxWindowMillis = 60 * 60 * 1_000;
|
|
335
|
+
const clampAdaptiveDurationMillis = millis => {
|
|
336
|
+
if (Number.isNaN(millis) || millis <= 0) return 1;
|
|
337
|
+
return Math.min(millis, adaptiveStateMaxWindowMillis);
|
|
338
|
+
};
|
|
331
339
|
/**
|
|
332
340
|
* Provides a process-local in-memory `RateLimiterStore`.
|
|
333
341
|
*
|
|
@@ -337,6 +345,19 @@ export class RateLimiterStore extends /*#__PURE__*/Context.Service()("effect/per
|
|
|
337
345
|
export const layerStoreMemory = /*#__PURE__*/Layer.sync(RateLimiterStore, () => {
|
|
338
346
|
const fixedCounters = new Map();
|
|
339
347
|
const tokenBuckets = new Map();
|
|
348
|
+
const adaptiveStates = new Map();
|
|
349
|
+
const getAdaptiveState = (key, now) => {
|
|
350
|
+
const state = adaptiveStates.get(key);
|
|
351
|
+
if (!state) return undefined;
|
|
352
|
+
if (state.expiresAt <= now) {
|
|
353
|
+
adaptiveStates.delete(key);
|
|
354
|
+
return undefined;
|
|
355
|
+
}
|
|
356
|
+
return state;
|
|
357
|
+
};
|
|
358
|
+
const cooldownExpiresAt = cooldownUntil => cooldownUntil + adaptiveStateTtlGraceMillis;
|
|
359
|
+
const learningExpiresAt = (now, fallbackWindow) => now + Duration.toMillis(fallbackWindow) + adaptiveStateTtlGraceMillis;
|
|
360
|
+
const learnedExpiresAt = (now, learnedWindowMillis) => now + learnedWindowMillis + adaptiveStateTtlGraceMillis;
|
|
340
361
|
return RateLimiterStore.of({
|
|
341
362
|
fixedWindow: options => Effect.clockWith(clock => Effect.sync(() => {
|
|
342
363
|
const refillRateMillis = Duration.toMillis(options.refillRate);
|
|
@@ -379,6 +400,126 @@ export const layerStoreMemory = /*#__PURE__*/Layer.sync(RateLimiterStore, () =>
|
|
|
379
400
|
bucket.tokens = newTokenCount;
|
|
380
401
|
}
|
|
381
402
|
return newTokenCount;
|
|
403
|
+
})),
|
|
404
|
+
adaptiveConsume: options => Effect.clockWith(clock => Effect.sync(() => {
|
|
405
|
+
const now = clock.currentTimeMillisUnsafe();
|
|
406
|
+
const state = getAdaptiveState(options.key, now);
|
|
407
|
+
if (!state) {
|
|
408
|
+
return {
|
|
409
|
+
delay: Duration.zero,
|
|
410
|
+
epoch: 0,
|
|
411
|
+
phase: "inactive"
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
if (state.phase === "cooldown") {
|
|
415
|
+
if (state.cooldownUntil > now) {
|
|
416
|
+
return {
|
|
417
|
+
delay: Duration.millis(state.cooldownUntil - now),
|
|
418
|
+
epoch: state.epoch,
|
|
419
|
+
phase: "cooldown"
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
state.phase = "learning";
|
|
423
|
+
state.epoch += 1;
|
|
424
|
+
state.learningStartedAt = now;
|
|
425
|
+
state.observedTokens = options.tokens;
|
|
426
|
+
state.expiresAt = learningExpiresAt(now, options.fallbackWindow);
|
|
427
|
+
return {
|
|
428
|
+
delay: Duration.zero,
|
|
429
|
+
epoch: state.epoch,
|
|
430
|
+
phase: "learning"
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
if (state.phase === "learning") {
|
|
434
|
+
state.observedTokens += options.tokens;
|
|
435
|
+
return {
|
|
436
|
+
delay: Duration.zero,
|
|
437
|
+
epoch: state.epoch,
|
|
438
|
+
phase: state.phase
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
if (state.phase === "learned") {
|
|
442
|
+
const refillRateMillis = state.learnedWindowMillis / state.learnedLimit;
|
|
443
|
+
if (state.cooldownUntil <= now) {
|
|
444
|
+
state.observedTokens = 0;
|
|
445
|
+
state.cooldownUntil = now;
|
|
446
|
+
}
|
|
447
|
+
state.observedTokens += options.tokens;
|
|
448
|
+
state.cooldownUntil += refillRateMillis * options.tokens;
|
|
449
|
+
const ttl = state.cooldownUntil - now;
|
|
450
|
+
const ttlTotal = state.observedTokens * refillRateMillis;
|
|
451
|
+
const elapsed = ttlTotal - ttl;
|
|
452
|
+
const windowNumber = Math.floor((state.observedTokens - 1) / state.learnedLimit);
|
|
453
|
+
const remaining = windowNumber * state.learnedWindowMillis - elapsed;
|
|
454
|
+
return {
|
|
455
|
+
delay: remaining <= 0 ? Duration.zero : Duration.millis(remaining),
|
|
456
|
+
epoch: state.epoch,
|
|
457
|
+
phase: state.phase
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
return {
|
|
461
|
+
delay: Duration.zero,
|
|
462
|
+
epoch: state.epoch,
|
|
463
|
+
phase: state.phase
|
|
464
|
+
};
|
|
465
|
+
})),
|
|
466
|
+
adaptiveFeedback: options => Effect.clockWith(clock => Effect.sync(() => {
|
|
467
|
+
if (options.status !== 429 || options.retryAfter === undefined) return;
|
|
468
|
+
const retryAfterMillis = clampAdaptiveDurationMillis(Duration.toMillis(options.retryAfter));
|
|
469
|
+
const now = clock.currentTimeMillisUnsafe();
|
|
470
|
+
const cooldownUntil = now + retryAfterMillis;
|
|
471
|
+
const state = getAdaptiveState(options.key, now);
|
|
472
|
+
if (!state) {
|
|
473
|
+
if (options.epoch !== 0) return;
|
|
474
|
+
adaptiveStates.set(options.key, {
|
|
475
|
+
phase: "cooldown",
|
|
476
|
+
epoch: 0,
|
|
477
|
+
cooldownUntil,
|
|
478
|
+
learningStartedAt: 0,
|
|
479
|
+
observedTokens: 0,
|
|
480
|
+
learnedLimit: 0,
|
|
481
|
+
learnedWindowMillis: 0,
|
|
482
|
+
expiresAt: cooldownExpiresAt(cooldownUntil)
|
|
483
|
+
});
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
if (state.epoch !== options.epoch) return;
|
|
487
|
+
if (state.phase === "cooldown") {
|
|
488
|
+
state.cooldownUntil = Math.max(state.cooldownUntil, cooldownUntil);
|
|
489
|
+
state.expiresAt = cooldownExpiresAt(state.cooldownUntil);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
if (state.phase === "learning") {
|
|
493
|
+
const acceptedTokens = state.observedTokens - options.tokens;
|
|
494
|
+
if (acceptedTokens <= 0) {
|
|
495
|
+
state.phase = "cooldown";
|
|
496
|
+
state.cooldownUntil = cooldownUntil;
|
|
497
|
+
state.learningStartedAt = 0;
|
|
498
|
+
state.observedTokens = 0;
|
|
499
|
+
state.learnedLimit = 0;
|
|
500
|
+
state.learnedWindowMillis = 0;
|
|
501
|
+
state.expiresAt = cooldownExpiresAt(cooldownUntil);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const learnedWindowMillis = clampAdaptiveDurationMillis(now - state.learningStartedAt + retryAfterMillis);
|
|
505
|
+
state.phase = "learned";
|
|
506
|
+
state.epoch += 1;
|
|
507
|
+
state.cooldownUntil = state.learningStartedAt + learnedWindowMillis;
|
|
508
|
+
state.observedTokens = acceptedTokens;
|
|
509
|
+
state.learnedLimit = acceptedTokens;
|
|
510
|
+
state.learnedWindowMillis = learnedWindowMillis;
|
|
511
|
+
state.expiresAt = learnedExpiresAt(now, learnedWindowMillis);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
if (state.phase === "learned") {
|
|
515
|
+
state.phase = "cooldown";
|
|
516
|
+
state.cooldownUntil = cooldownUntil;
|
|
517
|
+
state.learningStartedAt = 0;
|
|
518
|
+
state.observedTokens = 0;
|
|
519
|
+
state.learnedLimit = 0;
|
|
520
|
+
state.learnedWindowMillis = 0;
|
|
521
|
+
state.expiresAt = cooldownExpiresAt(cooldownUntil);
|
|
522
|
+
}
|
|
382
523
|
}))
|
|
383
524
|
});
|
|
384
525
|
});
|
|
@@ -394,6 +535,8 @@ export const makeStoreRedis = /*#__PURE__*/Effect.fnUntraced(function* (options)
|
|
|
394
535
|
const redis = yield* Redis.Redis;
|
|
395
536
|
const fixedWindow = redis.eval(fixedWindowScript);
|
|
396
537
|
const tokenBucket = redis.eval(tokenBucketScript);
|
|
538
|
+
const adaptiveConsume = redis.eval(adaptiveConsumeScript);
|
|
539
|
+
const adaptiveFeedback = redis.eval(adaptiveFeedbackScript);
|
|
397
540
|
return RateLimiterStore.of({
|
|
398
541
|
fixedWindow(options) {
|
|
399
542
|
const key = `${prefix}${options.key}`;
|
|
@@ -415,6 +558,30 @@ export const makeStoreRedis = /*#__PURE__*/Effect.fnUntraced(function* (options)
|
|
|
415
558
|
cause
|
|
416
559
|
})
|
|
417
560
|
})));
|
|
561
|
+
},
|
|
562
|
+
adaptiveConsume(options) {
|
|
563
|
+
const key = `${prefix}${options.key}:adaptive`;
|
|
564
|
+
return Effect.map(Effect.mapError(adaptiveConsume(key, options.tokens, Duration.toMillis(options.fallbackWindow), adaptiveStateTtlGraceMillis), cause => new RateLimiterError({
|
|
565
|
+
reason: new RateLimitStoreError({
|
|
566
|
+
message: `Failed to execute adaptiveConsume rate limiting command`,
|
|
567
|
+
cause: cause.cause
|
|
568
|
+
})
|
|
569
|
+
})), ([delayMillis, epoch, phase]) => ({
|
|
570
|
+
delay: delayMillis <= 0 ? Duration.zero : Duration.millis(delayMillis),
|
|
571
|
+
epoch,
|
|
572
|
+
phase
|
|
573
|
+
}));
|
|
574
|
+
},
|
|
575
|
+
adaptiveFeedback(options) {
|
|
576
|
+
if (options.status !== 429 || options.retryAfter === undefined) return Effect.void;
|
|
577
|
+
const retryAfterMillis = clampAdaptiveDurationMillis(Duration.toMillis(options.retryAfter));
|
|
578
|
+
const key = `${prefix}${options.key}:adaptive`;
|
|
579
|
+
return Effect.asVoid(Effect.mapError(adaptiveFeedback(key, options.epoch, options.tokens, retryAfterMillis, adaptiveStateTtlGraceMillis, adaptiveStateMaxWindowMillis), cause => new RateLimiterError({
|
|
580
|
+
reason: new RateLimitStoreError({
|
|
581
|
+
message: `Failed to execute adaptiveFeedback rate limiting command`,
|
|
582
|
+
cause: cause.cause
|
|
583
|
+
})
|
|
584
|
+
})));
|
|
418
585
|
}
|
|
419
586
|
});
|
|
420
587
|
});
|
|
@@ -428,7 +595,7 @@ local limit = tonumber(ARGV[3])
|
|
|
428
595
|
local current = tonumber(redis.call("GET", key))
|
|
429
596
|
|
|
430
597
|
if not current then
|
|
431
|
-
local nextpttl = refillms * tokens
|
|
598
|
+
local nextpttl = math.max(1, math.ceil(refillms * tokens))
|
|
432
599
|
redis.call("SET", key, tokens, "PX", nextpttl)
|
|
433
600
|
return { tokens, nextpttl }
|
|
434
601
|
end
|
|
@@ -439,7 +606,7 @@ if limit and next > limit then
|
|
|
439
606
|
return { next, currentpttl }
|
|
440
607
|
end
|
|
441
608
|
|
|
442
|
-
local nextpttl = currentpttl + (refillms * tokens)
|
|
609
|
+
local nextpttl = math.max(1, math.ceil(currentpttl + (refillms * tokens)))
|
|
443
610
|
redis.call("SET", key, next, "PX", nextpttl)
|
|
444
611
|
return { next, nextpttl }
|
|
445
612
|
`
|
|
@@ -480,6 +647,231 @@ redis.call("SET", last_refill_key, last_refill, "PX", ttl)
|
|
|
480
647
|
return next
|
|
481
648
|
`
|
|
482
649
|
}).withReturnType();
|
|
650
|
+
const adaptiveConsumeScript = /*#__PURE__*/Redis.script((key, tokens, fallbackWindowMillis, ttlGraceMillis) => [key, tokens, fallbackWindowMillis, ttlGraceMillis], {
|
|
651
|
+
numberOfKeys: 1,
|
|
652
|
+
lua: `
|
|
653
|
+
local key = KEYS[1]
|
|
654
|
+
local tokens = tonumber(ARGV[1])
|
|
655
|
+
local fallback_window_ms = tonumber(ARGV[2])
|
|
656
|
+
local ttl_grace_ms = tonumber(ARGV[3])
|
|
657
|
+
|
|
658
|
+
local time = redis.call("TIME")
|
|
659
|
+
local now = (tonumber(time[1]) * 1000) + math.floor(tonumber(time[2]) / 1000)
|
|
660
|
+
|
|
661
|
+
local phase = redis.call("HGET", key, "phase")
|
|
662
|
+
if not phase then
|
|
663
|
+
return { 0, 0, "inactive" }
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
local epoch = tonumber(redis.call("HGET", key, "epoch") or "0")
|
|
667
|
+
|
|
668
|
+
if phase == "cooldown" then
|
|
669
|
+
local cooldown_until = tonumber(redis.call("HGET", key, "cooldownUntil") or "0")
|
|
670
|
+
if cooldown_until > now then
|
|
671
|
+
return { math.ceil(cooldown_until - now), epoch, "cooldown" }
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
epoch = epoch + 1
|
|
675
|
+
redis.call(
|
|
676
|
+
"HSET",
|
|
677
|
+
key,
|
|
678
|
+
"phase",
|
|
679
|
+
"learning",
|
|
680
|
+
"epoch",
|
|
681
|
+
epoch,
|
|
682
|
+
"cooldownUntil",
|
|
683
|
+
0,
|
|
684
|
+
"learningStartedAt",
|
|
685
|
+
now,
|
|
686
|
+
"observedTokens",
|
|
687
|
+
tokens,
|
|
688
|
+
"learnedLimit",
|
|
689
|
+
0,
|
|
690
|
+
"learnedWindowMillis",
|
|
691
|
+
0
|
|
692
|
+
)
|
|
693
|
+
redis.call("PEXPIRE", key, math.max(1, math.ceil(fallback_window_ms + ttl_grace_ms)))
|
|
694
|
+
return { 0, epoch, "learning" }
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
if phase == "learning" then
|
|
698
|
+
local observed_tokens = tonumber(redis.call("HGET", key, "observedTokens") or "0") + tokens
|
|
699
|
+
redis.call("HSET", key, "observedTokens", observed_tokens)
|
|
700
|
+
return { 0, epoch, "learning" }
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
if phase == "learned" then
|
|
704
|
+
local cooldown_until = tonumber(redis.call("HGET", key, "cooldownUntil") or "0")
|
|
705
|
+
local observed_tokens = tonumber(redis.call("HGET", key, "observedTokens") or "0")
|
|
706
|
+
local learned_limit = tonumber(redis.call("HGET", key, "learnedLimit") or "0")
|
|
707
|
+
local learned_window_ms = tonumber(redis.call("HGET", key, "learnedWindowMillis") or "0")
|
|
708
|
+
local refill_rate_ms = learned_window_ms / learned_limit
|
|
709
|
+
|
|
710
|
+
if cooldown_until <= now then
|
|
711
|
+
observed_tokens = 0
|
|
712
|
+
cooldown_until = now
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
observed_tokens = observed_tokens + tokens
|
|
716
|
+
cooldown_until = cooldown_until + (refill_rate_ms * tokens)
|
|
717
|
+
|
|
718
|
+
local ttl = cooldown_until - now
|
|
719
|
+
local ttl_total = observed_tokens * refill_rate_ms
|
|
720
|
+
local elapsed = ttl_total - ttl
|
|
721
|
+
local window_number = math.floor((observed_tokens - 1) / learned_limit)
|
|
722
|
+
local remaining = (window_number * learned_window_ms) - elapsed
|
|
723
|
+
|
|
724
|
+
redis.call("HSET", key, "observedTokens", observed_tokens, "cooldownUntil", cooldown_until)
|
|
725
|
+
|
|
726
|
+
if remaining <= 0 then
|
|
727
|
+
return { 0, epoch, "learned" }
|
|
728
|
+
end
|
|
729
|
+
return { math.ceil(remaining), epoch, "learned" }
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
return { 0, epoch, phase }
|
|
733
|
+
`
|
|
734
|
+
}).withReturnType();
|
|
735
|
+
const adaptiveFeedbackScript = /*#__PURE__*/Redis.script((key, epoch, tokens, retryAfterMillis, ttlGraceMillis, maxWindowMillis) => [key, epoch, tokens, retryAfterMillis, ttlGraceMillis, maxWindowMillis], {
|
|
736
|
+
numberOfKeys: 1,
|
|
737
|
+
lua: `
|
|
738
|
+
local key = KEYS[1]
|
|
739
|
+
local feedback_epoch = tonumber(ARGV[1])
|
|
740
|
+
local tokens = tonumber(ARGV[2])
|
|
741
|
+
local retry_after_ms = tonumber(ARGV[3])
|
|
742
|
+
local ttl_grace_ms = tonumber(ARGV[4])
|
|
743
|
+
local max_window_ms = tonumber(ARGV[5])
|
|
744
|
+
|
|
745
|
+
if retry_after_ms <= 0 then
|
|
746
|
+
retry_after_ms = 1
|
|
747
|
+
end
|
|
748
|
+
if retry_after_ms > max_window_ms then
|
|
749
|
+
retry_after_ms = max_window_ms
|
|
750
|
+
end
|
|
751
|
+
|
|
752
|
+
local time = redis.call("TIME")
|
|
753
|
+
local now = (tonumber(time[1]) * 1000) + math.floor(tonumber(time[2]) / 1000)
|
|
754
|
+
local cooldown_until = now + retry_after_ms
|
|
755
|
+
|
|
756
|
+
local phase = redis.call("HGET", key, "phase")
|
|
757
|
+
if not phase then
|
|
758
|
+
if feedback_epoch ~= 0 then
|
|
759
|
+
return 0
|
|
760
|
+
end
|
|
761
|
+
redis.call(
|
|
762
|
+
"HSET",
|
|
763
|
+
key,
|
|
764
|
+
"phase",
|
|
765
|
+
"cooldown",
|
|
766
|
+
"epoch",
|
|
767
|
+
0,
|
|
768
|
+
"cooldownUntil",
|
|
769
|
+
cooldown_until,
|
|
770
|
+
"learningStartedAt",
|
|
771
|
+
0,
|
|
772
|
+
"observedTokens",
|
|
773
|
+
0,
|
|
774
|
+
"learnedLimit",
|
|
775
|
+
0,
|
|
776
|
+
"learnedWindowMillis",
|
|
777
|
+
0
|
|
778
|
+
)
|
|
779
|
+
redis.call("PEXPIRE", key, math.max(1, math.ceil(retry_after_ms + ttl_grace_ms)))
|
|
780
|
+
return 1
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
local epoch = tonumber(redis.call("HGET", key, "epoch") or "0")
|
|
784
|
+
if epoch ~= feedback_epoch then
|
|
785
|
+
return 0
|
|
786
|
+
end
|
|
787
|
+
|
|
788
|
+
if phase == "cooldown" then
|
|
789
|
+
local current_cooldown_until = tonumber(redis.call("HGET", key, "cooldownUntil") or "0")
|
|
790
|
+
if current_cooldown_until > cooldown_until then
|
|
791
|
+
cooldown_until = current_cooldown_until
|
|
792
|
+
end
|
|
793
|
+
redis.call("HSET", key, "cooldownUntil", cooldown_until)
|
|
794
|
+
redis.call("PEXPIRE", key, math.max(1, math.ceil((cooldown_until - now) + ttl_grace_ms)))
|
|
795
|
+
return 1
|
|
796
|
+
end
|
|
797
|
+
|
|
798
|
+
if phase == "learning" then
|
|
799
|
+
local observed_tokens = tonumber(redis.call("HGET", key, "observedTokens") or "0")
|
|
800
|
+
local accepted_tokens = observed_tokens - tokens
|
|
801
|
+
if accepted_tokens <= 0 then
|
|
802
|
+
redis.call(
|
|
803
|
+
"HSET",
|
|
804
|
+
key,
|
|
805
|
+
"phase",
|
|
806
|
+
"cooldown",
|
|
807
|
+
"cooldownUntil",
|
|
808
|
+
cooldown_until,
|
|
809
|
+
"learningStartedAt",
|
|
810
|
+
0,
|
|
811
|
+
"observedTokens",
|
|
812
|
+
0,
|
|
813
|
+
"learnedLimit",
|
|
814
|
+
0,
|
|
815
|
+
"learnedWindowMillis",
|
|
816
|
+
0
|
|
817
|
+
)
|
|
818
|
+
redis.call("PEXPIRE", key, math.max(1, math.ceil(retry_after_ms + ttl_grace_ms)))
|
|
819
|
+
return 1
|
|
820
|
+
end
|
|
821
|
+
|
|
822
|
+
local learning_started_at = tonumber(redis.call("HGET", key, "learningStartedAt") or now)
|
|
823
|
+
local learned_window_ms = (now - learning_started_at) + retry_after_ms
|
|
824
|
+
if learned_window_ms <= 0 then
|
|
825
|
+
learned_window_ms = 1
|
|
826
|
+
end
|
|
827
|
+
if learned_window_ms > max_window_ms then
|
|
828
|
+
learned_window_ms = max_window_ms
|
|
829
|
+
end
|
|
830
|
+
local next_epoch = epoch + 1
|
|
831
|
+
redis.call(
|
|
832
|
+
"HSET",
|
|
833
|
+
key,
|
|
834
|
+
"phase",
|
|
835
|
+
"learned",
|
|
836
|
+
"epoch",
|
|
837
|
+
next_epoch,
|
|
838
|
+
"cooldownUntil",
|
|
839
|
+
learning_started_at + learned_window_ms,
|
|
840
|
+
"observedTokens",
|
|
841
|
+
accepted_tokens,
|
|
842
|
+
"learnedLimit",
|
|
843
|
+
accepted_tokens,
|
|
844
|
+
"learnedWindowMillis",
|
|
845
|
+
learned_window_ms
|
|
846
|
+
)
|
|
847
|
+
redis.call("PEXPIRE", key, math.max(1, math.ceil(learned_window_ms + ttl_grace_ms)))
|
|
848
|
+
return 1
|
|
849
|
+
end
|
|
850
|
+
|
|
851
|
+
if phase == "learned" then
|
|
852
|
+
redis.call(
|
|
853
|
+
"HSET",
|
|
854
|
+
key,
|
|
855
|
+
"phase",
|
|
856
|
+
"cooldown",
|
|
857
|
+
"cooldownUntil",
|
|
858
|
+
cooldown_until,
|
|
859
|
+
"learningStartedAt",
|
|
860
|
+
0,
|
|
861
|
+
"observedTokens",
|
|
862
|
+
0,
|
|
863
|
+
"learnedLimit",
|
|
864
|
+
0,
|
|
865
|
+
"learnedWindowMillis",
|
|
866
|
+
0
|
|
867
|
+
)
|
|
868
|
+
redis.call("PEXPIRE", key, math.max(1, math.ceil(retry_after_ms + ttl_grace_ms)))
|
|
869
|
+
return 1
|
|
870
|
+
end
|
|
871
|
+
|
|
872
|
+
return 0
|
|
873
|
+
`
|
|
874
|
+
}).withReturnType();
|
|
483
875
|
/**
|
|
484
876
|
* Provides a Redis-backed `RateLimiterStore` using `makeStoreRedis`.
|
|
485
877
|
*
|