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