Upsilon Http Client
🧩 Syntax:
import { ProxyServerUnavailableError } from '#core/errors/proxy_server_unavailable_error'
import { ApiHttpClient } from '#core/http_clients/api_http_client'
import { FetchHttpClient } from '#core/http_clients/fetch_http_client'
import { isServerErrorResponse } from '#core/modules/error/error_utility'
import { ApplicationRuntimeConfigurationService } from '#core/modules/runtime/configurations/application_runtime_configuration'
import { getApplicationRuntimeConfiguration } from '#core/modules/runtime/utils/runtime_configuration_utility'
import { HttpClient, HttpClientRequest } from '@effect/platform'
import { Effect, Either, Schedule } from 'effect'
import { isEqual, joinURL, parsePath, withLeadingSlash } from 'ufo'
import { EventEmitterIdentifier } from '~/constants/event_emitter_identifier_enum'
import { emitter } from '~/core/emittery'
export class UpsilonHttpClient extends Effect.Service<UpsilonHttpClient>()('client:http:upsilon', {
dependencies: [ApplicationRuntimeConfigurationService.Default, FetchHttpClient.Default, ApiHttpClient.Default],
effect: Effect.gen(function * () {
const { BaseHttpClient } = yield * FetchHttpClient
const api = yield * ApiHttpClient
const applicationConfig = yield * getApplicationRuntimeConfiguration(config => ({
application_url: config.application_url,
upsilon_base_path: config.upsilon.base_path,
}))
const upsilonClient = BaseHttpClient.pipe(
HttpClient.mapRequest(HttpClientRequest.updateUrl(withLeadingSlash)),
HttpClient.mapRequest(HttpClientRequest.prependUrl(joinURL(applicationConfig.application_url, applicationConfig.upsilon_base_path))),
HttpClient.followRedirects(3),
)
function json(request: HttpClientRequest.HttpClientRequest) {
return Effect.gen(function * () {
const result = yield * api.json(request, upsilonClient).pipe(Effect.either)
if (Either.isLeft(result)) {
if (isServerErrorResponse(result.left) && [502, 503, 504].includes(result.left.status)) {
return yield * new ProxyServerUnavailableError({ name: 'upsilon' }, 'Upsilon proxy server is unavailable at the moment. Please try again later.')
}
if (!isEqual(parsePath(request.url).pathname, '/health') && result.left._tag !== 'error:app:client_network_offline') {
emitter.emit(EventEmitterIdentifier.UPSILON_AVAILABILITY_CHANGED, 'available')
}
return yield * Effect.fail(result.left)
}
if (!isEqual(parsePath(request.url).pathname, '/health')) {
emitter.emit(EventEmitterIdentifier.UPSILON_AVAILABILITY_CHANGED, 'available')
}
return yield * Effect.succeed(result.right)
}).pipe(
Effect.tapErrorTag('error:app:proxy_server_unavailable', error => Effect.sync(() => {
if (error.name === 'upsilon') {
emitter.emit(EventEmitterIdentifier.UPSILON_AVAILABILITY_CHANGED, 'unavailable')
}
})),
Effect.tapErrorTag('error:server_error_response:unauthorized', () => Effect.sync(() => {
emitter.emit(EventEmitterIdentifier.AUTHENTICATION_STATUS_CHANGED, { authenticated: false, user: null })
})),
Effect.retry({
while: error => error._tag === 'error:app:proxy_server_unavailable' && error.name === 'upsilon' && import.meta.client,
schedule: Schedule.exponential('800 millis'),
times: 5,
}),
)
}
return {
/**
* Perform a request with the JSON accept header and return the JSON response.
* If the response is an error, it will be decoded and returned as an error.
*
* - It will also handle the client network offline error and retry the request if the client is offline.
* - It will also handle the proxy server unavailable error and retry the request if the proxy server is unavailable.
*
* @param request The request to perform.
*/
json,
}
}),
}) {}