@tinybirdco/sdk 0.0.47 → 0.0.48

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 (82) hide show
  1. package/dist/cli/commands/migrate.test.js +187 -7
  2. package/dist/cli/commands/migrate.test.js.map +1 -1
  3. package/dist/generator/connection.d.ts.map +1 -1
  4. package/dist/generator/connection.js +3 -0
  5. package/dist/generator/connection.js.map +1 -1
  6. package/dist/generator/connection.test.js +8 -0
  7. package/dist/generator/connection.test.js.map +1 -1
  8. package/dist/generator/datasource.d.ts.map +1 -1
  9. package/dist/generator/datasource.js +3 -0
  10. package/dist/generator/datasource.js.map +1 -1
  11. package/dist/generator/datasource.test.js +50 -0
  12. package/dist/generator/datasource.test.js.map +1 -1
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +2 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/index.test.js +3 -0
  18. package/dist/index.test.js.map +1 -1
  19. package/dist/migrate/emit-ts.d.ts.map +1 -1
  20. package/dist/migrate/emit-ts.js +109 -32
  21. package/dist/migrate/emit-ts.js.map +1 -1
  22. package/dist/migrate/parse-connection.d.ts.map +1 -1
  23. package/dist/migrate/parse-connection.js +13 -2
  24. package/dist/migrate/parse-connection.js.map +1 -1
  25. package/dist/migrate/parse-datasource.d.ts.map +1 -1
  26. package/dist/migrate/parse-datasource.js +39 -4
  27. package/dist/migrate/parse-datasource.js.map +1 -1
  28. package/dist/migrate/parse-pipe.d.ts.map +1 -1
  29. package/dist/migrate/parse-pipe.js +3 -2
  30. package/dist/migrate/parse-pipe.js.map +1 -1
  31. package/dist/migrate/types.d.ts +3 -0
  32. package/dist/migrate/types.d.ts.map +1 -1
  33. package/dist/schema/connection.d.ts +2 -0
  34. package/dist/schema/connection.d.ts.map +1 -1
  35. package/dist/schema/connection.js.map +1 -1
  36. package/dist/schema/datasource.d.ts +3 -1
  37. package/dist/schema/datasource.d.ts.map +1 -1
  38. package/dist/schema/datasource.js +8 -1
  39. package/dist/schema/datasource.js.map +1 -1
  40. package/dist/schema/datasource.test.js +12 -0
  41. package/dist/schema/datasource.test.js.map +1 -1
  42. package/dist/schema/engines.d.ts.map +1 -1
  43. package/dist/schema/engines.js +3 -0
  44. package/dist/schema/engines.js.map +1 -1
  45. package/dist/schema/engines.test.js +16 -0
  46. package/dist/schema/engines.test.js.map +1 -1
  47. package/dist/schema/secret.d.ts +6 -0
  48. package/dist/schema/secret.d.ts.map +1 -0
  49. package/dist/schema/secret.js +14 -0
  50. package/dist/schema/secret.js.map +1 -0
  51. package/dist/schema/secret.test.d.ts +2 -0
  52. package/dist/schema/secret.test.d.ts.map +1 -0
  53. package/dist/schema/secret.test.js +14 -0
  54. package/dist/schema/secret.test.js.map +1 -0
  55. package/dist/schema/types.d.ts +5 -0
  56. package/dist/schema/types.d.ts.map +1 -1
  57. package/dist/schema/types.js +6 -0
  58. package/dist/schema/types.js.map +1 -1
  59. package/dist/schema/types.test.js +12 -0
  60. package/dist/schema/types.test.js.map +1 -1
  61. package/package.json +1 -1
  62. package/src/cli/commands/migrate.test.ts +279 -7
  63. package/src/generator/connection.test.ts +13 -0
  64. package/src/generator/connection.ts +4 -0
  65. package/src/generator/datasource.test.ts +60 -0
  66. package/src/generator/datasource.ts +3 -0
  67. package/src/index.test.ts +4 -0
  68. package/src/index.ts +3 -0
  69. package/src/migrate/emit-ts.ts +109 -38
  70. package/src/migrate/parse-connection.ts +15 -2
  71. package/src/migrate/parse-datasource.ts +53 -4
  72. package/src/migrate/parse-pipe.ts +5 -3
  73. package/src/migrate/types.ts +3 -0
  74. package/src/schema/connection.ts +2 -0
  75. package/src/schema/datasource.test.ts +16 -0
  76. package/src/schema/datasource.ts +13 -2
  77. package/src/schema/engines.test.ts +18 -0
  78. package/src/schema/engines.ts +3 -0
  79. package/src/schema/secret.test.ts +19 -0
  80. package/src/schema/secret.ts +16 -0
  81. package/src/schema/types.test.ts +14 -0
  82. package/src/schema/types.ts +10 -0
@@ -15,7 +15,7 @@ const EXPECTED_COMPLEX_OUTPUT = `/**
15
15
  * Review endpoint output schemas and any defaults before production use.
16
16
  */
17
17
 
18
- import { defineKafkaConnection, defineDatasource, definePipe, defineMaterializedView, defineCopyPipe, node, t, engine, column, p } from "@tinybirdco/sdk";
18
+ import { defineKafkaConnection, defineDatasource, definePipe, defineMaterializedView, defineCopyPipe, node, t, engine, p } from "@tinybirdco/sdk";
19
19
 
20
20
  // Connections
21
21
 
@@ -36,12 +36,12 @@ export const stream = defineKafkaConnection("stream", {
36
36
  export const events = defineDatasource("events", {
37
37
  description: "Events from Kafka stream",
38
38
  schema: {
39
- event_id: column(t.string(), { jsonPath: "$.event_id" }),
40
- user_id: column(t.uint64(), { jsonPath: "$.user.id" }),
41
- env: column(t.string().default("prod"), { jsonPath: "$.env" }),
42
- is_test: column(t.bool().default(false), { jsonPath: "$.meta.is_test" }),
43
- updated_at: column(t.dateTime(), { jsonPath: "$.updated_at" }),
44
- payload: column(t.string().default("{}").codec("ZSTD(1)"), { jsonPath: "$.payload" }),
39
+ event_id: t.string().jsonPath("$.event_id"),
40
+ user_id: t.uint64().jsonPath("$.user.id"),
41
+ env: t.string().default("prod").jsonPath("$.env"),
42
+ is_test: t.bool().default(false).jsonPath("$.meta.is_test"),
43
+ updated_at: t.dateTime().jsonPath("$.updated_at"),
44
+ payload: t.string().default("{}").codec("ZSTD(1)").jsonPath("$.payload"),
45
45
  },
46
46
  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 } }),
47
47
  kafka: {
@@ -615,4 +615,276 @@ IMPORT_FROM_TIMESTAMP 2024-01-01T00:00:00Z
615
615
  expect(output).toContain('schedule: "@auto"');
616
616
  expect(output).toContain('fromTimestamp: "2024-01-01T00:00:00Z"');
617
617
  });
618
+
619
+ it("migrates KAFKA_STORE_RAW_VALUE datasource directive", async () => {
620
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
621
+ tempDirs.push(tempDir);
622
+
623
+ writeFile(
624
+ tempDir,
625
+ "stream.connection",
626
+ `TYPE kafka
627
+ KAFKA_BOOTSTRAP_SERVERS localhost:9092
628
+ `
629
+ );
630
+
631
+ writeFile(
632
+ tempDir,
633
+ "events.datasource",
634
+ `SCHEMA >
635
+ event_id String
636
+
637
+ ENGINE "MergeTree"
638
+ ENGINE_SORTING_KEY "event_id"
639
+ KAFKA_CONNECTION_NAME stream
640
+ KAFKA_TOPIC events_topic
641
+ KAFKA_STORE_RAW_VALUE True
642
+ `
643
+ );
644
+
645
+ const result = await runMigrate({
646
+ cwd: tempDir,
647
+ patterns: ["."],
648
+ strict: true,
649
+ });
650
+
651
+ expect(result.success).toBe(true);
652
+ expect(result.errors).toHaveLength(0);
653
+
654
+ const output = fs.readFileSync(result.outputPath, "utf-8");
655
+ expect(output).toContain("kafka: {");
656
+ expect(output).toContain("connection: stream");
657
+ expect(output).toContain('topic: "events_topic"');
658
+ expect(output).toContain("storeRawValue: true");
659
+ });
660
+
661
+ it("migrates kafka schema registry and engine is deleted directives", async () => {
662
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
663
+ tempDirs.push(tempDir);
664
+
665
+ writeFile(
666
+ tempDir,
667
+ "stream.connection",
668
+ `TYPE kafka
669
+ KAFKA_BOOTSTRAP_SERVERS kafka.example.com:9092
670
+ KAFKA_SCHEMA_REGISTRY_URL https://registry-user:registry-pass@registry.example.com
671
+ # Optional registry auth details
672
+ `
673
+ );
674
+
675
+ writeFile(
676
+ tempDir,
677
+ "events.datasource",
678
+ `SCHEMA >
679
+ event_id String
680
+
681
+ ENGINE "MergeTree"
682
+ ENGINE_SORTING_KEY "event_id"
683
+ KAFKA_CONNECTION_NAME stream
684
+ KAFKA_TOPIC events_topic
685
+ KAFKA_STORE_RAW_VALUE True
686
+ `
687
+ );
688
+
689
+ writeFile(
690
+ tempDir,
691
+ "events_state.datasource",
692
+ `SCHEMA >
693
+ # logical delete marker
694
+ _is_deleted UInt8,
695
+ event_id String,
696
+ version_ts DateTime
697
+
698
+ ENGINE "ReplacingMergeTree"
699
+ ENGINE_SORTING_KEY "event_id"
700
+ ENGINE_VER "version_ts"
701
+ ENGINE_IS_DELETED "_is_deleted"
702
+ `
703
+ );
704
+
705
+ writeFile(
706
+ tempDir,
707
+ "events_state_mv.pipe",
708
+ `NODE latest
709
+ SQL >
710
+ SELECT
711
+ toUInt8(0) AS _is_deleted,
712
+ event_id,
713
+ now() AS version_ts
714
+ FROM events
715
+ # materialized definition
716
+ TYPE MATERIALIZED
717
+ DATASOURCE events_state
718
+ `
719
+ );
720
+
721
+ const result = await runMigrate({
722
+ cwd: tempDir,
723
+ patterns: ["."],
724
+ strict: true,
725
+ });
726
+
727
+ expect(result.success).toBe(true);
728
+ expect(result.errors).toHaveLength(0);
729
+
730
+ const output = fs.readFileSync(result.outputPath, "utf-8");
731
+ expect(output).toContain(
732
+ 'schemaRegistryUrl: "https://registry-user:registry-pass@registry.example.com"'
733
+ );
734
+ expect(output).toContain("storeRawValue: true");
735
+ expect(output).toContain(
736
+ 'engine: engine.replacingMergeTree({ sortingKey: "event_id", ver: "version_ts", isDeleted: "_is_deleted" })'
737
+ );
738
+ });
739
+
740
+ it("migrates datasource with mixed explicit and default json paths", async () => {
741
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
742
+ tempDirs.push(tempDir);
743
+
744
+ writeFile(
745
+ tempDir,
746
+ "mixed_paths.datasource",
747
+ `SCHEMA >
748
+ event_id String \`json:$.payload.id\`,
749
+ event_type String
750
+
751
+ ENGINE "MergeTree"
752
+ ENGINE_SORTING_KEY "event_id"
753
+ `
754
+ );
755
+
756
+ const result = await runMigrate({
757
+ cwd: tempDir,
758
+ patterns: ["."],
759
+ strict: true,
760
+ });
761
+
762
+ expect(result.success).toBe(true);
763
+ expect(result.errors).toHaveLength(0);
764
+
765
+ const output = fs.readFileSync(result.outputPath, "utf-8");
766
+ expect(output).toContain('event_id: t.string().jsonPath("$.payload.id")');
767
+ expect(output).toContain("event_type: t.string()");
768
+ expect(output).not.toContain("jsonPaths: false");
769
+ });
770
+
771
+ it("normalizes backticked schema column names to valid object keys", async () => {
772
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
773
+ tempDirs.push(tempDir);
774
+
775
+ writeFile(
776
+ tempDir,
777
+ "backticked.datasource",
778
+ `SCHEMA >
779
+ \`_is_deleted\` UInt8 \`json:$._is_deleted\`,
780
+ \`id\` UUID \`json:$.id\`
781
+
782
+ ENGINE "MergeTree"
783
+ ENGINE_SORTING_KEY "id"
784
+ `
785
+ );
786
+
787
+ const result = await runMigrate({
788
+ cwd: tempDir,
789
+ patterns: ["."],
790
+ strict: true,
791
+ });
792
+
793
+ expect(result.success).toBe(true);
794
+ expect(result.errors).toHaveLength(0);
795
+
796
+ const output = fs.readFileSync(result.outputPath, "utf-8");
797
+ expect(output).toContain('_is_deleted: t.uint8().jsonPath("$._is_deleted")');
798
+ expect(output).toContain('id: t.uuid().jsonPath("$.id")');
799
+ expect(output).not.toContain("`_is_deleted`:");
800
+ expect(output).not.toContain("`id`:");
801
+ });
802
+
803
+ it("emits secret helper for tb_secret template values", async () => {
804
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
805
+ tempDirs.push(tempDir);
806
+
807
+ writeFile(
808
+ tempDir,
809
+ "stream.connection",
810
+ `TYPE kafka
811
+ KAFKA_BOOTSTRAP_SERVERS localhost:9092
812
+ `
813
+ );
814
+
815
+ writeFile(
816
+ tempDir,
817
+ "events.datasource",
818
+ `SCHEMA >
819
+ id UUID
820
+
821
+ ENGINE "MergeTree"
822
+ ENGINE_SORTING_KEY "id"
823
+ KAFKA_CONNECTION_NAME stream
824
+ KAFKA_TOPIC events_topic
825
+ KAFKA_GROUP_ID {{ tb_secret("KAFKA_GROUP_ID_LOCAL_ds_accounts", "accounts_1737295200") }}
826
+ `
827
+ );
828
+
829
+ const result = await runMigrate({
830
+ cwd: tempDir,
831
+ patterns: ["."],
832
+ strict: true,
833
+ });
834
+
835
+ expect(result.success).toBe(true);
836
+ expect(result.errors).toHaveLength(0);
837
+
838
+ const output = fs.readFileSync(result.outputPath, "utf-8");
839
+ expect(output).toContain('import {');
840
+ expect(output).toContain('secret } from "@tinybirdco/sdk";');
841
+ expect(output).not.toContain("const secret = (name: string, defaultValue?: string) =>");
842
+ expect(output).toContain(
843
+ 'groupId: secret("KAFKA_GROUP_ID_LOCAL_ds_accounts", "accounts_1737295200"),'
844
+ );
845
+ expect(output).not.toContain(
846
+ 'groupId: "{{ tb_secret(\\"KAFKA_GROUP_ID_LOCAL_ds_accounts\\", \\"accounts_1737295200\\") }}",'
847
+ );
848
+ });
849
+
850
+ it("does not emit secret helper when no tb_secret template values are present", async () => {
851
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
852
+ tempDirs.push(tempDir);
853
+
854
+ writeFile(
855
+ tempDir,
856
+ "stream.connection",
857
+ `TYPE kafka
858
+ KAFKA_BOOTSTRAP_SERVERS localhost:9092
859
+ `
860
+ );
861
+
862
+ writeFile(
863
+ tempDir,
864
+ "events.datasource",
865
+ `SCHEMA >
866
+ id UUID
867
+
868
+ ENGINE "MergeTree"
869
+ ENGINE_SORTING_KEY "id"
870
+ KAFKA_CONNECTION_NAME stream
871
+ KAFKA_TOPIC events_topic
872
+ KAFKA_GROUP_ID events-group
873
+ `
874
+ );
875
+
876
+ const result = await runMigrate({
877
+ cwd: tempDir,
878
+ patterns: ["."],
879
+ strict: true,
880
+ });
881
+
882
+ expect(result.success).toBe(true);
883
+ expect(result.errors).toHaveLength(0);
884
+
885
+ const output = fs.readFileSync(result.outputPath, "utf-8");
886
+ expect(output).not.toContain(", secret,");
887
+ expect(output).not.toContain("const secret = (name: string, defaultValue?: string) =>");
888
+ expect(output).toContain('groupId: "events-group",');
889
+ });
618
890
  });
@@ -51,6 +51,19 @@ describe("Connection Generator", () => {
51
51
  expect(result.content).toContain('KAFKA_SECRET {{ tb_secret("KAFKA_SECRET") }}');
52
52
  });
53
53
 
54
+ it("includes schema registry URL when provided", () => {
55
+ const conn = defineKafkaConnection("my_kafka", {
56
+ bootstrapServers: "kafka.example.com:9092",
57
+ schemaRegistryUrl: "https://registry-user:registry-pass@registry.example.com",
58
+ });
59
+
60
+ const result = generateConnection(conn);
61
+
62
+ expect(result.content).toContain(
63
+ "KAFKA_SCHEMA_REGISTRY_URL https://registry-user:registry-pass@registry.example.com"
64
+ );
65
+ });
66
+
54
67
  it("includes SSL CA PEM when provided", () => {
55
68
  const conn = defineKafkaConnection("my_kafka", {
56
69
  bootstrapServers: "kafka.example.com:9092",
@@ -42,6 +42,10 @@ function generateKafkaConnection(connection: KafkaConnectionDefinition): string
42
42
  parts.push(`KAFKA_SECRET ${options.secret}`);
43
43
  }
44
44
 
45
+ if (options.schemaRegistryUrl) {
46
+ parts.push(`KAFKA_SCHEMA_REGISTRY_URL ${options.schemaRegistryUrl}`);
47
+ }
48
+
45
49
  if (options.sslCaPem) {
46
50
  parts.push(`KAFKA_SSL_CA_PEM ${options.sslCaPem}`);
47
51
  }
@@ -263,6 +263,43 @@ describe('Datasource Generator', () => {
263
263
  expect(schemaLines[1]).toContain(',');
264
264
  expect(schemaLines[2]).not.toContain(',');
265
265
  });
266
+
267
+ it('autogenerates jsonPath when jsonPaths is enabled and no explicit path is set', () => {
268
+ const ds = defineDatasource('test_ds', {
269
+ schema: {
270
+ event_id: t.string().nullable(),
271
+ },
272
+ });
273
+
274
+ const result = generateDatasource(ds);
275
+ expect(result.content).toContain('event_id Nullable(String) `json:$.event_id`');
276
+ });
277
+
278
+ it('uses explicit jsonPath from validator modifier when jsonPaths is enabled', () => {
279
+ const ds = defineDatasource('test_ds', {
280
+ schema: {
281
+ event_id: t.string().nullable().jsonPath('$.explicit_path'),
282
+ },
283
+ });
284
+
285
+ const result = generateDatasource(ds);
286
+ expect(result.content).toContain('event_id Nullable(String) `json:$.explicit_path`');
287
+ expect(result.content).not.toContain('`json:$.event_id`');
288
+ });
289
+
290
+ it('omits json paths when jsonPaths is false even if column has explicit jsonPath modifier', () => {
291
+ const ds = defineDatasource('test_ds', {
292
+ jsonPaths: false,
293
+ schema: {
294
+ event_id: t.string().nullable().jsonPath('$.explicit_path'),
295
+ },
296
+ });
297
+
298
+ const result = generateDatasource(ds);
299
+ expect(result.content).toContain('event_id Nullable(String)');
300
+ expect(result.content).not.toContain('`json:$.explicit_path`');
301
+ expect(result.content).not.toContain('`json:$.event_id`');
302
+ });
266
303
  });
267
304
 
268
305
  describe('generateAllDatasources', () => {
@@ -380,6 +417,29 @@ describe('Datasource Generator', () => {
380
417
  expect(result.content).toContain('KAFKA_AUTO_OFFSET_RESET earliest');
381
418
  });
382
419
 
420
+ it('includes store raw value when provided', () => {
421
+ const kafkaConn = defineKafkaConnection('my_kafka', {
422
+ bootstrapServers: 'kafka.example.com:9092',
423
+ });
424
+
425
+ const ds = defineDatasource('kafka_events', {
426
+ schema: {
427
+ timestamp: t.dateTime(),
428
+ event: t.string(),
429
+ },
430
+ engine: engine.mergeTree({ sortingKey: ['timestamp'] }),
431
+ kafka: {
432
+ connection: kafkaConn,
433
+ topic: 'events',
434
+ storeRawValue: true,
435
+ },
436
+ });
437
+
438
+ const result = generateDatasource(ds);
439
+
440
+ expect(result.content).toContain('KAFKA_STORE_RAW_VALUE True');
441
+ });
442
+
383
443
  it('generates complete Kafka datasource with all options', () => {
384
444
  const kafkaConn = defineKafkaConnection('my_kafka', {
385
445
  bootstrapServers: 'kafka.example.com:9092',
@@ -166,6 +166,9 @@ function generateKafkaConfig(kafka: KafkaConfig): string {
166
166
  if (kafka.autoOffsetReset) {
167
167
  parts.push(`KAFKA_AUTO_OFFSET_RESET ${kafka.autoOffsetReset}`);
168
168
  }
169
+ if (kafka.storeRawValue !== undefined) {
170
+ parts.push(`KAFKA_STORE_RAW_VALUE ${kafka.storeRawValue ? "True" : "False"}`);
171
+ }
169
172
 
170
173
  return parts.join("\n");
171
174
  }
package/src/index.test.ts CHANGED
@@ -10,4 +10,8 @@ describe("root public exports", () => {
10
10
  expect(typeof sdk.defineProject).toBe("function");
11
11
  expect(typeof sdk.Tinybird).toBe("function");
12
12
  });
13
+
14
+ it("exports secret utility", () => {
15
+ expect(typeof sdk.secret).toBe("function");
16
+ });
13
17
  });
package/src/index.ts CHANGED
@@ -97,6 +97,9 @@ export type {
97
97
  VersionedCollapsingMergeTreeConfig,
98
98
  } from "./schema/engines.js";
99
99
 
100
+ // ============ Utilities ============
101
+ export { secret } from "./schema/secret.js";
102
+
100
103
  // ============ Datasource ============
101
104
  export { defineDatasource, isDatasourceDefinition, column, getColumnType, getColumnJsonPath, getColumnNames } from "./schema/datasource.js";
102
105
  export type {