@tinybirdco/sdk 0.0.40 → 0.0.42
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 +42 -1
- package/dist/api/api.d.ts +2 -0
- package/dist/api/api.d.ts.map +1 -1
- package/dist/api/api.js +1 -1
- package/dist/api/api.js.map +1 -1
- package/dist/api/api.test.js +14 -0
- package/dist/api/api.test.js.map +1 -1
- package/dist/api/resources.d.ts +72 -1
- package/dist/api/resources.d.ts.map +1 -1
- package/dist/api/resources.js +197 -1
- package/dist/api/resources.js.map +1 -1
- package/dist/api/resources.test.js +82 -1
- package/dist/api/resources.test.js.map +1 -1
- package/dist/cli/commands/migrate.d.ts +11 -0
- package/dist/cli/commands/migrate.d.ts.map +1 -0
- package/dist/cli/commands/migrate.js +196 -0
- package/dist/cli/commands/migrate.js.map +1 -0
- package/dist/cli/commands/migrate.test.d.ts +2 -0
- package/dist/cli/commands/migrate.test.d.ts.map +1 -0
- package/dist/cli/commands/migrate.test.js +473 -0
- package/dist/cli/commands/migrate.test.js.map +1 -0
- package/dist/cli/commands/pull.d.ts +59 -0
- package/dist/cli/commands/pull.d.ts.map +1 -0
- package/dist/cli/commands/pull.js +104 -0
- package/dist/cli/commands/pull.js.map +1 -0
- package/dist/cli/commands/pull.test.d.ts +2 -0
- package/dist/cli/commands/pull.test.d.ts.map +1 -0
- package/dist/cli/commands/pull.test.js +140 -0
- package/dist/cli/commands/pull.test.js.map +1 -0
- package/dist/cli/index.js +77 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/client/base.d.ts +18 -1
- package/dist/client/base.d.ts.map +1 -1
- package/dist/client/base.js +43 -2
- package/dist/client/base.js.map +1 -1
- package/dist/client/base.test.js +5 -1
- package/dist/client/base.test.js.map +1 -1
- package/dist/client/types.d.ts +4 -0
- package/dist/client/types.d.ts.map +1 -1
- package/dist/migrate/discovery.d.ts +7 -0
- package/dist/migrate/discovery.d.ts.map +1 -0
- package/dist/migrate/discovery.js +125 -0
- package/dist/migrate/discovery.js.map +1 -0
- package/dist/migrate/emit-ts.d.ts +4 -0
- package/dist/migrate/emit-ts.d.ts.map +1 -0
- package/dist/migrate/emit-ts.js +387 -0
- package/dist/migrate/emit-ts.js.map +1 -0
- package/dist/migrate/parse-connection.d.ts +3 -0
- package/dist/migrate/parse-connection.d.ts.map +1 -0
- package/dist/migrate/parse-connection.js +74 -0
- package/dist/migrate/parse-connection.js.map +1 -0
- package/dist/migrate/parse-datasource.d.ts +3 -0
- package/dist/migrate/parse-datasource.d.ts.map +1 -0
- package/dist/migrate/parse-datasource.js +324 -0
- package/dist/migrate/parse-datasource.js.map +1 -0
- package/dist/migrate/parse-pipe.d.ts +3 -0
- package/dist/migrate/parse-pipe.d.ts.map +1 -0
- package/dist/migrate/parse-pipe.js +332 -0
- package/dist/migrate/parse-pipe.js.map +1 -0
- package/dist/migrate/parse.d.ts +3 -0
- package/dist/migrate/parse.d.ts.map +1 -0
- package/dist/migrate/parse.js +18 -0
- package/dist/migrate/parse.js.map +1 -0
- package/dist/migrate/parser-utils.d.ts +20 -0
- package/dist/migrate/parser-utils.d.ts.map +1 -0
- package/dist/migrate/parser-utils.js +130 -0
- package/dist/migrate/parser-utils.js.map +1 -0
- package/dist/migrate/types.d.ts +110 -0
- package/dist/migrate/types.d.ts.map +1 -0
- package/dist/migrate/types.js +2 -0
- package/dist/migrate/types.js.map +1 -0
- package/dist/schema/project.d.ts +13 -27
- package/dist/schema/project.d.ts.map +1 -1
- package/dist/schema/project.js +14 -24
- package/dist/schema/project.js.map +1 -1
- package/dist/schema/project.test.js +25 -13
- package/dist/schema/project.test.js.map +1 -1
- package/package.json +1 -1
- package/src/api/api.test.ts +25 -0
- package/src/api/api.ts +3 -1
- package/src/api/resources.test.ts +121 -0
- package/src/api/resources.ts +292 -1
- package/src/cli/commands/migrate.test.ts +564 -0
- package/src/cli/commands/migrate.ts +240 -0
- package/src/cli/commands/pull.test.ts +173 -0
- package/src/cli/commands/pull.ts +177 -0
- package/src/cli/index.ts +112 -0
- package/src/client/base.test.ts +5 -1
- package/src/client/base.ts +56 -2
- package/src/client/types.ts +8 -0
- package/src/migrate/discovery.ts +151 -0
- package/src/migrate/emit-ts.ts +469 -0
- package/src/migrate/parse-connection.ts +128 -0
- package/src/migrate/parse-datasource.ts +453 -0
- package/src/migrate/parse-pipe.ts +518 -0
- package/src/migrate/parse.ts +20 -0
- package/src/migrate/parser-utils.ts +160 -0
- package/src/migrate/types.ts +125 -0
- package/src/schema/project.test.ts +25 -13
- package/src/schema/project.ts +33 -57
|
@@ -5,7 +5,13 @@ import {
|
|
|
5
5
|
listDatasources,
|
|
6
6
|
getDatasource,
|
|
7
7
|
listPipes,
|
|
8
|
+
listPipesV1,
|
|
8
9
|
getPipe,
|
|
10
|
+
listConnectors,
|
|
11
|
+
getDatasourceFile,
|
|
12
|
+
getPipeFile,
|
|
13
|
+
getConnectorFile,
|
|
14
|
+
pullAllResourceFiles,
|
|
9
15
|
fetchAllResources,
|
|
10
16
|
hasResources,
|
|
11
17
|
ResourceApiError,
|
|
@@ -66,6 +72,13 @@ const handlers = [
|
|
|
66
72
|
});
|
|
67
73
|
}),
|
|
68
74
|
|
|
75
|
+
// List pipes v1
|
|
76
|
+
http.get(`${BASE_URL}/v1/pipes`, () => {
|
|
77
|
+
return HttpResponse.json({
|
|
78
|
+
pipes: [{ name: "top_events" }, { name: "daily_stats_mv" }],
|
|
79
|
+
});
|
|
80
|
+
}),
|
|
81
|
+
|
|
69
82
|
// Get pipe detail - endpoint
|
|
70
83
|
http.get(`${BASE_URL}/v0/pipes/top_events`, () => {
|
|
71
84
|
return HttpResponse.json({
|
|
@@ -119,6 +132,45 @@ const handlers = [
|
|
|
119
132
|
],
|
|
120
133
|
});
|
|
121
134
|
}),
|
|
135
|
+
|
|
136
|
+
// List connectors
|
|
137
|
+
http.get(`${BASE_URL}/v0/connectors`, () => {
|
|
138
|
+
return HttpResponse.json({
|
|
139
|
+
connectors: [{ name: "main_kafka" }],
|
|
140
|
+
});
|
|
141
|
+
}),
|
|
142
|
+
|
|
143
|
+
// Raw datasource datafile
|
|
144
|
+
http.get(`${BASE_URL}/v0/datasources/events.datasource`, () => {
|
|
145
|
+
return new HttpResponse(
|
|
146
|
+
"SCHEMA >\n timestamp DateTime,\n event_name String",
|
|
147
|
+
{ headers: { "Content-Type": "text/plain" } }
|
|
148
|
+
);
|
|
149
|
+
}),
|
|
150
|
+
http.get(`${BASE_URL}/v0/datasources/users.datasource`, () => {
|
|
151
|
+
return new HttpResponse("SCHEMA >\n user_id String", {
|
|
152
|
+
headers: { "Content-Type": "text/plain" },
|
|
153
|
+
});
|
|
154
|
+
}),
|
|
155
|
+
|
|
156
|
+
// Raw pipe datafiles
|
|
157
|
+
http.get(`${BASE_URL}/v1/pipes/top_events.pipe`, () => {
|
|
158
|
+
return new HttpResponse("NODE endpoint\nSQL >\n SELECT 1", {
|
|
159
|
+
headers: { "Content-Type": "text/plain" },
|
|
160
|
+
});
|
|
161
|
+
}),
|
|
162
|
+
http.get(`${BASE_URL}/v1/pipes/daily_stats_mv.pipe`, () => {
|
|
163
|
+
return new HttpResponse("TYPE MATERIALIZED\nDATASOURCE daily_stats", {
|
|
164
|
+
headers: { "Content-Type": "text/plain" },
|
|
165
|
+
});
|
|
166
|
+
}),
|
|
167
|
+
|
|
168
|
+
// Raw connector datafile
|
|
169
|
+
http.get(`${BASE_URL}/v0/connectors/main_kafka.connection`, () => {
|
|
170
|
+
return new HttpResponse("TYPE kafka\nKAFKA_BOOTSTRAP_SERVERS localhost:9092", {
|
|
171
|
+
headers: { "Content-Type": "text/plain" },
|
|
172
|
+
});
|
|
173
|
+
}),
|
|
122
174
|
];
|
|
123
175
|
|
|
124
176
|
const server = setupServer(...handlers);
|
|
@@ -207,6 +259,26 @@ describe("listPipes", () => {
|
|
|
207
259
|
});
|
|
208
260
|
});
|
|
209
261
|
|
|
262
|
+
describe("listPipesV1", () => {
|
|
263
|
+
it("returns array of pipe names from /v1/pipes", async () => {
|
|
264
|
+
const result = await listPipesV1({ baseUrl: BASE_URL, token: TOKEN });
|
|
265
|
+
|
|
266
|
+
expect(result).toEqual(["top_events", "daily_stats_mv"]);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("falls back to /v0/pipes when /v1/pipes is unavailable", async () => {
|
|
270
|
+
server.use(
|
|
271
|
+
http.get(`${BASE_URL}/v1/pipes`, () => {
|
|
272
|
+
return new HttpResponse(null, { status: 404 });
|
|
273
|
+
})
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
const result = await listPipesV1({ baseUrl: BASE_URL, token: TOKEN });
|
|
277
|
+
|
|
278
|
+
expect(result).toEqual(["top_events", "daily_stats_mv"]);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
210
282
|
describe("getPipe", () => {
|
|
211
283
|
it("returns endpoint pipe info", async () => {
|
|
212
284
|
const result = await getPipe({ baseUrl: BASE_URL, token: TOKEN }, "top_events");
|
|
@@ -264,6 +336,55 @@ describe("fetchAllResources", () => {
|
|
|
264
336
|
});
|
|
265
337
|
});
|
|
266
338
|
|
|
339
|
+
describe("raw datafile APIs", () => {
|
|
340
|
+
it("lists connectors", async () => {
|
|
341
|
+
const result = await listConnectors({ baseUrl: BASE_URL, token: TOKEN });
|
|
342
|
+
expect(result).toEqual(["main_kafka"]);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it("gets datasource .datasource content", async () => {
|
|
346
|
+
const result = await getDatasourceFile(
|
|
347
|
+
{ baseUrl: BASE_URL, token: TOKEN },
|
|
348
|
+
"events"
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
expect(result).toContain("SCHEMA >");
|
|
352
|
+
expect(result).toContain("timestamp DateTime");
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it("gets pipe .pipe content", async () => {
|
|
356
|
+
const result = await getPipeFile(
|
|
357
|
+
{ baseUrl: BASE_URL, token: TOKEN },
|
|
358
|
+
"top_events"
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
expect(result).toContain("NODE endpoint");
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it("gets connector .connection content", async () => {
|
|
365
|
+
const result = await getConnectorFile(
|
|
366
|
+
{ baseUrl: BASE_URL, token: TOKEN },
|
|
367
|
+
"main_kafka"
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
expect(result).toContain("TYPE kafka");
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
describe("pullAllResourceFiles", () => {
|
|
375
|
+
it("pulls datasources, pipes, and connectors as raw datafiles", async () => {
|
|
376
|
+
const result = await pullAllResourceFiles({ baseUrl: BASE_URL, token: TOKEN });
|
|
377
|
+
|
|
378
|
+
expect(result.datasources).toHaveLength(2);
|
|
379
|
+
expect(result.pipes).toHaveLength(2);
|
|
380
|
+
expect(result.connections).toHaveLength(1);
|
|
381
|
+
|
|
382
|
+
expect(result.datasources[0]?.filename).toMatch(/\.datasource$/);
|
|
383
|
+
expect(result.pipes[0]?.filename).toMatch(/\.pipe$/);
|
|
384
|
+
expect(result.connections[0]?.filename).toMatch(/\.connection$/);
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
267
388
|
describe("hasResources", () => {
|
|
268
389
|
it("returns true when workspace has resources", async () => {
|
|
269
390
|
const result = await hasResources({ baseUrl: BASE_URL, token: TOKEN });
|
package/src/api/resources.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tinybird Resources API client
|
|
3
|
-
* Functions to list and fetch
|
|
3
|
+
* Functions to list and fetch resources from a workspace
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { WorkspaceApiConfig } from "./workspaces.js";
|
|
@@ -248,6 +248,36 @@ interface PipeDetailResponse {
|
|
|
248
248
|
materialized_datasource?: string;
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
// ============ Connector/Datafile Types ============
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Resource file type returned by pull operations
|
|
255
|
+
*/
|
|
256
|
+
export type ResourceFileType = "datasource" | "pipe" | "connection";
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Raw Tinybird datafile pulled from API
|
|
260
|
+
*/
|
|
261
|
+
export interface ResourceFile {
|
|
262
|
+
/** Resource name (without extension) */
|
|
263
|
+
name: string;
|
|
264
|
+
/** Resource kind */
|
|
265
|
+
type: ResourceFileType;
|
|
266
|
+
/** Filename with extension */
|
|
267
|
+
filename: string;
|
|
268
|
+
/** Raw datafile content */
|
|
269
|
+
content: string;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Grouped resource files returned by pull operations
|
|
274
|
+
*/
|
|
275
|
+
export interface PulledResourceFiles {
|
|
276
|
+
datasources: ResourceFile[];
|
|
277
|
+
pipes: ResourceFile[];
|
|
278
|
+
connections: ResourceFile[];
|
|
279
|
+
}
|
|
280
|
+
|
|
251
281
|
// ============ API Helper ============
|
|
252
282
|
|
|
253
283
|
/**
|
|
@@ -290,6 +320,107 @@ async function handleApiResponse<T>(
|
|
|
290
320
|
return response.json() as Promise<T>;
|
|
291
321
|
}
|
|
292
322
|
|
|
323
|
+
/**
|
|
324
|
+
* Handle API text response and throw appropriate errors
|
|
325
|
+
*/
|
|
326
|
+
async function handleApiTextResponse(
|
|
327
|
+
response: Response,
|
|
328
|
+
endpoint: string
|
|
329
|
+
): Promise<string> {
|
|
330
|
+
if (response.status === 401) {
|
|
331
|
+
throw new ResourceApiError("Invalid or expired token", 401, endpoint);
|
|
332
|
+
}
|
|
333
|
+
if (response.status === 403) {
|
|
334
|
+
throw new ResourceApiError(
|
|
335
|
+
"Insufficient permissions to access resources",
|
|
336
|
+
403,
|
|
337
|
+
endpoint
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
if (response.status === 404) {
|
|
341
|
+
throw new ResourceApiError("Resource not found", 404, endpoint);
|
|
342
|
+
}
|
|
343
|
+
if (!response.ok) {
|
|
344
|
+
const body = await response.text();
|
|
345
|
+
throw new ResourceApiError(
|
|
346
|
+
`API request failed: ${response.status} ${response.statusText}`,
|
|
347
|
+
response.status,
|
|
348
|
+
endpoint,
|
|
349
|
+
body
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
return response.text();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Extract resource names from API response arrays
|
|
357
|
+
*/
|
|
358
|
+
function extractNames(
|
|
359
|
+
data: Record<string, unknown>,
|
|
360
|
+
keys: string[]
|
|
361
|
+
): string[] {
|
|
362
|
+
for (const key of keys) {
|
|
363
|
+
const value = data[key];
|
|
364
|
+
if (!Array.isArray(value)) {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const names = value
|
|
369
|
+
.map((item) => {
|
|
370
|
+
if (typeof item === "string") {
|
|
371
|
+
return item;
|
|
372
|
+
}
|
|
373
|
+
if (
|
|
374
|
+
typeof item === "object" &&
|
|
375
|
+
item !== null &&
|
|
376
|
+
"name" in item &&
|
|
377
|
+
typeof (item as { name: unknown }).name === "string"
|
|
378
|
+
) {
|
|
379
|
+
return (item as { name: string }).name;
|
|
380
|
+
}
|
|
381
|
+
return null;
|
|
382
|
+
})
|
|
383
|
+
.filter((name): name is string => name !== null);
|
|
384
|
+
|
|
385
|
+
return names;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return [];
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Fetch text resource from the first successful endpoint.
|
|
393
|
+
* Falls back on 404 responses.
|
|
394
|
+
*/
|
|
395
|
+
async function fetchTextFromAnyEndpoint(
|
|
396
|
+
config: WorkspaceApiConfig,
|
|
397
|
+
endpoints: string[]
|
|
398
|
+
): Promise<string> {
|
|
399
|
+
let lastNotFound: ResourceApiError | null = null;
|
|
400
|
+
|
|
401
|
+
for (const endpoint of endpoints) {
|
|
402
|
+
const url = new URL(endpoint, config.baseUrl);
|
|
403
|
+
const response = await tinybirdFetch(url.toString(), {
|
|
404
|
+
method: "GET",
|
|
405
|
+
headers: {
|
|
406
|
+
Authorization: `Bearer ${config.token}`,
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
if (response.status === 404) {
|
|
411
|
+
lastNotFound = new ResourceApiError("Resource not found", 404, endpoint);
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return handleApiTextResponse(response, endpoint);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
throw (
|
|
419
|
+
lastNotFound ??
|
|
420
|
+
new ResourceApiError("Resource not found", 404, endpoints[0] ?? "unknown")
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
293
424
|
// ============ Datasource API ============
|
|
294
425
|
|
|
295
426
|
/**
|
|
@@ -424,6 +555,117 @@ export async function listPipes(
|
|
|
424
555
|
return data.pipes.map((p) => p.name);
|
|
425
556
|
}
|
|
426
557
|
|
|
558
|
+
/**
|
|
559
|
+
* List all pipes from the v1 endpoint.
|
|
560
|
+
* Falls back to v0 when v1 is unavailable.
|
|
561
|
+
*
|
|
562
|
+
* @param config - API configuration
|
|
563
|
+
* @returns Array of pipe names
|
|
564
|
+
*/
|
|
565
|
+
export async function listPipesV1(config: WorkspaceApiConfig): Promise<string[]> {
|
|
566
|
+
const endpoint = "/v1/pipes";
|
|
567
|
+
const url = new URL(endpoint, config.baseUrl);
|
|
568
|
+
|
|
569
|
+
const response = await tinybirdFetch(url.toString(), {
|
|
570
|
+
method: "GET",
|
|
571
|
+
headers: {
|
|
572
|
+
Authorization: `Bearer ${config.token}`,
|
|
573
|
+
},
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
// Older/self-hosted versions may not expose /v1/pipes.
|
|
577
|
+
if (response.status === 404) {
|
|
578
|
+
return listPipes(config);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const data = await handleApiResponse<Record<string, unknown>>(response, endpoint);
|
|
582
|
+
return extractNames(data, ["pipes", "data"]);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Get a datasource as native .datasource text
|
|
587
|
+
*
|
|
588
|
+
* @param config - API configuration
|
|
589
|
+
* @param name - Datasource name
|
|
590
|
+
* @returns Raw .datasource content
|
|
591
|
+
*/
|
|
592
|
+
export async function getDatasourceFile(
|
|
593
|
+
config: WorkspaceApiConfig,
|
|
594
|
+
name: string
|
|
595
|
+
): Promise<string> {
|
|
596
|
+
const encoded = encodeURIComponent(name);
|
|
597
|
+
return fetchTextFromAnyEndpoint(config, [
|
|
598
|
+
`/v0/datasources/${encoded}.datasource`,
|
|
599
|
+
`/v0/datasources/${encoded}?format=datasource`,
|
|
600
|
+
]);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Get a pipe as native .pipe text
|
|
605
|
+
*
|
|
606
|
+
* @param config - API configuration
|
|
607
|
+
* @param name - Pipe name
|
|
608
|
+
* @returns Raw .pipe content
|
|
609
|
+
*/
|
|
610
|
+
export async function getPipeFile(
|
|
611
|
+
config: WorkspaceApiConfig,
|
|
612
|
+
name: string
|
|
613
|
+
): Promise<string> {
|
|
614
|
+
const encoded = encodeURIComponent(name);
|
|
615
|
+
return fetchTextFromAnyEndpoint(config, [
|
|
616
|
+
`/v1/pipes/${encoded}.pipe`,
|
|
617
|
+
`/v0/pipes/${encoded}.pipe`,
|
|
618
|
+
`/v1/pipes/${encoded}?format=pipe`,
|
|
619
|
+
`/v0/pipes/${encoded}?format=pipe`,
|
|
620
|
+
]);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* List all connectors in the workspace
|
|
625
|
+
*
|
|
626
|
+
* @param config - API configuration
|
|
627
|
+
* @returns Array of connector names
|
|
628
|
+
*/
|
|
629
|
+
export async function listConnectors(
|
|
630
|
+
config: WorkspaceApiConfig
|
|
631
|
+
): Promise<string[]> {
|
|
632
|
+
const endpoint = "/v0/connectors";
|
|
633
|
+
const url = new URL(endpoint, config.baseUrl);
|
|
634
|
+
|
|
635
|
+
const response = await tinybirdFetch(url.toString(), {
|
|
636
|
+
method: "GET",
|
|
637
|
+
headers: {
|
|
638
|
+
Authorization: `Bearer ${config.token}`,
|
|
639
|
+
},
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
// Not all workspaces expose connectors. Treat missing endpoint as no connectors.
|
|
643
|
+
if (response.status === 404) {
|
|
644
|
+
return [];
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const data = await handleApiResponse<Record<string, unknown>>(response, endpoint);
|
|
648
|
+
return extractNames(data, ["connectors", "connections"]);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Get a connector as native .connection text
|
|
653
|
+
*
|
|
654
|
+
* @param config - API configuration
|
|
655
|
+
* @param name - Connector name
|
|
656
|
+
* @returns Raw .connection content
|
|
657
|
+
*/
|
|
658
|
+
export async function getConnectorFile(
|
|
659
|
+
config: WorkspaceApiConfig,
|
|
660
|
+
name: string
|
|
661
|
+
): Promise<string> {
|
|
662
|
+
const encoded = encodeURIComponent(name);
|
|
663
|
+
return fetchTextFromAnyEndpoint(config, [
|
|
664
|
+
`/v0/connectors/${encoded}.connection`,
|
|
665
|
+
`/v0/connectors/${encoded}?format=connection`,
|
|
666
|
+
]);
|
|
667
|
+
}
|
|
668
|
+
|
|
427
669
|
/**
|
|
428
670
|
* Get detailed information about a specific pipe
|
|
429
671
|
*
|
|
@@ -541,6 +783,55 @@ export async function fetchAllResources(
|
|
|
541
783
|
return { datasources, pipes };
|
|
542
784
|
}
|
|
543
785
|
|
|
786
|
+
/**
|
|
787
|
+
* Pull all datasource/pipe/connector datafiles from a workspace
|
|
788
|
+
*
|
|
789
|
+
* @param config - API configuration
|
|
790
|
+
* @returns Raw resource files grouped by type
|
|
791
|
+
*/
|
|
792
|
+
export async function pullAllResourceFiles(
|
|
793
|
+
config: WorkspaceApiConfig
|
|
794
|
+
): Promise<PulledResourceFiles> {
|
|
795
|
+
const [datasourceNames, pipeNames, connectorNames] = await Promise.all([
|
|
796
|
+
listDatasources(config),
|
|
797
|
+
listPipesV1(config),
|
|
798
|
+
listConnectors(config),
|
|
799
|
+
]);
|
|
800
|
+
|
|
801
|
+
const [datasources, pipes, connections] = await Promise.all([
|
|
802
|
+
Promise.all(
|
|
803
|
+
datasourceNames.map(async (name): Promise<ResourceFile> => ({
|
|
804
|
+
name,
|
|
805
|
+
type: "datasource",
|
|
806
|
+
filename: `${name}.datasource`,
|
|
807
|
+
content: await getDatasourceFile(config, name),
|
|
808
|
+
}))
|
|
809
|
+
),
|
|
810
|
+
Promise.all(
|
|
811
|
+
pipeNames.map(async (name): Promise<ResourceFile> => ({
|
|
812
|
+
name,
|
|
813
|
+
type: "pipe",
|
|
814
|
+
filename: `${name}.pipe`,
|
|
815
|
+
content: await getPipeFile(config, name),
|
|
816
|
+
}))
|
|
817
|
+
),
|
|
818
|
+
Promise.all(
|
|
819
|
+
connectorNames.map(async (name): Promise<ResourceFile> => ({
|
|
820
|
+
name,
|
|
821
|
+
type: "connection",
|
|
822
|
+
filename: `${name}.connection`,
|
|
823
|
+
content: await getConnectorFile(config, name),
|
|
824
|
+
}))
|
|
825
|
+
),
|
|
826
|
+
]);
|
|
827
|
+
|
|
828
|
+
return {
|
|
829
|
+
datasources,
|
|
830
|
+
pipes,
|
|
831
|
+
connections,
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
|
|
544
835
|
/**
|
|
545
836
|
* Check if a workspace has any resources
|
|
546
837
|
*
|