SIGN IN SIGN UP

fix: correct query param serialization for arrays, dates and null (#329)

`queryParamsStringify` silently corrupted GET query strings in three
ways. All of them failed silently — the request returned `200` with
wrong data rather than erroring, so callers got no signal that the
parameter was never sent.

## The bugs

**1. Arrays of objects serialized to `[object Object]`.** The array
branch used `param.join(',')`, which stringifies each element via
`String()`. The object branch immediately below it already used
`JSON.stringify`.

```js
{ sort: [{ field: 'created_at', direction: 1 }] }
// before → sort=%5Bobject%20Object%5D
// after  → sort=%5B%7B%22field%22%3A%22created_at%22...
```

**2. `Date` params lost their key.** The `instanceof Date` branch pushed
`param.toISOString()` with no `` `${k}=` `` prefix, dropping the key and
injecting a bare, unencoded value into the query string.

```js
{ limit: 10, start_time: new Date('2026-08-15T10:00:00Z') }
// before → limit=10&2026-08-15T10:00:00.000Z    (key gone, server never sees it)
// after  → limit=10&start_time=2026-08-15T10%3A00%3A00.000Z
```

**3. `null` serialized as the literal string `"null"`.** `typeof null
=== 'object'`, so it took the object branch and `JSON.stringify(null)`
produced `id_gt=null` on the wire. `undefined` was already dropped
correctly.

## Affected endpoints

Audited every `*Api.ts` under `src/gen` for GET query params typed as an
array of non-scalars or as `Date`:

| Bug | Endpoint | Param |
|---|---|---|
| 1 | `ChatApi.getReplies` | `sort?: SortParamRequest[]` |
| 1 | `VideoApi.queryCallSessionParticipantStats` | `sort?:
SortParamRequest[]` |
| 2 | `VideoApi.getCallParticipantSessionMetrics` | `since?: Date`,
`until?: Date` |
| 2 | `VideoApi.getCallStatsMap` | `start_time?: Date`, `end_time?:
Date` |

`getReplies` returned unsorted replies, so an ordering assertion failed
with no indication that the sort was never sent. For the Video
endpoints, any caller passing a time range silently got an unfiltered
one.

Not affected: `queryChannels`, `queryThreads`, `queryReminders` and
`getRetentionPolicyRuns` are POST, so their sorts travel in the JSON
body. `queryUsers`, `queryMembers` and `searchRoles` are GET but nest
`sort` inside a `payload` object, which already took the correct
`JSON.stringify` branch.

## The fix

Array handling moves to `src/utils/query-params.ts`, which resolves the
wire format in a single pass:

- Scalar arrays keep the comma-separated form (`ids=a,b`). Switching
those to JSON unconditionally — as `stream-chat`'s
`axiosParamsSerializer` does — would be a breaking wire-format change
for every string-array param, so the format is chosen per array. If the
backend accepts a JSON array everywhere, this could be simplified;
that's an API-owner call.
- Anything non-scalar is JSON encoded.
- `null`/`undefined` entries *within* an array are dropped, so a single
empty value can't flip a scalar array from `a,b` to a JSON array.

## Verification

Unit tests cover all four wire formats plus the empty-value cases.
Beyond that, the encodings were checked against the live API rather than
assumed:

- The server parses and validates the JSON `sort` array — `sort=notjson`
and a bare (non-array) JSON object are both rejected with `not a valid
JSON for field 'sort'`, and a bogus field name is rejected with `Sorting
is only supported on 'created_at' field`.
- `getReplies` with `limit=2` returns the two oldest replies for
`direction: 1` and the two newest for `direction: -1`, confirming the
sort is honoured end-to-end. Note the sort selects the page; in-page
order is always chronological.
- `getCallStatsMap` with ISO `start_time`/`end_time` fails on the
missing call rather than on the time format, confirming the dates parse.

The full suite shows no new failures; the remaining failures are
pre-existing on `main` (live-API integration tests and a
webhook-signature test).
O
Oliver Lazoroski committed
7f228fc6489a38a02a6b8e0d40072dd66d404030
Parent: d4f0c22
Committed by GitHub <noreply@github.com> on 8/18/2026, 7:35:32 AM