Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add types #45

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
const isElectron = require('is-electron')

const IS_ENV_WITH_DOM = typeof window === 'object' && typeof document === 'object' && document.nodeType === 9
// @ts-ignore
const IS_ELECTRON = isElectron()
const IS_BROWSER = IS_ENV_WITH_DOM && !IS_ELECTRON
const IS_ELECTRON_MAIN = IS_ELECTRON && !IS_ENV_WITH_DOM
const IS_ELECTRON_RENDERER = IS_ELECTRON && IS_ENV_WITH_DOM
const IS_NODE = typeof require === 'function' && typeof process !== 'undefined' && typeof process.release !== 'undefined' && process.release.name === 'node' && !IS_ELECTRON
// @ts-ignore
// eslint-disable-next-line no-undef
const IS_WEBWORKER = typeof importScripts === 'function' && typeof self !== 'undefined' && typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope
const IS_TEST = typeof process !== 'undefined' && typeof process.env !== 'undefined' && process.env.NODE_ENV === 'test'
Expand Down
6 changes: 4 additions & 2 deletions src/files/normalise-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,10 @@ async function * readBlob (blob, options) {

const getNextChunk = () => new Promise((resolve, reject) => {
reader.onloadend = e => {
const data = e.target.result
resolve(data.byteLength === 0 ? null : data)
if (e.target) {
const data = /** @type {ArrayBuffer} */(e.target.result)
resolve(data.byteLength === 0 ? null : data)
}
}
reader.onerror = reject

Expand Down
1 change: 1 addition & 0 deletions src/globalthis.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-nocheck
/* eslint-disable no-undef */
/* eslint-disable no-extend-native */
/* eslint-disable strict */
Expand Down
77 changes: 40 additions & 37 deletions src/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@

const fetch = require('node-fetch')
const merge = require('merge-options').bind({ ignoreUndefined: true })
const anySignal = require('any-signal')
const { URL, URLSearchParams } = require('iso-url')
const TextDecoder = require('./text-encoder')
const AbortController = require('abort-controller')
const anySignal = require('any-signal')
const { AbortController } = require('abort-controller')

const Request = fetch.Request
const Headers = fetch.Headers

/** @typedef {import("./types").HttpResponse} HttpResponse */

class TimeoutError extends Error {
constructor () {
Expand Down Expand Up @@ -73,20 +74,20 @@ const defaults = {
}

/**
* @typedef {Object} APIOptions - creates a new type named 'SpecialType'
* @prop {any} [body] - Request body
* @prop {Object} [json] - JSON shortcut
* @prop {string} [method] - GET, POST, PUT, DELETE, etc.
* @prop {string} [base] - The base URL to use in case url is a relative URL
* @prop {Headers|Record<string, string>} [headers] - Request header.
* @prop {number} [timeout] - Amount of time until request should timeout in ms.
* @prop {AbortSignal} [signal] - Signal to abort the request.
* @prop {URLSearchParams|Object} [searchParams] - URL search param.
* @prop {string} [credentials]
* @prop {boolean} [throwHttpErrors]
* @prop {function(URLSearchParams): URLSearchParams } [transformSearchParams]
* @prop {function(any): any} [transform] - When iterating the response body, transform each chunk with this function.
* @prop {function(Response): Promise<void>} [handleError] - Handle errors
* @typedef {object} APIOptions - creates a new type named 'SpecialType'
* @property {any} [body] - Request body
* @property {object} [json] - JSON shortcut
* @property {string} [method] - GET, POST, PUT, DELETE, etc.
* @property {string} [base] - The base URL to use in case url is a relative URL
* @property {Headers|Record<string,string>} [headers] - Request header.
* @property {number} [timeout] - Amount of time until request should timeout in ms.
* @property {AbortSignal} [signal] - Signal to abort the request.
* @property {URLSearchParams|object} [searchParams] - URL search param.
* @property {string} [credentials]
* @property {boolean} [throwHttpErrors]
* @property {function(URLSearchParams): URLSearchParams } [transformSearchParams]
* @property {function(any): any} [transform] - When iterating the response body, transform each chunk with this function.
* @property {function(Response): Promise<void>} [handleError] - Handle errors
*/

class HTTP {
Expand All @@ -104,12 +105,12 @@ class HTTP {
*
* @param {string | URL | Request} resource
* @param {APIOptions} options
* @returns {Promise<Response>}
* @returns {Promise<HttpResponse>}
*/
async fetch (resource, options = {}) {
/** @type {APIOptions} */
const opts = merge(this.opts, options)
opts.headers = new Headers(opts.headers)
// opts.headers = new Headers(opts.headers)

// validate resource type
if (typeof resource !== 'string' && !(resource instanceof URL || resource instanceof Request)) {
Expand Down Expand Up @@ -137,8 +138,9 @@ class HTTP {
}

if (opts.json !== undefined) {
opts.body = JSON.stringify(opts.json)
opts.headers.set('content-type', 'application/json')
opts.body = JSON.stringify(opts.json);

/** @type {Headers} */(opts.headers).set('content-type', 'application/json')
}

const abortController = new AbortController()
Expand Down Expand Up @@ -183,7 +185,7 @@ class HTTP {
/**
* @param {string | URL | Request} resource
* @param {APIOptions} options
* @returns {Promise<Response>}
* @returns {Promise<HttpResponse>}
*/
post (resource, options = {}) {
return this.fetch(resource, {
Expand All @@ -195,7 +197,7 @@ class HTTP {
/**
* @param {string | URL | Request} resource
* @param {APIOptions} options
* @returns {Promise<Response>}
* @returns {Promise<HttpResponse>}
*/
get (resource, options = {}) {
return this.fetch(resource, {
Expand All @@ -207,7 +209,7 @@ class HTTP {
/**
* @param {string | URL | Request} resource
* @param {APIOptions} options
* @returns {Promise<Response>}
* @returns {Promise<HttpResponse>}
*/
put (resource, options = {}) {
return this.fetch(resource, {
Expand All @@ -219,7 +221,7 @@ class HTTP {
/**
* @param {string | URL | Request} resource
* @param {APIOptions} options
* @returns {Promise<Response>}
* @returns {Promise<HttpResponse>}
*/
delete (resource, options = {}) {
return this.fetch(resource, {
Expand All @@ -231,7 +233,7 @@ class HTTP {
/**
* @param {string | URL | Request} resource
* @param {APIOptions} options
* @returns {Promise<Response>}
* @returns {Promise<HttpResponse>}
*/
options (resource, options = {}) {
return this.fetch(resource, {
Expand All @@ -245,7 +247,7 @@ class HTTP {
* Parses NDJSON chunks from an iterator
*
* @param {AsyncGenerator<Uint8Array, void, any>} source
* @returns {AsyncGenerator<Object, void, any>}
* @returns {AsyncGenerator<object, void, any>}
*/
const ndjson = async function * (source) {
const decoder = new TextDecoder()
Expand Down Expand Up @@ -320,39 +322,40 @@ const isAsyncIterator = (obj) => {
HTTP.HTTPError = HTTPError
HTTP.TimeoutError = TimeoutError
HTTP.streamToAsyncIterator = streamToAsyncIterator
HTTP.ndjson = (s) => ndjson(streamToAsyncIterator(s))

/**
* @param {string | URL | Request} resource
* @param {APIOptions} options
* @returns {Promise<Response>}
* @param {APIOptions} [options]
* @returns {Promise<HttpResponse>}
*/
HTTP.post = (resource, options) => new HTTP(options).post(resource, options)

/**
* @param {string | URL | Request} resource
* @param {APIOptions} options
* @returns {Promise<Response>}
* @param {APIOptions} [options]
* @returns {Promise<HttpResponse>}
*/
HTTP.get = (resource, options) => new HTTP(options).get(resource, options)

/**
* @param {string | URL | Request} resource
* @param {APIOptions} options
* @returns {Promise<Response>}
* @param {APIOptions} [options]
* @returns {Promise<HttpResponse>}
*/
HTTP.put = (resource, options) => new HTTP(options).put(resource, options)

/**
* @param {string | URL | Request} resource
* @param {APIOptions} options
* @returns {Promise<Response>}
* @param {APIOptions} [options]
* @returns {Promise<HttpResponse>}
*/
HTTP.delete = (resource, options) => new HTTP(options).delete(resource, options)

/**
* @param {string | URL | Request} resource
* @param {APIOptions} options
* @returns {Promise<Response>}
* @param {APIOptions} [options]
* @returns {Promise<HttpResponse>}
*/
HTTP.options = (resource, options) => new HTTP(options).options(resource, options)

Expand Down
5 changes: 5 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict'

module.exports = {
HTTP: require('./http')
}
5 changes: 5 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

export interface HttpResponse extends Response {
iterator: () => AsyncIterator<Uint8Array>;
ndjson: AsyncGeneratorFunction
}
28 changes: 15 additions & 13 deletions test/http.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,35 @@ const { expect } = require('aegir/utils/chai')
const HTTP = require('../src/http')
const toStream = require('it-to-stream')
const delay = require('delay')
const AbortController = require('abort-controller')
const { AbortController } = require('abort-controller')
const drain = require('it-drain')
const all = require('it-all')
const { isBrowser, isWebWorker } = require('../src/env')
const { Buffer } = require('buffer')

const ECHO_SERVER = /** @type {string|null} */(process.env.ECHO_SERVER)

describe('http', function () {
it('makes a GET request', async function () {
const req = await HTTP.get(`${process.env.ECHO_SERVER}/echo/query?test=one`)
const req = await HTTP.get(`${ECHO_SERVER}/echo/query?test=one`)
const rsp = await req.json()
expect(rsp).to.be.deep.eq({ test: 'one' })
})

it('makes a GET request with redirect', async function () {
const req = await HTTP.get(`${process.env.ECHO_SERVER}/redirect?to=${encodeURI(`${process.env.ECHO_SERVER}/echo/query?test=one`)}`)
const req = await HTTP.get(`${ECHO_SERVER}/redirect?to=${encodeURI(`${ECHO_SERVER}/echo/query?test=one`)}`)
const rsp = await req.json()
expect(rsp).to.be.deep.eq({ test: 'one' })
})

it('makes a GET request with a really short timeout', function () {
return expect(HTTP.get(`${process.env.ECHO_SERVER}/redirect?to=${encodeURI(`${process.env.ECHO_SERVER}/echo/query?test=one`)}`, {
return expect(HTTP.get(`${ECHO_SERVER}/redirect?to=${encodeURI(`${ECHO_SERVER}/echo/query?test=one`)}`, {
timeout: 1
})).to.eventually.be.rejectedWith().instanceOf(HTTP.TimeoutError)
})

it('respects headers', async function () {
const req = await HTTP.post(`${process.env.ECHO_SERVER}/echo/headers`, {
const req = await HTTP.post(`${ECHO_SERVER}/echo/headers`, {
headers: {
foo: 'bar'
}
Expand All @@ -46,13 +48,13 @@ describe('http', function () {
bar: 'baz'
}
})
const req = await http.post(`${process.env.ECHO_SERVER}/echo/headers`)
const req = await http.post(`${ECHO_SERVER}/echo/headers`)
const rsp = await req.json()
expect(rsp).to.have.property('bar', 'baz')
})

it('makes a JSON request', async () => {
const req = await HTTP.post(`${process.env.ECHO_SERVER}/echo`, {
const req = await HTTP.post(`${ECHO_SERVER}/echo`, {
json: {
test: 2
}
Expand All @@ -63,7 +65,7 @@ describe('http', function () {
})

it('makes a DELETE request', async () => {
const req = await HTTP.delete(`${process.env.ECHO_SERVER}/echo`, {
const req = await HTTP.delete(`${ECHO_SERVER}/echo`, {
json: {
test: 2
}
Expand All @@ -76,7 +78,7 @@ describe('http', function () {
it('allow async aborting', async function () {
const controller = new AbortController()

const res = HTTP.get(process.env.ECHO_SERVER, {
const res = HTTP.get(ECHO_SERVER, {
signal: controller.signal
})
controller.abort()
Expand All @@ -85,7 +87,7 @@ describe('http', function () {
})

it('parses the response as ndjson', async function () {
const res = await HTTP.post(`${process.env.ECHO_SERVER}/echo`, {
const res = await HTTP.post(`${ECHO_SERVER}/echo`, {
body: '{}\n{}'
})

Expand All @@ -96,7 +98,7 @@ describe('http', function () {

it('parses the response as an async iterable', async function () {
const res = await HTTP.post('echo', {
base: process.env.ECHO_SERVER,
base: ECHO_SERVER,
body: 'hello world'
})

Expand All @@ -120,7 +122,7 @@ describe('http', function () {
throw err
}())

const res = await HTTP.post(process.env.ECHO_SERVER, {
const res = await HTTP.post(ECHO_SERVER, {
body: toStream.readable(body)
})

Expand All @@ -143,7 +145,7 @@ describe('http', function () {
throw err
}())

const res = await HTTP.post(process.env.ECHO_SERVER, {
const res = await HTTP.post(ECHO_SERVER, {
body: toStream.readable(body),
signal: controller.signal
})
Expand Down