@typed/fp
Version:
Data Structures and Resources for fp-ts
352 lines • 9.24 kB
JavaScript
/**
* @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