Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

ky

Package Overview
Dependencies
Maintainers
1
Versions
79
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ky - npm Package Compare versions

Comparing version 0.1.0 to 0.2.0

264

index.js
'use strict';
var punycode = require('punycode');
var inherits = require('util').inherits;
var Emitter = require('events').EventEmitter;
var chalk = require('chalk');
var keypress = require('keypress');
var stdin = process.stdin;
var stdout = process.stdout;
function countSymbols(str) {
return punycode.ucs2.decode(str).length;
}
const isObject = value => value !== null && typeof value === 'object';
function showCursor(x, y) {
stdout.write('\x1b[?25h');
};
const deepMerge = (...sources) => {
let returnValue = {};
function hideCursor(x, y) {
stdout.write('\x1b[?25l');
};
for (const source of sources) {
if (Array.isArray(source)) {
if (!(Array.isArray(returnValue))) {
returnValue = [];
}
function move(x, y) {
stdout.write('\x1b[' + y + ';' + x + 'H');
returnValue = [...returnValue, ...source];
} else if (isObject(source)) {
for (let [key, value] of Object.entries(source)) {
if (isObject(value) && Reflect.has(returnValue, key)) {
value = deepMerge(returnValue[key], value);
}
returnValue = {...returnValue, [key]: value};
}
}
}
return returnValue;
};
function reset() {
clear();
move(0, 0);
showCursor();
}
const requestMethods = [
'get',
'post',
'put',
'patch',
'head',
'delete'
];
function clearLine() {
stdout.write('\x1b[2K');
}
const responseTypes = [
'json',
'text',
'formData',
'arrayBuffer',
'blob'
];
function clearDown() {
stdout.write('\x1b[J');
}
const retryMethods = new Set([
'get',
'put',
'head',
'delete',
'options',
'trace'
]);
function clear() {
stdout.write('\x1b[0m\x1b[1J');
clearDown();
}
const retryStatusCodes = new Set([
408,
413,
429,
500,
502,
503,
504
]);
function pluck(arr, prop) {
return arr.map(function (el) {
return el[prop];
});
class HTTPError extends Error {
constructor(response) {
super(response.statusText);
this.name = 'HTTPError';
this.response = response;
}
}
function List(items, opts) {
if (items.length === 0) {
throw new Error('At least one list item required');
class TimeoutError extends Error {
constructor() {
super('Request timed out');
this.name = 'TimeoutError';
}
opts = opts || {};
this.items = items || [];
this.pointer = opts.pointer || chalk.yellow('❯ ');
this.y = typeof opts.y === 'number' ? opts.x : 3;
this.x = typeof opts.x === 'number' ? opts.x : 5;
this.header = opts.header;
this.footer = opts.footer;
this.current = items[0].id;
}
inherits(List, Emitter);
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
List.prototype.render = function () {
var pointerLength = countSymbols(chalk.stripColor(this.pointer));
var padding = Array(pointerLength + 1).join(' ');
var y = this.y + 1;
const timeout = (promise, ms) => new Promise((resolve, reject) => {
promise.then(resolve, reject); // eslint-disable-line promise/prefer-await-to-then
clear();
(async () => {
await delay(ms);
reject(new TimeoutError());
})();
});
if (this.header) {
this.header.split('\n').forEach(function (el) {
move(this.x + pointerLength, y++);
stdout.write(el);
}, this);
move(this.x, y++);
}
class Ky {
constructor(input, options) {
this._input = input;
this._retryCount = 0;
this.items.forEach(function (el) {
var prefix = this.current === el.id ? this.pointer : padding;
var title = el.disabled ? chalk.gray(el.title) : el.title;
move(this.x, y++);
stdout.write(prefix + title);
}, this);
this._options = {
method: 'get',
credentials: 'same-origin', // TODO: This can be removed when the spec change is implemented in all browsers. Context: https://www.chromestatus.com/feature/4539473312350208
retry: 3,
timeout: 10000,
...options
};
if (this.footer) {
move(this.x, ++y);
this.footer.split('\n').forEach(function (el) {
move(this.x + pointerLength, y++);
stdout.write(el);
}, this);
}
};
this._timeout = this._options.timeout;
delete this._options.timeout;
List.prototype.init = function () {
hideCursor();
this.render();
keypress(stdin);
stdin.setRawMode(true);
stdin.resume();
stdin.on('keypress', this.onKeypress.bind(this));
};
const headers = new window.Headers(this._options.headers || {});
List.prototype.destroy = function () {
reset();
showCursor();
stdin.removeListener('keypress', this.onKeypress.bind(this));
stdin.pause();
};
if (this._options.json) {
headers.set('content-type', 'application/json');
this._options.body = JSON.stringify(this._options.json);
delete this._options.json;
}
List.prototype.change = function (id) {
this.emit('change2', id);
this.current = id;
this.render();
};
this._options.headers = headers;
List.prototype.prev = function () {
var i = pluck(this.items, 'id').indexOf(this.current) - 1;
var item = this.items[i < 0 ? this.items.length - 1 : i];
this._response = this._fetch();
this.change(item.id);
for (const type of responseTypes) {
this._response[type] = this._retry(async () => {
if (this._retryCount > 0) {
this._response = this._fetch();
}
if (item.disabled) {
this.prev();
}
};
const response = await this._response;
List.prototype.next = function () {
var i = pluck(this.items, 'id').indexOf(this.current) + 1;
var item = this.items[i % this.items.length];
if (!response.ok) {
throw new HTTPError(response);
}
this.change(item.id);
return response.clone()[type]();
});
}
if (item.disabled) {
this.next();
return this._response;
}
};
List.prototype.onKeypress = function (ch, key) {
if (!key) {
return;
}
_retry(fn) {
if (!retryMethods.has(this._options.method.toLowerCase())) {
return fn;
}
this.emit('keypress', key, this.current);
const retry = async () => {
try {
return await fn();
} catch (error) {
const shouldRetryStatusCode = error instanceof HTTPError ? retryStatusCodes.has(error.response.status) : true;
if (!(error instanceof TimeoutError) && shouldRetryStatusCode && this._retryCount < this._options.retry) {
this._retryCount++;
const BACKOFF_FACTOR = 0.3;
const delaySeconds = BACKOFF_FACTOR * (2 ** (this._retryCount - 1));
await delay(delaySeconds * 1000);
return retry();
}
if (key.name === 'escape' || key.ctrl && key.name === 'c') {
this.destroy();
throw error;
}
};
return retry;
}
if (key.name === 'up') {
this.prev();
_fetch() {
return timeout(window.fetch(this._input, this._options), this._timeout);
}
}
if (key.name === 'down') {
this.next();
const createInstance = (defaults = {}) => {
const ky = (input, options) => new Ky(input, deepMerge({}, defaults, options));
for (const method of requestMethods) {
ky[method] = (input, options) => new Ky(input, deepMerge({}, defaults, options, {method}));
}
if (key.name === 'return') {
this.emit('select', this.current);
}
return ky;
};
module.exports = List;
module.exports = createInstance();
module.exports.extend = defaults => createInstance(defaults);
module.exports.HTTPError = HTTPError;
module.exports.TimeoutError = TimeoutError;
{
"name": "ky",
"version": "0.1.0",
"description": "Easy list UI for your CLI app",
"license": "MIT",
"repository": "sindresorhus/ky",
"bin": "cli.js",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "http://sindresorhus.com"
},
"engines": {
"node": ">=0.10.0"
},
"files": [
"index.js"
],
"devDependencies": {
"mocha": "*"
},
"dependencies": {
"chalk": "^0.4.0",
"keypress": "^0.2.1"
}
"name": "ky",
"version": "0.2.0",
"description": "Tiny and elegant HTTP client based on the browser Fetch API",
"license": "MIT",
"repository": "sindresorhus/ky",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
},
"engines": {
"node": ">=8"
},
"scripts": {
"test": "xo && nyc ava"
},
"files": [
"index.js"
],
"keywords": [
"fetch",
"request",
"requests",
"http",
"https",
"fetching",
"get",
"url",
"curl",
"wget",
"net",
"network",
"ajax",
"api",
"rest",
"xhr",
"browser",
"got",
"axios",
"node-fetch"
],
"devDependencies": {
"ava": "1.0.0-beta.8",
"body": "^5.1.0",
"create-test-server": "2.1.1",
"delay": "^4.0.0",
"node-fetch": "^2.2.0",
"nyc": "^13.0.1",
"xo": "^0.23.0"
},
"xo": {
"envs": [
"browser"
]
}
}

@@ -1,6 +0,31 @@

# ky [![Build Status](https://travis-ci.org/sindresorhus/ky.svg?branch=master)](https://travis-ci.org/sindresorhus/ky)
<div align="center">
<br>
<div>
<img width="600" src="media/logo.svg" alt="ky">
</div>
<br>
<br>
<br>
</div>
> Easy list UI for your CLI app
> Ky is a tiny and elegant HTTP client based on the browser [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch)
[![Build Status](https://travis-ci.com/sindresorhus/ky.svg?branch=master)](https://travis-ci.com/sindresorhus/ky) [![codecov](https://codecov.io/gh/sindresorhus/ky/branch/master/graph/badge.svg)](https://codecov.io/gh/sindresorhus/ky)
Ky targets [modern browsers](#browser-support). For older browsers, you will need to transpile and use a [`fetch` polyfill](https://github.com/github/fetch). For Node.js, check out [Got](https://github.com/sindresorhus/got).
1 KB *(minified & gzipped)*, one file, and no dependencies.
## Benefits over plain `fetch`
- Simpler API
- Method shortcuts (`ky.post()`)
- Treats non-200 status codes as errors
- Retries failed requests
- JSON option
- Timeout support
- Instances with custom defaults
## Install

@@ -12,38 +37,122 @@

<a href="https://www.patreon.com/sindresorhus">
<img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" width="160">
</a>
## Usage
```js
var chalk = require('chalk');
var List = require('ky');
import ky from 'ky';
var items = [
{id: 'foo', title: 'Ponies'},
{id: 'bar', title: 'OMG NO', disabled: true},
{id: 'baz', title: 'javaSCript'},
{id: 'bal', title: 'Some Highlander Whiz'}
];
(async () => {
const json = await ky.post('https://some-api.com', {json: {foo: true}}).json();
var list = new List(items, {
header: chalk.bgYellow.black('Unicorns & rainbows'),
footer: 'IAMA FOOTER'
});
console.log(json);
//=> `{data: '🦄'}`
})();
```
list.init();
With plain `fetch`, it would be:
list.on('keypress', function (key, selected) {
if (key.name === 'return') {
list.header = require('chalk').red('Candy??');
list.render();
```js
(async () => {
class HTTPError extends Error {}
const response = await fetch('https://sindresorhus.com', {
method: 'POST',
body: JSON.stringify({foo: true}),
headers: {
'content-type': 'application/json'
}
});
if (!response.ok) {
throw new HTTPError(`Fetch error:`, response.statusText);
}
});
list.on('change', function () {
list.header = 'change';
});
const json = await response.json();
console.log(json);
//=> `{data: '🦄'}`
})();
```
## API
### ky(input, [options])
The `input` and `options` are the same as [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch), with some exceptions:
- The `credentials` option is `same-origin` by default, which is the default in the spec too, but not all browsers have caught up yet.
- Adds some more options. See below.
Returns a [`Response` object](https://developer.mozilla.org/en-US/docs/Web/API/Response) with [`Body` methods](https://developer.mozilla.org/en-US/docs/Web/API/Body#Methods) added for convenience. So you can, for example, call `ky.json()` directly on the `Response` without having to await it first. Unlike the `Body` methods of `window.Fetch`; these will throw an `HTTPError` if the response status is not in the range `200...299`.
#### options
Type: `Object`
##### json
Type: `Object`
Shortcut for sending JSON. Use this instead of the `body` option. Accepts a plain object which will be `JSON.stringify()`'d and the correct header will be set for you.
### ky.get(input, [options])
### ky.post(input, [options])
### ky.put(input, [options])
### ky.patch(input, [options])
### ky.head(input, [options])
### ky.delete(input, [options])
Sets `options.method` to the method name and makes a request.
#### retry
Type: `number`<br>
Default: `2`
Retry failed requests made with one of the below methods that result in a network error or one of the below status codes.
Methods: `GET` `PUT` `HEAD` `DELETE` `OPTIONS` `TRACE`<br>
Status codes: [`408`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) [`413`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/413) [`429`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) [`500`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) [`502`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502) [`503`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503) [`504`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504)
#### timeout
Type: `number`<br>
Default: `10000`
Timeout in milliseconds for getting a response.
### ky.extend(defaultOptions)
Create a new `ky` instance with some defaults overridden with your own.
#### defaultOptions
Type: `Object`
### ky.HTTPError
Exposed for `instanceof` checks. The error has a `response` property with the [`Response` object](https://developer.mozilla.org/en-US/docs/Web/API/Response).
### ky.TimeoutError
The error thrown when the request times out.
## Browser support
The latest version of Chrome, Firefox, and Safari.
## Related
- [got](https://github.com/sindresorhus/got) - Simplified HTTP requests for Node.js
## License
MIT © [Sindre Sorhus](https://sindresorhus.com)

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc