-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathstrategy-executor.ts
More file actions
82 lines (75 loc) · 2.9 KB
/
strategy-executor.ts
File metadata and controls
82 lines (75 loc) · 2.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import { Effect, Schedule, Duration, pipe, Data } from 'effect'
import {
FetchMetaParams,
ContractMetaResolverStrategy,
GetContractMetaStrategy,
MissingMetaError,
} from './request-model.js'
import type { CircuitBreaker } from '../circuit-breaker/circuit-breaker.js'
import { RequestPool } from '../circuit-breaker/request-pool.js'
import * as Constants from '../circuit-breaker/constants.js'
export class MissingHealthyStrategy extends Data.TaggedError('MissingHealthyStrategy')<{
chainId: number
strategies: string[]
}> {
constructor(params: { chainId: number; strategies: string[] }) {
super(params)
}
}
export const make = (circuitBreaker: CircuitBreaker, requestPool: RequestPool) => {
const executeStrategy = (strategy: ContractMetaResolverStrategy, params: FetchMetaParams) => {
return pipe(
Effect.request(
new GetContractMetaStrategy({
address: params.address,
chainId: params.chainId,
strategyId: strategy.id,
}),
strategy.resolver,
),
Effect.withRequestCaching(true),
Effect.timeout(Duration.decode(Constants.STRATEGY_TIMEOUT)),
// Treate MissingMetaError as a success for circuit breaker
Effect.catchTag('MissingMetaError', (error) => {
return Effect.gen(function* () {
yield* Effect.logDebug(`Meta strategy ${strategy.id} found no metadata: ${error.message}`)
return yield* Effect.succeed(error)
})
}),
Effect.retry(
Schedule.exponential(Duration.decode(Constants.INITIAL_RETRY_DELAY)).pipe(
Schedule.compose(Schedule.recurs(Constants.DEFAULT_RETRY_TIMES)),
),
),
(effect) => circuitBreaker.withCircuitBreaker(strategy.id, effect),
(effect) => requestPool.withPoolManagement(params.chainId, effect),
Effect.flatMap((data) => (data instanceof MissingMetaError ? Effect.fail(data) : Effect.succeed(data))),
)
}
const executeStrategiesSequentially = (strategies: ContractMetaResolverStrategy[], params: FetchMetaParams) =>
Effect.gen(function* () {
// Filter out unhealthy strategies first
const healthyStrategies: ContractMetaResolverStrategy[] = []
for (const strategy of strategies) {
const isHealthy = yield* circuitBreaker.isHealthy(strategy.id)
if (isHealthy) {
healthyStrategies.push(strategy)
} else {
yield* Effect.logDebug(`Skipping unhealthy meta strategy: ${strategy.id}`)
}
}
if (healthyStrategies.length === 0) {
return yield* Effect.fail(
new MissingHealthyStrategy({
chainId: params.chainId,
strategies: strategies.map((s) => s.id),
}),
)
}
// Try strategies one by one until one succeeds
return yield* Effect.validateFirst(healthyStrategies, (strategy) => executeStrategy(strategy, params))
})
return {
executeStrategiesSequentially,
}
}