npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

wtbx-type-safe-fetch

v3.0.3

Published

支持攔截器功能的類型安全與自動推導的 fetch (更: 修正 merge-same-request 響應時沒有清空緩存區數組)

Downloads

25

Readme

wtbx-type-safe-fetch

快速開始

import { tsFetch } from 'wtbx-type-safe-fetch'

// type is '123'
const result = await tsFetch<'123'>('/api_path')

Api

tsFetch()

將原生 fetch 的一二餐合併以及 method 的 type 更完善而已

type LuCaseString<S extends string> = S | Uppercase<S>

type TsFetchMethod = LuCaseString<'get' | 'put' | 'post' | 'delete' | 'options'>

type TsFetchRequestInit = Omit<RequestInit, 'method'> & {
  method?: TsFetchMethod
}

type TsFetch = {
  <R = Response>(req: TsFetchRequestInit): Promise<R>
}

// 內部實現僅如此,對於 req 沒有做任何的更動,僅做中間件與攔截器的處理而已
tsFetch('api_path')

// 以上代碼內部運行為:
try {
  // req 會被塞入 url
  interceptor.fori.call.request(req)
  const response = await fetch(req.url, req)
  return interceptor.fori.call.response(response)
} catch (error) {
  return interceptor.fori.call.error(req)
}

new()

重新創建一個 tsFetch 只是創建出來的不再有 new() 方法

watch

提供三種攔截器,分別為(運作時機可以參考上方的 tsFetch() api 文檔):

  • request 在 fetch() 前處理
  • response 在 fetch() 後處理
  • error 當出錯時才會處理
// 後面的 request 攔截器會沿用前面的 req
tsFetch.watch.request((req) => {})

// 後面的 response 攔截器會沿用前面的 response
tsFetch.watch.response((req, res) => {})

// 後面的 response 攔截器會沿用前面的 response
tsFetch.watch.error((req, error) => {})
// 補上預設的類型定義

// request
type TsFetchRequestListener<Req extends TsFetchListenerRequestInit = TsFetchListenerRequestInit> = (
  req: Req,
) => Req | Promise<Req>

// response
type TsFetchResponseListener<
  Req extends TsFetchListenerRequestInit = TsFetchListenerRequestInit,
  Res = Response,
  Return = Res | Promise<Res>,
> = (
  req: Readonly<Req>,
  res: Res,
) => Return

// error
type TsFetchErrorListener<
  Err extends Error = Error,
  Req extends TsFetchListenerRequestInit = TsFetchListenerRequestInit,
  Return = Response | undefined | Promise<Response | undefined>,
> = (error: Err, req: Readonly<Req>, res: Return) => Return


// 攔截器用到的類型
type TsFetchListenerRequestInit = TsFetchRequestInit & { url: string }

type TsFetchRequestInit = Omit<RequestInit, 'method'> & {
  method?: TsFetchMethod
}

middlewares

文檔有空再改,2+版不提供 middleware api

log

會在 res, error 裡進行 console.log

import { tsFetch } from 'wtbx-type-safe-fetch'
import { log } from 'wtbx-type-safe-fetch/middlewares/log'

tsFetch.middleware(log)
// 自行 call api 就能看到了

method-url

用來將寫在路徑上的 method 替換成 method 傳入

import { tsFetch } from 'wtbx-type-safe-fetch'
import { methodUrl } from 'wtbx-type-safe-fetch/middlewares/method-url'

tsFetch.middleware(methodUrl)

tsFetch(
  {
    url: 'post:/hello/world',
  } 
  /* 
    將被轉換為以下
    {
      url: '/hello/world',
      method: 'post',
    }
   */
)

mock

配合 wtbx-vite-mock-apis 使用

安裝

$ pnpm add -D wtbx-vite-mock-apis

使用

import { tsFetch } from 'wtbx-type-safe-fetch'
import { mock } from 'wtbx-type-safe-fetch/middlewares/mock'

tsFetch.middleware(mock)

// 將去 mock 目錄下找 user.js 來 call
// 路徑被轉換成 method: get, url: /cat?mockFile=user
tsFetch('mock:user:get:/cat')

// 將去 mock 目錄下找 index.js 來 call
// 路徑被轉換成 method: get, url: /cat?mockFile=index
tsFetch('mock::get:/cat')

params-and-body-parser

將傳入的 params 替換成 query-string,傳入的 body 轉換成字串並掛上 content-type application/json,但需要自行安裝 query-string package

安裝

# 內部使用的是 @9 版
$ pnpm add query-string

使用

import { tsFetch } from 'wtbx-type-safe-fetch'
import { paramsAndBodyParser } from 'wtbx-type-safe-fetch/middlewares/params-and-body-parser'

tsFetch.middleware(paramsAndBodyParser)

tsFetch(
  {
    url: '/hello/world',
    method: 'post',
    params: { hello: 'wrold', year: 2024 },
    body: { author: 'twjw' }
  }
  /* 
    將被轉換為以下
    {
      url: '/hello/world?hello=world&year=2024',
      method: 'post',
      body: '{"author":"twjw"}'
      headers: { 'Content-Type': 'application/json' }
    }
   */
)

path-params-url

將 url 參數轉換成與參數名匹配的 pathParams 對應的 key-value

import { tsFetch } from 'wtbx-type-safe-fetch'
import { pathParamsUrl } from 'wtbx-type-safe-fetch/middlewares/path-params-url'

tsFetch.middleware(pathParamsUrl)

tsFetch(
  {
    url: '/:version/cat',
    method: 'get',
    pathParams: {
      version: 'v1',
    },
  }
  /* 
    將被轉換為以下
    {
      url: '/v1/cat',
      method: 'get',
      pathParams: {
        version: 'v1',
      },
    }
   */
)

最佳實踐

如果只是使用基本的類型定義沒法很完善的處理整個項目的 api 類型,在調用時仍然要自己傳入響應值得泛型,以及每個路徑需要傳遞的參數類型也需要手動傳,對於安全性與方便度是較為低下的,所以項目提供了專門處理這情況的類型定義

TsFetchTemplate

可以使用內部提供的 TsFetchTemplate 類型來蓋掉預設的 tsFetch() 的類型,這樣就可以以類型來驅動

前置

const fetch2 = tsFetch as unknown as TsFetchTemplate<{}>
// or 區別在你要改哪個而已,因為要改變原始類型,所以要額外申明變量來接
const fetch2 = tsFetch.new() as unknown as TsFetchTemplate<{}>

使用

首先劃分目錄來將 api 或服務相關的類型與處理放在一起,我會這麼做:

service
  fetch2 我直接取名 fetch2,不過名字喜好看個人,也可以叫 myFetch ...
    middlewares
      xxx.ts
    api-types
      xxx.ts
    index.ts

定義 api 類型

// service/api-types/cat.ts
import { TsFetchTemplateDefineApis } from 'wtbx-type-safe-fetch'

// 使用 namespace 包裹對應的 API 類型
export namespace Cat {
  // 裡面的類型可以使用 webstorm plugin 生成
  // 我是使用 Json2ts plugin 來做這件事
  // 安裝後在要生成的地方按下右鍵 > 點擊 Json2ts 來生成
  export type Params = {
    size: string
  }
  
  export type Response = {
    url: string
  }
}

// TsFetchTemplateDefineApis 可以傳入的 key 有
// headers, params, body, response
// 前三者只要有任一必傳,那麼調用時二參必定要傳,可以查看下方的說明
export type Apis = TsFetchTemplateDefineApis<{
  'get:/cat': {
    params: Cat.Params
    response: Cat.Response
  }
  // 如果路徑上有 :xxx 將會要求傳入 pathParmas 參數,對應的參數名為 : 後面的單詞
  'get:/:version/cat': {
    response: Cat.Response
  }
}>
  
// service/api-types/dog.ts
import {TsFetchTemplateDefineApis} from "wtbx-type-safe-fetch";

export type Apis = TsFetchTemplateDefineApis<{
  'get:/dog': { response: never }
}>

將 tsFetch 的類型蓋掉並導出使用

// service/fetch2/index.ts
import {tsFetch, TsFetchTemplate} from 'wtbx-type-safe-fetch'
import {paramsAndBodyParser} from 'wtbx-type-safe-fetch/middlewares/params-and-body-parser'
import {methodUrl} from 'wtbx-type-safe-fetch/middlewares/method-url'
import {pathParamsUrl} from "wtbx-type-safe-fetch/middlewares/path-params-url";
import {type Apis as CatApis} from '@/service/api-types/cat.ts'
import {type Apis as DogApis} from '@/service/api-types/dog.ts'

const fetch2 = tsFetch as unknown as TsFetchTemplate<
  // 將 apis 類型全部引入後合起來傳入到 TsFetchTemplate 泛型參數裡
  CatApis
  & DogApis
>

// TsFetchTemplate 是以以下三個中間件驅動的,所以要裝上
fetch2.middleware(methodUrl)
fetch2.middleware(pathParamsUrl)
fetch2.middleware(paramsAndBodyParser)

export {
  fetch2,
}


// 使用處(此處可以看出該類型的有用性)
// success,因為沒有要傳的參數,所以不會報錯
fetch2('get:/dog')

// fail,因為 cat 需傳遞 params.size,所以二參必須傳入
fetch2('get:/cat')

// success,有傳入 params.size
fetch2('get:/cat', {
  params: {
    size: 'big',
  },
})

// fail,未傳入 pathParams.version
fetch2('get:/:version/cat')

// success,有傳入 pathParams.version
// pathParams 需搭配上方的 pathParamsUrl 中間件來處理
// 最終轉換出來的 url 為 get:/v1/cat
fetch2('get:/:version/cat', {
  pathParams: {
    version: 'v1',
  },
})

// 補充說明
// fetch2 調用的首參會自動彈出定義的路徑的自動補全下拉窗
// 且不管是 fetch2 調用的首參或是定義處的 key,使用 webstorm 的 shift+f6 重構
// 都能直接將定義處跟所有掉用處的路徑同時重構,這就是該類型的強大之處!!!!
fetch2('get:/dog')