npm package: @ly-js/request
Description: request of ly-js
Author: liyu
Latest version: 0.1.0

# `@ly-js/request`

基于 Vue 3 的轻量请求工具集，涵盖：

- 组合式请求 `createRequest`/`useRequest`（含重试、全屏 Loading、请求中断）
- 业务基类 `BaseService`（CRUD、分页、批量删除、上传、参数序列化）
- 错误模型 `HttpError` 与 `isHttpError`
- 常量 `HTTP_STATUS_MESSAGE`

适配任意实现了 `HttpInstance` 接口的 HTTP 客户端（如 axios）。

## 安装

#### npm

```bash
npm i @ly-js/request --save
```

#### yarn

```bash
yarn add @ly-js/request
```

#### pnpm

```bash
pnpm add @ly-js/request
```

## 快速上手（组合式请求）

```ts
import { createRequest, RequestState } from '@ly-js/request/use'

// 可选：提供全屏 Loading 的启停，供内部调用
const useRequest = createRequest({
  fullLoadingConfig: {
    start: opts => {
      /* 打开全屏 Loading */
    },
    stop: () => {
      /* 关闭全屏 Loading */
    },
  },
})

// 任意异步函数皆可：支持 Promise、[Promise, abort]、或带 abort 的 Promise（RequestPromise）
const fetchUser = async (id: number) => {
  const res = await api.get(`/user/${id}`)
  return res
}

const { data, loading, error, state, mutate, abort } = useRequest(fetchUser, {
  immediate: false,
  retry: 2,
  retryDelay: 300,
  retryStrategy: 'immediate', // or 'exponential'
  beforeRequest: id => console.log('before', id),
  afterRequest: res => res, // 可返回转换后的数据写入 data
  success: d => console.log('success', d),
  fail: e => console.warn('fail', e),
  fullLoading: true, // 触发 fullLoadingConfig.start/stop
})

// 触发请求
await mutate(1)
// 中断进行中的请求
abort()

// 状态枚举
;RequestState.IDLE | PENDING | SUCCESS | FAILURE | ABORTED
```

### `UseRequestOptions`

- `immediate`: 是否初始化后立即触发一次请求
- `initialData`: `data` 的初始值
- `retry`/`retryDelay`/`retryStrategy`: 自动重试配置
- `beforeRequest(...args)`/`afterRequest(result)`/`success(data)`/`fail(err)`: 生命周期钩子
- `fullLoading`: 带参或布尔值，结合 `fullLoadingConfig` 使用

### 返回值

- `data: Ref<T | undefined>`
- `loading: Ref<boolean>`
- `error: Ref<any>`
- `state: Ref<RequestState>`
- `mutate(...args): Promise<T | undefined>`
- `abort(): void`

## `UseHelper` 快速构造 use\* Hook

```ts
import { UseHelper } from '@ly-js/request/use'

// 初始化内部 useRequest（只需一次）
UseHelper.createUseSWR({})

// 将任意函数包装为 use Hook，并支持默认参数来源（argsSource）
const useAdd = UseHelper.createUse(async (a: number, b: number) => a + b)

// 1) 无默认参数：直接透传 mutate 入参
useAdd().mutate(1, 2)

// 2) 提供默认参数：数组/元组/函数/Ref/Computed 皆可；mutate 的入参按位置覆盖
const { mutate, data } = useAdd({}, () => [1, 2] as const)
await mutate() // 使用默认 [1,2]
await mutate(2) // 覆盖首参 -> [2,2]
```

> 迁移提示：`UseHelper.createHooks` 已废弃（deprecated），不再文档化。

## `BaseService`（业务基类）

`BaseService<G, A = G, D = G, U = A, F = G, DF = G>` 针对典型 REST 资源封装了常用能力：

```ts
import { BaseService } from '@ly-js/request/core'

class UserService extends BaseService<{ id: number; name: string }> {
  pk = 'id'
  constructor(http?: any) {
    super('user', http)
  }
}

// 设置全局 http 实例（可选）
BaseService.setHttp(http)

// 可选：设置上传实现
BaseService.setUpload(async (url, file, params, config, onProgress) => {
  // 返回值需是 HttpResponse 结构（见类型定义）
  return http.request({
    url,
    method: config?.method || 'post',
    data: {
      /* FormData */
    },
    onUploadProgress: onProgress,
  })
})

const svc = new UserService()
await svc.list()
await svc.get({ id: 1 })
await svc.post({ id: 0, name: 'a' })
await svc.put({ id: 1, name: 'b' })
await svc.delete({ id: 2 })
await svc.update({ id: 3, name: 'c' }) // 有 id 走 put，否则走 post
await svc.multipleDelete({ ids: [1, 2] })
await svc.clear({ all: true })

// 上传（优先走自定义 setUpload；否则浏览器环境回退到 webUpload）
await svc.upload('/upload', file, { biz: 1 }, { method: 'post' }, e => console.log(e.percent))
```

### 方法清单

- `query(url, params?, config?, qsConfig?)`: GET + `qs` 序列化
- `list(params?, config?)`: `/${module}` 列表，返回 `{ code, msg, data }`
- `paging(params, config?)`: `/${module}` 分页
- `get(params, config?)`: `/${module}/:id`
- `post(data, config?)`: `/${module}`
- `put(params, config?)`: `/${module}/:id`
- `delete(params, config?)`: `/${module}/:id`
- `multipleDelete(params, config?, qsConfig?)`: `/${module}` 批量删除，带序列化
- `clear(params, config?, qsConfig?)`: `/${module}/clear`
- `update(params, config?)`: 根据 `pk` 是否存在决定 `post` 或 `put`
- `upload(url, file|UploadParams, params?, config?, onProgress?)`: 自定义上传或 `webUpload`

### 可覆写点

- `extractResponseData(response)`: 默认取 `response.data`
- `serializer(params, qsConfig)`: 默认 `arrayFormat: 'repeat'`

## axios 集成与中断（abort）示例

```ts
import axios from 'axios'
import type { HttpInstance } from '@ly-js/request/core'
import { BaseService } from '@ly-js/request/core'

// 1) 创建 axios 实例
const instance = axios.create({ baseURL: '/api' })

// 2) 适配 HttpInstance，并实现 initAbortController 用于中断
const http: HttpInstance = {
  request: config => instance.request(config).then(r => r.data),
  get: (url, config) => instance.get(url, config).then(r => r.data),
  delete: (url, config) => instance.delete(url, config).then(r => r.data),
  head: (url, config) => instance.head(url, config).then(r => r.data),
  options: (url, config) => instance.options(url, config).then(r => r.data),
  post: (url, data, config) => instance.post(url, data, config).then(r => r.data),
  put: (url, data, config) => instance.put(url, data, config).then(r => r.data),
  patch: (url, data, config) => instance.patch(url, data, config).then(r => r.data),
  initAbortController: (_url, config) => {
    const controller = new AbortController()
    ;(config as any).signal = controller.signal
    return () => controller.abort('abort by user')
  },
}

// 3) 注入至 BaseService（也可直接传入到具体 Service 实例构造函数）
BaseService.setHttp(http)

// 4) 可选：注入上传实现（使用 axios 原生 upload 事件）
BaseService.setUpload(async (url, file, params, config, onProgress) => {
  const form = new FormData()
  form.append('file', file as any)
  if (params) Object.entries(params).forEach(([k, v]) => form.append(k, v as any))
  return instance.request({
    url,
    method: (config?.method as any) || 'post',
    data: form,
    headers: { 'Content-Type': 'multipart/form-data' },
    onUploadProgress: onProgress,
  })
})
```

## 错误处理

```ts
import { HttpError, isHttpError } from '@ly-js/request/core'

try {
  await svc.get({ id: 404 })
} catch (e) {
  if (isHttpError(e)) {
    console.log(e.status, e.statusText, e.data)
  }
}
```

## 常量

```ts
import { HTTP_STATUS_MESSAGE } from '@ly-js/request/constants'
// 例如：HTTP_STATUS_MESSAGE[404] => '404: 资源不存在'
```

## 包导出

- `@ly-js/request`: 全量导出（`constants/core/use`）
- `@ly-js/request/core`: 核心能力（`BaseService/HttpError/RequestPromise/types`）
- `@ly-js/request/use`: 组合式请求（`createRequest/UseHelper/RequestState`）
- `@ly-js/request/constants`: 常量与类型

## 构建与外部依赖说明

- ESM/CJS 构建：仅将 `vue`、`@ly-js/utils`、`qs` 声明为 external，打包时会保留为 import/require，由你的构建工具解析。
- IIFE 构建：仅将上述三者作为外部，其他依赖均已内联打包，产物为 `dist/index.global.js`，以 `lyRequest` 为全局变量名。
- 使用时确保提供这三者：
  - `vue >= 3.5`
  - `@ly-js/utils`
  - `qs`

## 兼容性

- 需要 `vue >= 3.5`
- 默认使用浏览器或任意支持 `AbortController` 的环境实现中断；在不支持的环境下，可忽略 `initAbortController`，则 `abort()` 不会真正取消底层请求，但仍会切换状态并忽略结果

## 迁移与废弃

- `UseHelper.createHooks` 已废弃（deprecated），请迁移到 `UseHelper.createUse`