diff --git a/.changepacks/changepack_log_woJN1gnWyhet6mV17d56e.json b/.changepacks/changepack_log_woJN1gnWyhet6mV17d56e.json new file mode 100644 index 0000000..f087949 --- /dev/null +++ b/.changepacks/changepack_log_woJN1gnWyhet6mV17d56e.json @@ -0,0 +1 @@ +{"changes":{"packages/fetch/package.json":"Patch","packages/core/package.json":"Patch"},"note":"Fix query issue","date":"2025-12-04T03:48:47.984060900Z"} \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7a1a9f8..c4b7743 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -69,6 +69,6 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true files: ./coverage/lcov.info - - run: bun run publish --access public --ignore-scripts + - run: bunx @changepacks/cli publish env: NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/packages/core/src/additional.ts b/packages/core/src/additional.ts index e7d2e05..6291af4 100644 --- a/packages/core/src/additional.ts +++ b/packages/core/src/additional.ts @@ -11,7 +11,9 @@ export type RequiredOptions = keyof T extends undefined export type DevupApiRequestInit = Omit & { body?: object | RequestInit['body'] params?: Record - query?: Record + query?: + | ConstructorParameters[0] + | Record middleware?: Middleware[] } diff --git a/packages/core/src/middleware.ts b/packages/core/src/middleware.ts index e169d9e..526a96f 100644 --- a/packages/core/src/middleware.ts +++ b/packages/core/src/middleware.ts @@ -4,8 +4,8 @@ import type { PromiseOr } from './utils' export interface MiddlewareCallbackParams { request: Request schemaPath: string - params?: Record - query?: Record + params?: DevupApiRequestInit['params'] + query?: DevupApiRequestInit['query'] headers?: DevupApiRequestInit['headers'] body?: DevupApiRequestInit['body'] } diff --git a/packages/fetch/src/__tests__/utils.test.ts b/packages/fetch/src/__tests__/utils.test.ts index 6fda001..e43b935 100644 --- a/packages/fetch/src/__tests__/utils.test.ts +++ b/packages/fetch/src/__tests__/utils.test.ts @@ -1,5 +1,5 @@ import { expect, test } from 'bun:test' -import { getApiEndpoint, isPlainObject } from '../utils' +import { getApiEndpoint, getQueryString, isPlainObject } from '../utils' test.each([ [{}, true], @@ -106,3 +106,57 @@ test.each([ ])('getApiEndpoint: baseUrl=%s, path=%s, params=%s -> %s', (baseUrl, path, params, expected) => { expect(getApiEndpoint(baseUrl, path, params)).toBe(expected) }) + +test.each([ + ['a=1&b=2', 'a=1&b=2'], + ['', ''], + ['key=value&test=123', 'key=value&test=123'], + ['x=1&y=2&z=3', 'x=1&y=2&z=3'], + [{ a: '1', b: '2' }, 'a=1&b=2'], + [{}, ''], + [{ key: 'value', test: '123' }, 'key=value&test=123'], + [{ x: '1', y: '2', z: '3' }, 'x=1&y=2&z=3'], + [{ a: 1, b: 2 }, 'a=1&b=2'], + [{ a: '1', b: 2, c: 'test' }, 'a=1&b=2&c=test'], + [{ a: ['1', '2', '3'] }, 'a=1&a=2&a=3'], + [{ a: [1, 2, 3] }, 'a=1&a=2&a=3'], + [{ a: [1, '2', 3] }, 'a=1&a=2&a=3'], + [new URLSearchParams('a=1&b=2'), 'a=1&b=2'], + [new URLSearchParams(''), ''], + [new URLSearchParams('key=value&test=123'), 'key=value&test=123'], + [ + [ + ['a', '1'], + ['b', '2'], + ] as [string, string][], + 'a=1&b=2', + ], + [ + [ + ['key', 'value'], + ['test', '123'], + ] as [string, string][], + 'key=value&test=123', + ], + [ + [ + ['x', '1'], + ['y', '2'], + ['z', '3'], + ] as [string, string][], + 'x=1&y=2&z=3', + ], + [ + [ + ['x', '1'], + ['x', '2'], + ['x', '3'], + ] as [string, string][], + 'x=1&x=2&x=3', + ], +])('getQueryString: %s query -> "%s"', (query, expected) => { + const result = getQueryString( + query as NonNullable[0]>, + ) + expect(result.toString()).toBe(expected) +}) diff --git a/packages/fetch/src/api.ts b/packages/fetch/src/api.ts index eba556b..1990f39 100644 --- a/packages/fetch/src/api.ts +++ b/packages/fetch/src/api.ts @@ -22,7 +22,7 @@ import type { } from '@devup-api/core' import { convertResponse } from './response-converter' import { getApiEndpointInfo } from './url-map' -import { getApiEndpoint, isPlainObject } from './utils' +import { getApiEndpoint, getQueryString, isPlainObject } from './utils' // biome-ignore lint/suspicious/noExplicitAny: any is used to allow for flexibility in the type export type DevupApiResponse = @@ -236,7 +236,7 @@ export class DevupApi> { DevupApiResponse, ExtractValue> > { const { method, url } = getApiEndpointInfo(path, this.serverName) - const { middleware = [], ...restOptions } = options[0] || {} + const { middleware = [], query, ...restOptions } = options[0] || {} const mergedOptions = { ...this.defaultOptions, ...restOptions, @@ -248,6 +248,7 @@ export class DevupApi> { if (requestOptions.body && isPlainObject(requestOptions.body)) { requestOptions.body = JSON.stringify(requestOptions.body) } + const queryString = query ? `?${getQueryString(query).toString()}` : '' let request = new Request( getApiEndpoint( this.baseUrl, @@ -260,7 +261,7 @@ export class DevupApi> { > } ).params, - ), + ) + queryString, requestOptions as RequestInit, ) diff --git a/packages/fetch/src/utils.ts b/packages/fetch/src/utils.ts index 44762d7..17e2991 100644 --- a/packages/fetch/src/utils.ts +++ b/packages/fetch/src/utils.ts @@ -1,3 +1,5 @@ +import type { DevupApiRequestInit } from '@devup-api/core' + export function isPlainObject(obj: unknown): obj is object { if (obj === null || typeof obj !== 'object') return false @@ -16,3 +18,25 @@ export function getApiEndpoint( } return ret } + +export function getQueryString( + query: NonNullable, +): URLSearchParams { + if (typeof query === 'string') { + return new URLSearchParams(query) + } + if (isPlainObject(query)) { + const params = new URLSearchParams() + for (const [key, value] of Object.entries(query)) { + if (Array.isArray(value)) { + for (const v of value) { + params.append(key, String(v)) + } + } else { + params.append(key, String(value)) + } + } + return params + } + return new URLSearchParams(query) +}