effect 2.3.1 → 2.3.3
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/cjs/Effect.js.map +1 -1
- package/dist/cjs/Either.js +4 -1
- package/dist/cjs/Either.js.map +1 -1
- package/dist/cjs/Option.js +4 -1
- package/dist/cjs/Option.js.map +1 -1
- package/dist/cjs/RateLimiter.js +67 -1
- package/dist/cjs/RateLimiter.js.map +1 -1
- package/dist/cjs/Scheduler.js +3 -4
- package/dist/cjs/Scheduler.js.map +1 -1
- package/dist/cjs/internal/clock.js +2 -3
- package/dist/cjs/internal/clock.js.map +1 -1
- package/dist/cjs/internal/effect/circular.js +10 -8
- package/dist/cjs/internal/effect/circular.js.map +1 -1
- package/dist/cjs/internal/fiberRuntime.js +12 -2
- package/dist/cjs/internal/fiberRuntime.js.map +1 -1
- package/dist/cjs/internal/rateLimiter.js +39 -8
- package/dist/cjs/internal/rateLimiter.js.map +1 -1
- package/dist/cjs/internal/version.js +1 -1
- package/dist/dts/Effect.d.ts +7 -3
- package/dist/dts/Effect.d.ts.map +1 -1
- package/dist/dts/Either.d.ts +4 -0
- package/dist/dts/Either.d.ts.map +1 -1
- package/dist/dts/FiberSet.d.ts +4 -4
- package/dist/dts/FiberSet.d.ts.map +1 -1
- package/dist/dts/Option.d.ts +4 -0
- package/dist/dts/Option.d.ts.map +1 -1
- package/dist/dts/RateLimiter.d.ts +109 -10
- package/dist/dts/RateLimiter.d.ts.map +1 -1
- package/dist/dts/Scheduler.d.ts.map +1 -1
- package/dist/dts/index.d.ts +1 -5
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/internal/version.d.ts +1 -1
- package/dist/esm/Effect.js.map +1 -1
- package/dist/esm/Either.js +4 -1
- package/dist/esm/Either.js.map +1 -1
- package/dist/esm/Option.js +4 -1
- package/dist/esm/Option.js.map +1 -1
- package/dist/esm/RateLimiter.js +66 -0
- package/dist/esm/RateLimiter.js.map +1 -1
- package/dist/esm/Scheduler.js +3 -4
- package/dist/esm/Scheduler.js.map +1 -1
- package/dist/esm/index.js +1 -5
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/clock.js +2 -3
- package/dist/esm/internal/clock.js.map +1 -1
- package/dist/esm/internal/effect/circular.js +10 -8
- package/dist/esm/internal/effect/circular.js.map +1 -1
- package/dist/esm/internal/fiberRuntime.js +12 -2
- package/dist/esm/internal/fiberRuntime.js.map +1 -1
- package/dist/esm/internal/rateLimiter.js +36 -6
- package/dist/esm/internal/rateLimiter.js.map +1 -1
- package/dist/esm/internal/version.js +1 -1
- package/package.json +1 -1
- package/src/Effect.ts +7 -3
- package/src/Either.ts +8 -1
- package/src/FiberSet.ts +4 -4
- package/src/Option.ts +8 -1
- package/src/RateLimiter.ts +111 -14
- package/src/Scheduler.ts +3 -4
- package/src/index.ts +1 -5
- package/src/internal/clock.ts +2 -3
- package/src/internal/effect/circular.ts +13 -11
- package/src/internal/fiberRuntime.ts +13 -3
- package/src/internal/rateLimiter.ts +81 -19
- package/src/internal/version.ts +1 -1
- package/dist/cjs/internal/timeout.js +0 -24
- package/dist/cjs/internal/timeout.js.map +0 -1
- package/dist/dts/internal/timeout.d.ts +0 -7
- package/dist/dts/internal/timeout.d.ts.map +0 -1
- package/dist/esm/internal/timeout.js +0 -18
- package/dist/esm/internal/timeout.js.map +0 -1
- package/src/internal/timeout.ts +0 -23
package/src/FiberSet.ts
CHANGED
|
@@ -26,7 +26,7 @@ export type TypeId = typeof TypeId
|
|
|
26
26
|
* @since 2.0.0
|
|
27
27
|
* @categories models
|
|
28
28
|
*/
|
|
29
|
-
export interface FiberSet<out A, out E =
|
|
29
|
+
export interface FiberSet<out A = unknown, out E = unknown>
|
|
30
30
|
extends Pipeable, Inspectable.Inspectable, Iterable<Fiber.RuntimeFiber<A, E>>
|
|
31
31
|
{
|
|
32
32
|
readonly [TypeId]: TypeId
|
|
@@ -92,7 +92,7 @@ const unsafeMake = <A, E>(): FiberSet<A, E> => {
|
|
|
92
92
|
* @since 2.0.0
|
|
93
93
|
* @categories constructors
|
|
94
94
|
*/
|
|
95
|
-
export const make = <A, E =
|
|
95
|
+
export const make = <A = unknown, E = unknown>(): Effect.Effect<FiberSet<A, E>, never, Scope.Scope> =>
|
|
96
96
|
Effect.acquireRelease(Effect.sync(() => unsafeMake<A, E>()), clear)
|
|
97
97
|
|
|
98
98
|
/**
|
|
@@ -101,7 +101,7 @@ export const make = <A, E = never>(): Effect.Effect<FiberSet<A, E>, never, Scope
|
|
|
101
101
|
* @since 2.0.0
|
|
102
102
|
* @categories constructors
|
|
103
103
|
*/
|
|
104
|
-
export const makeRuntime = <
|
|
104
|
+
export const makeRuntime = <R = never, A = unknown, E = unknown>(): Effect.Effect<
|
|
105
105
|
<XE extends E, XA extends A>(
|
|
106
106
|
effect: Effect.Effect<XA, XE, R>,
|
|
107
107
|
options?: Runtime.RunForkOptions | undefined
|
|
@@ -234,7 +234,7 @@ export const run: {
|
|
|
234
234
|
* @since 2.0.0
|
|
235
235
|
* @categories combinators
|
|
236
236
|
*/
|
|
237
|
-
export const runtime: <A, E
|
|
237
|
+
export const runtime: <A, E>(
|
|
238
238
|
self: FiberSet<A, E>
|
|
239
239
|
) => <R = never>() => Effect.Effect<
|
|
240
240
|
<XE extends E, XA extends A>(
|
package/src/Option.ts
CHANGED
|
@@ -648,12 +648,19 @@ export const flatMap: {
|
|
|
648
648
|
export const andThen: {
|
|
649
649
|
<A, B>(f: (a: A) => Option<B>): (self: Option<A>) => Option<B>
|
|
650
650
|
<B>(f: Option<B>): <A>(self: Option<A>) => Option<B>
|
|
651
|
+
<A, B>(f: (a: A) => B): (self: Option<A>) => Option<B>
|
|
652
|
+
<B>(f: B): <A>(self: Option<A>) => Option<B>
|
|
651
653
|
<A, B>(self: Option<A>, f: (a: A) => Option<B>): Option<B>
|
|
652
654
|
<A, B>(self: Option<A>, f: Option<B>): Option<B>
|
|
655
|
+
<A, B>(self: Option<A>, f: (a: A) => B): Option<B>
|
|
656
|
+
<A, B>(self: Option<A>, f: B): Option<B>
|
|
653
657
|
} = dual(
|
|
654
658
|
2,
|
|
655
659
|
<A, B>(self: Option<A>, f: (a: A) => Option<B> | Option<B>): Option<B> =>
|
|
656
|
-
|
|
660
|
+
flatMap(self, (a) => {
|
|
661
|
+
const b = isFunction(f) ? f(a) : f
|
|
662
|
+
return isOption(b) ? b : some(b)
|
|
663
|
+
})
|
|
657
664
|
)
|
|
658
665
|
|
|
659
666
|
/**
|
package/src/RateLimiter.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Limits the number of calls to a resource to a maximum amount in some interval
|
|
3
|
-
*
|
|
4
|
-
* Note that only the moment of starting the effect is rate limited: the number of concurrent executions is not bounded.
|
|
5
|
-
*
|
|
6
|
-
* Calls are queued up in an unbounded queue until capacity becomes available.
|
|
2
|
+
* Limits the number of calls to a resource to a maximum amount in some interval.
|
|
7
3
|
*
|
|
8
4
|
* @since 2.0.0
|
|
9
5
|
*/
|
|
@@ -13,11 +9,10 @@ import * as internal from "./internal/rateLimiter.js"
|
|
|
13
9
|
import type { Scope } from "./Scope.js"
|
|
14
10
|
|
|
15
11
|
/**
|
|
16
|
-
* Limits the number of calls to a resource to a maximum amount in some interval
|
|
17
|
-
*
|
|
18
|
-
* Note that only the moment of starting the effect is rate limited: the number of concurrent executions is not bounded.
|
|
12
|
+
* Limits the number of calls to a resource to a maximum amount in some interval.
|
|
19
13
|
*
|
|
20
|
-
*
|
|
14
|
+
* Note that only the moment of starting the effect is rate limited: the number
|
|
15
|
+
* of concurrent executions is not bounded.
|
|
21
16
|
*
|
|
22
17
|
* @since 2.0.0
|
|
23
18
|
* @category models
|
|
@@ -27,11 +22,113 @@ export interface RateLimiter {
|
|
|
27
22
|
}
|
|
28
23
|
|
|
29
24
|
/**
|
|
25
|
+
* @since 2.0.0
|
|
26
|
+
*/
|
|
27
|
+
export declare namespace RateLimiter {
|
|
28
|
+
/**
|
|
29
|
+
* @since 2.0.0
|
|
30
|
+
* @category models
|
|
31
|
+
*/
|
|
32
|
+
export interface Options {
|
|
33
|
+
/**
|
|
34
|
+
* The maximum number of requests that should be allowed.
|
|
35
|
+
*/
|
|
36
|
+
readonly limit: number
|
|
37
|
+
/**
|
|
38
|
+
* The interval to utilize for rate-limiting requests. The semantics of the
|
|
39
|
+
* specified `interval` vary depending on the chosen `algorithm`:
|
|
40
|
+
*
|
|
41
|
+
* `token-bucket`: The maximum number of requests will be spread out over
|
|
42
|
+
* the provided interval if no tokens are available.
|
|
43
|
+
*
|
|
44
|
+
* For example, for a `RateLimiter` using the `token-bucket` algorithm with
|
|
45
|
+
* a `limit` of `10` and an `interval` of `1 seconds`, `1` request can be
|
|
46
|
+
* made every `100 millis`.
|
|
47
|
+
*
|
|
48
|
+
* `fixed-window`: The maximum number of requests will be reset during each
|
|
49
|
+
* interval. For example, for a `RateLimiter` using the `fixed-window`
|
|
50
|
+
* algorithm with a `limit` of `10` and an `interval` of `1 seconds`, a
|
|
51
|
+
* maximum of `10` requests can be made each second.
|
|
52
|
+
*/
|
|
53
|
+
readonly interval: DurationInput
|
|
54
|
+
/**
|
|
55
|
+
* The algorithm to utilize for rate-limiting requests.
|
|
56
|
+
*
|
|
57
|
+
* Defaults to `token-bucket`.
|
|
58
|
+
*/
|
|
59
|
+
readonly algorithm?: "fixed-window" | "token-bucket"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Constructs a new `RateLimiter` which will utilize the specified algorithm
|
|
65
|
+
* to limit requests (defaults to `token-bucket`).
|
|
66
|
+
*
|
|
67
|
+
* Notes
|
|
68
|
+
* - Only the moment of starting the effect is rate limited. The number of concurrent executions is not bounded.
|
|
69
|
+
* - Instances of `RateLimiter` can be composed.
|
|
70
|
+
* - The "cost" per effect can be changed. See {@link withCost}
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* import { Effect, RateLimiter } from "effect";
|
|
74
|
+
* import { compose } from "effect/Function"
|
|
75
|
+
*
|
|
76
|
+
* const program = Effect.scoped(
|
|
77
|
+
* Effect.gen(function* ($) {
|
|
78
|
+
* const perMinuteRL = yield* $(RateLimiter.make({ limit: 30, interval: "1 minutes" }))
|
|
79
|
+
* const perSecondRL = yield* $(RateLimiter.make({ limit: 2, interval: "1 seconds" }))
|
|
80
|
+
*
|
|
81
|
+
* // This rate limiter respects both the 30 calls per minute
|
|
82
|
+
* // and the 2 calls per second constraints.
|
|
83
|
+
* const rateLimit = compose(perMinuteRL, perSecondRL)
|
|
84
|
+
*
|
|
85
|
+
* // simulate repeated calls
|
|
86
|
+
* for (let n = 0; n < 100; n++) {
|
|
87
|
+
* // wrap the effect we want to limit with rateLimit
|
|
88
|
+
* yield* $(rateLimit(Effect.log("Calling RateLimited Effect")));
|
|
89
|
+
* }
|
|
90
|
+
* })
|
|
91
|
+
* );
|
|
92
|
+
*
|
|
30
93
|
* @since 2.0.0
|
|
31
94
|
* @category constructors
|
|
32
95
|
*/
|
|
33
|
-
export const make: (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
96
|
+
export const make: (options: RateLimiter.Options) => Effect<RateLimiter, never, Scope> = internal.make
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Alters the per-effect cost of the rate-limiter.
|
|
100
|
+
*
|
|
101
|
+
* This can be used for "credit" based rate-limiting where different API endpoints
|
|
102
|
+
* cost a different number of credits within a time window.
|
|
103
|
+
* Eg: 1000 credits / hour, where a query costs 1 credit and a mutation costs 5 credits.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* import { Effect, RateLimiter } from "effect";
|
|
107
|
+
* import { compose } from "effect/Function";
|
|
108
|
+
*
|
|
109
|
+
* const program = Effect.scoped(
|
|
110
|
+
* Effect.gen(function* ($) {
|
|
111
|
+
* // Create a rate limiter that has an hourly limit of 1000 credits
|
|
112
|
+
* const rateLimiter = yield* $(RateLimiter.make({ limit: 1000, interval: "1 hours" }));
|
|
113
|
+
* // Query API costs 1 credit per call ( 1 is the default cost )
|
|
114
|
+
* const queryAPIRL = compose(rateLimiter, RateLimiter.withCost(1));
|
|
115
|
+
* // Mutation API costs 5 credits per call
|
|
116
|
+
* const mutationAPIRL = compose(rateLimiter, RateLimiter.withCost(5));
|
|
117
|
+
|
|
118
|
+
* // Use the pre-defined rate limiters
|
|
119
|
+
* yield* $(queryAPIRL(Effect.log("Sample Query")));
|
|
120
|
+
* yield* $(mutationAPIRL(Effect.log("Sample Mutation")));
|
|
121
|
+
*
|
|
122
|
+
* // Or set a cost on-the-fly
|
|
123
|
+
* yield* $(
|
|
124
|
+
* rateLimiter(Effect.log("Another query with a different cost")).pipe(
|
|
125
|
+
* RateLimiter.withCost(3)
|
|
126
|
+
* )
|
|
127
|
+
* );
|
|
128
|
+
* })
|
|
129
|
+
* );
|
|
130
|
+
*
|
|
131
|
+
* @since 2.0.0
|
|
132
|
+
* @category combinators
|
|
133
|
+
*/
|
|
134
|
+
export const withCost: (cost: number) => <A, E, R>(effect: Effect<A, E, R>) => Effect<A, E, R> = internal.withCost
|
package/src/Scheduler.ts
CHANGED
|
@@ -8,7 +8,6 @@ import type { FiberRef } from "./FiberRef.js"
|
|
|
8
8
|
import { dual } from "./Function.js"
|
|
9
9
|
import { globalValue } from "./GlobalValue.js"
|
|
10
10
|
import * as core from "./internal/core.js"
|
|
11
|
-
import * as timeout from "./internal/timeout.js"
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* @since 2.0.0
|
|
@@ -107,7 +106,7 @@ export class MixedScheduler implements Scheduler {
|
|
|
107
106
|
*/
|
|
108
107
|
private starve(depth = 0) {
|
|
109
108
|
if (depth >= this.maxNextTickBeforeTimer) {
|
|
110
|
-
|
|
109
|
+
setTimeout(() => this.starveInternal(0), 0)
|
|
111
110
|
} else {
|
|
112
111
|
Promise.resolve(void 0).then(() => this.starveInternal(depth + 1))
|
|
113
112
|
}
|
|
@@ -337,14 +336,14 @@ export const makeBatched = (
|
|
|
337
336
|
* @category constructors
|
|
338
337
|
*/
|
|
339
338
|
export const timer = (ms: number, shouldYield: Scheduler["shouldYield"] = defaultShouldYield) =>
|
|
340
|
-
make((task) =>
|
|
339
|
+
make((task) => setTimeout(task, ms), shouldYield)
|
|
341
340
|
|
|
342
341
|
/**
|
|
343
342
|
* @since 2.0.0
|
|
344
343
|
* @category constructors
|
|
345
344
|
*/
|
|
346
345
|
export const timerBatched = (ms: number, shouldYield: Scheduler["shouldYield"] = defaultShouldYield) =>
|
|
347
|
-
makeBatched((task) =>
|
|
346
|
+
makeBatched((task) => setTimeout(task, ms), shouldYield)
|
|
348
347
|
|
|
349
348
|
/** @internal */
|
|
350
349
|
export const currentScheduler: FiberRef<Scheduler> = globalValue(
|
package/src/index.ts
CHANGED
|
@@ -545,11 +545,7 @@ export * as Queue from "./Queue.js"
|
|
|
545
545
|
export * as Random from "./Random.js"
|
|
546
546
|
|
|
547
547
|
/**
|
|
548
|
-
* Limits the number of calls to a resource to a maximum amount in some interval
|
|
549
|
-
*
|
|
550
|
-
* Note that only the moment of starting the effect is rate limited: the number of concurrent executions is not bounded.
|
|
551
|
-
*
|
|
552
|
-
* Calls are queued up in an unbounded queue until capacity becomes available.
|
|
548
|
+
* Limits the number of calls to a resource to a maximum amount in some interval.
|
|
553
549
|
*
|
|
554
550
|
* @since 2.0.0
|
|
555
551
|
*/
|
package/src/internal/clock.ts
CHANGED
|
@@ -5,7 +5,6 @@ import type * as Effect from "../Effect.js"
|
|
|
5
5
|
import * as Either from "../Either.js"
|
|
6
6
|
import { constFalse } from "../Function.js"
|
|
7
7
|
import * as core from "./core.js"
|
|
8
|
-
import * as timeout from "./timeout.js"
|
|
9
8
|
|
|
10
9
|
/** @internal */
|
|
11
10
|
const ClockSymbolKey = "effect/Clock"
|
|
@@ -29,12 +28,12 @@ export const globalClockScheduler: Clock.ClockScheduler = {
|
|
|
29
28
|
return constFalse
|
|
30
29
|
}
|
|
31
30
|
let completed = false
|
|
32
|
-
const handle =
|
|
31
|
+
const handle = setTimeout(() => {
|
|
33
32
|
completed = true
|
|
34
33
|
task()
|
|
35
34
|
}, millis)
|
|
36
35
|
return () => {
|
|
37
|
-
|
|
36
|
+
clearTimeout(handle)
|
|
38
37
|
return !completed
|
|
39
38
|
}
|
|
40
39
|
}
|
|
@@ -65,22 +65,24 @@ class Semaphore {
|
|
|
65
65
|
return Either.right(core.succeed(n))
|
|
66
66
|
})
|
|
67
67
|
|
|
68
|
-
readonly updateTaken = (f: (n: number) => number): Effect.Effect<
|
|
68
|
+
readonly updateTaken = (f: (n: number) => number): Effect.Effect<number> =>
|
|
69
69
|
core.withFiberRuntime((fiber) => {
|
|
70
70
|
this.taken = f(this.taken)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
item
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
71
|
+
if (this.waiters.size > 0) {
|
|
72
|
+
fiber.getFiberRef(currentScheduler).scheduleTask(() => {
|
|
73
|
+
const iter = this.waiters.values()
|
|
74
|
+
let item = iter.next()
|
|
75
|
+
while (item.done === false && item.value() === true) {
|
|
76
|
+
item = iter.next()
|
|
77
|
+
}
|
|
78
|
+
}, fiber.getFiberRef(core.currentSchedulingPriority))
|
|
79
|
+
}
|
|
80
|
+
return core.succeed(this.free)
|
|
79
81
|
})
|
|
80
82
|
|
|
81
|
-
readonly release = (n: number): Effect.Effect<
|
|
83
|
+
readonly release = (n: number): Effect.Effect<number> => this.updateTaken((taken) => taken - n)
|
|
82
84
|
|
|
83
|
-
readonly releaseAll: Effect.Effect<
|
|
85
|
+
readonly releaseAll: Effect.Effect<number> = this.updateTaken((_) => 0)
|
|
84
86
|
|
|
85
87
|
readonly withPermits = (n: number) => <R, E, A>(self: Effect.Effect<R, E, A>) =>
|
|
86
88
|
core.uninterruptibleMask((restore) =>
|
|
@@ -2771,7 +2771,12 @@ export const zipLeftOptions = dual<
|
|
|
2771
2771
|
) => Effect.Effect<A, E | E2, R | R2>
|
|
2772
2772
|
>(
|
|
2773
2773
|
(args) => core.isEffect(args[1]),
|
|
2774
|
-
(self, that, options) =>
|
|
2774
|
+
(self, that, options) => {
|
|
2775
|
+
if (options?.concurrent !== true && (options?.batching === undefined || options.batching === false)) {
|
|
2776
|
+
return core.zipLeft(self, that)
|
|
2777
|
+
}
|
|
2778
|
+
return zipWithOptions(self, that, (a, _) => a, options)
|
|
2779
|
+
}
|
|
2775
2780
|
)
|
|
2776
2781
|
|
|
2777
2782
|
/** @internal */
|
|
@@ -2794,11 +2799,16 @@ export const zipRightOptions: {
|
|
|
2794
2799
|
} = dual((args) => core.isEffect(args[1]), <A, E, R, A2, E2, R2>(
|
|
2795
2800
|
self: Effect.Effect<A, E, R>,
|
|
2796
2801
|
that: Effect.Effect<A2, E2, R2>,
|
|
2797
|
-
options
|
|
2802
|
+
options?: {
|
|
2798
2803
|
readonly concurrent?: boolean | undefined
|
|
2799
2804
|
readonly batching?: boolean | "inherit" | undefined
|
|
2800
2805
|
}
|
|
2801
|
-
): Effect.Effect<A2, E2 | E, R2 | R> =>
|
|
2806
|
+
): Effect.Effect<A2, E2 | E, R2 | R> => {
|
|
2807
|
+
if (options?.concurrent !== true && (options?.batching === undefined || options.batching === false)) {
|
|
2808
|
+
return core.zipRight(self, that)
|
|
2809
|
+
}
|
|
2810
|
+
return zipWithOptions(self, that, (_, b) => b, options)
|
|
2811
|
+
})
|
|
2802
2812
|
|
|
2803
2813
|
/** @internal */
|
|
2804
2814
|
export const zipWithOptions: {
|
|
@@ -1,30 +1,92 @@
|
|
|
1
1
|
import type { DurationInput } from "../Duration.js"
|
|
2
|
+
import * as Duration from "../Duration.js"
|
|
2
3
|
import * as Effect from "../Effect.js"
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import * as
|
|
4
|
+
import * as FiberRef from "../FiberRef.js"
|
|
5
|
+
import { globalValue } from "../GlobalValue.js"
|
|
6
|
+
import type * as RateLimiter from "../RateLimiter.js"
|
|
7
|
+
import type * as Scope from "../Scope.js"
|
|
6
8
|
|
|
7
9
|
/** @internal */
|
|
8
|
-
export const make = (
|
|
9
|
-
|
|
10
|
+
export const make = ({
|
|
11
|
+
algorithm = "token-bucket",
|
|
12
|
+
interval,
|
|
13
|
+
limit
|
|
14
|
+
}: RateLimiter.RateLimiter.Options): Effect.Effect<
|
|
15
|
+
RateLimiter.RateLimiter,
|
|
10
16
|
never,
|
|
11
|
-
Scope
|
|
17
|
+
Scope.Scope
|
|
18
|
+
> => {
|
|
19
|
+
switch (algorithm) {
|
|
20
|
+
case "fixed-window": {
|
|
21
|
+
return fixedWindow(limit, interval)
|
|
22
|
+
}
|
|
23
|
+
case "token-bucket": {
|
|
24
|
+
return tokenBucket(limit, interval)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const tokenBucket = (limit: number, window: DurationInput): Effect.Effect<
|
|
30
|
+
RateLimiter.RateLimiter,
|
|
31
|
+
never,
|
|
32
|
+
Scope.Scope
|
|
12
33
|
> =>
|
|
13
34
|
Effect.gen(function*(_) {
|
|
14
|
-
const
|
|
35
|
+
const millisPerToken = Math.ceil(Duration.toMillis(window) / limit)
|
|
15
36
|
const semaphore = yield* _(Effect.makeSemaphore(limit))
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
37
|
+
const latch = yield* _(Effect.makeSemaphore(0))
|
|
38
|
+
const refill: Effect.Effect<void> = Effect.sleep(millisPerToken).pipe(
|
|
39
|
+
Effect.zipRight(latch.releaseAll),
|
|
40
|
+
Effect.zipRight(semaphore.release(1)),
|
|
41
|
+
Effect.flatMap((free) => free === limit ? Effect.unit : refill)
|
|
42
|
+
)
|
|
43
|
+
yield* _(
|
|
44
|
+
latch.take(1),
|
|
45
|
+
Effect.zipRight(refill),
|
|
46
|
+
Effect.forever,
|
|
47
|
+
Effect.forkScoped,
|
|
48
|
+
Effect.interruptible
|
|
49
|
+
)
|
|
50
|
+
const take = Effect.uninterruptibleMask((restore) =>
|
|
51
|
+
Effect.flatMap(
|
|
52
|
+
FiberRef.get(currentCost),
|
|
53
|
+
(cost) => Effect.zipRight(restore(semaphore.take(cost)), latch.release(1))
|
|
54
|
+
)
|
|
27
55
|
)
|
|
28
|
-
const take = Effect.zipRight(semaphore.take(1), reset)
|
|
29
56
|
return (effect) => Effect.zipRight(take, effect)
|
|
30
57
|
})
|
|
58
|
+
|
|
59
|
+
const fixedWindow = (limit: number, window: DurationInput): Effect.Effect<
|
|
60
|
+
RateLimiter.RateLimiter,
|
|
61
|
+
never,
|
|
62
|
+
Scope.Scope
|
|
63
|
+
> =>
|
|
64
|
+
Effect.gen(function*(_) {
|
|
65
|
+
const semaphore = yield* _(Effect.makeSemaphore(limit))
|
|
66
|
+
const latch = yield* _(Effect.makeSemaphore(0))
|
|
67
|
+
yield* _(
|
|
68
|
+
latch.take(1),
|
|
69
|
+
Effect.zipRight(Effect.sleep(window)),
|
|
70
|
+
Effect.zipRight(latch.releaseAll),
|
|
71
|
+
Effect.zipRight(semaphore.releaseAll),
|
|
72
|
+
Effect.forever,
|
|
73
|
+
Effect.forkScoped,
|
|
74
|
+
Effect.interruptible
|
|
75
|
+
)
|
|
76
|
+
const take = Effect.uninterruptibleMask((restore) =>
|
|
77
|
+
Effect.flatMap(
|
|
78
|
+
FiberRef.get(currentCost),
|
|
79
|
+
(cost) => Effect.zipRight(restore(semaphore.take(cost)), latch.release(1))
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
return (effect) => Effect.zipRight(take, effect)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
/** @internal */
|
|
86
|
+
const currentCost = globalValue(
|
|
87
|
+
Symbol.for("effect/RateLimiter/currentCost"),
|
|
88
|
+
() => FiberRef.unsafeMake(1)
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
/** @internal */
|
|
92
|
+
export const withCost = (cost: number) => Effect.locally(currentCost, cost)
|
package/src/internal/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const moduleVersion = "2.3.
|
|
1
|
+
export const moduleVersion = "2.3.3"
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.set = exports.clear = void 0;
|
|
7
|
-
/**
|
|
8
|
-
* Bun currently has a bug where `setTimeout` doesn't behave correctly with a 0ms delay.
|
|
9
|
-
*
|
|
10
|
-
* @see https://github.com/oven-sh/bun/issues/3333
|
|
11
|
-
*/
|
|
12
|
-
/** @internal */
|
|
13
|
-
const isBun = typeof process === "undefined" ? false : !!process?.isBun;
|
|
14
|
-
/** @internal */
|
|
15
|
-
const clear = exports.clear = isBun ? id => clearInterval(id) : id => clearTimeout(id);
|
|
16
|
-
/** @internal */
|
|
17
|
-
const set = exports.set = isBun ? (fn, ms) => {
|
|
18
|
-
const id = setInterval(() => {
|
|
19
|
-
fn();
|
|
20
|
-
clearInterval(id);
|
|
21
|
-
}, ms);
|
|
22
|
-
return id;
|
|
23
|
-
} : (fn, ms) => setTimeout(fn, ms);
|
|
24
|
-
//# sourceMappingURL=timeout.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"timeout.js","names":["isBun","process","clear","exports","id","clearInterval","clearTimeout","set","fn","ms","setInterval","setTimeout"],"sources":["../../../src/internal/timeout.ts"],"sourcesContent":[null],"mappings":";;;;;;AAAA;;;;;AAMA;AACA,MAAMA,KAAK,GAAG,OAAOC,OAAO,KAAK,WAAW,GAAG,KAAK,GAAG,CAAC,CAAGA,OAAe,EAAED,KAAM;AAElF;AACO,MAAME,KAAK,GAAAC,OAAA,CAAAD,KAAA,GAAiCF,KAAK,GAAII,EAAE,IAAKC,aAAa,CAACD,EAAE,CAAC,GAAIA,EAAE,IAAKE,YAAY,CAACF,EAAE,CAAC;AAE/G;AACO,MAAMG,GAAG,GAAAJ,OAAA,CAAAI,GAAA,GAAmDP,KAAK,GACtE,CAACQ,EAAc,EAAEC,EAAU,KAAI;EAC7B,MAAML,EAAE,GAAGM,WAAW,CAAC,MAAK;IAC1BF,EAAE,EAAE;IACJH,aAAa,CAACD,EAAE,CAAC;EACnB,CAAC,EAAEK,EAAE,CAAC;EAEN,OAAOL,EAAE;AACX,CAAC,GACD,CAACI,EAAc,EAAEC,EAAU,KAAKE,UAAU,CAACH,EAAE,EAAEC,EAAE,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"timeout.d.ts","sourceRoot":"","sources":["../../../src/internal/timeout.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bun currently has a bug where `setTimeout` doesn't behave correctly with a 0ms delay.
|
|
3
|
-
*
|
|
4
|
-
* @see https://github.com/oven-sh/bun/issues/3333
|
|
5
|
-
*/
|
|
6
|
-
/** @internal */
|
|
7
|
-
const isBun = typeof process === "undefined" ? false : !!process?.isBun;
|
|
8
|
-
/** @internal */
|
|
9
|
-
export const clear = isBun ? id => clearInterval(id) : id => clearTimeout(id);
|
|
10
|
-
/** @internal */
|
|
11
|
-
export const set = isBun ? (fn, ms) => {
|
|
12
|
-
const id = setInterval(() => {
|
|
13
|
-
fn();
|
|
14
|
-
clearInterval(id);
|
|
15
|
-
}, ms);
|
|
16
|
-
return id;
|
|
17
|
-
} : (fn, ms) => setTimeout(fn, ms);
|
|
18
|
-
//# sourceMappingURL=timeout.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"timeout.js","names":["isBun","process","clear","id","clearInterval","clearTimeout","set","fn","ms","setInterval","setTimeout"],"sources":["../../../src/internal/timeout.ts"],"sourcesContent":[null],"mappings":"AAAA;;;;;AAMA;AACA,MAAMA,KAAK,GAAG,OAAOC,OAAO,KAAK,WAAW,GAAG,KAAK,GAAG,CAAC,CAAGA,OAAe,EAAED,KAAM;AAElF;AACA,OAAO,MAAME,KAAK,GAAiCF,KAAK,GAAIG,EAAE,IAAKC,aAAa,CAACD,EAAE,CAAC,GAAIA,EAAE,IAAKE,YAAY,CAACF,EAAE,CAAC;AAE/G;AACA,OAAO,MAAMG,GAAG,GAAmDN,KAAK,GACtE,CAACO,EAAc,EAAEC,EAAU,KAAI;EAC7B,MAAML,EAAE,GAAGM,WAAW,CAAC,MAAK;IAC1BF,EAAE,EAAE;IACJH,aAAa,CAACD,EAAE,CAAC;EACnB,CAAC,EAAEK,EAAE,CAAC;EAEN,OAAOL,EAAE;AACX,CAAC,GACD,CAACI,EAAc,EAAEC,EAAU,KAAKE,UAAU,CAACH,EAAE,EAAEC,EAAE,CAAC"}
|
package/src/internal/timeout.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bun currently has a bug where `setTimeout` doesn't behave correctly with a 0ms delay.
|
|
3
|
-
*
|
|
4
|
-
* @see https://github.com/oven-sh/bun/issues/3333
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/** @internal */
|
|
8
|
-
const isBun = typeof process === "undefined" ? false : !!((process as any)?.isBun)
|
|
9
|
-
|
|
10
|
-
/** @internal */
|
|
11
|
-
export const clear: (id: NodeJS.Timeout) => void = isBun ? (id) => clearInterval(id) : (id) => clearTimeout(id)
|
|
12
|
-
|
|
13
|
-
/** @internal */
|
|
14
|
-
export const set: (fn: () => void, ms: number) => NodeJS.Timeout = isBun ?
|
|
15
|
-
(fn: () => void, ms: number) => {
|
|
16
|
-
const id = setInterval(() => {
|
|
17
|
-
fn()
|
|
18
|
-
clearInterval(id)
|
|
19
|
-
}, ms)
|
|
20
|
-
|
|
21
|
-
return id
|
|
22
|
-
} :
|
|
23
|
-
(fn: () => void, ms: number) => setTimeout(fn, ms)
|