diff --git a/CHANGELOG.md b/CHANGELOG.md index 74e420c..b6eaeee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.0.83] - 2026-07-20 + +### Fixed + +- Query params that are plain objects (including `p.json()` values) are now JSON-serialized instead of becoming `"[object Object]"` via `String(value)`. Pre-stringified JSON strings and primitive params are unchanged. + ## [0.0.82] - 2026-07-15 ### Changed diff --git a/package.json b/package.json index 69b1a1d..5b56ee5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tinybirdco/sdk", - "version": "0.0.82", + "version": "0.0.83", "description": "TypeScript SDK for Tinybird Forward - define datasources and pipes as TypeScript", "type": "module", "main": "./dist/index.js", diff --git a/src/api/api.test.ts b/src/api/api.test.ts index 2480cec..44d45cc 100644 --- a/src/api/api.test.ts +++ b/src/api/api.test.ts @@ -142,6 +142,69 @@ describe("TinybirdApi", () => { ).rejects.toThrow("Date values are not supported for query parameter"); }); + it("JSON-stringifies plain object query params (p.json())", async () => { + let configOverridesParam: string | null = null; + let preStringifiedParam: string | null = null; + let objectArrayParams: string[] = []; + let limitParam: string | null = null; + let tagsParams: string[] = []; + + server.use( + http.get(`${BASE_URL}/v0/pipes/json_echo.json`, ({ request }) => { + const url = new URL(request.url); + configOverridesParam = url.searchParams.get("configOverrides"); + preStringifiedParam = url.searchParams.get("preStringified"); + objectArrayParams = url.searchParams.getAll("items"); + limitParam = url.searchParams.get("limit"); + tagsParams = url.searchParams.getAll("tags"); + + return HttpResponse.json({ + data: [{ key_count: 1 }], + meta: [{ name: "key_count", type: "UInt64" }], + rows: 1, + statistics: { + elapsed: 0.001, + rows_read: 1, + bytes_read: 10, + }, + }); + }) + ); + + const api = createTinybirdApi({ + baseUrl: BASE_URL, + token: "p.default-token", + }); + + await api.query("json_echo", { + configOverrides: { foo: 1 }, + preStringified: '{"foo":1}', + items: [{ a: 1 }, { b: 2 }], + limit: 5, + tags: ["a", "b"], + }); + + expect(configOverridesParam).toBe('{"foo":1}'); + expect(configOverridesParam).not.toBe("[object Object]"); + expect(preStringifiedParam).toBe('{"foo":1}'); + expect(objectArrayParams).toEqual(['{"a":1}', '{"b":2}']); + expect(limitParam).toBe("5"); + expect(tagsParams).toEqual(["a", "b"]); + }); + + it("throws when array query params include Date values", async () => { + const api = createTinybirdApi({ + baseUrl: BASE_URL, + token: "p.default-token", + }); + + await expect( + api.query("top_pages", { + tags: [new Date("2024-01-01T00:00:00.000Z")], + }) + ).rejects.toThrow("Date values are not supported for query parameter"); + }); + it("ingests rows via tinybirdApi.ingest", async () => { let datasourceName: string | null = null; let waitParam: string | null = null; diff --git a/src/api/api.ts b/src/api/api.ts index dab93fe..53b9230 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -17,6 +17,26 @@ const DEFAULT_TIMEOUT = 30000; const DEFAULT_INGEST_RETRY_503_BASE_DELAY_MS = 200; const DEFAULT_INGEST_RETRY_503_MAX_DELAY_MS = 3000; +/** + * Serialize a single query-param value for the Tinybird pipes API. + * Plain objects (e.g. p.json() params) become JSON strings; Dates are rejected; + * everything else uses String(value). + */ +function serializeQueryParamValue(key: string, value: unknown): string { + if (value instanceof Date) { + throw new Error( + `Date values are not supported for query parameter "${key}". ` + + "Pass a string in YYYY-MM-DD HH:MM:SS format (or YYYY-MM-DD HH:MM:SS.sss for DateTime64)." + ); + } + + if (typeof value === "object" && value !== null && !Array.isArray(value)) { + return JSON.stringify(value); + } + + return String(value); +} + /** * Public, decoupled Tinybird API wrapper configuration */ @@ -214,25 +234,12 @@ export class TinybirdApi { if (Array.isArray(value)) { for (const item of value) { - if (item instanceof Date) { - throw new Error( - `Date values are not supported for query parameter "${key}". ` + - "Pass a string in YYYY-MM-DD HH:MM:SS format (or YYYY-MM-DD HH:MM:SS.sss for DateTime64)." - ); - } - url.searchParams.append(key, String(item)); + url.searchParams.append(key, serializeQueryParamValue(key, item)); } continue; } - if (value instanceof Date) { - throw new Error( - `Date values are not supported for query parameter "${key}". ` + - "Pass a string in YYYY-MM-DD HH:MM:SS format (or YYYY-MM-DD HH:MM:SS.sss for DateTime64)." - ); - } - - url.searchParams.set(key, String(value)); + url.searchParams.set(key, serializeQueryParamValue(key, value)); } const response = await this.request(url.toString(), {