Security News
PyPI Introduces Digital Attestations to Strengthen Python Package Security
PyPI now supports digital attestations, enhancing security and trust by allowing package maintainers to verify the authenticity of Python packages.
A request lib based on fetch with plugins support.
Features:
axios.create
/ axios.interceptors
/ .get/post/put/patch/delete/head/options
axios
?'stream'
, 'document'
, 'arraybuffer'
, or 'blob'
?axios
to xior
fetch
to xior
xior use the native fetch API, offering several advantages:
axios
?While popular and convenient, Axios currently lacks native edge runtime support (see: https://github.com/axios/axios/issues/5523). This can be an issue for specific use cases like Next.js serverless functions and middleware files, where fetch offers built-in caching and revalidation mechanisms (see: https://nextjs.org/docs/app/api-reference/functions/fetch).
While you can certainly create your own wrapper library around fetch, xior offers a pre-built solution with a familiar API, plugin support for extensibility, and potentially a more streamlined development experience.
# npm
npm install xior
# pnpm
pnpm add xior
# bun
bun add xior
# yarn
yarn add xior
import xior from 'xior';
export const xiorInstance = xior.create({
baseURL: 'https://apiexampledomian.com/api',
headers: {
// put your common custom headers here
},
});
GET
HEAD
method is same usage withGET
async function run() {
const { data } = await xiorInstance.get('/');
// with params
const { data: data2 } = await xiorInstance.get('/', { params: { a: 1, b: 2 } });
// with headers
const { data: data3 } = await xiorInstance.get('/', {
params: { a: 1, b: 2 },
headers: {
'content-type': 'application/x-www-form-urlencoded',
},
});
// types
const { data: data4 } = await xiorInstance.get<{ field1: string; field2: number }>('/');
}
POST
DELETE
/PUT
/PATCH
/OPTIONS
methods are same usage withPOST
async function run() {
const { data: data3 } = await xiorInstance.post<{ field1: string; field2: number }>(
'/',
{ a: 1, b: '2' },
{
params: { id: 1 },
headers: {
'content-type': 'application/json',
},
}
);
}
xior supports file uploads using the FormData
API and provides an optional 'xior/plugins/progress'
plugin for simulating upload progress, usage similar to Axios.
import Xior from 'xior';
import uploadDownloadProgressPlugin from 'xior/plugins/progress';
const http = Xior.create({});
http.plugins.use(
uploadDownloadProgressPlugin({
progressDuration: 5 * 1000,
})
);
const formData = FormData();
formData.append('file', fileObject);
formData.append('field1', 'val1');
formData.append('field2', 'val2');
http.post('/upload', formData, {
onUploadProgress(e) {
console.log(`Upload progress: ${e.progress}%`);
},
// progressDuration: 10 * 1000
});
xior supports interceptors similar to Axios, allowing you to modify requests and handle responses programmatically.
Request inteceptors:
import xior, { merge } from 'xior';
const http = xior.create({
// ...options
});
http.inteceptors.request.use((config) => {
const token = localStorage.getItem('REQUEST_TOKEN');
if (!token) return config;
return merge(config, {
headers: {
Authorization: `Bearer ${token}`,
},
});
});
// One more inteceptor for request
http.inteceptors.request.use((config) => {
return config;
});
Response inteceptors:
import xior, { merge } from 'xior';
const http = xior.create({});
http.inteceptors.response.use(
(result) => {
const { data, request: config, response: originalResponse } = result;
return result;
},
async (error) => {
if (error?.response?.status === 401) {
localStorage.removeItem('REQUEST_TOKEN');
}
}
);
Timeout:
import xior from 'xior';
const instance = xior.create({
timeout: 120 * 1000, // set default timeout
});
await instance.post(
'http://httpbin.org',
{
a: 1,
b: 2,
},
{
timeout: 60 * 1000, // override default timeout 120 * 1000
}
);
Cancel request:
import xior from 'xior';
const instance = xior.create();
const controller = new AbortController();
xiorInstance.get('http://httpbin.org', { signal: controller.signal }).then((res) => {
console.log(res.data);
});
class CancelRequestError extends Error {}
controller.abort(new CancelRequestError()); // abort request with custom error
xior offers a variety of built-in plugins to enhance its functionality:
Usage:
import xior from 'xior';
import errorRetryPlugin from 'xior/plugins/error-retry';
import throttlePlugin from 'xior/plugins/throttle';
import cachePlugin from 'xior/plugins/cache';
import uploadDownloadProgressPlugin from 'xior/plugins/progress';
const http = xior.create();
http.plugins.use(errorRetryPlugin());
http.plugins.use(throttlePlugin());
http.plugins.use(cachePlugin());
http.plugins.use(uploadDownloadProgressPlugin());
Retry the failed request with special times
API:
function errorRetryPlugin(options: {
retryTimes?: number;
retryInterval?: number;
enableRetry?: boolean | (error: Xiorconfig, error: XiorRequestConfig) => boolean;
}): XiorPlugin;
The options
object:
Param | Type | Default value | Description |
---|---|---|---|
retryTimes | number | 2 | Set the retry times for failed request |
retryInterval | number | 3000 | After first time retry, the next retries interval time, default interval is 3 seconds |
enableRetry | boolean | ((config: Xiorconfig, error: XiorRequestConfig) => boolean) | (config, error) => config.method === 'GET' | Default only retry if GET request error and retryTimes > 0 |
Basic usage:
import xior from 'xior';
import errorRetryPlugin from 'xior/plugins/error-retry';
const http = xior.create();
http.plugins.use(
errorRetryPlugin({
retryTimes: 3,
retryInterval: 3000,
})
);
// if request error, max retry 3 times until success
http.get('/api1');
// if request error, will not retry, because `retryTimes: 0`
http.get('/api2', { retryTimes: 0 });
// if POST request error, will not retry
http.post('/api1');
// Use `enableRetry: true` to support post method, max retry 5 times until success
http.post('/api1', null, { retryTimes: 5, enableRetry: true });
Throttle GET requests(or custom) most once per threshold milliseconds, filter repeat requests in certain time.
API:
function throttleRequestPlugin(options: {
/** threshold in milliseconds, default: 1000ms */
threshold?: number;
/**
* check if we need enable throttle, default only `GET` method enable
*/
enableThrottle?: boolean | ((config?: XiorRequestConfig) => boolean);
throttleCache?: ICacheLike<RecordedCache>;
}): XiorPlugin;
The options
object:
You can override default value in each request's own config (Except
throttleCache
)
Param | Type | Default value | Description |
---|---|---|---|
threshold | number | 1000 | The number of milliseconds to throttle request invocations to |
enableThrottle | boolean | ((config: Xiorconfig) => boolean) | (config) => config.method === 'GET' | Default only enabled in GET request |
throttleCache | CacheLike | lru(10) | CacheLike instance that will be used for storing throttled requests, use tiny-lru module |
Basic usage:
import xior from 'xior';
import throttlePlugin from 'xior/plugins/throttle';
const http = xior.create();
http.plugins.use(throttlePlugin());
http.get('/'); // make real http request
http.get('/'); // response from cache
http.get('/'); // response from cache
http.post('/'); // make real http request
http.post('/'); // make real http request
http.post('/'); // make real http request
http.post('/', null, {
enableThrottle: true,
}); // make real http request
http.post('/', null, {
enableThrottle: true,
}); // response from cache
http.post('/', null, {
enableThrottle: true,
}); // response from cache
Makes xior cacheable
Good to know: Next.js already support cache for fetch in server side. More detail
API:
function cachePlugin(options: {
enableCache?: boolean | ((config?: XiorRequestConfig) => boolean);
defaultCache?: ICacheLike<XiorPromise>;
}): XiorPlugin;
The options
object:
Param | Type | Default value | Description |
---|---|---|---|
enableCache | boolean | ((config: Xiorconfig) => boolean) | (config) => config.method === 'GET' | Default only enabled in GET request |
defaultCache | CacheLike | lru(100, 1000*60*5) | will used for storing requests by default, except you define a custom Cache with your request config, use tiny-lru module |
Basic usage:
import xior from 'xior';
import cachePlugin from 'xior/plugins/cache';
const http = xior.create();
http.plugins.use(cachePlugin());
http.get('/users'); // make real http request
http.get('/users'); // get cache from previous request
http.get('/users', { enableCache: false }); // disable cache manually and the the real http request
http.post('/users'); // default no cache for post
// enable cache manually in post request
http.post('/users', { enableCache: true }); // make real http request
http.post('/users', { enableCache: true }); // get cache from previous request
Advanced:
import xior from 'xior';
import cachePlugin from 'xior/plugins/cache';
import { lru } from 'tiny-lru';
const http = xior.create({
baseURL: 'https://example-domain.com/api',
headers: { 'Cache-Control': 'no-cache' },
});
http.plugins.use(
cachePlugin({
// disable the default cache
enableCache: false,
})
);
http.get('/users', { enableCache: true }); // manually enable cache for this request
http.get('/users', { enableCache: true }); // get cache from previous request
const cacheA = lru(100);
// a actual request made and cached due to force update configured
http.get('/users', { enableCache: true, defaultCache: cacheA, forceUpdate: true });
Enable upload and download progress like axios, but the progress is simulated, This means it doesn't represent the actual progress but offers a user experience similar to libraries like axios.
API:
function progressPlugin(options: {
/** default: 5*1000 ms */
progressDuration?: number;
}): XiorPlugin;
The options
object:
Param | Type | Default value | Description |
---|---|---|---|
progressDuration | number | 5000 | The upload or download progress grow to 99% duration |
Basic usage:
import xior from 'xior';
import uploadDownloadProgressPlugin from 'xior/plugins/progress';
const http = xior.create({});
http.plugins.use(uploadDownloadProgressPlugin());
const formData = FormData();
formData.append('file', fileObject);
formData.append('field1', 'val1');
formData.append('field2', 'val2');
http.post('/upload', formData, {
// simulate upload progress to 99% in 10 seconds, default is 5 seconds
progressDuration: 10 * 1000,
onUploadProgress(e) {
console.log(`Upload progress: ${e.progress}%`);
},
// onDownloadProgress(e) {
// console.log(`Download progress: ${e.progress}%`);
// },
});
xior let you easily to create custom plugins.
Here are examples:
import xior from 'xior';
const instance = xior.create();
instance.plugins.use(function logPlugin(adapter) {
return async (config) => {
const start = Date.now();
const res = await adapter(config);
console.log('%s %s %s take %sms', config.method, config.url, res.status, Date.now() - start);
return res;
};
});
Check src/plugins
xior has built-in helper functions, may useful for you:
import lru from 'tiny-lru';
import {
encodeParams,
merge as deepMerge,
delay as sleep,
buildSortedURL,
isAbsoluteURL,
} from 'xior';
xior frequently asked questions.
axios
?No, but xior offers a similar API like axios: axios.create
/ axios.interceptors
/ .get/post/put/patch/delete/head/options
.
Yes, xior works anywhere where the native fetch
API is supported.
Even if the environment doesn't support fetch
, you can use a fetch
polyfill like for older browsers.
For cloudflare worker user, if use TypeScript template, change the
moduleResolution
from'node'
to'Bundler'
'stream'
, 'document'
, 'arraybuffer'
, or 'blob'
?To handle such responses, use the responseType: 'stream'
option in your request:
import xior from 'xior';
const http = xior.create({ baseURL });
const { response } = await http.post<{ file: any; body: Record<string, string> }>(
'/stream/10',
null,
{ responseType: 'stream' }
);
const reader = response.body!.getReader();
let chunk;
for await (chunk of readChunks(reader)) {
console.log(`received chunk of size ${chunk.length}`);
}
You can use a polyfill for the fetch
API. Check the file src/tests/polyfill.test.ts
for a potential example.
The original name axior
was unavailable on npm, so when removed the "a": axior.
If you have any questions, feel free to create issues.
axios
to xioraxios:
import axios from 'axios';
// Make a request for a user with a given ID
axios
.get('/user?ID=12345')
.then(function (response) {
// handle success
console.log(response);
})
.catch(function (error) {
// handle error
console.log(error);
})
.finally(function () {
// always executed
});
// Optionally the request above could also be done as
axios
.get('/user', {
params: {
ID: 12345,
},
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
})
.finally(function () {
// always executed
});
// Want to use async/await? Add the `async` keyword to your outer function/method.
async function getUser() {
try {
const response = await axios.get('/user?ID=12345');
console.log(response);
} catch (error) {
console.error(error);
}
}
xior:
import xior from 'xior';
// or import { xior } from 'xior';
const axios = xior.create();
// Make a request for a user with a given ID
axios
.get('/user?ID=12345')
.then(function (response) {
// handle success
console.log(response);
})
.catch(function (error) {
// handle error
console.log(error);
})
.finally(function () {
// always executed
});
// Optionally the request above could also be done as
axios
.get('/user', {
params: {
ID: 12345,
},
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
})
.finally(function () {
// always executed
});
// Want to use async/await? Add the `async` keyword to your outer function/method.
async function getUser() {
try {
const response = await axios.get('/user?ID=12345');
console.log(response);
} catch (error) {
console.error(error);
}
}
axios:
import axios from 'axios';
axios
.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone',
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
xior:
import xior from 'xior';
// or import { xior } from 'xior';
const axios = xior.create();
axios
.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone',
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
axios:
import axios from 'axios';
const instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: { 'X-Custom-Header': 'foobar' },
});
xior:
import axios from 'xior';
const instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: { 'X-Custom-Header': 'foobar' },
});
responseType: 'stream'
axios:
import axios from 'axios';
// GET request for remote image in node.js
axios({
method: 'get',
url: 'https://bit.ly/2mTM3nY',
responseType: 'stream',
}).then(function (response) {
response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'));
});
xior:
import xior from 'xior';
const axios = xior.create();
axios
.get('https://bit.ly/2mTM3nY', {
responseType: 'stream',
})
.then(async function ({ response, config }) {
const buffer = Buffer.from(await response.arrayBuffer());
return writeFile('ada_lovelace.jpg', buffer);
});
fetch
to xiorfetch:
async function logMovies() {
const response = await fetch('http://example.com/movies.json?page=1&perPage=10');
const movies = await response.json();
console.log(movies);
}
xior:
import xior from 'xior';
const http = xior.create({
baseURL: 'http://example.com',
});
async function logMovies() {
const { data: movies } = await http.get('/movies.json', {
params: {
page: 1,
perPage: 10,
},
});
console.log(movies);
}
fetch:
// Example POST method implementation:
async function postData(url = '', data = {}) {
// Default options are marked with *
const response = await fetch(url, {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json',
// 'Content-Type': 'application/x-www-form-urlencoded',
},
redirect: 'follow', // manual, *follow, error
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
body: JSON.stringify(data), // body data type must match "Content-Type" header
});
return response.json(); // parses JSON response into native JavaScript objects
}
postData('https://example.com/answer', { answer: 42 }).then((data) => {
console.log(data); // JSON data parsed by `data.json()` call
});
xior:
import xior from 'xior';
const http = xior.create({
baseURL: 'http://example.com',
});
http
.post(
'/answer',
{ answer: 42 },
{
mode: 'cors', // no-cors, *cors, same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json',
// 'Content-Type': 'application/x-www-form-urlencoded',
},
redirect: 'follow', // manual, *follow, error
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
}
)
.then(({ data }) => {
console.log(data);
});
fetch:
const controller = new AbortController();
const signal = controller.signal;
const url = 'video.mp4';
const downloadBtn = document.querySelector('#download');
const abortBtn = document.querySelector('#abort');
downloadBtn.addEventListener('click', async () => {
try {
const response = await fetch(url, { signal });
console.log('Download complete', response);
} catch (error) {
console.error(`Download error: ${error.message}`);
}
});
abortBtn.addEventListener('click', () => {
controller.abort();
console.log('Download aborted');
});
xior:
import xior from 'xior';
const http = xior.create();
const controller = new AbortController();
const signal = controller.signal;
const url = 'video.mp4';
const downloadBtn = document.querySelector('#download');
const abortBtn = document.querySelector('#abort');
downloadBtn.addEventListener('click', async () => {
try {
const response = await http.get(url, { signal });
console.log('Download complete', response);
} catch (error) {
console.error(`Download error: ${error.message}`);
}
});
abortBtn.addEventListener('click', () => {
controller.abort();
console.log('Download aborted');
});
fetch:
fetch('https://example.com', {
credentials: 'include',
});
xior:
import xior from 'xior';
const http = xior.create();
http.get('https://example.com', {
credentials: 'include',
});
fetch:
async function upload(formData) {
try {
const response = await fetch('https://example.com/profile/avatar', {
method: 'PUT',
body: formData,
});
const result = await response.json();
console.log('Success:', result);
} catch (error) {
console.error('Error:', error);
}
}
const formData = new FormData();
const fileField = document.querySelector('input[type="file"]');
formData.append('username', 'abc123');
formData.append('avatar', fileField.files[0]);
upload(formData);
xior:
Add
{responseType: 'stream'}
will tell xior no need process response, and return original response in format{response}
import xior from 'xior';
const http = xior.create({
baseURL: 'https://example.com',
});
async function upload(formData) {
try {
const { data: result } = await http.put('/profile/avatar', formData);
console.log('Success:', result);
} catch (error) {
console.error('Error:', error);
}
}
const formData = new FormData();
const fileField = document.querySelector('input[type="file"]');
formData.append('username', 'abc123');
formData.append('avatar', fileField.files[0]);
upload(formData);
fetch:
async function* makeTextFileLineIterator(fileURL) {
const utf8Decoder = new TextDecoder('utf-8');
const response = await fetch(fileURL);
const reader = response.body.getReader();
let { value: chunk, done: readerDone } = await reader.read();
chunk = chunk ? utf8Decoder.decode(chunk) : '';
const newline = /\r?\n/gm;
let startIndex = 0;
let result;
while (true) {
const result = newline.exec(chunk);
if (!result) {
if (readerDone) break;
const remainder = chunk.substr(startIndex);
({ value: chunk, done: readerDone } = await reader.read());
chunk = remainder + (chunk ? utf8Decoder.decode(chunk) : '');
startIndex = newline.lastIndex = 0;
continue;
}
yield chunk.substring(startIndex, result.index);
startIndex = newline.lastIndex;
}
if (startIndex < chunk.length) {
// Last line didn't end in a newline char
yield chunk.substr(startIndex);
}
}
async function run() {
for await (const line of makeTextFileLineIterator(urlOfFile)) {
processLine(line);
}
}
run();
xior:
Add
{responseType: 'stream'}
will tell xior no need process response, and return original response in format{response}
import xior from 'xior';
const http = xior.create();
async function* makeTextFileLineIterator(fileURL) {
const utf8Decoder = new TextDecoder('utf-8');
const { response } = await http.get(fileURL, { responseType: 'stream' });
const reader = response.body.getReader();
let { value: chunk, done: readerDone } = await reader.read();
chunk = chunk ? utf8Decoder.decode(chunk) : '';
const newline = /\r?\n/gm;
let startIndex = 0;
let result;
while (true) {
const result = newline.exec(chunk);
if (!result) {
if (readerDone) break;
const remainder = chunk.substr(startIndex);
({ value: chunk, done: readerDone } = await reader.read());
chunk = remainder + (chunk ? utf8Decoder.decode(chunk) : '');
startIndex = newline.lastIndex = 0;
continue;
}
yield chunk.substring(startIndex, result.index);
startIndex = newline.lastIndex;
}
if (startIndex < chunk.length) {
// Last line didn't end in a newline char
yield chunk.substr(startIndex);
}
}
async function run() {
for await (const line of makeTextFileLineIterator(urlOfFile)) {
processLine(line);
}
}
run();
Without the support of these resources, xior wouldn't be possible:
v0.0.8 2024-02-29
paramsSerializer
and withCredentials
FAQs
A lite request lib based on fetch with plugin support and similar API to axios.
The npm package xior receives a total of 2,735 weekly downloads. As such, xior popularity was classified as popular.
We found that xior demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
PyPI now supports digital attestations, enhancing security and trust by allowing package maintainers to verify the authenticity of Python packages.
Security News
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.