Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eslint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ export default antfu({
'test/prefer-lowercase-title': ['error', {
ignore: ['describe'],
}],
'unicorn/throw-new-error': 'off',
},
})
7 changes: 3 additions & 4 deletions src/Bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import * as Context from 'effect/Context'

export type Bot<E = never, R = never> = Effect.Effect<void, E, R | Update>

export class Update extends Context.Tag('@grom.js/effect-tg/Bot/Update')<
Update,
BotApi.Types.Update
>() {}
export interface Update extends Readonly<BotApi.Types.Update> {}

export const Update: Context.Tag<Update, Update> = Context.GenericTag<Update>('@grom.js/effect-tg/Bot/Update')

export interface Middleware {
<E, R>(self: Bot<E, R>): Bot<any, any>
Expand Down
15 changes: 7 additions & 8 deletions src/BotApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@ import * as BotApiTransport from './BotApiTransport.ts'
import * as BotApiUrl from './BotApiUrl.ts'
import * as internal from './internal/botApi.ts'

export type { MethodParams, MethodResults, Service, Types }
export type { MethodParams, MethodResults, Types }

export class BotApi extends Context.Tag('@grom.js/effect-tg/BotApi')<
BotApi,
Service
>() {}
export interface BotApi extends Readonly<Service> {}

export const BotApi: Context.Tag<BotApi, BotApi> = Context.GenericTag<BotApi>('@grom.js/effect-tg/BotApi')

export interface BotApiMethod<TMethod extends keyof MethodParams> {
(...args: MethodArgs<TMethod>): Effect.Effect<
Expand All @@ -47,8 +46,8 @@ export const callMethod: <TMethod extends keyof MethodParams>(
) as any

export const make: (args: {
transport: BotApiTransport.Service
}) => Service = internal.make
transport: BotApiTransport.BotApiTransport
}) => BotApi = internal.make

export const layer: Layer.Layer<
BotApi,
Expand Down Expand Up @@ -95,7 +94,7 @@ export const layerConfig = (options: {
* - Adding custom retry logic or error handling
* - Integrating with monitoring or debugging tools
*/
readonly transformTransport?: (transport: BotApiTransport.Service) => BotApiTransport.Service
readonly transformTransport?: (transport: BotApiTransport.BotApiTransport) => BotApiTransport.BotApiTransport
}): Layer.Layer<BotApi, ConfigError.ConfigError, HttpClient.HttpClient> => {
const {
token,
Expand Down
44 changes: 19 additions & 25 deletions src/BotApiError.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import type * as HttpBody from '@effect/platform/HttpBody'
import type * as HttpClientError from '@effect/platform/HttpClientError'
import type * as BotApiTransport from './BotApiTransport.ts'
import * as Data from 'effect/Data'
import { TypeIdError } from '@effect/platform/Error'
import * as Duration from 'effect/Duration'
import * as Match from 'effect/Match'
import * as Option from 'effect/Option'
import * as Predicate from 'effect/Predicate'
import * as Dialog from './Dialog.ts'
import * as internal from './internal/botApiError.ts'

export const TypeId = '@grom.js/effect-tg/BotApiError'
export const TypeId: unique symbol = Symbol.for('@grom.js/effect-tg/BotApiError')

export type TypeId = typeof TypeId

Expand All @@ -25,14 +25,12 @@ export type BotApiError =
/**
* Error caused by the transport when accessing Bot API.
*/
export class TransportError extends Data.TaggedError('TransportError')<{
cause:
export class TransportError extends TypeIdError(TypeId, 'TransportError')<{
readonly cause:
| HttpClientError.HttpClientError
| HttpBody.HttpBodyError
}> {
readonly [TypeId]: TypeId = TypeId

override get message() {
override get message(): string {
return Match.value(this.cause).pipe(
Match.tagsExhaustive({
RequestError: e => e.message,
Expand All @@ -48,43 +46,39 @@ export class TransportError extends Data.TaggedError('TransportError')<{
}
}

export class MethodFailed extends Data.TaggedError('MethodFailed')<{
response: FailureResponse
possibleReason: MethodFailureReason
export class MethodFailed extends TypeIdError(TypeId, 'MethodFailed')<{
readonly response: FailureResponse
readonly possibleReason: MethodFailureReason
}> {
readonly [TypeId]: TypeId = TypeId

override get message() {
return `(${this.response.error_code}) ${this.response.description}`
}
}

export class GroupUpgraded extends Data.TaggedError('GroupUpgraded')<{
response: FailureResponse
supergroup: Dialog.Supergroup
export class GroupUpgraded extends TypeIdError(TypeId, 'GroupUpgraded')<{
readonly response: FailureResponse
readonly supergroup: Dialog.Supergroup
}> {
readonly [TypeId]: TypeId = TypeId

override get message() {
return `Group has been upgraded to a supergroup with ID ${this.supergroup.id}.`
}
}

export class RateLimited extends Data.TaggedError('RateLimited')<{
response: FailureResponse
retryAfter: Duration.Duration
export class RateLimited extends TypeIdError(TypeId, 'RateLimited')<{
readonly response: FailureResponse
readonly retryAfter: Duration.Duration
}> {
readonly [TypeId]: TypeId = TypeId

override get message() {
return `Flood limit exceeded. Should wait for ${Duration.format(this.retryAfter)} before retrying.`
}
}

export class InternalServerError extends Data.TaggedError('InternalServerError')<{
response: FailureResponse
export class InternalServerError extends TypeIdError(TypeId, 'InternalServerError')<{
readonly response: FailureResponse
}> {
readonly [TypeId]: TypeId = TypeId
override get message() {
return `Internal error (${this.response.error_code}): ${this.response.description}`
}
}

export const fromResponse = (response: FailureResponse): BotApiError => {
Expand Down
15 changes: 6 additions & 9 deletions src/BotApiTransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,15 @@ import * as Layer from 'effect/Layer'
import * as BotApiUrl from './BotApiUrl.ts'
import * as internal from './internal/botApiTransport.ts'

export class BotApiTransport extends Context.Tag('@grom.js/effect-tg/BotApiTransport')<
BotApiTransport,
Service
>() {}

export interface Service {
sendRequest: (
export interface BotApiTransport {
readonly sendRequest: (
method: string,
params: unknown,
) => Effect.Effect<BotApiResponse, BotApiError.TransportError>
}

export const BotApiTransport: Context.Tag<BotApiTransport, BotApiTransport> = Context.GenericTag<BotApiTransport>('@grom.js/effect-tg/BotApiTransport')

/**
* @see https://core.telegram.org/bots/api#making-requests
*/
Expand All @@ -36,8 +33,8 @@ export type BotApiResponse =

export const make: (options: {
httpClient: HttpClient.HttpClient
botApiUrl: BotApiUrl.Service
}) => Service = internal.make
botApiUrl: BotApiUrl.BotApiUrl
}) => BotApiTransport = internal.make

export const layer: Layer.Layer<
BotApiTransport,
Expand Down
17 changes: 7 additions & 10 deletions src/BotApiUrl.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
import * as Context from 'effect/Context'

export class BotApiUrl extends Context.Tag('@grom.js/effect-tg/BotApiUrl')<
BotApiUrl,
Service
>() {}

export interface Service {
toMethod: (method: string) => URL
toFile: (filePath: string) => URL
export interface BotApiUrl {
readonly toMethod: (method: string) => URL
readonly toFile: (filePath: string) => URL
}

export const makeProd = (token: string): Service => (
export const BotApiUrl: Context.Tag<BotApiUrl, BotApiUrl> = Context.GenericTag<BotApiUrl>('@grom.js/effect-tg/BotApiUrl')

export const makeProd = (token: string): BotApiUrl => (
{
toMethod: (method: string) => new URL(`https://api.telegram.org/bot${token}/${method}`),
toFile: (filePath: string) => new URL(`https://api.telegram.org/file/bot${token}/${filePath}`),
}
)

export const makeTest = (token: string): Service => (
export const makeTest = (token: string): BotApiUrl => (
{
toMethod: (method: string) => new URL(`https://api.telegram.org/bot${token}/test/${method}`),
// TODO: make sure this works in test environment
Expand Down
Loading
Loading