typesense-js
typesense-js copied to clipboard
Axios is not supported on Edge Runtime
Description
As described in this ticket, Axios is not supported (and I don't think it will in any time near future) and you cannot use this library on an Serverless edge functions for example like Vercel, AWS etc. (or any service worker like Cloudflare)
https://github.com/axios/axios/issues/5523
https://vercel.com/guides/library-sdk-compatible-with-vercel-edge-runtime-and-functions#library-recommendations
Steps to reproduce
- Spin up a new NextJS app, create a new api endpoint using edge runtime. It will fail.
import typeSenseService from "@/services/typesense/typesense";
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
export const config = {
runtime: "edge",
};
export default async function handler(req: NextRequest) {
if (req.method !== "POST") {
return NextResponse.json({ error: "no" }, { status: 500 });
}
try {
const { searchParams } = new URL(req?.url ?? "");
const term = searchParams.get("term") as string;
console.log(term);
// Just a simple typesense search here
const data = typeSenseService.search("index_name", term);
return NextResponse.json({ result: data });
} catch (e: any) {
console.log(e);
return NextResponse.json({ error: "no" }, { status: 500 });
}
Expected Behavior
- It should work if you move to fetch, or create more agnostic way to pass the httpClient. Example below:
export interface Fetch {
(url: string, init?: RequestInit): Promise<Response>
}
export interface RequestInit {
headers?: any
method?: string
body?: string
redirect?: string
}
export interface Response {
headers: Headers
ok: boolean
status: number
statusText: string
text: () => Promise<string>
json: () => Promise<any>
}
export interface CallInfo extends RequestInit {
name: string
type: string
url: string
status: number
statusText: string
error?: Error
}
export interface FetcherOptions {
record?: (info: CallInfo, data: string | Blob | ArrayBuffer | any) => Promise<void>
}
export interface Fetcher {
(name: string, url: string, init?: RequestInit, childId?: string): Promise<Response>
}
export interface Recorder {
(info: CallInfo, data: string | Blob | ArrayBuffer | any): Promise<void>
}
const record = async (
name: string,
url: string,
init: RequestInit | undefined,
type: string,
options: FetcherOptions,
response: Response,
data: string | ArrayBuffer | Blob | any
): Promise<void> => {
if (!options.record) {
return
}
const info: CallInfo = {
...(init || {}),
name,
url,
type,
status: response.status,
statusText: response.statusText
}
await options.record(info, data)
}
export default function wrap(fetch: Fetch, options: FetcherOptions = {}): Fetcher {
return async (name: string, url: string, config: RequestInit = { headers: {} }): Promise<Response> => {
const response = await fetch(url, config)
const wrapMethod = (res: Response, methodName: string): void => {
// @ts-ignore
const original = res[methodName].bind(res)
// @ts-ignore
res[methodName] = async (...args) => {
const result = await original(...args)
await record(name, url, config, methodName, options, response, result)
return result
}
}
wrapMethod(response, 'json')
wrapMethod(response, 'text')
return response
}
}
// Usage
const httpClient = wrap(axios, options)
const httpClient = wrap(fetch, options)
const ticketResponse = httpClient('unique-endpoint', '/api/tickets', {
method: 'POST'
})
Actual Behavior
it... should work :D
Metadata
Typesense Version: 1.5.4
OS: Mac OS X
Any temporarily solutions to this?
It would be a non-trivial amount of work to replace axios in typesense-js.
So the workaround would be to use say fetch
and call the Typesense HTTP APIs directly.
It's worth exploring have an edge/deno supported js/ts library as many users are switching to such implementations for their backend.
Related: https://github.com/typesense/typesense-js/issues/95
That axios is not working on edge workers like Deno Deploy, Vercel Edge Functions or Cloudflare Workers is an absolute dealbreaker for me.
You can just make fetch('<urhost>/collection/
) calls but it is annoying. That's what we do though.
I use pnpm aliases with redaxios
In my package.json
I specify:
"axios": "npm:redaxios@^0.5.1",
And I also have the following: .pnpmfile.cjs
function readPackage(pkg) {
if (pkg.dependencies && pkg.name === "typesense" && pkg.dependencies.axios) {
pkg.dependencies.axios = "npm:redaxios@^0.5.1";
}
return pkg;
}
module.exports = {
hooks: {
readPackage,
},
};
This setup works on Cloudflare Workers, not sure about other platforms.