@tinybirdco/sdk 0.0.18 → 0.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -0
- package/dist/api/api.d.ts +89 -0
- package/dist/api/api.d.ts.map +1 -0
- package/dist/api/api.js +218 -0
- package/dist/api/api.js.map +1 -0
- package/dist/api/api.test.d.ts +2 -0
- package/dist/api/api.test.d.ts.map +1 -0
- package/dist/api/api.test.js +226 -0
- package/dist/api/api.test.js.map +1 -0
- package/dist/cli/index.js +8 -9
- package/dist/cli/index.js.map +1 -1
- package/dist/client/base.d.ts +3 -17
- package/dist/client/base.d.ts.map +1 -1
- package/dist/client/base.js +31 -153
- package/dist/client/base.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
- package/src/api/api.test.ts +295 -0
- package/src/api/api.ts +345 -0
- package/src/cli/index.ts +8 -10
- package/src/client/base.ts +34 -181
- package/src/index.ts +14 -0
package/src/client/base.ts
CHANGED
|
@@ -8,15 +8,9 @@ import type {
|
|
|
8
8
|
IngestResult,
|
|
9
9
|
QueryOptions,
|
|
10
10
|
IngestOptions,
|
|
11
|
-
TinybirdErrorResponse,
|
|
12
11
|
} from "./types.js";
|
|
13
12
|
import { TinybirdError } from "./types.js";
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Default timeout for requests (30 seconds)
|
|
18
|
-
*/
|
|
19
|
-
const DEFAULT_TIMEOUT = 30000;
|
|
13
|
+
import { TinybirdApi, TinybirdApiError } from "../api/api.js";
|
|
20
14
|
|
|
21
15
|
/**
|
|
22
16
|
* Resolved token info from dev mode
|
|
@@ -57,7 +51,7 @@ interface ResolvedTokenInfo {
|
|
|
57
51
|
*/
|
|
58
52
|
export class TinybirdClient {
|
|
59
53
|
private readonly config: ClientConfig;
|
|
60
|
-
private readonly
|
|
54
|
+
private readonly apisByToken = new Map<string, TinybirdApi>();
|
|
61
55
|
private tokenPromise: Promise<ResolvedTokenInfo> | null = null;
|
|
62
56
|
private resolvedToken: string | null = null;
|
|
63
57
|
|
|
@@ -75,8 +69,6 @@ export class TinybirdClient {
|
|
|
75
69
|
...config,
|
|
76
70
|
baseUrl: config.baseUrl.replace(/\/$/, ""),
|
|
77
71
|
};
|
|
78
|
-
|
|
79
|
-
this.fetchFn = createTinybirdFetcher(config.fetch ?? globalThis.fetch);
|
|
80
72
|
}
|
|
81
73
|
|
|
82
74
|
/**
|
|
@@ -159,39 +151,12 @@ export class TinybirdClient {
|
|
|
159
151
|
options: QueryOptions = {}
|
|
160
152
|
): Promise<QueryResult<T>> {
|
|
161
153
|
const token = await this.getToken();
|
|
162
|
-
const url = new URL(`/v0/pipes/${pipeName}.json`, this.config.baseUrl);
|
|
163
|
-
|
|
164
|
-
// Add parameters to query string
|
|
165
|
-
for (const [key, value] of Object.entries(params)) {
|
|
166
|
-
if (value !== undefined && value !== null) {
|
|
167
|
-
if (Array.isArray(value)) {
|
|
168
|
-
// Handle array parameters
|
|
169
|
-
for (const item of value) {
|
|
170
|
-
url.searchParams.append(key, String(item));
|
|
171
|
-
}
|
|
172
|
-
} else if (value instanceof Date) {
|
|
173
|
-
// Handle Date objects
|
|
174
|
-
url.searchParams.set(key, value.toISOString());
|
|
175
|
-
} else {
|
|
176
|
-
url.searchParams.set(key, String(value));
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
154
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
},
|
|
186
|
-
signal: this.createAbortSignal(options.timeout, options.signal),
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
if (!response.ok) {
|
|
190
|
-
await this.handleErrorResponse(response);
|
|
155
|
+
try {
|
|
156
|
+
return await this.getApi(token).query<T>(pipeName, params, options);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
this.rethrowApiError(error);
|
|
191
159
|
}
|
|
192
|
-
|
|
193
|
-
const result = (await response.json()) as QueryResult<T>;
|
|
194
|
-
return result;
|
|
195
160
|
}
|
|
196
161
|
|
|
197
162
|
/**
|
|
@@ -223,39 +188,13 @@ export class TinybirdClient {
|
|
|
223
188
|
events: T[],
|
|
224
189
|
options: IngestOptions = {}
|
|
225
190
|
): Promise<IngestResult> {
|
|
226
|
-
if (events.length === 0) {
|
|
227
|
-
return { successful_rows: 0, quarantined_rows: 0 };
|
|
228
|
-
}
|
|
229
|
-
|
|
230
191
|
const token = await this.getToken();
|
|
231
|
-
const url = new URL("/v0/events", this.config.baseUrl);
|
|
232
|
-
url.searchParams.set("name", datasourceName);
|
|
233
192
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Convert events to NDJSON format
|
|
239
|
-
const ndjson = events
|
|
240
|
-
.map((event) => JSON.stringify(this.serializeEvent(event)))
|
|
241
|
-
.join("\n");
|
|
242
|
-
|
|
243
|
-
const response = await this.fetch(url.toString(), {
|
|
244
|
-
method: "POST",
|
|
245
|
-
headers: {
|
|
246
|
-
Authorization: `Bearer ${token}`,
|
|
247
|
-
"Content-Type": "application/x-ndjson",
|
|
248
|
-
},
|
|
249
|
-
body: ndjson,
|
|
250
|
-
signal: this.createAbortSignal(options.timeout, options.signal),
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
if (!response.ok) {
|
|
254
|
-
await this.handleErrorResponse(response);
|
|
193
|
+
try {
|
|
194
|
+
return await this.getApi(token).ingestBatch(datasourceName, events, options);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
this.rethrowApiError(error);
|
|
255
197
|
}
|
|
256
|
-
|
|
257
|
-
const result = (await response.json()) as IngestResult;
|
|
258
|
-
return result;
|
|
259
198
|
}
|
|
260
199
|
|
|
261
200
|
/**
|
|
@@ -270,127 +209,41 @@ export class TinybirdClient {
|
|
|
270
209
|
options: QueryOptions = {}
|
|
271
210
|
): Promise<QueryResult<T>> {
|
|
272
211
|
const token = await this.getToken();
|
|
273
|
-
const url = new URL("/v0/sql", this.config.baseUrl);
|
|
274
|
-
|
|
275
|
-
const response = await this.fetch(url.toString(), {
|
|
276
|
-
method: "POST",
|
|
277
|
-
headers: {
|
|
278
|
-
Authorization: `Bearer ${token}`,
|
|
279
|
-
"Content-Type": "text/plain",
|
|
280
|
-
},
|
|
281
|
-
body: sql,
|
|
282
|
-
signal: this.createAbortSignal(options.timeout, options.signal),
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
if (!response.ok) {
|
|
286
|
-
await this.handleErrorResponse(response);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const result = (await response.json()) as QueryResult<T>;
|
|
290
|
-
return result;
|
|
291
|
-
}
|
|
292
212
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
event: T
|
|
298
|
-
): Record<string, unknown> {
|
|
299
|
-
const serialized: Record<string, unknown> = {};
|
|
300
|
-
|
|
301
|
-
for (const [key, value] of Object.entries(event)) {
|
|
302
|
-
if (value instanceof Date) {
|
|
303
|
-
// Convert Date to ISO string
|
|
304
|
-
serialized[key] = value.toISOString();
|
|
305
|
-
} else if (value instanceof Map) {
|
|
306
|
-
// Convert Map to object
|
|
307
|
-
serialized[key] = Object.fromEntries(value);
|
|
308
|
-
} else if (typeof value === "bigint") {
|
|
309
|
-
// Convert BigInt to string (ClickHouse will parse it)
|
|
310
|
-
serialized[key] = value.toString();
|
|
311
|
-
} else if (Array.isArray(value)) {
|
|
312
|
-
// Recursively serialize array elements
|
|
313
|
-
serialized[key] = value.map((item) =>
|
|
314
|
-
typeof item === "object" && item !== null
|
|
315
|
-
? this.serializeEvent(item as Record<string, unknown>)
|
|
316
|
-
: item instanceof Date
|
|
317
|
-
? item.toISOString()
|
|
318
|
-
: item
|
|
319
|
-
);
|
|
320
|
-
} else if (typeof value === "object" && value !== null) {
|
|
321
|
-
// Recursively serialize nested objects
|
|
322
|
-
serialized[key] = this.serializeEvent(value as Record<string, unknown>);
|
|
323
|
-
} else {
|
|
324
|
-
serialized[key] = value;
|
|
325
|
-
}
|
|
213
|
+
try {
|
|
214
|
+
return await this.getApi(token).sql<T>(sql, options);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
this.rethrowApiError(error);
|
|
326
217
|
}
|
|
327
|
-
|
|
328
|
-
return serialized;
|
|
329
218
|
}
|
|
330
219
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
timeout?: number,
|
|
336
|
-
existingSignal?: AbortSignal
|
|
337
|
-
): AbortSignal | undefined {
|
|
338
|
-
const timeoutMs = timeout ?? this.config.timeout ?? DEFAULT_TIMEOUT;
|
|
339
|
-
|
|
340
|
-
// If no timeout and no existing signal, return undefined
|
|
341
|
-
if (!timeoutMs && !existingSignal) {
|
|
342
|
-
return undefined;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// If only existing signal, return it
|
|
346
|
-
if (!timeoutMs && existingSignal) {
|
|
347
|
-
return existingSignal;
|
|
220
|
+
private getApi(token: string): TinybirdApi {
|
|
221
|
+
const existing = this.apisByToken.get(token);
|
|
222
|
+
if (existing) {
|
|
223
|
+
return existing;
|
|
348
224
|
}
|
|
349
225
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
226
|
+
const api = new TinybirdApi({
|
|
227
|
+
baseUrl: this.config.baseUrl,
|
|
228
|
+
token,
|
|
229
|
+
fetch: this.config.fetch,
|
|
230
|
+
timeout: this.config.timeout,
|
|
231
|
+
});
|
|
357
232
|
|
|
358
|
-
|
|
359
|
-
return
|
|
233
|
+
this.apisByToken.set(token, api);
|
|
234
|
+
return api;
|
|
360
235
|
}
|
|
361
236
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
try {
|
|
370
|
-
rawBody = await response.text();
|
|
371
|
-
errorResponse = JSON.parse(rawBody) as TinybirdErrorResponse;
|
|
372
|
-
} catch {
|
|
373
|
-
// Failed to parse error response - include raw body in message
|
|
374
|
-
if (rawBody) {
|
|
375
|
-
throw new TinybirdError(
|
|
376
|
-
`Request failed with status ${response.status}: ${rawBody}`,
|
|
377
|
-
response.status,
|
|
378
|
-
undefined
|
|
379
|
-
);
|
|
380
|
-
}
|
|
237
|
+
private rethrowApiError(error: unknown): never {
|
|
238
|
+
if (error instanceof TinybirdApiError) {
|
|
239
|
+
throw new TinybirdError(
|
|
240
|
+
error.message,
|
|
241
|
+
error.statusCode,
|
|
242
|
+
error.response
|
|
243
|
+
);
|
|
381
244
|
}
|
|
382
245
|
|
|
383
|
-
|
|
384
|
-
errorResponse?.error ?? `Request failed with status ${response.status}`;
|
|
385
|
-
|
|
386
|
-
throw new TinybirdError(message, response.status, errorResponse);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
* Internal fetch wrapper
|
|
391
|
-
*/
|
|
392
|
-
private fetch(url: string, init?: RequestInit): Promise<Response> {
|
|
393
|
-
return this.fetchFn(url, init);
|
|
246
|
+
throw error;
|
|
394
247
|
}
|
|
395
248
|
}
|
|
396
249
|
|
package/src/index.ts
CHANGED
|
@@ -214,6 +214,20 @@ export type {
|
|
|
214
214
|
TypedDatasourceIngest,
|
|
215
215
|
} from "./client/types.js";
|
|
216
216
|
|
|
217
|
+
// ============ Public Tinybird API ============
|
|
218
|
+
export {
|
|
219
|
+
TinybirdApi,
|
|
220
|
+
TinybirdApiError,
|
|
221
|
+
createTinybirdApi,
|
|
222
|
+
createTinybirdApiWrapper,
|
|
223
|
+
} from "./api/api.js";
|
|
224
|
+
export type {
|
|
225
|
+
TinybirdApiConfig,
|
|
226
|
+
TinybirdApiQueryOptions,
|
|
227
|
+
TinybirdApiIngestOptions,
|
|
228
|
+
TinybirdApiRequestInit,
|
|
229
|
+
} from "./api/api.js";
|
|
230
|
+
|
|
217
231
|
// ============ Preview Environment ============
|
|
218
232
|
export {
|
|
219
233
|
isPreviewEnvironment,
|