UNPKG

@typed/fp

Version:

Data Structures and Resources for fp-ts

352 lines 9.24 kB
/** * @typed/fp/node is a place to place implementations of environment from other modules that require or * are best used with implementations specifically for node.js. * @since 0.9.4 */ import * as Ei from 'fp-ts/Either'; import * as fs from 'fs'; import fetch from 'node-fetch'; import * as D from './Disposable'; import * as E from './Env'; import { fromPromiseK } from './EnvEither'; import * as R from './Resume'; /** * @category Environment * @since 0.9.4 */ export const HttpEnv = { http: E.fromResumeK(httpFetchRequest) }; function httpFetchRequest(uri, options = {}) { return R.async((cb) => { const { method = 'GET', headers = {}, body } = options; const disposable = D.settable(); const abortController = new AbortController(); disposable.addDisposable({ dispose: () => abortController.abort(), }); const init = { method, headers: Object.entries(headers).map(([key, value = '']) => [key, value]), body: body !== null && body !== void 0 ? body : undefined, credentials: 'include', signal: abortController.signal, }; async function makeRequest() { const response = await fetch(uri, init); const headers = {}; response.headers.forEach((value, key) => { headers[key] = value; }); const httpResponse = { status: response.status, body: await response.json().catch(() => response.text()), headers, }; if (!disposable.isDisposed()) { disposable.addDisposable(cb(Ei.right(httpResponse))); } } makeRequest().catch((error) => { if (!disposable.isDisposed()) { disposable.addDisposable(cb(Ei.left(error))); } }); return disposable; }); } /** * @category FS * @since 0.13.1 */ export const chmod = fromPromiseK(fs.promises.chmod); /** * @category FS * @since 0.13.1 */ export const copyFile = fromPromiseK(fs.promises.copyFile); /** * @category FS * @since 0.13.1 */ export const link = fromPromiseK(fs.promises.link); /** * @category FS * @since 0.13.1 */ export const mkdir = fromPromiseK(fs.promises.mkdir); /** * @category FS * @since 0.13.1 */ export const read = fromPromiseK(fs.promises.read); /** * @category FS * @since 0.13.1 */ export const readFile = fromPromiseK(fs.promises.readFile); /** * @category FS * @since 0.13.1 */ export const readdir = fromPromiseK(fs.promises.readdir); /** * @category FS * @since 0.13.1 */ export const rm = fromPromiseK(fs.promises.rm); /** * @category FS * @since 0.13.1 */ export const rmdir = fromPromiseK(fs.promises.rmdir); /** * @category FS * @since 0.13.1 */ export const stat = fromPromiseK(fs.promises.stat); /** * @category FS * @since 0.13.1 */ export const symlink = fromPromiseK(fs.promises.symlink); /** * @category FS * @since 0.13.1 */ export const unlink = fromPromiseK(fs.promises.unlink); /** * @category FS * @since 0.13.1 */ export const write = fromPromiseK(fs.promises.write); /** * @category FS * @since 0.13.1 */ export const writeFile = fromPromiseK(fs.promises.writeFile); /** * An in-memory implementation of `History`. * @category In-Memory Mock * @since 0.13.2 * @internal */ class ServerHistory { constructor(location) { // Does not affect behavior this.scrollRestoration = 'auto'; // tslint:disable-next-line:variable-name this._index = 0; this.location = location; this._states = [{ state: null, url: this.location.pathname }]; } set index(value) { this._index = value; const { url } = this._states[this._index]; this.location.replace(url); } get index() { return this._index; } get length() { return this._states.length; } get state() { const { state } = this._states[this.index]; return state; } go(quanity = 0) { if (quanity === 0) { return void 0; } const minIndex = 0; const maxIndex = this.length - 1; this.index = Math.max(minIndex, Math.min(maxIndex, this.index + quanity)); } back() { this.go(-1); } forward() { this.go(1); } pushState(state, _, url) { this._states = this._states.slice(0, this.index).concat({ state, url }); this.index = this._states.length - 1; } replaceState(state, _, url) { this._states[this.index] = { state, url }; } } const HTTPS_PROTOCOL = 'https:'; const HTTPS_DEFAULT_PORT = '443'; const HTTP_DEFAULT_PORT = '80'; /** * An in-memory implementation of `Location`. * @category In-Memory Mock * @since 0.13.2 * @internal */ class ServerLocation { constructor(url) { this.updateHref(url); } get ancestorOrigins() { return []; } get hash() { return parseValue('hash', this); } set hash(value) { const hash = value.startsWith('#') ? value : '#' + value; replace('hash', hash, this); } get host() { return parseValue('host', this); } set host(value) { replace('host', value, this); } get hostname() { return parseValue('hostname', this); } set hostname(value) { replace('hostname', value, this); } get pathname() { return parseValue('pathname', this); } set pathname(value) { replace('pathname', value, this); } get port() { const { href } = this; const { port, protocol } = parseHref(href); if (port) { return port; } return protocol === HTTPS_PROTOCOL ? HTTPS_DEFAULT_PORT : HTTP_DEFAULT_PORT; } set port(value) { replace('port', value, this); } get protocol() { return parseValue('protocol', this) || 'http:'; } set protocol(value) { replace('protocol', value, this); } get search() { return parseValue('search', this); } set search(value) { const search = value.startsWith('?') ? value : '?' + value; replace('search', search, this); } get origin() { return this.protocol + '//' + this.host; } assign(url) { this.updateHref(url); if (this.history) { this.history.pushState(null, '', this.href); } } reload() { // Does not have defined behavior outside of browser } replace(url) { this.updateHref(url); if (this.history) { this.history.replaceState(null, '', this.href); } } toString() { return this.href; } // ServerLocation Specific setHistory(history) { this.history = history; return this; } updateHref(url) { const parsed = parseHref(url); const { host, relative } = parsed; let href = parsed.href; if (this.host && !host) { href = this.host + href; } const { protocol } = parseHref(href); if (href !== relative && this.protocol && !protocol) { href = this.protocol + '//' + href; } this.href = href; } } function replace(key, value, location) { const { href } = location; const currentValue = parseHref(href)[key]; const updateHref = href.replace(currentValue, value); location.replace(updateHref); if (location.history) { location.history.pushState(null, '', location.href); } } function parseValue(key, location) { return parseHref(location.href)[key]; } const HREF_REGEX = /^(?:([^:/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:/?#]*)(?::(\d*))?))?((((?:[^?#/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/; /** * Parses an href into JSON. * @category Parser * @since 0.13.2 * */ export function parseHref(href) { const matches = HREF_REGEX.exec(href); const parsedHref = {}; for (let i = 0; i < keyCount; ++i) { const key = keys[i]; let value = matches[i] || ''; if (key === 'search' && value) { value = '?' + value; } if (key === 'protocol' && value && !value.endsWith(':')) { value = value + ':'; } if (key === 'hash') { value = '#' + value; } parsedHref[key] = value; } return parsedHref; } const keys = [ 'href', 'protocol', 'host', 'userInfo', 'username', 'password', 'hostname', 'port', 'relative', 'pathname', 'directory', 'file', 'search', 'hash', ]; const keyCount = keys.length; /** * Create A History Environment that works in browser and non-browser environments * @param href :: initial href to use * @category Environment * @since 0.13.2 */ export function createHistoryEnv(href = '/') { const serverLocation = new ServerLocation(href); const serverHistory = new ServerHistory(serverLocation); serverLocation.setHistory(serverHistory); return { location: serverLocation, history: serverHistory, }; } //# sourceMappingURL=node.js.map