@tinybirdco/sdk 0.0.4 → 0.0.7

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.
Files changed (165) hide show
  1. package/README.md +52 -13
  2. package/dist/api/branches.d.ts.map +1 -1
  3. package/dist/api/branches.js +6 -5
  4. package/dist/api/branches.js.map +1 -1
  5. package/dist/api/branches.test.js +32 -6
  6. package/dist/api/branches.test.js.map +1 -1
  7. package/dist/api/build.d.ts.map +1 -1
  8. package/dist/api/build.js +2 -1
  9. package/dist/api/build.js.map +1 -1
  10. package/dist/api/deploy.d.ts +42 -3
  11. package/dist/api/deploy.d.ts.map +1 -1
  12. package/dist/api/deploy.js +162 -19
  13. package/dist/api/deploy.js.map +1 -1
  14. package/dist/api/deploy.test.js +83 -31
  15. package/dist/api/deploy.test.js.map +1 -1
  16. package/dist/api/fetcher.d.ts +6 -0
  17. package/dist/api/fetcher.d.ts.map +1 -0
  18. package/dist/api/fetcher.js +13 -0
  19. package/dist/api/fetcher.js.map +1 -0
  20. package/dist/api/local.d.ts.map +1 -1
  21. package/dist/api/local.js +5 -4
  22. package/dist/api/local.js.map +1 -1
  23. package/dist/api/local.test.js.map +1 -1
  24. package/dist/api/resources.d.ts +178 -0
  25. package/dist/api/resources.d.ts.map +1 -0
  26. package/dist/api/resources.js +245 -0
  27. package/dist/api/resources.js.map +1 -0
  28. package/dist/api/resources.test.d.ts +2 -0
  29. package/dist/api/resources.test.d.ts.map +1 -0
  30. package/dist/api/resources.test.js +255 -0
  31. package/dist/api/resources.test.js.map +1 -0
  32. package/dist/api/workspaces.d.ts.map +1 -1
  33. package/dist/api/workspaces.js +2 -1
  34. package/dist/api/workspaces.js.map +1 -1
  35. package/dist/api/workspaces.test.js +9 -1
  36. package/dist/api/workspaces.test.js.map +1 -1
  37. package/dist/cli/auth.d.ts.map +1 -1
  38. package/dist/cli/auth.js +2 -1
  39. package/dist/cli/auth.js.map +1 -1
  40. package/dist/cli/commands/build.d.ts +3 -4
  41. package/dist/cli/commands/build.d.ts.map +1 -1
  42. package/dist/cli/commands/build.js +23 -25
  43. package/dist/cli/commands/build.js.map +1 -1
  44. package/dist/cli/commands/deploy.d.ts +41 -0
  45. package/dist/cli/commands/deploy.d.ts.map +1 -0
  46. package/dist/cli/commands/deploy.js +92 -0
  47. package/dist/cli/commands/deploy.js.map +1 -0
  48. package/dist/cli/commands/dev.d.ts.map +1 -1
  49. package/dist/cli/commands/dev.js +7 -3
  50. package/dist/cli/commands/dev.js.map +1 -1
  51. package/dist/cli/commands/init.d.ts +38 -1
  52. package/dist/cli/commands/init.d.ts.map +1 -1
  53. package/dist/cli/commands/init.js +434 -23
  54. package/dist/cli/commands/init.js.map +1 -1
  55. package/dist/cli/commands/init.test.js +190 -30
  56. package/dist/cli/commands/init.test.js.map +1 -1
  57. package/dist/cli/index.js +80 -15
  58. package/dist/cli/index.js.map +1 -1
  59. package/dist/cli/utils/package-manager.d.ts +8 -0
  60. package/dist/cli/utils/package-manager.d.ts.map +1 -0
  61. package/dist/cli/utils/package-manager.js +45 -0
  62. package/dist/cli/utils/package-manager.js.map +1 -0
  63. package/dist/cli/utils/package-manager.test.d.ts +2 -0
  64. package/dist/cli/utils/package-manager.test.d.ts.map +1 -0
  65. package/dist/cli/utils/package-manager.test.js +85 -0
  66. package/dist/cli/utils/package-manager.test.js.map +1 -0
  67. package/dist/client/base.d.ts.map +1 -1
  68. package/dist/client/base.js +2 -1
  69. package/dist/client/base.js.map +1 -1
  70. package/dist/codegen/index.d.ts +39 -0
  71. package/dist/codegen/index.d.ts.map +1 -0
  72. package/dist/codegen/index.js +300 -0
  73. package/dist/codegen/index.js.map +1 -0
  74. package/dist/codegen/index.test.d.ts +2 -0
  75. package/dist/codegen/index.test.d.ts.map +1 -0
  76. package/dist/codegen/index.test.js +310 -0
  77. package/dist/codegen/index.test.js.map +1 -0
  78. package/dist/codegen/type-mapper.d.ts +20 -0
  79. package/dist/codegen/type-mapper.d.ts.map +1 -0
  80. package/dist/codegen/type-mapper.js +238 -0
  81. package/dist/codegen/type-mapper.js.map +1 -0
  82. package/dist/codegen/type-mapper.test.d.ts +2 -0
  83. package/dist/codegen/type-mapper.test.d.ts.map +1 -0
  84. package/dist/codegen/type-mapper.test.js +167 -0
  85. package/dist/codegen/type-mapper.test.js.map +1 -0
  86. package/dist/codegen/utils.d.ts +46 -0
  87. package/dist/codegen/utils.d.ts.map +1 -0
  88. package/dist/codegen/utils.js +141 -0
  89. package/dist/codegen/utils.js.map +1 -0
  90. package/dist/codegen/utils.test.d.ts +2 -0
  91. package/dist/codegen/utils.test.d.ts.map +1 -0
  92. package/dist/codegen/utils.test.js +178 -0
  93. package/dist/codegen/utils.test.js.map +1 -0
  94. package/dist/generator/index.d.ts +3 -0
  95. package/dist/generator/index.d.ts.map +1 -1
  96. package/dist/generator/index.js +17 -1
  97. package/dist/generator/index.js.map +1 -1
  98. package/dist/generator/index.test.js +104 -1
  99. package/dist/generator/index.test.js.map +1 -1
  100. package/dist/generator/loader.d.ts +15 -0
  101. package/dist/generator/loader.d.ts.map +1 -1
  102. package/dist/generator/loader.js +24 -0
  103. package/dist/generator/loader.js.map +1 -1
  104. package/dist/schema/connection.d.ts.map +1 -1
  105. package/dist/schema/connection.js +3 -2
  106. package/dist/schema/connection.js.map +1 -1
  107. package/dist/schema/datasource.d.ts.map +1 -1
  108. package/dist/schema/datasource.js +3 -2
  109. package/dist/schema/datasource.js.map +1 -1
  110. package/dist/schema/params.d.ts.map +1 -1
  111. package/dist/schema/params.js +3 -2
  112. package/dist/schema/params.js.map +1 -1
  113. package/dist/schema/pipe.d.ts +2 -2
  114. package/dist/schema/pipe.d.ts.map +1 -1
  115. package/dist/schema/pipe.js +4 -4
  116. package/dist/schema/pipe.js.map +1 -1
  117. package/dist/schema/project.d.ts.map +1 -1
  118. package/dist/schema/project.js +3 -2
  119. package/dist/schema/project.js.map +1 -1
  120. package/dist/schema/types.d.ts.map +1 -1
  121. package/dist/schema/types.js +3 -2
  122. package/dist/schema/types.js.map +1 -1
  123. package/dist/test/handlers.d.ts +49 -0
  124. package/dist/test/handlers.d.ts.map +1 -1
  125. package/dist/test/handlers.js +45 -0
  126. package/dist/test/handlers.js.map +1 -1
  127. package/package.json +4 -2
  128. package/src/api/branches.test.ts +65 -57
  129. package/src/api/branches.ts +7 -5
  130. package/src/api/build.ts +2 -1
  131. package/src/api/deploy.test.ts +141 -36
  132. package/src/api/deploy.ts +231 -23
  133. package/src/api/fetcher.ts +17 -0
  134. package/src/api/local.test.ts +43 -31
  135. package/src/api/local.ts +5 -4
  136. package/src/api/resources.test.ts +332 -0
  137. package/src/api/resources.ts +555 -0
  138. package/src/api/workspaces.test.ts +15 -9
  139. package/src/api/workspaces.ts +3 -1
  140. package/src/cli/auth.ts +2 -1
  141. package/src/cli/commands/build.ts +29 -33
  142. package/src/cli/commands/deploy.ts +131 -0
  143. package/src/cli/commands/dev.ts +10 -3
  144. package/src/cli/commands/init.test.ts +239 -30
  145. package/src/cli/commands/init.ts +548 -26
  146. package/src/cli/index.ts +117 -20
  147. package/src/cli/utils/package-manager.test.ts +118 -0
  148. package/src/cli/utils/package-manager.ts +44 -0
  149. package/src/client/base.ts +3 -2
  150. package/src/codegen/index.test.ts +367 -0
  151. package/src/codegen/index.ts +379 -0
  152. package/src/codegen/type-mapper.test.ts +224 -0
  153. package/src/codegen/type-mapper.ts +265 -0
  154. package/src/codegen/utils.test.ts +221 -0
  155. package/src/codegen/utils.ts +174 -0
  156. package/src/generator/index.test.ts +121 -1
  157. package/src/generator/index.ts +19 -1
  158. package/src/generator/loader.ts +43 -0
  159. package/src/schema/connection.ts +3 -2
  160. package/src/schema/datasource.ts +3 -2
  161. package/src/schema/params.ts +3 -2
  162. package/src/schema/pipe.ts +4 -4
  163. package/src/schema/project.ts +3 -2
  164. package/src/schema/types.ts +3 -2
  165. package/src/test/handlers.ts +58 -0
@@ -0,0 +1,555 @@
1
+ /**
2
+ * Tinybird Resources API client
3
+ * Functions to list and fetch datasources and pipes from a workspace
4
+ */
5
+
6
+ import type { WorkspaceApiConfig } from "./workspaces.js";
7
+ import { tinybirdFetch } from "./fetcher.js";
8
+
9
+ /**
10
+ * Error thrown by resource API operations
11
+ */
12
+ export class ResourceApiError extends Error {
13
+ constructor(
14
+ message: string,
15
+ public readonly status: number,
16
+ public readonly endpoint: string,
17
+ public readonly body?: unknown
18
+ ) {
19
+ super(message);
20
+ this.name = "ResourceApiError";
21
+ }
22
+ }
23
+
24
+ // ============ Datasource Types ============
25
+
26
+ /**
27
+ * Column information from Tinybird API
28
+ */
29
+ export interface DatasourceColumn {
30
+ /** Column name */
31
+ name: string;
32
+ /** ClickHouse type (e.g., "String", "DateTime", "Nullable(String)") */
33
+ type: string;
34
+ /** JSON path for JSON extraction */
35
+ jsonpath?: string;
36
+ /** Default value expression */
37
+ default?: string;
38
+ /** Codec for compression */
39
+ codec?: string;
40
+ }
41
+
42
+ /**
43
+ * Engine information from Tinybird API
44
+ */
45
+ export interface DatasourceEngine {
46
+ /** Engine type (e.g., "MergeTree", "ReplacingMergeTree") */
47
+ type: string;
48
+ /** Sorting key columns */
49
+ sorting_key?: string;
50
+ /** Partition key expression */
51
+ partition_key?: string;
52
+ /** Primary key columns */
53
+ primary_key?: string;
54
+ /** TTL expression */
55
+ ttl?: string;
56
+ /** Version column (ReplacingMergeTree) */
57
+ ver?: string;
58
+ /** Sign column (CollapsingMergeTree) */
59
+ sign?: string;
60
+ /** Version column (VersionedCollapsingMergeTree) */
61
+ version?: string;
62
+ /** Summing columns (SummingMergeTree) */
63
+ summing_columns?: string;
64
+ }
65
+
66
+ /**
67
+ * Full datasource information from Tinybird API
68
+ */
69
+ export interface DatasourceInfo {
70
+ /** Datasource name */
71
+ name: string;
72
+ /** Human-readable description */
73
+ description?: string;
74
+ /** Column definitions */
75
+ columns: DatasourceColumn[];
76
+ /** Engine configuration */
77
+ engine: DatasourceEngine;
78
+ }
79
+
80
+ /**
81
+ * Datasource list item from /v0/datasources
82
+ */
83
+ interface DatasourceListItem {
84
+ name: string;
85
+ description?: string;
86
+ }
87
+
88
+ /**
89
+ * Column definition from API response
90
+ */
91
+ interface ColumnResponse {
92
+ name: string;
93
+ type: string;
94
+ jsonpath?: string;
95
+ default_value?: string;
96
+ codec?: string;
97
+ nullable?: boolean;
98
+ }
99
+
100
+ /**
101
+ * Engine object from API response
102
+ * Note: The detail endpoint uses different property names than the list endpoint
103
+ */
104
+ interface EngineResponse {
105
+ engine?: string;
106
+ // Detail endpoint uses these names
107
+ sorting_key?: string;
108
+ partition_key?: string;
109
+ primary_key?: string;
110
+ // List endpoint uses these names
111
+ engine_sorting_key?: string;
112
+ engine_partition_key?: string;
113
+ engine_primary_key?: string;
114
+ // Other engine properties
115
+ engine_ver?: string;
116
+ engine_sign?: string;
117
+ engine_version?: string;
118
+ engine_summing_columns?: string;
119
+ }
120
+
121
+ /**
122
+ * Datasource detail response from /v0/datasources/{name}
123
+ */
124
+ interface DatasourceDetailResponse {
125
+ name: string;
126
+ description?: string;
127
+ // Detail endpoint has columns under schema.columns
128
+ schema?: {
129
+ columns?: ColumnResponse[];
130
+ };
131
+ // List endpoint has columns at top level
132
+ columns?: ColumnResponse[];
133
+ engine?: EngineResponse;
134
+ sorting_key?: string;
135
+ partition_key?: string;
136
+ primary_key?: string;
137
+ ttl?: string;
138
+ }
139
+
140
+ // ============ Pipe Types ============
141
+
142
+ /**
143
+ * Node information from a pipe
144
+ */
145
+ export interface PipeNode {
146
+ /** Node name */
147
+ name: string;
148
+ /** SQL query */
149
+ sql: string;
150
+ /** Node description */
151
+ description?: string;
152
+ }
153
+
154
+ /**
155
+ * Parameter information from a pipe
156
+ */
157
+ export interface PipeParam {
158
+ /** Parameter name */
159
+ name: string;
160
+ /** ClickHouse type */
161
+ type: string;
162
+ /** Default value */
163
+ default?: string | number;
164
+ /** Whether the parameter is required */
165
+ required: boolean;
166
+ /** Parameter description */
167
+ description?: string;
168
+ }
169
+
170
+ /**
171
+ * Pipe type classification
172
+ */
173
+ export type PipeType = "endpoint" | "materialized" | "copy" | "pipe";
174
+
175
+ /**
176
+ * Full pipe information from Tinybird API
177
+ */
178
+ export interface PipeInfo {
179
+ /** Pipe name */
180
+ name: string;
181
+ /** Human-readable description */
182
+ description?: string;
183
+ /** Nodes in the pipe */
184
+ nodes: PipeNode[];
185
+ /** Query parameters */
186
+ params: PipeParam[];
187
+ /** Pipe type */
188
+ type: PipeType;
189
+ /** Endpoint configuration (if type is endpoint) */
190
+ endpoint?: {
191
+ enabled: boolean;
192
+ cache?: { enabled: boolean; ttl?: number };
193
+ };
194
+ /** Materialized view configuration (if type is materialized) */
195
+ materialized?: {
196
+ datasource: string;
197
+ };
198
+ /** Copy pipe configuration (if type is copy) */
199
+ copy?: {
200
+ target_datasource: string;
201
+ copy_schedule?: string;
202
+ copy_mode?: "append" | "replace";
203
+ };
204
+ /** Output column schema (for endpoints) */
205
+ output_columns: DatasourceColumn[];
206
+ }
207
+
208
+ /**
209
+ * Pipe list item from /v0/pipes
210
+ */
211
+ interface PipeListItem {
212
+ name: string;
213
+ description?: string;
214
+ type?: string;
215
+ endpoint?: string;
216
+ }
217
+
218
+ /**
219
+ * Pipe detail response from /v0/pipes/{name}
220
+ */
221
+ interface PipeDetailResponse {
222
+ name: string;
223
+ description?: string;
224
+ nodes?: Array<{
225
+ name: string;
226
+ sql: string;
227
+ description?: string;
228
+ params?: Array<{
229
+ name: string;
230
+ type: string;
231
+ default?: string | number;
232
+ required?: boolean;
233
+ description?: string;
234
+ }>;
235
+ columns?: Array<{
236
+ name: string;
237
+ type: string;
238
+ }>;
239
+ }>;
240
+ type?: string;
241
+ endpoint?: string;
242
+ copy_target_datasource?: string;
243
+ copy_schedule?: string;
244
+ copy_mode?: string;
245
+ materialized_datasource?: string;
246
+ }
247
+
248
+ // ============ API Helper ============
249
+
250
+ /**
251
+ * Handle API response and throw appropriate errors
252
+ */
253
+ async function handleApiResponse<T>(
254
+ response: Response,
255
+ endpoint: string
256
+ ): Promise<T> {
257
+ if (response.status === 401) {
258
+ throw new ResourceApiError(
259
+ "Invalid or expired token",
260
+ 401,
261
+ endpoint
262
+ );
263
+ }
264
+ if (response.status === 403) {
265
+ throw new ResourceApiError(
266
+ "Insufficient permissions to access resources",
267
+ 403,
268
+ endpoint
269
+ );
270
+ }
271
+ if (response.status === 404) {
272
+ throw new ResourceApiError(
273
+ "Resource not found",
274
+ 404,
275
+ endpoint
276
+ );
277
+ }
278
+ if (!response.ok) {
279
+ const body = await response.text();
280
+ throw new ResourceApiError(
281
+ `API request failed: ${response.status} ${response.statusText}`,
282
+ response.status,
283
+ endpoint,
284
+ body
285
+ );
286
+ }
287
+ return response.json() as Promise<T>;
288
+ }
289
+
290
+ // ============ Datasource API ============
291
+
292
+ /**
293
+ * List all datasources in the workspace
294
+ *
295
+ * @param config - API configuration
296
+ * @returns Array of datasource names
297
+ */
298
+ export async function listDatasources(
299
+ config: WorkspaceApiConfig
300
+ ): Promise<string[]> {
301
+ const url = new URL("/v0/datasources", config.baseUrl);
302
+
303
+ const response = await tinybirdFetch(url.toString(), {
304
+ method: "GET",
305
+ headers: {
306
+ Authorization: `Bearer ${config.token}`,
307
+ },
308
+ });
309
+
310
+ const data = await handleApiResponse<{ datasources: DatasourceListItem[] }>(
311
+ response,
312
+ "/v0/datasources"
313
+ );
314
+
315
+ return data.datasources.map((ds) => ds.name);
316
+ }
317
+
318
+ /**
319
+ * Get detailed information about a specific datasource
320
+ *
321
+ * @param config - API configuration
322
+ * @param name - Datasource name
323
+ * @returns Datasource information including schema and engine
324
+ */
325
+ export async function getDatasource(
326
+ config: WorkspaceApiConfig,
327
+ name: string
328
+ ): Promise<DatasourceInfo> {
329
+ const url = new URL(`/v0/datasources/${encodeURIComponent(name)}`, config.baseUrl);
330
+
331
+ const response = await tinybirdFetch(url.toString(), {
332
+ method: "GET",
333
+ headers: {
334
+ Authorization: `Bearer ${config.token}`,
335
+ },
336
+ });
337
+
338
+ const data = await handleApiResponse<DatasourceDetailResponse>(
339
+ response,
340
+ `/v0/datasources/${name}`
341
+ );
342
+
343
+ // Extract columns from either schema.columns (detail) or columns (list)
344
+ const rawColumns = data.schema?.columns ?? data.columns ?? [];
345
+
346
+ // Extract engine info from the engine object
347
+ const engineObj = data.engine;
348
+ const engineType = parseEngineType(engineObj?.engine);
349
+
350
+ // Engine properties can be in different places depending on the endpoint
351
+ const sortingKey =
352
+ engineObj?.sorting_key ?? engineObj?.engine_sorting_key ?? data.sorting_key;
353
+ const partitionKey =
354
+ engineObj?.partition_key ?? engineObj?.engine_partition_key ?? data.partition_key;
355
+ const primaryKey =
356
+ engineObj?.primary_key ?? engineObj?.engine_primary_key ?? data.primary_key;
357
+
358
+ return {
359
+ name: data.name,
360
+ description: data.description,
361
+ columns: rawColumns.map((col) => ({
362
+ name: col.name,
363
+ type: col.type,
364
+ jsonpath: col.jsonpath,
365
+ default: col.default_value,
366
+ codec: col.codec,
367
+ })),
368
+ engine: {
369
+ type: engineType,
370
+ sorting_key: sortingKey,
371
+ partition_key: partitionKey,
372
+ primary_key: primaryKey,
373
+ ttl: data.ttl,
374
+ ver: engineObj?.engine_ver,
375
+ sign: engineObj?.engine_sign,
376
+ version: engineObj?.engine_version,
377
+ summing_columns: engineObj?.engine_summing_columns,
378
+ },
379
+ };
380
+ }
381
+
382
+ /**
383
+ * Parse engine type from engine string
384
+ */
385
+ function parseEngineType(engineString?: string): string {
386
+ if (!engineString) {
387
+ return "MergeTree";
388
+ }
389
+
390
+ // Engine string might be like "MergeTree" or "ReplacingMergeTree(version_column)"
391
+ const match = engineString.match(/^(\w+)/);
392
+ return match ? match[1] : "MergeTree";
393
+ }
394
+
395
+ // ============ Pipe API ============
396
+
397
+ /**
398
+ * List all pipes in the workspace
399
+ *
400
+ * @param config - API configuration
401
+ * @returns Array of pipe names
402
+ */
403
+ export async function listPipes(
404
+ config: WorkspaceApiConfig
405
+ ): Promise<string[]> {
406
+ const url = new URL("/v0/pipes", config.baseUrl);
407
+
408
+ const response = await tinybirdFetch(url.toString(), {
409
+ method: "GET",
410
+ headers: {
411
+ Authorization: `Bearer ${config.token}`,
412
+ },
413
+ });
414
+
415
+ const data = await handleApiResponse<{ pipes: PipeListItem[] }>(
416
+ response,
417
+ "/v0/pipes"
418
+ );
419
+
420
+ return data.pipes.map((p) => p.name);
421
+ }
422
+
423
+ /**
424
+ * Get detailed information about a specific pipe
425
+ *
426
+ * @param config - API configuration
427
+ * @param name - Pipe name
428
+ * @returns Pipe information including nodes, params, and output schema
429
+ */
430
+ export async function getPipe(
431
+ config: WorkspaceApiConfig,
432
+ name: string
433
+ ): Promise<PipeInfo> {
434
+ const url = new URL(`/v0/pipes/${encodeURIComponent(name)}`, config.baseUrl);
435
+
436
+ const response = await tinybirdFetch(url.toString(), {
437
+ method: "GET",
438
+ headers: {
439
+ Authorization: `Bearer ${config.token}`,
440
+ },
441
+ });
442
+
443
+ const data = await handleApiResponse<PipeDetailResponse>(
444
+ response,
445
+ `/v0/pipes/${name}`
446
+ );
447
+
448
+ // Determine pipe type
449
+ let pipeType: PipeType = "pipe";
450
+ if (data.endpoint) {
451
+ pipeType = "endpoint";
452
+ } else if (data.materialized_datasource) {
453
+ pipeType = "materialized";
454
+ } else if (data.copy_target_datasource) {
455
+ pipeType = "copy";
456
+ }
457
+
458
+ // Extract nodes
459
+ const nodes: PipeNode[] = (data.nodes ?? []).map((node) => ({
460
+ name: node.name,
461
+ sql: node.sql,
462
+ description: node.description,
463
+ }));
464
+
465
+ // Extract params from all nodes (they're typically on the first node)
466
+ const params: PipeParam[] = [];
467
+ const seenParams = new Set<string>();
468
+ for (const node of data.nodes ?? []) {
469
+ for (const param of node.params ?? []) {
470
+ if (!seenParams.has(param.name)) {
471
+ seenParams.add(param.name);
472
+ params.push({
473
+ name: param.name,
474
+ type: param.type,
475
+ default: param.default,
476
+ required: param.required ?? true,
477
+ description: param.description,
478
+ });
479
+ }
480
+ }
481
+ }
482
+
483
+ // Extract output columns from the last node
484
+ const lastNode = data.nodes?.[data.nodes.length - 1];
485
+ const outputColumns: DatasourceColumn[] = (lastNode?.columns ?? []).map((col) => ({
486
+ name: col.name,
487
+ type: col.type,
488
+ }));
489
+
490
+ return {
491
+ name: data.name,
492
+ description: data.description,
493
+ nodes,
494
+ params,
495
+ type: pipeType,
496
+ endpoint: pipeType === "endpoint" ? { enabled: true } : undefined,
497
+ materialized: data.materialized_datasource
498
+ ? { datasource: data.materialized_datasource }
499
+ : undefined,
500
+ copy: data.copy_target_datasource
501
+ ? {
502
+ target_datasource: data.copy_target_datasource,
503
+ copy_schedule: data.copy_schedule,
504
+ copy_mode: data.copy_mode as "append" | "replace" | undefined,
505
+ }
506
+ : undefined,
507
+ output_columns: outputColumns,
508
+ };
509
+ }
510
+
511
+ // ============ Convenience Functions ============
512
+
513
+ /**
514
+ * Fetch all resources from a workspace
515
+ *
516
+ * @param config - API configuration
517
+ * @returns All datasources and pipes with full details
518
+ */
519
+ export async function fetchAllResources(
520
+ config: WorkspaceApiConfig
521
+ ): Promise<{
522
+ datasources: DatasourceInfo[];
523
+ pipes: PipeInfo[];
524
+ }> {
525
+ // List all resources first
526
+ const [datasourceNames, pipeNames] = await Promise.all([
527
+ listDatasources(config),
528
+ listPipes(config),
529
+ ]);
530
+
531
+ // Fetch details in parallel
532
+ const [datasources, pipes] = await Promise.all([
533
+ Promise.all(datasourceNames.map((name) => getDatasource(config, name))),
534
+ Promise.all(pipeNames.map((name) => getPipe(config, name))),
535
+ ]);
536
+
537
+ return { datasources, pipes };
538
+ }
539
+
540
+ /**
541
+ * Check if a workspace has any resources
542
+ *
543
+ * @param config - API configuration
544
+ * @returns True if the workspace has at least one datasource or pipe
545
+ */
546
+ export async function hasResources(
547
+ config: WorkspaceApiConfig
548
+ ): Promise<boolean> {
549
+ const [datasourceNames, pipeNames] = await Promise.all([
550
+ listDatasources(config),
551
+ listPipes(config),
552
+ ]);
553
+
554
+ return datasourceNames.length > 0 || pipeNames.length > 0;
555
+ }
@@ -9,6 +9,12 @@ import {
9
9
  const mockFetch = vi.fn();
10
10
  global.fetch = mockFetch;
11
11
 
12
+ function expectFromParam(url: string) {
13
+ const parsed = new URL(url);
14
+ expect(parsed.searchParams.get("from")).toBe("ts-sdk");
15
+ return parsed;
16
+ }
17
+
12
18
  describe("Workspace API client", () => {
13
19
  const config: WorkspaceApiConfig = {
14
20
  baseUrl: "https://api.tinybird.co",
@@ -38,15 +44,15 @@ describe("Workspace API client", () => {
38
44
 
39
45
  const result = await getWorkspace(config);
40
46
 
41
- expect(mockFetch).toHaveBeenCalledWith(
42
- "https://api.tinybird.co/v1/workspace",
43
- {
44
- method: "GET",
45
- headers: {
46
- Authorization: "Bearer p.test-token",
47
- },
48
- }
49
- );
47
+ const [url, init] = mockFetch.mock.calls[0];
48
+ const parsed = expectFromParam(url);
49
+ expect(parsed.pathname).toBe("/v1/workspace");
50
+ expect(init).toEqual({
51
+ method: "GET",
52
+ headers: {
53
+ Authorization: "Bearer p.test-token",
54
+ },
55
+ });
50
56
  expect(result).toEqual(mockWorkspace);
51
57
  });
52
58
 
@@ -2,6 +2,8 @@
2
2
  * Tinybird Workspace API client
3
3
  */
4
4
 
5
+ import { tinybirdFetch } from "./fetcher.js";
6
+
5
7
  /**
6
8
  * Workspace information from Tinybird API
7
9
  */
@@ -56,7 +58,7 @@ export async function getWorkspace(
56
58
  ): Promise<TinybirdWorkspace> {
57
59
  const url = new URL("/v1/workspace", config.baseUrl);
58
60
 
59
- const response = await fetch(url.toString(), {
61
+ const response = await tinybirdFetch(url.toString(), {
60
62
  method: "GET",
61
63
  headers: {
62
64
  Authorization: `Bearer ${config.token}`,
package/src/cli/auth.ts CHANGED
@@ -8,6 +8,7 @@ import * as http from "node:http";
8
8
  import { spawn } from "node:child_process";
9
9
  import { platform } from "node:os";
10
10
  import { URL } from "node:url";
11
+ import { tinybirdFetch } from "../api/fetcher.js";
11
12
 
12
13
  /**
13
14
  * Port for the local OAuth callback server
@@ -259,7 +260,7 @@ export async function exchangeCodeForTokens(
259
260
  const url = new URL("/api/cli-login", authHost);
260
261
  url.searchParams.set("code", code);
261
262
 
262
- const response = await fetch(url.toString());
263
+ const response = await tinybirdFetch(url.toString());
263
264
 
264
265
  if (!response.ok) {
265
266
  const body = await response.text();