1.0.5 • Published 10 months ago

@wymjs/type-safe-fetch v1.0.5

Weekly downloads
-
License
ISC
Repository
-
Last release
10 months ago

@wymjs/type-safe-fetch

支持攔截器功能的類型安全與自動推導的 fetch

內部代碼很單純,就是將原生的 fetch 擴展 request, response, error 三個攔截器,其他與原生的 fetch 無任何不同

最主要的功能是提供了 fetch 的類型安全規範類型,也可以使用原生的 fetch 並覆蓋原生類型來使用

安裝

$ pnpm i @wymjs/type-safe-fetch

# 使用到 tool/params-and-body-parser 需安裝
$ pnpm i query-string

使用

這裡僅提供該庫的最佳實踐方式,詳細可查閱

// 有些內容沒寫,不是很重要,需要了解完整請查閱上方連結倉庫
// @/service/fetch2/api-type/user.ts
import { TsFetchTemplateDefineApis } from '@wymjs/type-safe-fetch'
import { ApiResponse } from '@/service/fetch2/type.ts'

// 使用 namespace 將每個 api 路徑的響應區分開來
export namespace Login {
  export type Params = {
    expiredMinutes?: number
  }
  
  export type Body = {
    username: string
    password: string
  }

  // 定義 Response 時可以使用生成工具來處理,
  // 我是使用 Jetbrains 的 Json2ts 來生成
  // 將指標點到這裡後按下右鍵,生成的彈窗勾選 type
  // 然後 Root name 輸入 Response 後按下 generate 就可以生成好類型了
  export type Response = {
    token: string
  }
}

export namespace Profile {
  export type Response = {
    id: number
    name: string
  }
}

export type Apis = TsFetchTemplateDefineApis<{
  // key 為 api 路徑,規則為 {method}:{path}
  // vscode 使用重構可以替換所有用到這 key 的值(webstorm 本來也可以的QQ)
  'get:/api/user/profile': {
    // 響應類型,此為必填,用 ApiResponse 將 Response 包裹住
    // ApiResponse 為統一的 api 響應格式
    response: ApiResponse<Profile.Response>
  }
  
  'post:/api/user/login': {
    // headers, params, body 都可以綁定類型
    // 在 fetch2() 一參傳入對應的路徑時,
    // 將會自動推斷二參有沒有什麼必傳參數沒傳的
    params?: Login.Params
    body: Login.Body
    response: ApiResponse<Login.Response>
  }
  
  // 動態路由參數,使用 : 連接即可,後續使用 pathParams 替換
  'get:/api/user/:id': {
    // ...
  }
}>



// @/service/fetch2/index.ts
import { createTsFetch, TsFetchTemplate } from '@wymjs/type-safe-fetch'
import { createMockTool } from '@wymjs/type-safe-fetch/tool/mock'
import { createMethodUrlTool } from '@wymjs/type-safe-fetch/tool/method-url'
import { createPathParamsUrlTool } from '@wymjs/type-safe-fetch/tool/path-params-url'
import { createParamsAndBodyParserTool } from '@wymjs/type-safe-fetch/tool/params-and-body-parser'
import { createMergeSameRequestTool } from '@wymjs/type-safe-fetch/tool/merge-same-request'
import { createLogTool } from '@wymjs/type-safe-fetch/tool/log'
import { envConfig } from '~env-config'
import { ApiResponse, MyListenerRequestInit, MyRequestInit } from '@/service/fetch2/type.ts'
import { commonApiErrorResponse, commonApiResponse, passAuthRequest, checkApiPermission } from '@/service/fetch2/helper/watch.ts'

const isLocal = envConfig.vite.isLocal

const fetch2 = createTsFetch() as unknown as TsFetchTemplate<
  import('@/service/fetch2/api-type/user.ts').Apis,
  MyRequestInit
>

// vite 開發運行環境下支持 @wymjs/vite-mock-apis 的功能
const mockTool = createMockTool()
// 將路徑的方法轉換成 method,如:post:/api/hello
const methodUrlTool = createMethodUrlTool()
// 將路徑參數轉換成匹配的 pathParams key-value
// 比方說:fetch2('/api/user/:id', { pathParams: { id: '1' } })
const pathParamsUrlTool = createPathParamsUrlTool()
// 將 params 轉成 querystring 以及 body 自動轉成字串傳入
const paramsAndBodyParserTool = createParamsAndBodyParserTool()
// 合併相同路徑請求,可以迴圈 call 相同路徑的 api 確認是否只 call 一次 api
const mergeSameRequestTool = createMergeSameRequestTool<
  Error,
  MyListenerRequestInit,
  ApiResponse<any>
>()
// vite 開發運行環境下 log 響應值(可選)
const logTool = createLogTool<Error, MyListenerRequestInit, ApiResponse<any>>()

// request 攔截器:泛型為 req/return 的類型
fetch2.watch.request<MyListenerRequestInit>(req => {
  if (isLocal) mockTool.transform(req)

  req.originUrl = req.url
  mergeSameRequestTool.defer(req.originUrl, req)
  methodUrlTool.transform(req)
  pathParamsUrlTool.transform(req)
  passAuthRequest(req)
  paramsAndBodyParserTool.transform(req)

  return req
})

// response 攔截器:
//   泛型 1 為 req 的類型
//   泛型 2 為 res 的類型
//   泛型 3 為 return 的類型
fetch2.watch.response<
  MyListenerRequestInit,
  Response,
  ApiResponse<any> | Promise<ApiResponse<any>>
>(async (req, res) => {
  checkApiPermission(req, res)
  const _res = await commonApiResponse(req, res)

  if (isLocal) logTool.log(req, _res)
  mergeSameRequestTool.resolve(req.originUrl, _res)

  return _res
})

// 錯誤攔截器:有寫錯誤攔截的話,除非攔截器內寫到報錯,
// 不然 fetch2() 執行後絕不會出現錯誤,就不用 try/catch 來包裹
//   泛型 1 為 error 的類型
//   泛型 2 為 req 的類型
//   泛型 3 為 res/return 的類型
fetch2.watch.error<Error, MyListenerRequestInit, ApiResponse<any> | Promise<ApiResponse<any>>>(
  async (error, req, res) => {
    const mergeResponse = await mergeSameRequestTool.waiting(req.originUrl, req, error)

    if (mergeResponse !== undefined) return mergeResponse
    if (isLocal) logTool.error(error, req)

    return commonApiErrorResponse(error, req, res)
  },
)

export { fetch2 }



// 使用
// 當你輸入一參路徑時你將會發現 IDE 彈出下拉列出所有路徑
fetch2('post:/api/user/login', {
  // body 類型未錯將會報錯
  body: {
    username: 'admin',
    password: '0000',
  },
  // params 定義為可選,所以可以不傳
  // ... 其他參數跟原生的一樣
})

fetch2('get:/api/user/:id', {
  // 動態路由參數這樣替換
  pathParams: { id: '1' },
})
1.0.5

10 months ago

1.0.4

10 months ago

1.0.3

11 months ago

1.0.2

11 months ago

1.0.1

11 months ago

1.0.0

11 months ago