@tinybirdco/sdk 0.0.73 → 0.0.75

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 (43) hide show
  1. package/dist/api/deploy.d.ts.map +1 -1
  2. package/dist/api/deploy.js +28 -25
  3. package/dist/api/deploy.js.map +1 -1
  4. package/dist/api/deploy.test.js +45 -0
  5. package/dist/api/deploy.test.js.map +1 -1
  6. package/dist/cli/commands/migrate.test.js +11 -0
  7. package/dist/cli/commands/migrate.test.js.map +1 -1
  8. package/dist/generator/connection.d.ts.map +1 -1
  9. package/dist/generator/connection.js +12 -0
  10. package/dist/generator/connection.js.map +1 -1
  11. package/dist/generator/connection.test.js +16 -0
  12. package/dist/generator/connection.test.js.map +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/migrate/emit-ts.d.ts.map +1 -1
  17. package/dist/migrate/emit-ts.js +18 -0
  18. package/dist/migrate/emit-ts.js.map +1 -1
  19. package/dist/migrate/parse-connection.d.ts.map +1 -1
  20. package/dist/migrate/parse-connection.js +35 -0
  21. package/dist/migrate/parse-connection.js.map +1 -1
  22. package/dist/migrate/parse-connection.test.js +14 -0
  23. package/dist/migrate/parse-connection.test.js.map +1 -1
  24. package/dist/migrate/types.d.ts +4 -0
  25. package/dist/migrate/types.d.ts.map +1 -1
  26. package/dist/schema/connection.d.ts +12 -0
  27. package/dist/schema/connection.d.ts.map +1 -1
  28. package/dist/schema/connection.js.map +1 -1
  29. package/dist/schema/connection.test.js +15 -0
  30. package/dist/schema/connection.test.js.map +1 -1
  31. package/package.json +1 -1
  32. package/src/api/deploy.test.ts +66 -0
  33. package/src/api/deploy.ts +32 -29
  34. package/src/cli/commands/migrate.test.ts +11 -0
  35. package/src/generator/connection.test.ts +23 -0
  36. package/src/generator/connection.ts +16 -0
  37. package/src/index.ts +1 -0
  38. package/src/migrate/emit-ts.ts +21 -0
  39. package/src/migrate/parse-connection.test.ts +27 -0
  40. package/src/migrate/parse-connection.ts +40 -0
  41. package/src/migrate/types.ts +4 -0
  42. package/src/schema/connection.test.ts +21 -0
  43. package/src/schema/connection.ts +13 -0
package/src/api/deploy.ts CHANGED
@@ -217,37 +217,40 @@ export async function deployToMain(
217
217
  );
218
218
  }
219
219
 
220
- // Step 0: Clean up any stale non-live deployments that might block the new deployment
221
- try {
222
- const deploymentsUrl = `${baseUrl}/v1/deployments`;
223
- const deploymentsResponse = await tinybirdFetch(deploymentsUrl, {
224
- headers: {
225
- Authorization: `Bearer ${config.token}`,
226
- },
227
- });
228
-
229
- if (deploymentsResponse.ok) {
230
- const deploymentsBody = (await deploymentsResponse.json()) as DeploymentsListResponse;
231
- const staleDeployments = deploymentsBody.deployments.filter(
232
- (d) => !d.live && d.status !== "live"
233
- );
234
-
235
- for (const stale of staleDeployments) {
236
- if (debug) {
237
- console.log(`[debug] Cleaning up stale deployment: ${stale.id} (status: ${stale.status})`);
220
+ // Step 0: Clean up any stale non-live deployments that might block the new deployment.
221
+ // Skipped in check mode so validation runs don't tear down in-flight deployments.
222
+ if (!options?.check) {
223
+ try {
224
+ const deploymentsUrl = `${baseUrl}/v1/deployments`;
225
+ const deploymentsResponse = await tinybirdFetch(deploymentsUrl, {
226
+ headers: {
227
+ Authorization: `Bearer ${config.token}`,
228
+ },
229
+ });
230
+
231
+ if (deploymentsResponse.ok) {
232
+ const deploymentsBody = (await deploymentsResponse.json()) as DeploymentsListResponse;
233
+ const staleDeployments = deploymentsBody.deployments.filter(
234
+ (d) => !d.live && d.status !== "live"
235
+ );
236
+
237
+ for (const stale of staleDeployments) {
238
+ if (debug) {
239
+ console.log(`[debug] Cleaning up stale deployment: ${stale.id} (status: ${stale.status})`);
240
+ }
241
+ await tinybirdFetch(`${baseUrl}/v1/deployments/${stale.id}`, {
242
+ method: "DELETE",
243
+ headers: {
244
+ Authorization: `Bearer ${config.token}`,
245
+ },
246
+ });
238
247
  }
239
- await tinybirdFetch(`${baseUrl}/v1/deployments/${stale.id}`, {
240
- method: "DELETE",
241
- headers: {
242
- Authorization: `Bearer ${config.token}`,
243
- },
244
- });
245
248
  }
246
- }
247
- } catch (e) {
248
- // Ignore errors during cleanup - we'll try to deploy anyway
249
- if (debug) {
250
- console.log(`[debug] Failed to clean up stale deployments: ${e}`);
249
+ } catch (e) {
250
+ // Ignore errors during cleanup - we'll try to deploy anyway
251
+ if (debug) {
252
+ console.log(`[debug] Failed to clean up stale deployments: ${e}`);
253
+ }
251
254
  }
252
255
  }
253
256
 
@@ -845,6 +845,12 @@ INDEXES >
845
845
  "stream.connection",
846
846
  `TYPE kafka
847
847
  KAFKA_BOOTSTRAP_SERVERS kafka.example.com:9092
848
+ KAFKA_SECURITY_PROTOCOL SASL_SSL
849
+ KAFKA_SASL_MECHANISM OAUTHBEARER
850
+ KAFKA_SASL_OAUTHBEARER_METHOD AWS
851
+ KAFKA_SASL_OAUTHBEARER_AWS_REGION eu-west-1
852
+ KAFKA_SASL_OAUTHBEARER_AWS_ROLE_ARN {{ tb_secret("KAFKA_AWS_ROLE_ARN") }}
853
+ KAFKA_SASL_OAUTHBEARER_AWS_EXTERNAL_ID {{ tb_secret("KAFKA_AWS_EXTERNAL_ID") }}
848
854
  KAFKA_SCHEMA_REGISTRY_URL https://registry-user:registry-pass@registry.example.com
849
855
  # Optional registry auth details
850
856
  `
@@ -909,6 +915,11 @@ DATASOURCE events_state
909
915
  expect(output).toContain(
910
916
  'schemaRegistryUrl: "https://registry-user:registry-pass@registry.example.com"'
911
917
  );
918
+ expect(output).toContain('saslMechanism: "OAUTHBEARER"');
919
+ expect(output).toContain('saslOauthbearerMethod: "AWS"');
920
+ expect(output).toContain('saslOauthbearerAwsRegion: "eu-west-1"');
921
+ expect(output).toContain('saslOauthbearerAwsRoleArn: secret("KAFKA_AWS_ROLE_ARN")');
922
+ expect(output).toContain('saslOauthbearerAwsExternalId: secret("KAFKA_AWS_EXTERNAL_ID")');
912
923
  expect(output).toContain("storeRawValue: true");
913
924
  expect(output).toContain(
914
925
  'engine: engine.replacingMergeTree({ sortingKey: "event_id", ver: "version_ts", isDeleted: "_is_deleted" })'
@@ -152,6 +152,29 @@ describe("Connection Generator", () => {
152
152
  });
153
153
  });
154
154
 
155
+ it("generates Kafka AWS IAM OAUTHBEARER settings", () => {
156
+ const conn = defineKafkaConnection("aws_msk", {
157
+ bootstrapServers: "b-1.msk.example.com:9098,b-2.msk.example.com:9098",
158
+ securityProtocol: "SASL_SSL",
159
+ saslMechanism: "OAUTHBEARER",
160
+ saslOauthbearerMethod: "AWS",
161
+ saslOauthbearerAwsRegion: "eu-west-1",
162
+ saslOauthbearerAwsRoleArn: '{{ tb_secret("KAFKA_AWS_ROLE_ARN") }}',
163
+ saslOauthbearerAwsExternalId: '{{ tb_secret("KAFKA_AWS_EXTERNAL_ID") }}',
164
+ });
165
+
166
+ const result = generateConnection(conn);
167
+
168
+ expect(result.content).toContain("KAFKA_SASL_OAUTHBEARER_METHOD AWS");
169
+ expect(result.content).toContain("KAFKA_SASL_OAUTHBEARER_AWS_REGION eu-west-1");
170
+ expect(result.content).toContain(
171
+ 'KAFKA_SASL_OAUTHBEARER_AWS_ROLE_ARN {{ tb_secret("KAFKA_AWS_ROLE_ARN") }}'
172
+ );
173
+ expect(result.content).toContain(
174
+ 'KAFKA_SASL_OAUTHBEARER_AWS_EXTERNAL_ID {{ tb_secret("KAFKA_AWS_EXTERNAL_ID") }}'
175
+ );
176
+ });
177
+
155
178
  it("generates basic S3 connection with IAM role auth", () => {
156
179
  const conn = defineS3Connection("my_s3", {
157
180
  region: "us-east-1",
@@ -42,6 +42,22 @@ function generateKafkaConnection(connection: KafkaConnectionDefinition): string
42
42
  parts.push(`KAFKA_SASL_MECHANISM ${options.saslMechanism}`);
43
43
  }
44
44
 
45
+ if (options.saslOauthbearerMethod) {
46
+ parts.push(`KAFKA_SASL_OAUTHBEARER_METHOD ${options.saslOauthbearerMethod}`);
47
+ }
48
+
49
+ if (options.saslOauthbearerAwsRegion) {
50
+ parts.push(`KAFKA_SASL_OAUTHBEARER_AWS_REGION ${options.saslOauthbearerAwsRegion}`);
51
+ }
52
+
53
+ if (options.saslOauthbearerAwsRoleArn) {
54
+ parts.push(`KAFKA_SASL_OAUTHBEARER_AWS_ROLE_ARN ${options.saslOauthbearerAwsRoleArn}`);
55
+ }
56
+
57
+ if (options.saslOauthbearerAwsExternalId) {
58
+ parts.push(`KAFKA_SASL_OAUTHBEARER_AWS_EXTERNAL_ID ${options.saslOauthbearerAwsExternalId}`);
59
+ }
60
+
45
61
  if (options.key) {
46
62
  parts.push(`KAFKA_KEY ${options.key}`);
47
63
  }
package/src/index.ts CHANGED
@@ -137,6 +137,7 @@ export type {
137
137
  KafkaConnectionOptions,
138
138
  KafkaSecurityProtocol,
139
139
  KafkaSaslMechanism,
140
+ KafkaSaslOauthbearerMethod,
140
141
  S3ConnectionDefinition,
141
142
  S3ConnectionOptions,
142
143
  GCSConnectionDefinition,
@@ -64,6 +64,9 @@ function hasSecretTemplate(resources: ParsedResource[]): boolean {
64
64
  if (resource.secret) values.push(resource.secret);
65
65
  if (resource.sslCaPem) values.push(resource.sslCaPem);
66
66
  if (resource.schemaRegistryUrl) values.push(resource.schemaRegistryUrl);
67
+ if (resource.saslOauthbearerAwsRegion) values.push(resource.saslOauthbearerAwsRegion);
68
+ if (resource.saslOauthbearerAwsRoleArn) values.push(resource.saslOauthbearerAwsRoleArn);
69
+ if (resource.saslOauthbearerAwsExternalId) values.push(resource.saslOauthbearerAwsExternalId);
67
70
  } else if (resource.connectionType === "s3") {
68
71
  values.push(resource.region);
69
72
  if (resource.arn) values.push(resource.arn);
@@ -429,6 +432,24 @@ function emitConnection(
429
432
  if (connection.saslMechanism) {
430
433
  lines.push(` saslMechanism: ${emitStringOrSecret(connection.saslMechanism)},`);
431
434
  }
435
+ if (connection.saslOauthbearerMethod) {
436
+ lines.push(` saslOauthbearerMethod: ${emitStringOrSecret(connection.saslOauthbearerMethod)},`);
437
+ }
438
+ if (connection.saslOauthbearerAwsRegion) {
439
+ lines.push(
440
+ ` saslOauthbearerAwsRegion: ${emitStringOrSecret(connection.saslOauthbearerAwsRegion)},`
441
+ );
442
+ }
443
+ if (connection.saslOauthbearerAwsRoleArn) {
444
+ lines.push(
445
+ ` saslOauthbearerAwsRoleArn: ${emitStringOrSecret(connection.saslOauthbearerAwsRoleArn)},`
446
+ );
447
+ }
448
+ if (connection.saslOauthbearerAwsExternalId) {
449
+ lines.push(
450
+ ` saslOauthbearerAwsExternalId: ${emitStringOrSecret(connection.saslOauthbearerAwsExternalId)},`
451
+ );
452
+ }
432
453
  if (connection.key) {
433
454
  lines.push(` key: ${emitStringOrSecret(connection.key)},`);
434
455
  }
@@ -95,4 +95,31 @@ KAFKA_SSL_CA_PEM {{ tb_secret('KAFKA_SSL_CA_PEM') }}`
95
95
 
96
96
  expect(result).toHaveProperty("sslCaPem", "{{ tb_secret('KAFKA_SSL_CA_PEM') }}");
97
97
  });
98
+
99
+ it("parses Kafka AWS IAM OAUTHBEARER directives", () => {
100
+ const result = parseConnectionFile(
101
+ resource(
102
+ "aws_msk",
103
+ `TYPE kafka
104
+ KAFKA_BOOTSTRAP_SERVERS b-1.msk.example.com:9098,b-2.msk.example.com:9098
105
+ KAFKA_SECURITY_PROTOCOL SASL_SSL
106
+ KAFKA_SASL_MECHANISM OAUTHBEARER
107
+ KAFKA_SASL_OAUTHBEARER_METHOD AWS
108
+ KAFKA_SASL_OAUTHBEARER_AWS_REGION eu-west-1
109
+ KAFKA_SASL_OAUTHBEARER_AWS_ROLE_ARN {{ tb_secret("KAFKA_AWS_ROLE_ARN") }}
110
+ KAFKA_SASL_OAUTHBEARER_AWS_EXTERNAL_ID {{ tb_secret("KAFKA_AWS_EXTERNAL_ID") }}`
111
+ )
112
+ );
113
+
114
+ expect(result).toHaveProperty("saslOauthbearerMethod", "AWS");
115
+ expect(result).toHaveProperty("saslOauthbearerAwsRegion", "eu-west-1");
116
+ expect(result).toHaveProperty(
117
+ "saslOauthbearerAwsRoleArn",
118
+ '{{ tb_secret("KAFKA_AWS_ROLE_ARN") }}'
119
+ );
120
+ expect(result).toHaveProperty(
121
+ "saslOauthbearerAwsExternalId",
122
+ '{{ tb_secret("KAFKA_AWS_EXTERNAL_ID") }}'
123
+ );
124
+ });
98
125
  });
@@ -18,6 +18,10 @@ const CONNECTION_DIRECTIVES = new Set([
18
18
  "KAFKA_BOOTSTRAP_SERVERS",
19
19
  "KAFKA_SECURITY_PROTOCOL",
20
20
  "KAFKA_SASL_MECHANISM",
21
+ "KAFKA_SASL_OAUTHBEARER_METHOD",
22
+ "KAFKA_SASL_OAUTHBEARER_AWS_REGION",
23
+ "KAFKA_SASL_OAUTHBEARER_AWS_ROLE_ARN",
24
+ "KAFKA_SASL_OAUTHBEARER_AWS_EXTERNAL_ID",
21
25
  "KAFKA_KEY",
22
26
  "KAFKA_SECRET",
23
27
  "KAFKA_SCHEMA_REGISTRY_URL",
@@ -55,6 +59,10 @@ export function parseConnectionFile(
55
59
  | "SCRAM-SHA-512"
56
60
  | "OAUTHBEARER"
57
61
  | undefined;
62
+ let saslOauthbearerMethod: "AWS" | undefined;
63
+ let saslOauthbearerAwsRegion: string | undefined;
64
+ let saslOauthbearerAwsRoleArn: string | undefined;
65
+ let saslOauthbearerAwsExternalId: string | undefined;
58
66
  let key: string | undefined;
59
67
  let secret: string | undefined;
60
68
  let schemaRegistryUrl: string | undefined;
@@ -110,6 +118,26 @@ export function parseConnectionFile(
110
118
  }
111
119
  saslMechanism = value;
112
120
  break;
121
+ case "KAFKA_SASL_OAUTHBEARER_METHOD":
122
+ if (value !== "AWS") {
123
+ throw new MigrationParseError(
124
+ resource.filePath,
125
+ "connection",
126
+ resource.name,
127
+ `Unsupported KAFKA_SASL_OAUTHBEARER_METHOD: "${value}"`
128
+ );
129
+ }
130
+ saslOauthbearerMethod = value;
131
+ break;
132
+ case "KAFKA_SASL_OAUTHBEARER_AWS_REGION":
133
+ saslOauthbearerAwsRegion = value;
134
+ break;
135
+ case "KAFKA_SASL_OAUTHBEARER_AWS_ROLE_ARN":
136
+ saslOauthbearerAwsRoleArn = value;
137
+ break;
138
+ case "KAFKA_SASL_OAUTHBEARER_AWS_EXTERNAL_ID":
139
+ saslOauthbearerAwsExternalId = value;
140
+ break;
113
141
  case "KAFKA_KEY":
114
142
  key = value;
115
143
  break;
@@ -196,6 +224,10 @@ export function parseConnectionFile(
196
224
  bootstrapServers,
197
225
  securityProtocol,
198
226
  saslMechanism,
227
+ saslOauthbearerMethod,
228
+ saslOauthbearerAwsRegion,
229
+ saslOauthbearerAwsRoleArn,
230
+ saslOauthbearerAwsExternalId,
199
231
  key,
200
232
  secret,
201
233
  schemaRegistryUrl,
@@ -208,6 +240,10 @@ export function parseConnectionFile(
208
240
  bootstrapServers ||
209
241
  securityProtocol ||
210
242
  saslMechanism ||
243
+ saslOauthbearerMethod ||
244
+ saslOauthbearerAwsRegion ||
245
+ saslOauthbearerAwsRoleArn ||
246
+ saslOauthbearerAwsExternalId ||
211
247
  key ||
212
248
  secret ||
213
249
  schemaRegistryUrl ||
@@ -266,6 +302,10 @@ export function parseConnectionFile(
266
302
  bootstrapServers ||
267
303
  securityProtocol ||
268
304
  saslMechanism ||
305
+ saslOauthbearerMethod ||
306
+ saslOauthbearerAwsRegion ||
307
+ saslOauthbearerAwsRoleArn ||
308
+ saslOauthbearerAwsExternalId ||
269
309
  key ||
270
310
  secret ||
271
311
  schemaRegistryUrl ||
@@ -155,6 +155,10 @@ export interface KafkaConnectionModel {
155
155
  bootstrapServers: string;
156
156
  securityProtocol?: "SASL_SSL" | "PLAINTEXT" | "SASL_PLAINTEXT";
157
157
  saslMechanism?: "PLAIN" | "SCRAM-SHA-256" | "SCRAM-SHA-512" | "OAUTHBEARER";
158
+ saslOauthbearerMethod?: "AWS";
159
+ saslOauthbearerAwsRegion?: string;
160
+ saslOauthbearerAwsRoleArn?: string;
161
+ saslOauthbearerAwsExternalId?: string;
158
162
  key?: string;
159
163
  secret?: string;
160
164
  schemaRegistryUrl?: string;
@@ -60,6 +60,27 @@ describe("Connection Schema", () => {
60
60
  expect(oauthConn.options.saslMechanism).toBe("OAUTHBEARER");
61
61
  });
62
62
 
63
+ it("creates a Kafka connection with AWS IAM OAUTHBEARER auth", () => {
64
+ const conn = defineKafkaConnection("aws_msk", {
65
+ bootstrapServers: "b-1.msk.example.com:9098,b-2.msk.example.com:9098",
66
+ securityProtocol: "SASL_SSL",
67
+ saslMechanism: "OAUTHBEARER",
68
+ saslOauthbearerMethod: "AWS",
69
+ saslOauthbearerAwsRegion: "eu-west-1",
70
+ saslOauthbearerAwsRoleArn: '{{ tb_secret("KAFKA_AWS_ROLE_ARN") }}',
71
+ saslOauthbearerAwsExternalId: '{{ tb_secret("KAFKA_AWS_EXTERNAL_ID") }}',
72
+ });
73
+
74
+ expect(conn.options.saslOauthbearerMethod).toBe("AWS");
75
+ expect(conn.options.saslOauthbearerAwsRegion).toBe("eu-west-1");
76
+ expect(conn.options.saslOauthbearerAwsRoleArn).toBe(
77
+ '{{ tb_secret("KAFKA_AWS_ROLE_ARN") }}'
78
+ );
79
+ expect(conn.options.saslOauthbearerAwsExternalId).toBe(
80
+ '{{ tb_secret("KAFKA_AWS_EXTERNAL_ID") }}'
81
+ );
82
+ });
83
+
63
84
  it("supports different security protocols", () => {
64
85
  const plaintext = defineKafkaConnection("plaintext_kafka", {
65
86
  bootstrapServers: "localhost:9092",
@@ -17,6 +17,11 @@ export type KafkaSecurityProtocol = "SASL_SSL" | "PLAINTEXT" | "SASL_PLAINTEXT";
17
17
  */
18
18
  export type KafkaSaslMechanism = "PLAIN" | "SCRAM-SHA-256" | "SCRAM-SHA-512" | "OAUTHBEARER";
19
19
 
20
+ /**
21
+ * Kafka SASL OAUTHBEARER provider method options
22
+ */
23
+ export type KafkaSaslOauthbearerMethod = "AWS";
24
+
20
25
  /**
21
26
  * Options for creating a Kafka connection
22
27
  */
@@ -27,6 +32,14 @@ export interface KafkaConnectionOptions {
27
32
  securityProtocol?: KafkaSecurityProtocol;
28
33
  /** SASL mechanism for authentication */
29
34
  saslMechanism?: KafkaSaslMechanism;
35
+ /** SASL OAUTHBEARER provider method */
36
+ saslOauthbearerMethod?: KafkaSaslOauthbearerMethod;
37
+ /** AWS region for SASL OAUTHBEARER IAM authentication */
38
+ saslOauthbearerAwsRegion?: string;
39
+ /** AWS role ARN for SASL OAUTHBEARER IAM authentication */
40
+ saslOauthbearerAwsRoleArn?: string;
41
+ /** AWS external ID for SASL OAUTHBEARER IAM authentication */
42
+ saslOauthbearerAwsExternalId?: string;
30
43
  /** Kafka key/username - can use {{ tb_secret(...) }} */
31
44
  key?: string;
32
45
  /** Kafka secret/password - can use {{ tb_secret(...) }} */