# api-service

## 特性

- 内部请求使用 [Axios](https://github.com/axios/axios) 实现跨平台请求能力
- 支持 Axios 的所有能力，并进行一定程度的扩展如(retry, race condition, reqId, logger, urlParams 等)
- 将 Axios 超时转化为 AbortSignal 处理（axios 内置 timeout 无法处理 node 端连接超时）
- 支持预定义拦截器，可以将拦截器作为配置信息初始化
- 采用规范化装饰器 (@Service, @Api) 定义服务与 API，便于对 API 进行统一管理
- 自动将 API 定义转化为服务调用，调用方式与本地函数调用方式相同

## 安装

```
pnpm add @inventorjs/api-service@latest
```

## 接口说明

### Entities

- ApiService: 核心服务类，用于初始化服务，和提供核心接口调用功能，对服务进行初始化
  - init: 初始化所有服务，创建内部请求实例，全局调用一次
- @Service: Service 装饰器，用于提供服务定义信息
- @Api: Api 装饰器，用于提供 api 定义信息

### Types

```
// 提供服务定义配置，可通过 init/@Service/@Api 三个接口传入，并自动进行合并
// 配置合并顺序 defaults <- init <- @Service <- @Api <- apiCall
interface ApiConfig<D = unknown> extends AxiosRequestConfig<D> {
  // ...
  // 支持所有其他 Axios 配置参数

  // 扩展配置
  $ext?: {
    observe?: 'body' | 'response' // 选择获取响应对象/响应体(response.data)，默认 response
    rcChannel?: string // race condition channel, 用于解决请求竞态问题，返回最新的数据
    retry?: number // 失败重试次数，默认不重试，默认重试延时：Math.min(2 ** retryCount, 30) * 1000
    reqIdHeaderName?: string // reqId http header，用于再请求头中注入 reqId，默认不住入
    genReqId?: { (config: ApiConfig): string } // reqId 生成算法，默认使用 uuid 算法
    logger?: boolean | LoggerConfig // 日志配置，默认开启(浏览器端默认关闭)
    urlParams?: Record<string, number | string> // url 参数对象，支持如 "/:id" 的url，并进行自动替换
    requestInterceptors?: (typeof RequestInterceptor)[] // 请求拦截器, 注：拦截器只能在 @Service 层级以上配置
    responseInterceptors?: (typeof ResponseInterceptor)[] // 响应拦截器 注：拦截器只能在 @Service 层级以上配置
  }
}

// ApiService 定义
declare class ApiService {
  private static rcChannelMap
  private static inited
  static init({
    services,
    config: rootConfig,
  }: {
    services: ClassType<ApiService>[]
    config?: ApiConfig
  }): Promise<void>
  static apiCall<R, D = unknown>(
    this: ClassType<ApiService>,
    data?: D,
    config?: ApiConfig,
  ): Promise<unknown>
}

// @Service 定义
declare function Service(
  serviceConfig: ApiConfig,
): (Cls: ClassType<ApiService>) => void

// @Api 定义
declare function Api(
  apiConfig: ApiConfig,
): (
  Cls: ClassType<ApiService>,
  propertyKey: string,
  propertyDescriptor: PropertyDescriptor,
) => void
```

## 示例

```
// user.service.ts
import { Service, Api, ApiService, type ApiConfig } from '@inventorjs/api-service'
import { UserService } from './user.service'

@Service({
  baseURL: 'https://run.mocky.io/v3',
})
export class UserService extends ApiService {
  @Api({ url: '/d7389eca-12e9-4e0e-b55e-4704fe7cbfc4' })
  static getData(data?: void, config?: ApiConfig) {
    return this.apiCall<Record<string, unknown>>(data, config)
  }
}

// index.ts 初始化 ApiService
ApiService.init({
  services: [UserService],
  config: {
    $ext: {
      reqIdHeaderName: 'x-req-id',
      observe: 'body',
      retry: 3,
    },
  },
})

// call api somewhere
UserService.getData().then((d) => {
  console.log(d)
})
```
