suhaotian / xior Goto Github PK
View Code? Open in Web Editor NEWA lite request lib based on fetch with plugin support and similar API to axios.
Home Page: https://www.npmjs.com/package/xior
License: MIT License
A lite request lib based on fetch with plugin support and similar API to axios.
Home Page: https://www.npmjs.com/package/xior
License: MIT License
See: https://github.com/axios/axios?tab=readme-ov-file#multiple-interceptors
Currently with xior - if the response interceptor throws, the chain ends: https://github.com/suhaotian/xior/blob/main/src/xior.ts#L239
I think it would be ideal to follow the same logic that axios uses to aid with drop-in replacements. In the case of the xior-auth-refresh plugin, I've noticed that any subsequent response error interceptors won't run.
Like this.
fetch('https://...', { next: { revalidate: 3600 } })
export default async function Page() {
const res = await fetch('https://...', { next: { tags: ['collection'] } })
const data = await res.json()
// ...
}
Looks like the behavior isn't same as axios which removes undefined
values from request body and query string. This needs to be fixed for drop in replacement for axios.
Hi
First of all, great work on the package!
I've been using it, but after upgrading to version 0.5.1, all the typechecks that were previously passing started failing and not sure if this was expected behaviour and I need to migrate something or it there's bug/other issue.
I'm receiving the typescript error: Error: Unsafe argument of type 'any' assigned to a parameter of type 'Xior'. @typescript-eslint/no-unsafe-argument
everywhere.
An example of what was working before was that I created an instance:
import xior from "xior";
const neonInstance = xior.create({
baseURL: "https://console.neon.tech/api/v2",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.NEON_API_KEY}`,
},
});
export default neonInstance;
And then add the type as follows when performing a request:
import {
type NeonApiProjectOperation,
} from "@/types/neonApi";
import neonInstance from "@/utils/neon/neonInstance";
const neonProjectOperation =
await neonInstance.get<NeonApiProjectOperation>(
`/projects/${projectId}/operations/${operation.id}`,
);
const operationData = neonProjectOperation.data.operation;
From the typescript definitions in the project, my guess is that this should still work, but maybe I'm missing that I needed to update something else?
When you type axios, VS Code auto-import like this:
import axios from 'axios';
axios.post()
However, when you type xior, VS code auto-import like this:
import { xior } from 'xior';
axios.post()
That is because xior has a named export called xior, which has higher priority than the default export. And they are different things?
import xior from 'xior'; // instance
import { xior } from 'xior'; // class
In my opinion, class should always be CapitalCase. It should be:
import xior from 'xior'; // instance
import { Xior } from 'xior'; // class
Hello!
First of all, thank you for the perfect fetch library, but the only thing missing for me is transformRequest method from Axios to modify per-request options.
For example, if I need to completely remove some headers from specific requests, like Content-Type.
In axios we can do it like so:
axiosInstance.post('/endpoint', { ... }, {
transformRequest: [
(data, headers) => {
delete headers.common['Content-Type'];
return JSON.stringify(data);
},
],
});
Instead of creating separate clients or modifying defaults.
https://stackoverflow.com/questions/46656474/axios-remove-headers-authorization-in-1-call-only
Guys, give xior.js a try, if have problems or questions, please feel free to create issue.
xior doesn't support proxy yet, if you need proxy feature, keep use axios
According:
M4-Park-Checklist/m4-parks-frontend#6
AlexandreBellas/bling-erp-api-js#23
taraswww777/taraswww777.dev#58
alchemyplatform/alchemy-sdk-js#398
bancorprotocol/carbon-app#1040
vespaiach/axios-fetch-adapter#31
vespaiach/axios-fetch-adapter#18
vespaiach/axios-fetch-adapter#15
vespaiach/axios-fetch-adapter#2
haverstack/axios-fetch-adapter#7
Uniswap/smart-order-router#434
mailerlite/mailerlite-nodejs#31
3forges/poc-preact-rtk-flowbite#9
vespaiach/axios-fetch-adapter#20
vespaiach/axios-fetch-adapter#27
EnesAkkal/SWE-573-Ismail-Enes-Akkal#12
SetuHQ/upi-deeplinks-node-sdk#25
vespaiach/axios-fetch-adapter#31
vespaiach/axios-fetch-adapter#27
getlarge/nestjs-ory-integration#40
cqframework/cql-tests-runner#10
kookmin-sw/capstone-2024-04#114
syonosuke743/basic-vision-builder#10
vespaiach/axios-fetch-adapter#29
haverstack/axios-fetch-adapter#7
fac31/mohammed-anxhela-discord-bot#37
ArielleWaks/fetch-a-friend-frontend#14
When i try to push I simply get
Error: The Edge Function "my/file/path" is referencing unsupported modules:
- path/to/my/file.ts: xior
Hi there. I'm giving xior a try on Next.js 14, but the issue is I need to send client certificates (certificate.crt, certificate.key) in all requests along with access_token acquired during authentication.
I've messed around with axios on Next.js API Routes where I have access to node and I got something along the following lines working.
I'm setting up the certificates with axios as bellow:
/lib/banking-api.ts
import https from 'https'
import fs from 'fs'
import axios from 'axios'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/auth'
const baseURL = process.env.BANKING_API_BASE_URL
const certFile = fs.readFileSync('certificates/api-cert.crt')
const keyFile = fs.readFileSync('certificates/api-cert.key')
export const httpsAgent = new https.Agent({
cert: certFile,
key: keyFile,
//passphrase: ''
})
Here I finish axios configuration such as passing token to headers config...
export const httpClient = axios.create({
baseURL: baseURL,
headers: {
'x-account': process.env.BANKING_API_ACCOUNT,
withCredentials: true
},
httpsAgent: httpsAgent
})
httpClient.interceptors.request.use(
async config => {
const session = await getServerSession(authOptions)
const token = session?.user?.access_token
config.headers.Authorization = token ? `Bearer ${token}` : ''
return config
},
error => {
return Promise.reject(error)
}
)
const api = httpClient
export default api
Within API routes I've tried to use Next.js 14 fetch()
but it seems not to support the https.agent
configuration so I couldn't set up the certificates. I've tried something along the lines bellow:
import { httpsAgent } from '@/lib/banking-api'
const result = await fetch('https://api.banking.com', {
(...)
agent: httpsAgent
})
Bellow is an API route that successfuly works as expected, but given the way I've set up axios as httpClient
under /lib/banking-api.ts
, a few issues arrise as I explain ahead.
/api/banking/balance/route.ts
'use server'
import type { NextApiHandler } from 'next'
import { NextApiRequest, NextApiResponse } from 'next'
import { NextResponse } from 'next/server'
import { httpClient } from '@/lib/banking-api'
const dateSimple = () => new Date().toISOString().slice(0, 10) // 2024-04-19
export const GET: NextApiHandler = async (request: NextApiRequest, response: NextApiResponse) => {
try {
// calling external api service with custom axios config
const response = await httpClient.get('https://api.banking.com/v2/balance', {
params: {
date: dateSimple()
}
})
const data = response.data
return NextResponse.json(data, { status: 200 })
} catch (error) {
return NextResponse.json(error, { status: 500 })
}
})
On client components or pages explicity setting 'use client'
directive, if I make a request to https://localhost:3000/api/banking/balance
using Next.js fetch() I successfully get a response data from the API route above. I then use React.useState()
to manage the data for the component or page...
However, If I try to import the custom axios httpClient
and use it to make requests within client components, 'fs' and 'https' modules won't load giving "module not found" error as long as they're used server side only.
On server components or pages if I make a request to https://localhost:3000/api/banking/balance
using Next.js fetch() I get a 401 unauthorized response as long as fetch() is not sending the client certificates in the request.
Let's consider the following code:
import xior, { type XiorResponse, type XiorRequestConfig } from 'xior';
const filter = {
ids: [1, 2, 3],
dates: [new Date(), new Date()],
dateFrom: new Date(),
dateTo: new Date(),
};
http.request<FilterTestResponse>({
url: url,
method: 'GET',
params: {
filter: filter,
},
...$config,
});
If I execute it in a browser then only ids
property will be serialized and present in the query, but dates
, dateFrom
and dateTo
won't be there because dates are objects and they will be handled by encodeParams
through a if (typeof value === 'object')
branch which is not expected.
The fix I produced for my ASPNET custom serialized looks like this and it's working fine with both cases:
function encodeParams<T = any>(
params: T,
encodeURI = true,
parentKey: string | null = null
): string {
if (params === undefined || params === null) return '';
const encodedParams: string[] = [];
const encodeURIFunc = encodeURI ? encodeURIComponent : (v: string) => v;
const encodeValue = (value: any) =>
encodeURIFunc(value instanceof Date && !Number.isNaN(value) ? value.toISOString() : value);
for (const key in params) {
if (Object.prototype.hasOwnProperty.call(params, key)) {
const value = (params as any)[key];
if (value !== undefined) {
const encodedKey = parentKey ? `${parentKey}.${encodeURIFunc(key)}` : encodeURIFunc(key);
if (Array.isArray(value)) {
// If the value is an array, encode each element individually
value.forEach((element) => {
encodedParams.push(`${encodeURIFunc(encodedKey)}=${encodeValue(element)}`);
});
} else if (value instanceof Date && !Number.isNaN(value)) {
// If the value is a Date, convert it to ISO format
encodedParams.push(`${encodeURIFunc(encodedKey)}=${encodeValue(value)}`);
} else if (typeof value === 'object') {
// If the value is an object or array, recursively encode its contents
const result = encodeParams(value, encodeURI, encodedKey);
if (result !== '') encodedParams.push(result);
} else {
// Otherwise, encode the key-value pair
encodedParams.push(`${encodeURIFunc(encodedKey)}=${encodeValue(value)}`);
}
}
}
}
return encodedParams.join('&');
}
export const http = xior.create({
baseURL: '',
paramsSerializer: encodeParams,
});
But please note to not use this code, because it already has support for different approach for query params serialization (i.e. filter.ids=1&filter.ids=2&...
as opposed to your previous mechanism filter[ids][0]=1&filter[ids][1]=2&...
withCredentials
is xhr option, so axios has:
axios.defaults.withCredentials = true;
However, xior uses fetch, fetch has different option credentials
for the same functionality:
xior.defaults.credentials = 'include';
This makes xior ignoring withCredentials
.
When I set the responseType: 'blob' for a request, the response.body is undefined, similar to the following axios issue
axios/axios#1392
Example something like this
xior.post(
'http://assets.xxxxx.org:3000/uploads/3ba6921c-031e-426b-94a3-a4b966fc145f/documents/0f6a1d1166.pdf',
{
headers: {
Accept: 'application/pdf',
},
responseType: 'blob',
},
)
.then(response => {
console.log(response.data); //undefined
})
I was wondering why arrays are encoded in a way that my backend server does not understood them and I stumbled upon code for handling the params:
if (typeof value === 'object') {
// If the value is an object or array, recursively encode its contents
const result = encodeParams(value, encodeURI, encodedKey);
if (result !== '') encodedParams.push(result);
} else if (Array.isArray(value)) { // This will never be triggered because arrays are objects and they are handled in previous `if` statement
// If the value is an array, encode each element individually
value.forEach((element, index) => {
const arrayKey = `${encodedKey}[${index}]`;
encodedParams.push(`${encodeURIFunc(arrayKey)}=${encodeURIFunc(element)}`);
});
} else { ... }
Shouldn't be if (Array.isArray(value))
be placed before check for if (typeof value === 'object')
? Or maybe this check can be removed altogether?
Either way it won't particularly help me with my case (ASPNET uses totally different approach at encoding arrays...), but I just found it and I was intrigued by it.
In the following example, I added a request interceptor, a response interceptor and a plugin. However, the response interceptor is not working as long as here is the plugin. Remove the plugin and the response interceptor is working again. Request interceptor is not affected.
import xior from "xior";
xior.interceptors.request.use((request) => {
document.getElementById("request").textContent =
"Request Interceptor Is Working!";
return request;
});
xior.interceptors.response.use((response) => {
document.getElementById("response").textContent =
"Response Interceptor Is Working!";
return response;
});
xior.plugins.use((adaptor) => async (request) => ({
status: 200,
statusText: "OK",
data: {
foo: "bar",
},
}));
xior.get("/");
https://codesandbox.io/p/sandbox/xior-plugin-interceptor-24xfhk
I config xior with:
xior.defaults.credentials = 'include';
CORS GET requests works fine (copy request as fetch):
fetch('xxxx', {
headers: {
accept: '*/*',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
'cache-control': 'no-cache',
pragma: 'no-cache',
priority: 'u=1, i',
'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-site',
},
referrer: 'xxx',
referrerPolicy: 'strict-origin-when-cross-origin',
body: null,
method: 'GET',
mode: 'cors',
credentials: 'include',
});
However, CORS POST request doesn't work (copy request as fetch):
fetch('xxxx', {
headers: {
'content-type': 'application/json',
'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
},
referrer: 'xxx',
referrerPolicy: 'strict-origin-when-cross-origin',
body: '{}',
method: 'POST',
mode: 'cors',
credentials: 'omit',
});
There are some really old back-end systems that don't support JSON API. Instead, they support encoded URI data as body.
axios supports a usage like this:
axios.post('/foobar', new URLSearchParams({
foo: 'bar',
}));
axios will automatically set content-type
header to application/x-www-form-urlencoded;charset=UTF-8
;
However, xior doesn't seem to support this. When I write:
xior.post('/foobar', new URLSearchParams({
foo: 'bar',
}));
xior will send a request with no content-type
header, and back-end will reject it.
I can fix my code by manually adding the missing header. But maybe it makes sense that xior can do this automatically for users.
Hi,
First off, thank you for doing this brilliant port - we were stuck migrating to NextJS app router (SSR bits) because of our dependency on Axios. We generate our strongly typed axios clients via swagger-typescript-api
. Although this could generate fetch clients, they would require substantial work to function with our existing stack which is axios-centric. We have converted generator that to generate xior
clients via templating, so the last bit for us was making sure all our existing token refresh/login/logout stuff still worked.
Anyway, I digress, but long story short, your method for refreshing auth tokens we found didn't suit our use case, even with throttling and deduping, we would get a lot of refresh calls, and in addition, the failed requests (although they would retry) would show loads of errors while failing before retrying.
There is this brilliant library - https://github.com/Flyrell/axios-auth-refresh - which handles pausing the request pipeline, queuing them up, refreshing the token, and then letting the queue run afterwards which solves these problems.
I did a messy monkey patch to convert it to work with xior as a proof of concept, and so far it has slotted right in as an alternative and works (so far) exactly the way we found the axios version of the library to work for us.
Anyway, I just wanted to give you a heads up: https://github.com/Audiu/xior-auth-refresh
Once again, thank you so much for this library!
Each time an interceptor is added via xior.interceptors.request.use
and xior.interceptors.response.use
, it is pushed to an array and remains there indefinitely. There is no automatic cleanup mechanism for these interceptors, leading to a potential memory leak and performance issue when making multiple requests that add interceptors dynamically.
Example code snippet:
const apiClient = xior.create({
baseURL: "https://api.restful-api.dev/objects?id=3&id=5&id=10",
});
document.getElementById("btn").addEventListener("click", async (e) => {
await getData();
console.log(xior); // Here you can see how many interceptors are inside of both REQI and RESI arrays.
});
async function getData() {
xior.interceptors.request.use((config) => {
return config;
});
xior.interceptors.response.use((res) => {
return res;
});
const data = await xior.get("/");
return data;
}
Interceptors accumulate in the interceptor arrays (REQI
and RESI
) and are not cleaned up, leading to increased memory usage and potential performance impacts.
I found that by manually ejecting the interceptors after their use within a request, the issue with interceptor accumulation can be temporarily resolved. Here's the code snippet:
const apiClient = xior.create({
baseUrl: "https://api.restful-api.dev/objects?id=3&id=5&id=10",
});
document.getElementById("btn").addEventListener("click", async (e) => {
await getData();
console.log(xior);
});
async function getData() {
// Adding interceptors
const f1 = xior.interceptors.request.use((config) => {
return config;
});
const f2 = xior.interceptors.response.use((res) => {
return res;
});
// Performing the request
const data = await xior.get("/");
// Ejecting interceptors after their use
xior.interceptors.request.eject(f1);
xior.interceptors.response.eject(f2);
return data;
}
eject
function to prevent memory leaks.A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.