Skip to content

Commit

Permalink
feat: fetch types (nodejs#997)
Browse files Browse the repository at this point in the history
  • Loading branch information
wojpawlik authored Aug 25, 2021
1 parent 643c957 commit 5425b77
Show file tree
Hide file tree
Showing 7 changed files with 492 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# https://editorconfig.org/

root = true

[*]
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
4 changes: 4 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import MockAgent from './types/mock-agent'
import mockErrors from './types/mock-errors'
import { request, pipeline, stream, connect, upgrade } from './types/api'

export * from './types/fetch'
export * from './types/file'
export * from './types/formdata'

export { Dispatcher, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, MockClient, MockPool, MockAgent, mockErrors }
export default Undici

Expand Down
144 changes: 144 additions & 0 deletions test/types/fetch.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { URL } from 'url'
import { Blob } from 'buffer'
import { expectType, expectError } from 'tsd'
import {
BodyInit,
fetch,
FormData,
Headers,
HeadersInit,
Request,
RequestCache,
RequestCredentials,
RequestDestination,
RequestInit,
RequestMode,
RequestRedirect,
Response,
ResponseInit,
ResponseType,
} from '../..'

const requestInit: RequestInit = {}
const responseInit: ResponseInit = { status: 200, statusText: 'OK' }

declare const request: Request
declare const headers: Headers
declare const response: Response

expectType<string | undefined>(requestInit.method)
expectType<boolean | undefined>(requestInit.keepalive)
expectType<HeadersInit | undefined>(requestInit.headers)
expectType<BodyInit | undefined>(requestInit.body)
expectType<RequestRedirect | undefined>(requestInit.redirect)
expectType<string | undefined>(requestInit.integrity)
expectType<AbortSignal | undefined>(requestInit.signal)

expectType<number | undefined>(responseInit.status)
expectType<string | undefined>(responseInit.statusText)
expectType<HeadersInit | undefined>(responseInit.headers)

expectType<Headers>(new Headers())
expectType<Headers>(new Headers({}))
expectType<Headers>(new Headers([]))
expectType<Headers>(new Headers(headers))
expectType<Headers>(new Headers(undefined))

expectType<Request>(new Request(request))
expectType<Request>(new Request('https://example.com'))
expectType<Request>(new Request(new URL('https://example.com')))
expectType<Request>(new Request(request, requestInit))
expectType<Request>(new Request('https://example.com', requestInit))
expectType<Request>(new Request(new URL('https://example.com'), requestInit))

expectType<Promise<Response>>(fetch(request))
expectType<Promise<Response>>(fetch('https://example.com'))
expectType<Promise<Response>>(fetch(new URL('https://example.com')))
expectType<Promise<Response>>(fetch(request, requestInit))
expectType<Promise<Response>>(fetch('https://example.com', requestInit))
expectType<Promise<Response>>(fetch(new URL('https://example.com'), requestInit))

expectType<Response>(new Response())
expectType<Response>(new Response(null))
expectType<Response>(new Response('string'))
expectType<Response>(new Response(new Blob([])))
expectType<Response>(new Response(new FormData()))
expectType<Response>(new Response(new Int8Array()))
expectType<Response>(new Response(new Uint8Array()))
expectType<Response>(new Response(new Uint8ClampedArray()))
expectType<Response>(new Response(new Int16Array()))
expectType<Response>(new Response(new Uint16Array()))
expectType<Response>(new Response(new Int32Array()))
expectType<Response>(new Response(new Uint32Array()))
expectType<Response>(new Response(new Float32Array()))
expectType<Response>(new Response(new Float64Array()))
expectType<Response>(new Response(new BigInt64Array()))
expectType<Response>(new Response(new BigUint64Array()))
expectType<Response>(new Response(new ArrayBuffer(0)))
expectType<Response>(new Response(null, responseInit))
expectType<Response>(new Response('string', responseInit))
expectType<Response>(new Response(new Blob([]), responseInit))
expectType<Response>(new Response(new FormData(), responseInit))
expectType<Response>(new Response(new Int8Array(), responseInit))
expectType<Response>(new Response(new Uint8Array(), responseInit))
expectType<Response>(new Response(new Uint8ClampedArray(), responseInit))
expectType<Response>(new Response(new Int16Array(), responseInit))
expectType<Response>(new Response(new Uint16Array(), responseInit))
expectType<Response>(new Response(new Int32Array(), responseInit))
expectType<Response>(new Response(new Uint32Array(), responseInit))
expectType<Response>(new Response(new Float32Array(), responseInit))
expectType<Response>(new Response(new Float64Array(), responseInit))
expectType<Response>(new Response(new BigInt64Array(), responseInit))
expectType<Response>(new Response(new BigUint64Array(), responseInit))
expectType<Response>(new Response(new ArrayBuffer(0), responseInit))
expectType<Response>(Response.error())
expectType<Response>(Response.redirect('https://example.com', 301))
expectType<Response>(Response.redirect('https://example.com', 302))
expectType<Response>(Response.redirect('https://example.com', 303))
expectType<Response>(Response.redirect('https://example.com', 307))
expectType<Response>(Response.redirect('https://example.com', 308))
expectError(Response.redirect('https://example.com', NaN))

expectType<void>(headers.append('key', 'value'))
expectType<void>(headers.delete('key'))
expectType<string | null>(headers.get('key'))
expectType<boolean>(headers.has('key'))
expectType<void>(headers.set('key', 'value'))
expectType<IterableIterator<string>>(headers.keys())
expectType<IterableIterator<string>>(headers.values())
expectType<IterableIterator<[string, string]>>(headers.entries())

expectType<RequestCache>(request.cache)
expectType<RequestCredentials>(request.credentials)
expectType<RequestDestination>(request.destination)
expectType<Headers>(request.headers)
expectType<string>(request.integrity)
expectType<string>(request.method)
expectType<RequestMode>(request.mode)
expectType<RequestRedirect>(request.redirect)
expectType<string>(request.referrerPolicy)
expectType<string>(request.url)
expectType<boolean>(request.keepalive)
expectType<AbortSignal>(request.signal)
expectType<boolean>(request.bodyUsed)
expectType<Promise<Buffer>>(request.arrayBuffer())
expectType<Promise<Blob>>(request.blob())
expectType<Promise<FormData>>(request.formData())
expectType<Promise<unknown>>(request.json())
expectType<Promise<string>>(request.text())
expectType<Request>(request.clone())

expectType<Headers>(response.headers)
expectType<boolean>(response.ok)
expectType<number>(response.status)
expectType<string>(response.statusText)
expectType<ResponseType>(response.type)
expectType<string>(response.url)
expectType<boolean>(response.redirected)
expectType<boolean>(response.bodyUsed)
expectType<Promise<Buffer>>(response.arrayBuffer())
expectType<Promise<Blob>>(response.blob())
expectType<Promise<FormData>>(response.formData())
expectType<Promise<unknown>>(response.json())
expectType<Promise<string>>(response.text())
expectType<Response>(response.clone())
22 changes: 22 additions & 0 deletions test/types/formdata.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Blob } from 'buffer'
import { expectAssignable, expectType } from 'tsd'
import { File, FormData } from "../.."

declare const blob: Blob
const formData = new FormData()
expectType<FormData>(formData)

expectType<void>(formData.append('key', 'value'))
expectType<void>(formData.append('key', blob))
expectType<void>(formData.set('key', 'value'))
expectType<void>(formData.set('key', blob))
expectType<File | string | null>(formData.get('key'))
expectType<File | string | null>(formData.get('key'))
expectType<Array<File | string>>(formData.getAll('key'))
expectType<Array<File | string>>(formData.getAll('key'))
expectType<boolean>(formData.has('key'))
expectType<void>(formData.delete('key'))
expectAssignable<IterableIterator<string>>(formData.keys())
expectAssignable<IterableIterator<File | string>>(formData.values())
expectAssignable<IterableIterator<[string, File | string]>>(formData.entries())
expectAssignable<IterableIterator<[string, File | string]>>(formData[Symbol.iterator]())
178 changes: 178 additions & 0 deletions types/fetch.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// based on https://github.com/Ethan-Arrowood/undici-fetch/blob/249269714db874351589d2d364a0645d5160ae71/index.d.ts (MIT license)
// and https://github.com/node-fetch/node-fetch/blob/914ce6be5ec67a8bab63d68510aabf07cb818b6d/index.d.ts (MIT license)
/// <reference types="node" />

import { Blob } from 'buffer'
import { URL, URLSearchParams } from 'url'
import { FormData } from './formdata'

export type RequestInfo = string | URL | Request

export declare function fetch (
input: RequestInfo,
init?: RequestInit
): Promise<Response>

declare class ControlledAsyncIterable implements AsyncIterable<Uint8Array> {
constructor (input: AsyncIterable<Uint8Array> | Iterable<Uint8Array>)
data: AsyncIterable<Uint8Array>
disturbed: boolean
readonly [Symbol.asyncIterator]: () => AsyncIterator<Uint8Array>
}

export type BodyInit =
| ArrayBuffer
| AsyncIterable<Uint8Array>
| Blob
| FormData
| Iterable<Uint8Array>
| NodeJS.ArrayBufferView
| URLSearchParams
| null
| string

export interface BodyMixin {
readonly body: ControlledAsyncIterable | null
readonly bodyUsed: boolean

readonly arrayBuffer: () => Promise<Buffer>
readonly blob: () => Promise<Blob>
readonly formData: () => Promise<FormData>
readonly json: () => Promise<unknown>
readonly text: () => Promise<string>
}

export type HeadersInit = Iterable<[string, string]> | Record<string, string>

export declare class Headers implements Iterable<[string, string]> {
constructor (init?: HeadersInit)
readonly append: (name: string, value: string) => void
readonly delete: (name: string) => void
readonly get: (name: string) => string | null
readonly has: (name: string) => boolean
readonly set: (name: string, value: string) => void
readonly forEach: (
callbackfn: (value: string, key: string, iterable: Headers) => void,
thisArg?: unknown
) => void

readonly keys: () => IterableIterator<string>
readonly values: () => IterableIterator<string>
readonly entries: () => IterableIterator<[string, string]>
readonly [Symbol.iterator]: () => Iterator<[string, string]>
}

export type RequestCache =
| 'default'
| 'force-cache'
| 'no-cache'
| 'no-store'
| 'only-if-cached'
| 'reload'

export type RequestCredentials = 'omit' | 'include' | 'same-origin'

type RequestDestination =
| ''
| 'audio'
| 'audioworklet'
| 'document'
| 'embed'
| 'font'
| 'image'
| 'manifest'
| 'object'
| 'paintworklet'
| 'report'
| 'script'
| 'sharedworker'
| 'style'
| 'track'
| 'video'
| 'worker'
| 'xslt'

export interface RequestInit {
readonly method?: string
readonly keepalive?: boolean
readonly headers?: HeadersInit
readonly body?: BodyInit
readonly redirect?: RequestRedirect
readonly integrity?: string
readonly signal?: AbortSignal
}

export type RequestMode = 'cors' | 'navigate' | 'no-cors' | 'same-origin'

export type RequestRedirect = 'error' | 'follow' | 'manual'

export declare class Request implements BodyMixin {
constructor (input: RequestInfo, init?: RequestInit)

readonly cache: RequestCache
readonly credentials: RequestCredentials
readonly destination: RequestDestination
readonly headers: Headers
readonly integrity: string
readonly method: string
readonly mode: RequestMode
readonly redirect: RequestRedirect
readonly referrerPolicy: string
readonly url: string

readonly keepalive: boolean
readonly signal: AbortSignal

readonly body: ControlledAsyncIterable | null
readonly bodyUsed: boolean

readonly arrayBuffer: () => Promise<Buffer>
readonly blob: () => Promise<Blob>
readonly formData: () => Promise<FormData>
readonly json: () => Promise<unknown>
readonly text: () => Promise<string>

readonly clone: () => Request
}

export interface ResponseInit {
readonly status?: number
readonly statusText?: string
readonly headers?: HeadersInit
}

export type ResponseType =
| 'basic'
| 'cors'
| 'default'
| 'error'
| 'opaque'
| 'opaqueredirect'

export type ResponseRedirectStatus = 301 | 302 | 303 | 307 | 308

export declare class Response implements BodyMixin {
constructor (body?: BodyInit, init?: ResponseInit)

readonly headers: Headers
readonly ok: boolean
readonly status: number
readonly statusText: string
readonly type: ResponseType
readonly url: string
readonly redirected: boolean

readonly body: ControlledAsyncIterable | null
readonly bodyUsed: boolean

readonly arrayBuffer: () => Promise<Buffer>
readonly blob: () => Promise<Blob>
readonly formData: () => Promise<FormData>
readonly json: () => Promise<unknown>
readonly text: () => Promise<string>

readonly clone: () => Response

static error (): Response
static redirect (url: string | URL, status: ResponseRedirectStatus): Response
}
Loading

0 comments on commit 5425b77

Please sign in to comment.