@tinybirdco/sdk 0.0.47 → 0.0.49

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 (107) hide show
  1. package/README.md +53 -3
  2. package/dist/cli/commands/migrate.d.ts.map +1 -1
  3. package/dist/cli/commands/migrate.js +32 -0
  4. package/dist/cli/commands/migrate.js.map +1 -1
  5. package/dist/cli/commands/migrate.test.js +585 -8
  6. package/dist/cli/commands/migrate.test.js.map +1 -1
  7. package/dist/generator/connection.d.ts.map +1 -1
  8. package/dist/generator/connection.js +3 -0
  9. package/dist/generator/connection.js.map +1 -1
  10. package/dist/generator/connection.test.js +8 -0
  11. package/dist/generator/connection.test.js.map +1 -1
  12. package/dist/generator/datasource.d.ts.map +1 -1
  13. package/dist/generator/datasource.js +3 -0
  14. package/dist/generator/datasource.js.map +1 -1
  15. package/dist/generator/datasource.test.js +50 -0
  16. package/dist/generator/datasource.test.js.map +1 -1
  17. package/dist/generator/pipe.d.ts.map +1 -1
  18. package/dist/generator/pipe.js +31 -1
  19. package/dist/generator/pipe.js.map +1 -1
  20. package/dist/generator/pipe.test.js +50 -1
  21. package/dist/generator/pipe.test.js.map +1 -1
  22. package/dist/index.d.ts +3 -2
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +3 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/index.test.js +3 -0
  27. package/dist/index.test.js.map +1 -1
  28. package/dist/migrate/emit-ts.d.ts.map +1 -1
  29. package/dist/migrate/emit-ts.js +159 -41
  30. package/dist/migrate/emit-ts.js.map +1 -1
  31. package/dist/migrate/parse-connection.d.ts.map +1 -1
  32. package/dist/migrate/parse-connection.js +13 -2
  33. package/dist/migrate/parse-connection.js.map +1 -1
  34. package/dist/migrate/parse-datasource.d.ts.map +1 -1
  35. package/dist/migrate/parse-datasource.js +115 -52
  36. package/dist/migrate/parse-datasource.js.map +1 -1
  37. package/dist/migrate/parse-pipe.d.ts.map +1 -1
  38. package/dist/migrate/parse-pipe.js +257 -46
  39. package/dist/migrate/parse-pipe.js.map +1 -1
  40. package/dist/migrate/parser-utils.d.ts +5 -0
  41. package/dist/migrate/parser-utils.d.ts.map +1 -1
  42. package/dist/migrate/parser-utils.js +22 -0
  43. package/dist/migrate/parser-utils.js.map +1 -1
  44. package/dist/migrate/types.d.ts +25 -3
  45. package/dist/migrate/types.d.ts.map +1 -1
  46. package/dist/schema/connection.d.ts +2 -0
  47. package/dist/schema/connection.d.ts.map +1 -1
  48. package/dist/schema/connection.js.map +1 -1
  49. package/dist/schema/datasource.d.ts +3 -1
  50. package/dist/schema/datasource.d.ts.map +1 -1
  51. package/dist/schema/datasource.js +8 -1
  52. package/dist/schema/datasource.js.map +1 -1
  53. package/dist/schema/datasource.test.js +13 -0
  54. package/dist/schema/datasource.test.js.map +1 -1
  55. package/dist/schema/engines.d.ts.map +1 -1
  56. package/dist/schema/engines.js +3 -0
  57. package/dist/schema/engines.js.map +1 -1
  58. package/dist/schema/engines.test.js +16 -0
  59. package/dist/schema/engines.test.js.map +1 -1
  60. package/dist/schema/pipe.d.ts +90 -3
  61. package/dist/schema/pipe.d.ts.map +1 -1
  62. package/dist/schema/pipe.js +84 -0
  63. package/dist/schema/pipe.js.map +1 -1
  64. package/dist/schema/pipe.test.js +70 -1
  65. package/dist/schema/pipe.test.js.map +1 -1
  66. package/dist/schema/secret.d.ts +6 -0
  67. package/dist/schema/secret.d.ts.map +1 -0
  68. package/dist/schema/secret.js +14 -0
  69. package/dist/schema/secret.js.map +1 -0
  70. package/dist/schema/secret.test.d.ts +2 -0
  71. package/dist/schema/secret.test.d.ts.map +1 -0
  72. package/dist/schema/secret.test.js +14 -0
  73. package/dist/schema/secret.test.js.map +1 -0
  74. package/dist/schema/types.d.ts +5 -0
  75. package/dist/schema/types.d.ts.map +1 -1
  76. package/dist/schema/types.js +6 -0
  77. package/dist/schema/types.js.map +1 -1
  78. package/dist/schema/types.test.js +12 -0
  79. package/dist/schema/types.test.js.map +1 -1
  80. package/package.json +1 -1
  81. package/src/cli/commands/migrate.test.ts +859 -8
  82. package/src/cli/commands/migrate.ts +35 -0
  83. package/src/generator/connection.test.ts +13 -0
  84. package/src/generator/connection.ts +4 -0
  85. package/src/generator/datasource.test.ts +60 -0
  86. package/src/generator/datasource.ts +3 -0
  87. package/src/generator/pipe.test.ts +56 -1
  88. package/src/generator/pipe.ts +41 -1
  89. package/src/index.test.ts +4 -0
  90. package/src/index.ts +12 -0
  91. package/src/migrate/emit-ts.ts +161 -48
  92. package/src/migrate/parse-connection.ts +15 -2
  93. package/src/migrate/parse-datasource.ts +134 -71
  94. package/src/migrate/parse-pipe.ts +364 -69
  95. package/src/migrate/parser-utils.ts +36 -1
  96. package/src/migrate/types.ts +28 -3
  97. package/src/schema/connection.ts +2 -0
  98. package/src/schema/datasource.test.ts +17 -0
  99. package/src/schema/datasource.ts +13 -2
  100. package/src/schema/engines.test.ts +18 -0
  101. package/src/schema/engines.ts +3 -0
  102. package/src/schema/pipe.test.ts +89 -0
  103. package/src/schema/pipe.ts +188 -4
  104. package/src/schema/secret.test.ts +19 -0
  105. package/src/schema/secret.ts +16 -0
  106. package/src/schema/types.test.ts +14 -0
  107. package/src/schema/types.ts +10 -0
@@ -13,7 +13,7 @@ const EXPECTED_COMPLEX_OUTPUT = `/**
13
13
  * Review endpoint output schemas and any defaults before production use.
14
14
  */
15
15
 
16
- import { defineKafkaConnection, defineDatasource, definePipe, defineMaterializedView, defineCopyPipe, node, t, engine, column, p } from "@tinybirdco/sdk";
16
+ import { defineKafkaConnection, defineDatasource, definePipe, defineMaterializedView, defineCopyPipe, node, t, engine, p } from "@tinybirdco/sdk";
17
17
 
18
18
  // Connections
19
19
 
@@ -34,12 +34,12 @@ export const stream = defineKafkaConnection("stream", {
34
34
  export const events = defineDatasource("events", {
35
35
  description: "Events from Kafka stream",
36
36
  schema: {
37
- event_id: column(t.string(), { jsonPath: "$.event_id" }),
38
- user_id: column(t.uint64(), { jsonPath: "$.user.id" }),
39
- env: column(t.string().default("prod"), { jsonPath: "$.env" }),
40
- is_test: column(t.bool().default(false), { jsonPath: "$.meta.is_test" }),
41
- updated_at: column(t.dateTime(), { jsonPath: "$.updated_at" }),
42
- payload: column(t.string().default("{}").codec("ZSTD(1)"), { jsonPath: "$.payload" }),
37
+ event_id: t.string().jsonPath("$.event_id"),
38
+ user_id: t.uint64().jsonPath("$.user.id"),
39
+ env: t.string().default("prod").jsonPath("$.env"),
40
+ is_test: t.bool().default(false).jsonPath("$.meta.is_test"),
41
+ updated_at: t.dateTime().jsonPath("$.updated_at"),
42
+ payload: t.string().default("{}").codec("ZSTD(1)").jsonPath("$.payload"),
43
43
  },
44
44
  engine: engine.replacingMergeTree({ sortingKey: ["event_id", "user_id"], partitionKey: "toYYYYMM(updated_at)", primaryKey: "event_id", ttl: "updated_at + toIntervalDay(30)", ver: "updated_at", settings: { "index_granularity": 8192, "enable_mixed_granularity_parts": true } }),
45
45
  kafka: {
@@ -105,7 +105,7 @@ export const eventsEndpoint = definePipe("events_endpoint", {
105
105
  SELECT event_id, user_id, payload
106
106
  FROM events
107
107
  WHERE user_id = {{UInt64(user_id)}}
108
- AND env = {{String(env, 'prod')}}
108
+ AND env = {{String(env, 'prod')}}
109
109
  \`,
110
110
  }),
111
111
  node({
@@ -507,5 +507,582 @@ IMPORT_FROM_TIMESTAMP 2024-01-01T00:00:00Z
507
507
  expect(output).toContain('schedule: "@auto"');
508
508
  expect(output).toContain('fromTimestamp: "2024-01-01T00:00:00Z"');
509
509
  });
510
+ it("migrates KAFKA_STORE_RAW_VALUE datasource directive", async () => {
511
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
512
+ tempDirs.push(tempDir);
513
+ writeFile(tempDir, "stream.connection", `TYPE kafka
514
+ KAFKA_BOOTSTRAP_SERVERS localhost:9092
515
+ `);
516
+ writeFile(tempDir, "events.datasource", `SCHEMA >
517
+ event_id String
518
+
519
+ ENGINE "MergeTree"
520
+ ENGINE_SORTING_KEY "event_id"
521
+ KAFKA_CONNECTION_NAME stream
522
+ KAFKA_TOPIC events_topic
523
+ KAFKA_STORE_RAW_VALUE True
524
+ `);
525
+ const result = await runMigrate({
526
+ cwd: tempDir,
527
+ patterns: ["."],
528
+ strict: true,
529
+ });
530
+ expect(result.success).toBe(true);
531
+ expect(result.errors).toHaveLength(0);
532
+ const output = fs.readFileSync(result.outputPath, "utf-8");
533
+ expect(output).toContain("kafka: {");
534
+ expect(output).toContain("connection: stream");
535
+ expect(output).toContain('topic: "events_topic"');
536
+ expect(output).toContain("storeRawValue: true");
537
+ });
538
+ it("migrates kafka schema registry and engine is deleted directives", async () => {
539
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
540
+ tempDirs.push(tempDir);
541
+ writeFile(tempDir, "stream.connection", `TYPE kafka
542
+ KAFKA_BOOTSTRAP_SERVERS kafka.example.com:9092
543
+ KAFKA_SCHEMA_REGISTRY_URL https://registry-user:registry-pass@registry.example.com
544
+ # Optional registry auth details
545
+ `);
546
+ writeFile(tempDir, "events.datasource", `SCHEMA >
547
+ event_id String
548
+
549
+ ENGINE "MergeTree"
550
+ ENGINE_SORTING_KEY "event_id"
551
+ KAFKA_CONNECTION_NAME stream
552
+ KAFKA_TOPIC events_topic
553
+ KAFKA_STORE_RAW_VALUE True
554
+ `);
555
+ writeFile(tempDir, "events_state.datasource", `SCHEMA >
556
+ # logical delete marker
557
+ _is_deleted UInt8,
558
+ event_id String,
559
+ version_ts DateTime
560
+
561
+ ENGINE "ReplacingMergeTree"
562
+ ENGINE_SORTING_KEY "event_id"
563
+ ENGINE_VER "version_ts"
564
+ ENGINE_IS_DELETED "_is_deleted"
565
+ `);
566
+ writeFile(tempDir, "events_state_mv.pipe", `NODE latest
567
+ SQL >
568
+ SELECT
569
+ toUInt8(0) AS _is_deleted,
570
+ event_id,
571
+ now() AS version_ts
572
+ FROM events
573
+ # materialized definition
574
+ TYPE MATERIALIZED
575
+ DATASOURCE events_state
576
+ `);
577
+ const result = await runMigrate({
578
+ cwd: tempDir,
579
+ patterns: ["."],
580
+ strict: true,
581
+ });
582
+ expect(result.success).toBe(true);
583
+ expect(result.errors).toHaveLength(0);
584
+ const output = fs.readFileSync(result.outputPath, "utf-8");
585
+ expect(output).toContain('schemaRegistryUrl: "https://registry-user:registry-pass@registry.example.com"');
586
+ expect(output).toContain("storeRawValue: true");
587
+ expect(output).toContain('engine: engine.replacingMergeTree({ sortingKey: "event_id", ver: "version_ts", isDeleted: "_is_deleted" })');
588
+ });
589
+ it("migrates TYPE copy in lowercase", async () => {
590
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
591
+ tempDirs.push(tempDir);
592
+ writeFile(tempDir, "copy_target.datasource", `SCHEMA >
593
+ id String
594
+
595
+ ENGINE "MergeTree"
596
+ ENGINE_SORTING_KEY "id"
597
+ `);
598
+ writeFile(tempDir, "copy_lower.pipe", `NODE copy_node
599
+ SQL >
600
+ SELECT id
601
+ FROM copy_target
602
+ TYPE copy
603
+ TARGET_DATASOURCE copy_target
604
+ `);
605
+ const result = await runMigrate({
606
+ cwd: tempDir,
607
+ patterns: ["."],
608
+ strict: true,
609
+ });
610
+ expect(result.success).toBe(true);
611
+ expect(result.errors).toHaveLength(0);
612
+ const output = fs.readFileSync(result.outputPath, "utf-8");
613
+ expect(output).toContain('export const copyLower = defineCopyPipe("copy_lower", {');
614
+ expect(output).toContain("datasource: copyTarget,");
615
+ });
616
+ it("migrates TYPE ENDPOINT in uppercase", async () => {
617
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
618
+ tempDirs.push(tempDir);
619
+ writeFile(tempDir, "endpoint_upper.pipe", `NODE endpoint
620
+ SQL >
621
+ SELECT 1 AS id
622
+ FROM system.numbers
623
+ LIMIT 1
624
+ TYPE ENDPOINT
625
+ `);
626
+ const result = await runMigrate({
627
+ cwd: tempDir,
628
+ patterns: ["."],
629
+ strict: true,
630
+ });
631
+ expect(result.success).toBe(true);
632
+ expect(result.errors).toHaveLength(0);
633
+ const output = fs.readFileSync(result.outputPath, "utf-8");
634
+ expect(output).toContain('export const endpointUpper = definePipe("endpoint_upper", {');
635
+ expect(output).toContain("endpoint: true,");
636
+ });
637
+ it("parses multiline datasource blocks with flexible indentation", async () => {
638
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
639
+ tempDirs.push(tempDir);
640
+ writeFile(tempDir, "flex.datasource", `DESCRIPTION >
641
+ Flexible indentation description
642
+
643
+ SCHEMA >
644
+ id String,
645
+ env String
646
+
647
+ ENGINE "MergeTree"
648
+ ENGINE_SORTING_KEY "id"
649
+ FORWARD_QUERY >
650
+ SELECT id, env
651
+ FROM upstream_ds
652
+ SHARED_WITH >
653
+ workspace_a,
654
+ workspace_b
655
+ `);
656
+ const result = await runMigrate({
657
+ cwd: tempDir,
658
+ patterns: ["."],
659
+ strict: true,
660
+ });
661
+ expect(result.success).toBe(true);
662
+ expect(result.errors).toHaveLength(0);
663
+ const output = fs.readFileSync(result.outputPath, "utf-8");
664
+ expect(output).toContain('description: "Flexible indentation description"');
665
+ expect(output).toContain("forwardQuery: `");
666
+ expect(output).toContain("SELECT id, env");
667
+ expect(output).toContain("FROM upstream_ds");
668
+ expect(output).toContain('sharedWith: ["workspace_a", "workspace_b"],');
669
+ });
670
+ it("parses node SQL blocks with flexible indentation", async () => {
671
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
672
+ tempDirs.push(tempDir);
673
+ writeFile(tempDir, "flex_pipe.pipe", `NODE endpoint_node
674
+ DESCRIPTION >
675
+ Endpoint node description
676
+ SQL >
677
+ %
678
+ SELECT 1 AS id
679
+ FROM system.numbers
680
+ TYPE endpoint
681
+ `);
682
+ const result = await runMigrate({
683
+ cwd: tempDir,
684
+ patterns: ["."],
685
+ strict: true,
686
+ });
687
+ expect(result.success).toBe(true);
688
+ expect(result.errors).toHaveLength(0);
689
+ const output = fs.readFileSync(result.outputPath, "utf-8");
690
+ expect(output).toContain('description: "Endpoint node description"');
691
+ expect(output).toContain("SELECT 1 AS id");
692
+ expect(output).toContain("FROM system.numbers");
693
+ expect(output).toContain("endpoint: true,");
694
+ });
695
+ it("migrates datasource without engine directives", async () => {
696
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
697
+ tempDirs.push(tempDir);
698
+ writeFile(tempDir, "no_engine.datasource", `SCHEMA >
699
+ id String,
700
+ name String
701
+ `);
702
+ const result = await runMigrate({
703
+ cwd: tempDir,
704
+ patterns: ["."],
705
+ strict: true,
706
+ });
707
+ expect(result.success).toBe(true);
708
+ expect(result.errors).toHaveLength(0);
709
+ const output = fs.readFileSync(result.outputPath, "utf-8");
710
+ expect(output).toContain('export const noEngine = defineDatasource("no_engine", {');
711
+ expect(output).not.toContain("engine:");
712
+ expect(output).not.toContain(", engine,");
713
+ });
714
+ it("infers MergeTree when engine options exist without ENGINE directive", async () => {
715
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
716
+ tempDirs.push(tempDir);
717
+ writeFile(tempDir, "implicit_engine.datasource", `SCHEMA >
718
+ id String,
719
+ event_date Date
720
+
721
+ ENGINE_SORTING_KEY "id"
722
+ ENGINE_PARTITION_KEY "toYYYYMM(event_date)"
723
+ `);
724
+ const result = await runMigrate({
725
+ cwd: tempDir,
726
+ patterns: ["."],
727
+ strict: true,
728
+ });
729
+ expect(result.success).toBe(true);
730
+ expect(result.errors).toHaveLength(0);
731
+ const output = fs.readFileSync(result.outputPath, "utf-8");
732
+ expect(output).toContain('export const implicitEngine = defineDatasource("implicit_engine", {');
733
+ expect(output).toContain('engine: engine.mergeTree({ sortingKey: "id", partitionKey: "toYYYYMM(event_date)" }),');
734
+ });
735
+ it("supports quoted datasource token names with spaces", async () => {
736
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
737
+ tempDirs.push(tempDir);
738
+ writeFile(tempDir, "token_spaces.datasource", `TOKEN "ingestion (Data Source append)" APPEND
739
+
740
+ SCHEMA >
741
+ id String
742
+
743
+ ENGINE "MergeTree"
744
+ ENGINE_SORTING_KEY "id"
745
+ `);
746
+ const result = await runMigrate({
747
+ cwd: tempDir,
748
+ patterns: ["."],
749
+ strict: true,
750
+ });
751
+ expect(result.success).toBe(true);
752
+ expect(result.errors).toHaveLength(0);
753
+ const output = fs.readFileSync(result.outputPath, "utf-8");
754
+ expect(output).toContain('{ name: "ingestion (Data Source append)", permissions: ["APPEND"] },');
755
+ });
756
+ it("rejects unquoted datasource token names with spaces", async () => {
757
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
758
+ tempDirs.push(tempDir);
759
+ writeFile(tempDir, "bad_token.datasource", `TOKEN ingestion data APPEND
760
+
761
+ SCHEMA >
762
+ id String
763
+
764
+ ENGINE "MergeTree"
765
+ ENGINE_SORTING_KEY "id"
766
+ `);
767
+ const result = await runMigrate({
768
+ cwd: tempDir,
769
+ patterns: ["."],
770
+ strict: true,
771
+ });
772
+ expect(result.success).toBe(false);
773
+ expect(result.errors).toHaveLength(1);
774
+ expect(result.errors[0]?.message).toContain("Unsupported TOKEN syntax in strict mode");
775
+ });
776
+ it("allows empty datasource DESCRIPTION block", async () => {
777
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
778
+ tempDirs.push(tempDir);
779
+ writeFile(tempDir, "empty_ds_desc.datasource", `DESCRIPTION >
780
+
781
+ SCHEMA >
782
+ id String
783
+
784
+ ENGINE "MergeTree"
785
+ ENGINE_SORTING_KEY "id"
786
+ `);
787
+ const result = await runMigrate({
788
+ cwd: tempDir,
789
+ patterns: ["."],
790
+ strict: true,
791
+ });
792
+ expect(result.success).toBe(true);
793
+ expect(result.errors).toHaveLength(0);
794
+ const output = fs.readFileSync(result.outputPath, "utf-8");
795
+ expect(output).toContain('export const emptyDsDesc = defineDatasource("empty_ds_desc", {');
796
+ expect(output).toContain('description: "",');
797
+ });
798
+ it("allows empty node DESCRIPTION block", async () => {
799
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
800
+ tempDirs.push(tempDir);
801
+ writeFile(tempDir, "empty_node_desc.pipe", `NODE helper
802
+ DESCRIPTION >
803
+
804
+ SQL >
805
+ SELECT 1 AS id
806
+ TYPE endpoint
807
+ `);
808
+ const result = await runMigrate({
809
+ cwd: tempDir,
810
+ patterns: ["."],
811
+ strict: true,
812
+ });
813
+ expect(result.success).toBe(true);
814
+ expect(result.errors).toHaveLength(0);
815
+ const output = fs.readFileSync(result.outputPath, "utf-8");
816
+ expect(output).toContain('export const emptyNodeDesc = definePipe("empty_node_desc", {');
817
+ expect(output).toContain('description: "",');
818
+ });
819
+ it("supports keyword-style pipe param arguments", async () => {
820
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
821
+ tempDirs.push(tempDir);
822
+ writeFile(tempDir, "keyword_params.pipe", `NODE endpoint
823
+ SQL >
824
+ SELECT 1 AS id
825
+ WHERE tenant_id = {{ String(tenant_id, description="Tenant ID to filter", default="") }}
826
+ AND event_date >= {{ Date(from_date, description="Starting date", required=False) }}
827
+ AND days >= {{ Int32(days, 1, description="Days to include", required=True) }}
828
+ TYPE endpoint
829
+ `);
830
+ const result = await runMigrate({
831
+ cwd: tempDir,
832
+ patterns: ["."],
833
+ strict: true,
834
+ });
835
+ expect(result.success).toBe(true);
836
+ expect(result.errors).toHaveLength(0);
837
+ const output = fs.readFileSync(result.outputPath, "utf-8");
838
+ expect(output).toContain('export const keywordParams = definePipe("keyword_params", {');
839
+ expect(output).toContain('tenant_id: p.string().optional("").describe("Tenant ID to filter"),');
840
+ expect(output).toContain('from_date: p.date().optional().describe("Starting date"),');
841
+ expect(output).toContain('days: p.int32().optional(1).describe("Days to include"),');
842
+ });
843
+ it("migrates datasource with mixed explicit and default json paths", async () => {
844
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
845
+ tempDirs.push(tempDir);
846
+ writeFile(tempDir, "mixed_paths.datasource", `SCHEMA >
847
+ event_id String \`json:$.payload.id\`,
848
+ event_type String
849
+
850
+ ENGINE "MergeTree"
851
+ ENGINE_SORTING_KEY "event_id"
852
+ `);
853
+ const result = await runMigrate({
854
+ cwd: tempDir,
855
+ patterns: ["."],
856
+ strict: true,
857
+ });
858
+ expect(result.success).toBe(true);
859
+ expect(result.errors).toHaveLength(0);
860
+ const output = fs.readFileSync(result.outputPath, "utf-8");
861
+ expect(output).toContain('event_id: t.string().jsonPath("$.payload.id")');
862
+ expect(output).toContain("event_type: t.string()");
863
+ expect(output).not.toContain("jsonPaths: false");
864
+ });
865
+ it("normalizes backticked schema column names to valid object keys", async () => {
866
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
867
+ tempDirs.push(tempDir);
868
+ writeFile(tempDir, "backticked.datasource", `SCHEMA >
869
+ \`_is_deleted\` UInt8 \`json:$._is_deleted\`,
870
+ \`id\` UUID \`json:$.id\`
871
+
872
+ ENGINE "MergeTree"
873
+ ENGINE_SORTING_KEY "id"
874
+ `);
875
+ const result = await runMigrate({
876
+ cwd: tempDir,
877
+ patterns: ["."],
878
+ strict: true,
879
+ });
880
+ expect(result.success).toBe(true);
881
+ expect(result.errors).toHaveLength(0);
882
+ const output = fs.readFileSync(result.outputPath, "utf-8");
883
+ expect(output).toContain('_is_deleted: t.uint8().jsonPath("$._is_deleted")');
884
+ expect(output).toContain('id: t.uuid().jsonPath("$.id")');
885
+ expect(output).not.toContain("`_is_deleted`:");
886
+ expect(output).not.toContain("`id`:");
887
+ });
888
+ it("emits secret helper for tb_secret template values", async () => {
889
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
890
+ tempDirs.push(tempDir);
891
+ writeFile(tempDir, "stream.connection", `TYPE kafka
892
+ KAFKA_BOOTSTRAP_SERVERS localhost:9092
893
+ `);
894
+ writeFile(tempDir, "events.datasource", `SCHEMA >
895
+ id UUID
896
+
897
+ ENGINE "MergeTree"
898
+ ENGINE_SORTING_KEY "id"
899
+ KAFKA_CONNECTION_NAME stream
900
+ KAFKA_TOPIC events_topic
901
+ KAFKA_GROUP_ID {{ tb_secret("KAFKA_GROUP_ID_LOCAL_ds_accounts", "accounts_1737295200") }}
902
+ `);
903
+ const result = await runMigrate({
904
+ cwd: tempDir,
905
+ patterns: ["."],
906
+ strict: true,
907
+ });
908
+ expect(result.success).toBe(true);
909
+ expect(result.errors).toHaveLength(0);
910
+ const output = fs.readFileSync(result.outputPath, "utf-8");
911
+ expect(output).toContain('import {');
912
+ expect(output).toContain('secret } from "@tinybirdco/sdk";');
913
+ expect(output).not.toContain("const secret = (name: string, defaultValue?: string) =>");
914
+ expect(output).toContain('groupId: secret("KAFKA_GROUP_ID_LOCAL_ds_accounts", "accounts_1737295200"),');
915
+ expect(output).not.toContain('groupId: "{{ tb_secret(\\"KAFKA_GROUP_ID_LOCAL_ds_accounts\\", \\"accounts_1737295200\\") }}",');
916
+ });
917
+ it("does not emit secret helper when no tb_secret template values are present", async () => {
918
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
919
+ tempDirs.push(tempDir);
920
+ writeFile(tempDir, "stream.connection", `TYPE kafka
921
+ KAFKA_BOOTSTRAP_SERVERS localhost:9092
922
+ `);
923
+ writeFile(tempDir, "events.datasource", `SCHEMA >
924
+ id UUID
925
+
926
+ ENGINE "MergeTree"
927
+ ENGINE_SORTING_KEY "id"
928
+ KAFKA_CONNECTION_NAME stream
929
+ KAFKA_TOPIC events_topic
930
+ KAFKA_GROUP_ID events-group
931
+ `);
932
+ const result = await runMigrate({
933
+ cwd: tempDir,
934
+ patterns: ["."],
935
+ strict: true,
936
+ });
937
+ expect(result.success).toBe(true);
938
+ expect(result.errors).toHaveLength(0);
939
+ const output = fs.readFileSync(result.outputPath, "utf-8");
940
+ expect(output).not.toContain(", secret,");
941
+ expect(output).not.toContain("const secret = (name: string, defaultValue?: string) =>");
942
+ expect(output).toContain('groupId: "events-group",');
943
+ });
944
+ it("migrates Kafka sink pipes and emits sink config in TypeScript", async () => {
945
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
946
+ tempDirs.push(tempDir);
947
+ writeFile(tempDir, "events_kafka.connection", `TYPE kafka
948
+ KAFKA_BOOTSTRAP_SERVERS localhost:9092
949
+ `);
950
+ writeFile(tempDir, "events_sink.pipe", `NODE publish
951
+ SQL >
952
+ SELECT *
953
+ FROM events
954
+ WHERE env = {{String(env, 'prod')}}
955
+ TYPE sink
956
+ EXPORT_CONNECTION_NAME events_kafka
957
+ EXPORT_KAFKA_TOPIC events_out
958
+ EXPORT_SCHEDULE @on-demand
959
+ `);
960
+ const result = await runMigrate({
961
+ cwd: tempDir,
962
+ patterns: ["."],
963
+ strict: true,
964
+ });
965
+ expect(result.success).toBe(true);
966
+ expect(result.errors).toHaveLength(0);
967
+ expect(result.migrated.filter((resource) => resource.kind === "connection")).toHaveLength(1);
968
+ expect(result.migrated.filter((resource) => resource.kind === "pipe")).toHaveLength(1);
969
+ const output = fs.readFileSync(result.outputPath, "utf-8");
970
+ expect(output).toContain('export const eventsSink = defineSinkPipe("events_sink", {');
971
+ expect(output).toContain("params: {");
972
+ expect(output).toContain('env: p.string().optional("prod"),');
973
+ expect(output).toContain("sink: {");
974
+ expect(output).toContain("connection: eventsKafka");
975
+ expect(output).toContain('topic: "events_out"');
976
+ expect(output).toContain('schedule: "@on-demand"');
977
+ expect(output).not.toContain('strategy:');
978
+ });
979
+ it("migrates S3 sink pipes and emits compression/strategy config in TypeScript", async () => {
980
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
981
+ tempDirs.push(tempDir);
982
+ writeFile(tempDir, "exports_s3.connection", `TYPE s3
983
+ S3_REGION "us-east-1"
984
+ S3_ARN "arn:aws:iam::123456789012:role/tinybird-s3-access"
985
+ `);
986
+ writeFile(tempDir, "events_s3_sink.pipe", `NODE export
987
+ SQL >
988
+ SELECT * FROM events
989
+ TYPE sink
990
+ EXPORT_CONNECTION_NAME exports_s3
991
+ EXPORT_BUCKET_URI s3://exports/events/
992
+ EXPORT_FILE_TEMPLATE events_{date}
993
+ EXPORT_SCHEDULE @once
994
+ EXPORT_FORMAT ndjson
995
+ EXPORT_STRATEGY create_new
996
+ EXPORT_COMPRESSION gzip
997
+ `);
998
+ const result = await runMigrate({
999
+ cwd: tempDir,
1000
+ patterns: ["."],
1001
+ strict: true,
1002
+ });
1003
+ expect(result.success).toBe(true);
1004
+ expect(result.errors).toHaveLength(0);
1005
+ const output = fs.readFileSync(result.outputPath, "utf-8");
1006
+ expect(output).toContain('export const eventsS3Sink = defineSinkPipe("events_s3_sink", {');
1007
+ expect(output).toContain("sink: {");
1008
+ expect(output).toContain("connection: exportsS3");
1009
+ expect(output).toContain('bucketUri: "s3://exports/events/"');
1010
+ expect(output).toContain('fileTemplate: "events_{date}"');
1011
+ expect(output).toContain('schedule: "@once"');
1012
+ expect(output).toContain('format: "ndjson"');
1013
+ expect(output).toContain('strategy: "create_new"');
1014
+ expect(output).toContain('compression: "gzip"');
1015
+ });
1016
+ it("reports an error when sink pipe references a missing connection", async () => {
1017
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
1018
+ tempDirs.push(tempDir);
1019
+ writeFile(tempDir, "events_sink.pipe", `NODE publish
1020
+ SQL >
1021
+ SELECT * FROM events
1022
+ TYPE sink
1023
+ EXPORT_CONNECTION_NAME missing_connection
1024
+ EXPORT_KAFKA_TOPIC events_out
1025
+ EXPORT_SCHEDULE @on-demand
1026
+ `);
1027
+ const result = await runMigrate({
1028
+ cwd: tempDir,
1029
+ patterns: ["."],
1030
+ strict: true,
1031
+ });
1032
+ expect(result.success).toBe(false);
1033
+ expect(result.errors.map((error) => error.message)).toEqual(expect.arrayContaining([
1034
+ 'Sink pipe references missing/unmigrated connection "missing_connection".',
1035
+ ]));
1036
+ });
1037
+ it("reports an error when sink connection type does not match sink service", async () => {
1038
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
1039
+ tempDirs.push(tempDir);
1040
+ writeFile(tempDir, "events_kafka.connection", `TYPE kafka
1041
+ KAFKA_BOOTSTRAP_SERVERS localhost:9092
1042
+ `);
1043
+ writeFile(tempDir, "events_s3_sink.pipe", `NODE export
1044
+ SQL >
1045
+ SELECT * FROM events
1046
+ TYPE sink
1047
+ EXPORT_CONNECTION_NAME events_kafka
1048
+ EXPORT_BUCKET_URI s3://exports/events/
1049
+ EXPORT_FILE_TEMPLATE events_{date}
1050
+ EXPORT_SCHEDULE @once
1051
+ EXPORT_FORMAT csv
1052
+ `);
1053
+ const result = await runMigrate({
1054
+ cwd: tempDir,
1055
+ patterns: ["."],
1056
+ strict: true,
1057
+ });
1058
+ expect(result.success).toBe(false);
1059
+ expect(result.errors.map((error) => error.message)).toEqual(expect.arrayContaining([
1060
+ 'Sink pipe service "s3" is incompatible with connection "events_kafka" type "kafka".',
1061
+ ]));
1062
+ });
1063
+ it("supports comments in sink pipe files", async () => {
1064
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
1065
+ tempDirs.push(tempDir);
1066
+ writeFile(tempDir, "events_kafka.connection", `TYPE kafka
1067
+ KAFKA_BOOTSTRAP_SERVERS localhost:9092
1068
+ `);
1069
+ writeFile(tempDir, "events_sink.pipe", `# this pipe publishes records
1070
+ NODE publish
1071
+ SQL >
1072
+ SELECT * FROM events
1073
+ TYPE sink
1074
+ # Kafka target
1075
+ EXPORT_CONNECTION_NAME events_kafka
1076
+ EXPORT_KAFKA_TOPIC events_out
1077
+ EXPORT_SCHEDULE @on-demand
1078
+ `);
1079
+ const result = await runMigrate({
1080
+ cwd: tempDir,
1081
+ patterns: ["."],
1082
+ strict: true,
1083
+ });
1084
+ expect(result.success).toBe(true);
1085
+ expect(result.errors).toHaveLength(0);
1086
+ });
510
1087
  });
511
1088
  //# sourceMappingURL=migrate.test.js.map