@tinybirdco/sdk 0.0.41 → 0.0.43

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 (95) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +29 -3
  3. package/dist/api/resources.d.ts +72 -1
  4. package/dist/api/resources.d.ts.map +1 -1
  5. package/dist/api/resources.js +197 -1
  6. package/dist/api/resources.js.map +1 -1
  7. package/dist/api/resources.test.js +82 -1
  8. package/dist/api/resources.test.js.map +1 -1
  9. package/dist/cli/commands/migrate.d.ts +11 -0
  10. package/dist/cli/commands/migrate.d.ts.map +1 -0
  11. package/dist/cli/commands/migrate.js +196 -0
  12. package/dist/cli/commands/migrate.js.map +1 -0
  13. package/dist/cli/commands/migrate.test.d.ts +2 -0
  14. package/dist/cli/commands/migrate.test.d.ts.map +1 -0
  15. package/dist/cli/commands/migrate.test.js +473 -0
  16. package/dist/cli/commands/migrate.test.js.map +1 -0
  17. package/dist/cli/commands/pull.d.ts +59 -0
  18. package/dist/cli/commands/pull.d.ts.map +1 -0
  19. package/dist/cli/commands/pull.js +104 -0
  20. package/dist/cli/commands/pull.js.map +1 -0
  21. package/dist/cli/commands/pull.test.d.ts +2 -0
  22. package/dist/cli/commands/pull.test.d.ts.map +1 -0
  23. package/dist/cli/commands/pull.test.js +140 -0
  24. package/dist/cli/commands/pull.test.js.map +1 -0
  25. package/dist/cli/config.d.ts +10 -0
  26. package/dist/cli/config.d.ts.map +1 -1
  27. package/dist/cli/config.js +22 -0
  28. package/dist/cli/config.js.map +1 -1
  29. package/dist/cli/index.js +77 -0
  30. package/dist/cli/index.js.map +1 -1
  31. package/dist/generator/client.js +2 -2
  32. package/dist/generator/client.js.map +1 -1
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1 -1
  36. package/dist/index.js.map +1 -1
  37. package/dist/migrate/discovery.d.ts +7 -0
  38. package/dist/migrate/discovery.d.ts.map +1 -0
  39. package/dist/migrate/discovery.js +125 -0
  40. package/dist/migrate/discovery.js.map +1 -0
  41. package/dist/migrate/emit-ts.d.ts +4 -0
  42. package/dist/migrate/emit-ts.d.ts.map +1 -0
  43. package/dist/migrate/emit-ts.js +387 -0
  44. package/dist/migrate/emit-ts.js.map +1 -0
  45. package/dist/migrate/parse-connection.d.ts +3 -0
  46. package/dist/migrate/parse-connection.d.ts.map +1 -0
  47. package/dist/migrate/parse-connection.js +74 -0
  48. package/dist/migrate/parse-connection.js.map +1 -0
  49. package/dist/migrate/parse-datasource.d.ts +3 -0
  50. package/dist/migrate/parse-datasource.d.ts.map +1 -0
  51. package/dist/migrate/parse-datasource.js +324 -0
  52. package/dist/migrate/parse-datasource.js.map +1 -0
  53. package/dist/migrate/parse-pipe.d.ts +3 -0
  54. package/dist/migrate/parse-pipe.d.ts.map +1 -0
  55. package/dist/migrate/parse-pipe.js +332 -0
  56. package/dist/migrate/parse-pipe.js.map +1 -0
  57. package/dist/migrate/parse.d.ts +3 -0
  58. package/dist/migrate/parse.d.ts.map +1 -0
  59. package/dist/migrate/parse.js +18 -0
  60. package/dist/migrate/parse.js.map +1 -0
  61. package/dist/migrate/parser-utils.d.ts +20 -0
  62. package/dist/migrate/parser-utils.d.ts.map +1 -0
  63. package/dist/migrate/parser-utils.js +130 -0
  64. package/dist/migrate/parser-utils.js.map +1 -0
  65. package/dist/migrate/types.d.ts +110 -0
  66. package/dist/migrate/types.d.ts.map +1 -0
  67. package/dist/migrate/types.js +2 -0
  68. package/dist/migrate/types.js.map +1 -0
  69. package/dist/schema/project.d.ts +20 -9
  70. package/dist/schema/project.d.ts.map +1 -1
  71. package/dist/schema/project.js +127 -136
  72. package/dist/schema/project.js.map +1 -1
  73. package/dist/schema/project.test.js +22 -0
  74. package/dist/schema/project.test.js.map +1 -1
  75. package/package.json +2 -1
  76. package/src/api/resources.test.ts +121 -0
  77. package/src/api/resources.ts +292 -1
  78. package/src/cli/commands/migrate.test.ts +564 -0
  79. package/src/cli/commands/migrate.ts +240 -0
  80. package/src/cli/commands/pull.test.ts +173 -0
  81. package/src/cli/commands/pull.ts +177 -0
  82. package/src/cli/config.ts +26 -0
  83. package/src/cli/index.ts +112 -0
  84. package/src/generator/client.ts +2 -2
  85. package/src/index.ts +1 -1
  86. package/src/migrate/discovery.ts +151 -0
  87. package/src/migrate/emit-ts.ts +469 -0
  88. package/src/migrate/parse-connection.ts +128 -0
  89. package/src/migrate/parse-datasource.ts +453 -0
  90. package/src/migrate/parse-pipe.ts +518 -0
  91. package/src/migrate/parse.ts +20 -0
  92. package/src/migrate/parser-utils.ts +160 -0
  93. package/src/migrate/types.ts +125 -0
  94. package/src/schema/project.test.ts +28 -0
  95. package/src/schema/project.ts +173 -181
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Tinybird Resources API client
3
- * Functions to list and fetch datasources and pipes from a workspace
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
  *