Skip to content

Commit cc714ee

Browse files
feat!: replace ufo with native URL utils
1 parent 7726adb commit cc714ee

File tree

2 files changed

+109
-1
lines changed

2 files changed

+109
-1
lines changed

src/fetch.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Readable } from "node:stream";
22
import destr from "destr";
3-
import { withBase, withQuery } from "ufo";
3+
import { withBase, withQuery } from "./path";
44
import { createFetchError } from "./error";
55
import {
66
isPayloadMethod,

src/path.ts

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/* eslint-disable unicorn/prefer-at */
2+
export type QueryValue =
3+
| string
4+
| number
5+
| boolean
6+
| QueryValue[]
7+
| Record<string, any>
8+
| null
9+
| undefined;
10+
export type QueryObject = Record<string, QueryValue | QueryValue[]>;
11+
12+
/**
13+
* Removes the leading slash from the given path if it has one.
14+
*/
15+
export function withoutLeadingSlash(path?: string): string {
16+
if (!path || path === "/") {
17+
return "/";
18+
}
19+
20+
return path[0] === "/" ? path.slice(1) : path;
21+
}
22+
23+
/**
24+
* Removes the trailing slash from the given path if it has one.
25+
*/
26+
export function withoutTrailingSlash(path?: string): string {
27+
if (!path || path === "/") {
28+
return "/";
29+
}
30+
31+
return path[path.length - 1] === "/" ? path.slice(0, -1) : path;
32+
}
33+
34+
/**
35+
* Joins the given base URL and path, ensuring that there is only one slash between them.
36+
*/
37+
export function joinURL(base?: string, path?: string): string {
38+
if (!base || base === "/") {
39+
return path || "/";
40+
}
41+
42+
if (!path || path === "/") {
43+
return base || "/";
44+
}
45+
46+
const baseHasTrailing = base[base.length - 1] === "/";
47+
const pathHasLeading = path[0] === "/";
48+
if (baseHasTrailing && pathHasLeading) {
49+
return base + path.slice(1);
50+
}
51+
52+
if (!baseHasTrailing && !pathHasLeading) {
53+
return `${base}/${path}`;
54+
}
55+
56+
return base + path;
57+
}
58+
59+
/**
60+
* Adds the base path to the input path, if it is not already present.
61+
*/
62+
export function withBase(input = "", base = ""): string {
63+
if (!base || base === "/") {
64+
return input;
65+
}
66+
67+
const _base = withoutTrailingSlash(base);
68+
if (input.startsWith(_base)) {
69+
return input;
70+
}
71+
72+
return joinURL(_base, input);
73+
}
74+
75+
/**
76+
* Returns the URL with the given query parameters. If a query parameter is undefined, it is omitted.
77+
*/
78+
export function withQuery(input: string, query: QueryObject): string {
79+
const url = new URL(input, "http://localhost");
80+
const searchParams = new URLSearchParams(url.search);
81+
82+
for (const [key, value] of Object.entries(query)) {
83+
if (value === undefined) {
84+
searchParams.delete(key);
85+
} else if (typeof value === "number" || typeof value === "boolean") {
86+
searchParams.set(key, String(value));
87+
} else if (!value) {
88+
searchParams.set(key, "");
89+
} else if (Array.isArray(value)) {
90+
for (const item of value) {
91+
searchParams.append(key, String(item));
92+
}
93+
} else if (typeof value === "object") {
94+
searchParams.set(key, JSON.stringify(value));
95+
} else {
96+
searchParams.set(key, String(value));
97+
}
98+
}
99+
100+
url.search = searchParams.toString();
101+
let urlWithQuery = url.toString();
102+
103+
if (urlWithQuery.startsWith("http://localhost")) {
104+
urlWithQuery = urlWithQuery.slice(16);
105+
}
106+
107+
return urlWithQuery;
108+
}

0 commit comments

Comments
 (0)