Skip to content

Commit

Permalink
feat: support $fetch.raw and improve docs
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 committed Dec 12, 2020
1 parent 421dfe9 commit f9f70a5
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 45 deletions.
60 changes: 39 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
![ohmyfetch](https://user-images.githubusercontent.com/904724/101663230-bb578c80-3a4a-11eb-89eb-14cd3e08dd8c.png)

# 😱 ohmyfetch

[![npm version][npm-version-src]][npm-version-href]
[![Github Actions][github-actions-src]][github-actions-href]
[![Codecov][codecov-src]][codecov-href]
[![bundle][bundle-src]][bundle-href]

![ohmyfetch](https://user-images.githubusercontent.com/904724/101663230-bb578c80-3a4a-11eb-89eb-14cd3e08dd8c.png)

<!-- # 😱 ohmyfetch -->


<!-- [![npm downloads][npm-downloads-src]][npm-downloads-href] -->

## 🚀 Quick Start
Expand Down Expand Up @@ -33,54 +36,69 @@ import { $fetch } from 'ohmyfetch/node'
const { $fetch } = require('ohmyfetch/node')
```

![oh-my-fetch](https://media.giphy.com/media/Dn1QRA9hqMcoMz9zVZ/giphy.gif)
<details>
<summary>Spoiler</summary>
<img src="https://media.giphy.com/media/Dn1QRA9hqMcoMz9zVZ/giphy.gif">
</details>


## Why?

### ✔️ Parse Response
## ✔️ Parsing Response

- Smartly parse JSON and native values like `true` (using [destr](https://github.com/nuxt-contrib/destr))
- Fallback to text if cannot parse
- Secure against prototype pollution
`$fetch` Smartly parses JSON and native valuesusing [destr](https://github.com/nuxt-contrib/destr) and fallback to text if cannot parse

```js
const { users } = await $fetch('/api/users')
```

### ✔️ Handle Errors
## ✔️ Handling Errors

- Automatically throw errors when `response.ok` is `false`
- Friendly error message with compact stack (hiding internals)
- Parsed error body is available with `error.data`
`$fetch` Automatically throw errors when `response.ok` is `false` with a friendly error message and compact stack (hiding internals).

Parsed error body is available with `error.data`. You may also use `FetchError` type.

```ts
await $fetch('http://google.com/404')
// FetchError: 404 Not Found (http://google.com/404)
// at async main (/project/playground.ts:4:3)
```

### ✔️ Type Friendly
In order to bypass errors as reponse you can use `error.data`:

```ts
await $fetch(...).catch((error) => error.data)
```

## ✔️ Type Friendly

- Response can be type assisted
Response can be type assisted:

```ts
const { article } = await $fetch<Article>(`/api/article/${id}`)
// Auto complete working with article.id
```

### ✔️ Support baseURL

- Allow making factory functions to add baseURL
- Prepend baseURL with respecting trailing/leading slashes and query params for baseURL (using [ufo](https://github.com/nuxt-contrib/ufo))
## ✔️ Support baseURL

By setting `baseURL` option `$fetch` prepends it with respecting to trailing/leading slashes and query params for baseURL using [ufo](https://github.com/nuxt-contrib/ufo):

```js
await $fetch('/config', { baseURL })
```

### ✔️ Universal
## 🍣 Access to Raw Response

If you need to access raw response (for headers, etc), can use `$fetch.raw`:

```js
const response = await $fetch.raw('/sushi')

// response.data
// response.headers
// ...
```

Supporting browsers, workers and NodeJS targets

## 📦 Bundler Notes

Expand All @@ -105,7 +123,7 @@ This also guarantees we can introduce more utils without breaking the package an

By keep transpiling libraries we push web backward with legacy code which is unneeded for most of the users.

If you need to support legacy users, can optionally transpile the library to build pipelines.
If you need to support legacy users, can optionally transpile the library in build pipeline.

## License

Expand Down
4 changes: 3 additions & 1 deletion playground/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { $fetch } from '../src/node'

async function main () {
await $fetch('http://google.com/404')
const r = await $fetch<string>('http://google.com/404')
// eslint-disable-next-line no-console
console.log(r)
}

main().catch((err) => {
Expand Down
1 change: 0 additions & 1 deletion src/base.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export * from './types'
export * from './fetch'
export * from './error'
17 changes: 11 additions & 6 deletions src/error.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import type { Response, RequestInfo } from './types'
import type { FetchRequest, FetchResponse } from './fetch'

export class FetchError extends Error {
export class FetchError<T = any> extends Error {
name: 'FetchError' = 'FetchError'
request?: FetchRequest
response?: FetchResponse<T>
data?: T
}

export function createFetchError<T = any> (response: Response, input: RequestInfo, data: T): FetchError {
const message = `${response.status} ${response.statusText} (${input.toString()})`
const error = new FetchError(message)
export function createFetchError<T = any> (request: FetchRequest, response: FetchResponse<T>): FetchError<T> {
const message = `${response.status} ${response.statusText} (${request.toString()})`
const error: FetchError<T> = new FetchError(message)

Object.defineProperty(error, 'data', { get () { return data } })
Object.defineProperty(error, 'request', { get () { return request } })
Object.defineProperty(error, 'response', { get () { return response } })
Object.defineProperty(error, 'data', { get () { return response.data } })

const stack = error.stack
Object.defineProperty(error, 'stack', { get () { return normalizeStack(stack) } })
Expand Down
40 changes: 26 additions & 14 deletions src/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,43 @@
import destr from 'destr'
import { joinURL } from '@nuxt/ufo'
import type { Fetch, RequestInfo, RequestInit } from './types'
import type { Fetch, RequestInfo, RequestInit, Response } from './types'
import { createFetchError } from './error'

export interface CreateFetchOptions {
fetch: Fetch
}
export interface CreateFetchOptions { fetch: Fetch }

export type $FetchInput = RequestInfo
export type FetchRequest = RequestInfo

export interface $FetchOptions extends RequestInit {
export interface FetchOptions extends RequestInit {
baseURL?: string
response?: boolean
}

export type $Fetch<T = any> = (input: $FetchInput, opts?: $FetchOptions) => Promise<T>
export interface FetchResponse<T> extends Response { data?: T }

export interface $Fetch {
<T = any>(request: FetchRequest, opts?: FetchOptions): Promise<T>
raw<T = any>(request: FetchRequest, opts?: FetchOptions): Promise<FetchResponse<T>>
}

export function createFetch ({ fetch }: CreateFetchOptions): $Fetch {
return async function $fetch (input, opts) {
if (opts && opts.baseURL && typeof input === 'string') {
input = joinURL(opts.baseURL, input)
const raw: $Fetch['raw'] = async function (request, opts) {
if (opts && opts.baseURL && typeof request === 'string') {
request = joinURL(opts.baseURL, request)
}
const response = await fetch(input, opts)
const response: FetchResponse<any> = await fetch(request, opts)
const text = await response.text()
const data = destr(text)
response.data = destr(text)
if (!response.ok) {
throw createFetchError(response, input, data)
throw createFetchError(request, response)
}
return data
return response
}

const $fetch = function (request, opts) {
return raw(request, opts).then(r => r.data)
} as $Fetch

$fetch.raw = raw

return $fetch
}
6 changes: 4 additions & 2 deletions test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { listen, Listener } from 'listhen'
import { createApp } from '@nuxt/h2'
import { $fetch } from '../src/node'
import { $fetch, FetchError } from '../src/node'

describe('ohmyfetch', () => {
let listener: Listener
Expand All @@ -19,8 +19,10 @@ describe('ohmyfetch', () => {
})

it('404', async () => {
const err = await $fetch('404', { baseURL: listener.url }).catch(err => err)
const err: FetchError = await $fetch('404', { baseURL: listener.url }).catch(err => err)
expect(err.stack).toMatch('404 Not Found')
expect(err.data).toMatch('Not Found (404)')
expect(err.response?.data).toBe(err.data)
expect(err.request).toBe('http://localhost:3000/404')
})
})

0 comments on commit f9f70a5

Please sign in to comment.