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.
Files changed (41) hide show
  1. package/dist/Effect.d.ts +5 -2
  2. package/dist/Effect.d.ts.map +1 -1
  3. package/dist/Effect.js +4 -1
  4. package/dist/Effect.js.map +1 -1
  5. package/dist/Latch.d.ts +19 -0
  6. package/dist/Latch.d.ts.map +1 -1
  7. package/dist/Latch.js +11 -0
  8. package/dist/Latch.js.map +1 -1
  9. package/dist/Schema.d.ts +14 -1
  10. package/dist/Schema.d.ts.map +1 -1
  11. package/dist/Schema.js +14 -1
  12. package/dist/Schema.js.map +1 -1
  13. package/dist/SchemaAST.d.ts +17 -5
  14. package/dist/SchemaAST.d.ts.map +1 -1
  15. package/dist/SchemaAST.js +22 -7
  16. package/dist/SchemaAST.js.map +1 -1
  17. package/dist/internal/effect.js +17 -15
  18. package/dist/internal/effect.js.map +1 -1
  19. package/dist/unstable/http/HttpClient.d.ts +4 -0
  20. package/dist/unstable/http/HttpClient.d.ts.map +1 -1
  21. package/dist/unstable/http/HttpClient.js +56 -20
  22. package/dist/unstable/http/HttpClient.js.map +1 -1
  23. package/dist/unstable/observability/OtlpTracer.d.ts.map +1 -1
  24. package/dist/unstable/observability/OtlpTracer.js +2 -1
  25. package/dist/unstable/observability/OtlpTracer.js.map +1 -1
  26. package/dist/unstable/persistence/RateLimiter.d.ts +106 -3
  27. package/dist/unstable/persistence/RateLimiter.d.ts.map +1 -1
  28. package/dist/unstable/persistence/RateLimiter.js +397 -5
  29. package/dist/unstable/persistence/RateLimiter.js.map +1 -1
  30. package/dist/unstable/rpc/RpcServer.js +4 -4
  31. package/dist/unstable/rpc/RpcServer.js.map +1 -1
  32. package/package.json +1 -1
  33. package/src/Effect.ts +10 -4
  34. package/src/Latch.ts +21 -0
  35. package/src/Schema.ts +14 -1
  36. package/src/SchemaAST.ts +23 -7
  37. package/src/internal/effect.ts +30 -18
  38. package/src/unstable/http/HttpClient.ts +115 -24
  39. package/src/unstable/observability/OtlpTracer.ts +2 -1
  40. package/src/unstable/persistence/RateLimiter.ts +599 -5
  41. 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 fixed-window counters and token-bucket state.
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 persistent rate-limit
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":"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"}
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;IAEpD,QAAQ,CAAC,eAAe,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,MAAM,CAAC,MAAM,CAAC,qBAAqB,EAAE,gBAAgB,CAAC,CAAA;IAErH,QAAQ,CAAC,gBAAgB,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAA;CACvG;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,WAAW,EAAE,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,WAAW,CAAwC,CAAA;AAE1G;;;;;;;;;;GAUG;AACH,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,CAC9B,WAAW,EACX,KAAK,EACL,gBAAgB,CAiIhB,CAAA;AAEF;;;;;GAKG;AACH,eAAO,MAAM,KAAK,EAAE,KAAK,CAAC,KAAK,CAC7B,WAAW,EACX,KAAK,EACL,gBAAgB,CACiB,CAAA;AAEnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,CAC7C,CAAC,CAAC,OAAO,EAAE;IACT,QAAQ,CAAC,SAAS,CAAC,EAAE,cAAc,GAAG,cAAc,GAAG,SAAS,CAAA;IAChE,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;IAClD,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAA;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACrC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAC7F,KAAK,EACL,WAAW,CAQZ,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,MAAM,CACnC,CAAC,CAAC,OAAO,EAAE;IACT,QAAQ,CAAC,SAAS,CAAC,EAAE,cAAc,GAAG,cAAc,GAAG,SAAS,CAAA;IAChE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAA;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACrC,KAAK,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,CAAC,EACrD,KAAK,EACL,WAAW,CAcZ,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,WAAW,EAAE,WAAkE,CAAA;AAE5F;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,oDAAoD,CAAA;;;;;;;;AAE9E;;;;;;;;;GASG;AACH,qBAAa,iBAAkB,SAAQ,sBAQrC;IACA;;;;OAIG;IACH,IAAa,OAAO,IAAI,MAAM,CAE7B;CACF;;;;;;AAED;;;;;GAKG;AACH,qBAAa,mBAAoB,SAAQ,wBAMvC;CAAG;AAEL;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,GAAG,iBAAiB,GAAG,mBAAmB,CAAA;AAE5E;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,KAAK,CAAC;IAChD,OAAO,iBAAiB;IACxB,OAAO,mBAAmB;CAC3B,CAA0D,CAAA;;;;;AAE3D;;;;;;GAMG;AACH,qBAAa,gBAAiB,SAAQ,qBAGpC;gBAEY,KAAK,EAAE;QACjB,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAA;KACxC;IAWD;;;;OAIG;IACH,QAAQ,CAAC,CAAC,WAAW,CAAC,EAAE,WAAW,CAAc;IAEjD,IAAa,OAAO,IAAI,MAAM,CAE7B;CACF;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAA;IAEjC;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IAEtB;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAE1B;;OAEG;IACH,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,QAAQ,CAAA;CACvC;AAED;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAAA;AAE5E;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IAEpB;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IAEvB;;OAEG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAE9B;;OAEG;IACH,QAAQ,CAAC,cAAc,EAAE,QAAQ,CAAC,QAAQ,CAAA;CAC3C;AAED;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAA;IAEjC;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IAEtB;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAA;CAC9B;AAED;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IAEpB;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IAEtB;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IAEvB;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IAEvB;;OAEG;IACH,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,QAAQ,GAAG,SAAS,CAAA;CACnD;;IAgBG;;;;;;;;;OASG;0BACmB,CAAC,OAAO,EAAE;QAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;QACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;QACvB,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,QAAQ,CAAA;QACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;KACnC,KAAK,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAE5E;;;;;;;;;OASG;0BACmB,CAAC,OAAO,EAAE;QAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;QACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;QACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;QACtB,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,QAAQ,CAAA;QACtC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;KAChC,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAE7C;;;;;OAKG;8BACuB,CACxB,OAAO,EAAE,sBAAsB,KAC5B,MAAM,CAAC,MAAM,CAAC,qBAAqB,EAAE,gBAAgB,CAAC;IAE3D;;OAEG;+BACwB,CAAC,OAAO,EAAE,uBAAuB,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,gBAAgB,CAAC;;AA9D1G;;;;;;;;;;GAUG;AACH,qBAAa,gBAAiB,SAAQ,qBAqDgB;CAAG;AAqBzD;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,KAAK,CACxC,gBAAgB,CAiNhB,CAAA;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc;sBAEL,MAAM,GAAG,SAAS;;IA1SpC;;;;;;;;;OASG;0BACmB,CAAC,OAAO,EAAE;QAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;QACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;QACvB,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,QAAQ,CAAA;QACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;KACnC,KAAK,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAE5E;;;;;;;;;OASG;0BACmB,CAAC,OAAO,EAAE;QAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;QACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;QACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;QACtB,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,QAAQ,CAAA;QACtC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;KAChC,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAE7C;;;;;OAKG;8BACuB,CACxB,OAAO,EAAE,sBAAsB,KAC5B,MAAM,CAAC,MAAM,CAAC,qBAAqB,EAAE,gBAAgB,CAAC;IAE3D;;OAEG;+BACwB,CAAC,OAAO,EAAE,uBAAuB,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,gBAAgB,CAAC;sBA+VxG,CAAA;AA4UF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,CAC5B,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,KAC/C,KAAK,CAAC,KAAK,CACd,gBAAgB,EAChB,KAAK,EACL,KAAK,CAAC,KAAK,CAC2C,CAAA;AAExD;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GAChC,SAAS,MAAM,CAAC,IAAI,CAAC;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC,KAC7D,KAAK,CAAC,KAAK,CAAC,gBAAgB,EAAE,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,KAAK,CAI7D,CAAA"}
@@ -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 fixed-window counters and token-bucket state.
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 persistent rate-limit
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
  *