diff --git a/src/client/client.test.ts b/src/client/client.test.ts index 9f499b7f..97292c73 100644 --- a/src/client/client.test.ts +++ b/src/client/client.test.ts @@ -847,6 +847,27 @@ describe('$url() with a param option', () => { }) }) +describe('$url() with a query option', () => { + const app = new Hono().get( + '/posts', + validator('query', () => { + return {} as { filter: 'test' } + }), + (c) => c.json({ ok: true }) + ) + type AppType = typeof app + const client = hc('http://localhost') + + it('Should return the correct path - /posts?filter=test', async () => { + const url = client.posts.$url({ + query: { + filter: 'test', + }, + }) + expect(url.search).toBe('?filter=test') + }) +}) + describe('Client can be awaited', () => { it('Can be awaited without side effects', async () => { const client = hc('http://localhost') diff --git a/src/client/client.ts b/src/client/client.ts index f9b48792..2d5edeff 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -4,6 +4,7 @@ import { serialize } from '../utils/cookie' import type { UnionToIntersection } from '../utils/types' import type { Callback, Client, ClientRequestOptions } from './types' import { + buildSearchParams, deepMerge, mergePath, removeIndexString, @@ -49,20 +50,7 @@ class ClientRequestImpl { ) => { if (args) { if (args.query) { - for (const [k, v] of Object.entries(args.query)) { - if (v === undefined) { - continue - } - - this.queryParams ||= new URLSearchParams() - if (Array.isArray(v)) { - for (const v2 of v) { - this.queryParams.append(k, v2) - } - } else { - this.queryParams.set(k, v) - } - } + this.queryParams = buildSearchParams(args.query) } if (args.form) { @@ -172,10 +160,16 @@ export const hc = >( const path = parts.join('/') const url = mergePath(baseUrl, path) if (method === 'url') { - if (opts.args[0] && opts.args[0].param) { - return new URL(replaceUrlParam(url, opts.args[0].param)) + let result = url + if (opts.args[0]) { + if (opts.args[0].param) { + result = replaceUrlParam(url, opts.args[0].param) + } + if (opts.args[0].query) { + result = result + '?' + buildSearchParams(opts.args[0].query).toString() + } } - return new URL(url) + return new URL(result) } if (method === 'ws') { const webSocketUrl = replaceUrlProtocol( diff --git a/src/client/types.ts b/src/client/types.ts index 7a345370..29e5790e 100644 --- a/src/client/types.ts +++ b/src/client/types.ts @@ -38,7 +38,11 @@ export type ClientRequest = { $url: ( arg?: S[keyof S] extends { input: infer R } ? R extends { param: infer P } - ? { param: P } + ? R extends { query: infer Q } + ? { param: P; query: Q } + : { param: P } + : R extends { query: infer Q } + ? { query: Q } : {} : {} ) => URL diff --git a/src/client/utils.test.ts b/src/client/utils.test.ts index 7adc6b00..481c671f 100644 --- a/src/client/utils.test.ts +++ b/src/client/utils.test.ts @@ -1,4 +1,5 @@ import { + buildSearchParams, deepMerge, mergePath, removeIndexString, @@ -59,6 +60,18 @@ describe('replaceUrlParams', () => { }) }) +describe('buildSearchParams', () => { + it('Should build URLSearchParams correctly', () => { + const query = { + id: '123', + type: 'test', + tag: ['a', 'b'], + } + const searchParams = buildSearchParams(query) + expect(searchParams.toString()).toBe('id=123&type=test&tag=a&tag=b') + }) +}) + describe('replaceUrlProtocol', () => { it('Should replace http to ws', () => { const url = 'http://localhost' diff --git a/src/client/utils.ts b/src/client/utils.ts index 591b7bc7..9c81dc16 100644 --- a/src/client/utils.ts +++ b/src/client/utils.ts @@ -15,6 +15,26 @@ export const replaceUrlParam = (urlString: string, params: Record) => { + const searchParams = new URLSearchParams() + + for (const [k, v] of Object.entries(query)) { + if (v === undefined) { + continue + } + + if (Array.isArray(v)) { + for (const v2 of v) { + searchParams.append(k, v2) + } + } else { + searchParams.set(k, v) + } + } + + return searchParams +} + export const replaceUrlProtocol = (urlString: string, protocol: 'ws' | 'http') => { switch (protocol) { case 'ws':