From e3d8099c803a3f12e84336de62fe825d1377abaa Mon Sep 17 00:00:00 2001 From: densetsuuu Date: Thu, 29 Jan 2026 15:53:32 +0100 Subject: [PATCH] fix: prevent double ?? in URL when using query params --- packages/core/src/client/serializer.ts | 2 +- packages/core/tests/client_extras.spec.ts | 10 ++++++++++ packages/core/tests/serializer.spec.ts | 18 +++++++++--------- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/core/src/client/serializer.ts b/packages/core/src/client/serializer.ts index e53ea6b..294b207 100644 --- a/packages/core/src/client/serializer.ts +++ b/packages/core/src/client/serializer.ts @@ -33,5 +33,5 @@ export function buildSearchParams(query: QueryParameters): string { } serialize(query) - return parts.length ? `?${parts.join('&')}` : '' + return parts.length ? parts.join('&') : '' } diff --git a/packages/core/tests/client_extras.spec.ts b/packages/core/tests/client_extras.spec.ts index 223b8dd..21a5247 100644 --- a/packages/core/tests/client_extras.spec.ts +++ b/packages/core/tests/client_extras.spec.ts @@ -28,6 +28,16 @@ test.group('Client | getRoute', () => { }) }) +test.group('Client | urlFor', () => { + test('urlFor should not produce double ?? with query params', ({ assert }) => { + const tuyau = createTuyau({ baseUrl: 'http://localhost:3333', registry }) + + const result = tuyau.urlFor.get('users.index', {}, { qs: { var: 'hi' } }).url + assert.isFalse(result.includes('??'), `URL contains double "??": ${result}`) + assert.include(result, '?var=hi') + }) +}) + test.group('Client | Errors', () => { test('throws error for non-existent pattern', async ({ assert }) => { const tuyau = createTuyau({ baseUrl: 'http://localhost:3333', registry }) diff --git a/packages/core/tests/serializer.spec.ts b/packages/core/tests/serializer.spec.ts index e16be18..f310d52 100644 --- a/packages/core/tests/serializer.spec.ts +++ b/packages/core/tests/serializer.spec.ts @@ -5,26 +5,26 @@ import { buildSearchParams } from '../src/client/serializer.ts' test.group('buildSearchParams', () => { test('simple key-value pairs', ({ assert }) => { const result = buildSearchParams({ foo: 'bar', baz: 'qux' }) - assert.equal(result, '?foo=bar&baz=qux') + assert.equal(result, 'foo=bar&baz=qux') }) test('arrays with bracket notation', ({ assert }) => { const result = buildSearchParams({ ids: [1, 2, 3] }) - assert.equal(result, '?ids[]=1&ids[]=2&ids[]=3') + assert.equal(result, 'ids[]=1&ids[]=2&ids[]=3') }) test('nested objects with bracket notation', ({ assert }) => { const result = buildSearchParams({ filter: { name: { like: 'foo' }, price: { gte: 1200 } }, }) - assert.equal(result, '?filter[name][like]=foo&filter[price][gte]=1200') + assert.equal(result, 'filter[name][like]=foo&filter[price][gte]=1200') }) test('deeply nested objects', ({ assert }) => { const result = buildSearchParams({ a: { b: { c: { d: 'value' } } }, }) - assert.equal(result, '?a[b][c][d]=value') + assert.equal(result, 'a[b][c][d]=value') }) test('mixed: simple, arrays, and nested', ({ assert }) => { @@ -33,7 +33,7 @@ test.group('buildSearchParams', () => { ids: [1, 2], filter: { status: 'active' }, }) - assert.equal(result, '?page=1&ids[]=1&ids[]=2&filter[status]=active') + assert.equal(result, 'page=1&ids[]=1&ids[]=2&filter[status]=active') }) test('skips null and undefined values', ({ assert }) => { @@ -43,12 +43,12 @@ test.group('buildSearchParams', () => { missing: undefined, nested: { valid: 'yes', empty: null }, }) - assert.equal(result, '?foo=bar&nested[valid]=yes') + assert.equal(result, 'foo=bar&nested[valid]=yes') }) test('encodes special characters', ({ assert }) => { const result = buildSearchParams({ q: 'hello world', tag: 'foo&bar' }) - assert.equal(result, '?q=hello%20world&tag=foo%26bar') + assert.equal(result, 'q=hello%20world&tag=foo%26bar') }) test('handles empty object', ({ assert }) => { @@ -63,13 +63,13 @@ test.group('buildSearchParams', () => { test('boolean values', ({ assert }) => { const result = buildSearchParams({ active: true, deleted: false }) - assert.equal(result, '?active=true&deleted=false') + assert.equal(result, 'active=true&deleted=false') }) test('arrays inside nested objects', ({ assert }) => { const result = buildSearchParams({ filter: { tags: ['a', 'b'] }, }) - assert.equal(result, '?filter[tags][]=a&filter[tags][]=b') + assert.equal(result, 'filter[tags][]=a&filter[tags][]=b') }) })