From 104d9c6dde8c66026f53b8635ead2c054b9bced8 Mon Sep 17 00:00:00 2001 From: Joel Solano Date: Tue, 21 Oct 2025 19:31:52 +0200 Subject: [PATCH 1/5] fix: dynamic parameters collision --- packages/better-fetch/src/url.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/better-fetch/src/url.ts b/packages/better-fetch/src/url.ts index 4a8ad07..2df9107 100644 --- a/packages/better-fetch/src/url.ts +++ b/packages/better-fetch/src/url.ts @@ -1,11 +1,11 @@ import { methods } from "./create-fetch"; -import { BetterFetchOption } from "./types"; +import type { BetterFetchOption } from "./types"; /** * Normalize URL */ export function getURL(url: string, option?: BetterFetchOption) { - let { baseURL, params, query } = option || { + const { baseURL, params, query } = option || { query: {}, params: {}, baseURL: "", @@ -40,7 +40,7 @@ export function getURL(url: string, option?: BetterFetchOption) { } } else { for (const [key, value] of Object.entries(params)) { - path = path.replace(`:${key}`, String(value)); + path = path.replace(`/:${key}`, `/${value}`); } } } @@ -49,7 +49,9 @@ export function getURL(url: string, option?: BetterFetchOption) { if (path.startsWith("/")) path = path.slice(1); let queryParamString = queryParams.toString(); queryParamString = - queryParamString.length > 0 ? `?${queryParamString}`.replace(/\+/g, "%20") : ""; + queryParamString.length > 0 + ? `?${queryParamString}`.replace(/\+/g, "%20") + : ""; if (!basePath.startsWith("http")) { return `${basePath}${path}${queryParamString}`; } From 55eab6b104c6770efc89bf1ca076a0ddd0b12798 Mon Sep 17 00:00:00 2001 From: Joel Solano Date: Tue, 21 Oct 2025 20:02:41 +0200 Subject: [PATCH 2/5] chore: add test --- packages/better-fetch/src/test/fetch.test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/better-fetch/src/test/fetch.test.ts b/packages/better-fetch/src/test/fetch.test.ts index 5bdde68..cb4d069 100644 --- a/packages/better-fetch/src/test/fetch.test.ts +++ b/packages/better-fetch/src/test/fetch.test.ts @@ -566,6 +566,19 @@ describe("url", () => { expect(url.toString()).toBe("http://localhost:4001/param/%23test/item%201"); }); + it("dynamic params should not collide with url", async () => { + const url = getURL("/v1/places:searchNearby", { + baseURL: "http://localhost:4000", + params: { + searchNearby: "test", + }, + }); + + expect(url.toString()).toBe( + "http://localhost:4000/v1/places%3AsearchNearby", + ); + }); + it("should expand array values into multiple query parameters", () => { const url = getURL("/test", { query: { From d699b30b341a8ee6b76376c197fd2a6568884548 Mon Sep 17 00:00:00 2001 From: Joel Solano Date: Wed, 22 Oct 2025 00:22:15 +0200 Subject: [PATCH 3/5] fix: dont encode colon --- packages/better-fetch/src/test/fetch.test.ts | 2 +- packages/better-fetch/src/url.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/better-fetch/src/test/fetch.test.ts b/packages/better-fetch/src/test/fetch.test.ts index cb4d069..495f731 100644 --- a/packages/better-fetch/src/test/fetch.test.ts +++ b/packages/better-fetch/src/test/fetch.test.ts @@ -575,7 +575,7 @@ describe("url", () => { }); expect(url.toString()).toBe( - "http://localhost:4000/v1/places%3AsearchNearby", + "http://localhost:4000/v1/places:searchNearby", ); }); diff --git a/packages/better-fetch/src/url.ts b/packages/better-fetch/src/url.ts index 2079556..8064fbc 100644 --- a/packages/better-fetch/src/url.ts +++ b/packages/better-fetch/src/url.ts @@ -56,7 +56,11 @@ export function getURL(url: string, option?: BetterFetchOption) { } } - path = path.split("/").map(encodeURIComponent).join("/"); + path = path + .split("/") + // decode colon again so fetch doesn't break + .map((segment) => encodeURIComponent(segment).replace(/%3A/g, ":")) + .join("/"); if (path.startsWith("/")) path = path.slice(1); let queryParamString = queryParams.toString(); queryParamString = From bc24b7f2da195b5238402e1cf576a726f8c37881 Mon Sep 17 00:00:00 2001 From: Joel Solano Date: Thu, 23 Oct 2025 14:31:54 +0200 Subject: [PATCH 4/5] chore: update `InferParamPath` to reflect changes --- packages/better-fetch/src/create-fetch/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/better-fetch/src/create-fetch/types.ts b/packages/better-fetch/src/create-fetch/types.ts index 5f07d36..3f33de5 100644 --- a/packages/better-fetch/src/create-fetch/types.ts +++ b/packages/better-fetch/src/create-fetch/types.ts @@ -28,13 +28,13 @@ export type RemoveEmptyString = T extends string : T; export type InferParamPath = - Path extends `${infer _Start}:${infer Param}/${infer Rest}` + Path extends `${infer _Start}/:${infer Param}/${infer Rest}` ? { [K in | Param | keyof InferParamPath as RemoveEmptyString]: string; } - : Path extends `${infer _Start}:${infer Param}` + : Path extends `${infer _Start}/:${infer Param}` ? { [K in Param]: string } : Path extends `${infer _Start}/${infer Rest}` ? InferParamPath From cc14dc519aca23e2022e6a320c3db381a721661b Mon Sep 17 00:00:00 2001 From: Joel Solano Date: Thu, 23 Oct 2025 14:34:58 +0200 Subject: [PATCH 5/5] chore: fix types --- packages/better-fetch/src/test/create.test.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/better-fetch/src/test/create.test.ts b/packages/better-fetch/src/test/create.test.ts index 7fd39a0..3c3657d 100644 --- a/packages/better-fetch/src/test/create.test.ts +++ b/packages/better-fetch/src/test/create.test.ts @@ -270,10 +270,8 @@ describe("create-fetch-type-test", () => { const res = await $fetch("/", { throw: true, }); - expectTypeOf(res).toMatchTypeOf< - { message: string } - >(); - }) + expectTypeOf(res).toMatchTypeOf<{ message: string }>(); + }); it("should return unknown if no output is defined", () => { const res = $fetch("/"); @@ -423,8 +421,8 @@ describe("create-fetch-type-test", () => { params: {}, }); $fetch("/post/:id/:title", { + // @ts-expect-error params: { - //@ts-expect-error title: 1, }, });