@tinybirdco/sdk 0.0.77 → 0.0.79

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 (122) hide show
  1. package/README.md +27 -2
  2. package/dist/api/branches.d.ts +4 -3
  3. package/dist/api/branches.d.ts.map +1 -1
  4. package/dist/api/branches.js +4 -4
  5. package/dist/api/branches.js.map +1 -1
  6. package/dist/api/branches.test.js +34 -1
  7. package/dist/api/branches.test.js.map +1 -1
  8. package/dist/cli/commands/build.d.ts.map +1 -1
  9. package/dist/cli/commands/build.js +7 -1
  10. package/dist/cli/commands/build.js.map +1 -1
  11. package/dist/cli/commands/build.test.js +159 -0
  12. package/dist/cli/commands/build.test.js.map +1 -1
  13. package/dist/cli/commands/clear.d.ts.map +1 -1
  14. package/dist/cli/commands/clear.js +4 -1
  15. package/dist/cli/commands/clear.js.map +1 -1
  16. package/dist/cli/commands/deploy.test.js +1 -0
  17. package/dist/cli/commands/deploy.test.js.map +1 -1
  18. package/dist/cli/commands/dev.d.ts.map +1 -1
  19. package/dist/cli/commands/dev.js +7 -1
  20. package/dist/cli/commands/dev.js.map +1 -1
  21. package/dist/cli/commands/generate.test.js +3 -0
  22. package/dist/cli/commands/generate.test.js.map +1 -1
  23. package/dist/cli/commands/migrate.d.ts.map +1 -1
  24. package/dist/cli/commands/migrate.js +14 -1
  25. package/dist/cli/commands/migrate.js.map +1 -1
  26. package/dist/cli/commands/migrate.test.js +60 -0
  27. package/dist/cli/commands/migrate.test.js.map +1 -1
  28. package/dist/cli/commands/preview.d.ts.map +1 -1
  29. package/dist/cli/commands/preview.js +4 -1
  30. package/dist/cli/commands/preview.js.map +1 -1
  31. package/dist/cli/commands/preview.test.js +116 -2
  32. package/dist/cli/commands/preview.test.js.map +1 -1
  33. package/dist/cli/config-types.d.ts +4 -0
  34. package/dist/cli/config-types.d.ts.map +1 -1
  35. package/dist/cli/config-types.js +1 -1
  36. package/dist/cli/config-types.js.map +1 -1
  37. package/dist/cli/config.d.ts +4 -2
  38. package/dist/cli/config.d.ts.map +1 -1
  39. package/dist/cli/config.js +27 -0
  40. package/dist/cli/config.js.map +1 -1
  41. package/dist/cli/config.test.js +79 -0
  42. package/dist/cli/config.test.js.map +1 -1
  43. package/dist/cli/index.js +2 -2
  44. package/dist/cli/index.js.map +1 -1
  45. package/dist/client/base.d.ts.map +1 -1
  46. package/dist/client/base.js +4 -1
  47. package/dist/client/base.js.map +1 -1
  48. package/dist/client/base.test.js +2 -1
  49. package/dist/client/base.test.js.map +1 -1
  50. package/dist/generator/connection.d.ts.map +1 -1
  51. package/dist/generator/connection.js +15 -1
  52. package/dist/generator/connection.js.map +1 -1
  53. package/dist/generator/connection.test.js +10 -1
  54. package/dist/generator/connection.test.js.map +1 -1
  55. package/dist/generator/datasource.d.ts.map +1 -1
  56. package/dist/generator/datasource.js +17 -1
  57. package/dist/generator/datasource.js.map +1 -1
  58. package/dist/generator/datasource.test.js +44 -1
  59. package/dist/generator/datasource.test.js.map +1 -1
  60. package/dist/index.d.ts +4 -4
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +1 -1
  63. package/dist/index.js.map +1 -1
  64. package/dist/migrate/emit-ts.d.ts.map +1 -1
  65. package/dist/migrate/emit-ts.js +28 -0
  66. package/dist/migrate/emit-ts.js.map +1 -1
  67. package/dist/migrate/parse-connection.d.ts +2 -2
  68. package/dist/migrate/parse-connection.d.ts.map +1 -1
  69. package/dist/migrate/parse-connection.js +60 -6
  70. package/dist/migrate/parse-connection.js.map +1 -1
  71. package/dist/migrate/parse-connection.test.js +18 -0
  72. package/dist/migrate/parse-connection.test.js.map +1 -1
  73. package/dist/migrate/parse-datasource.d.ts.map +1 -1
  74. package/dist/migrate/parse-datasource.js +27 -2
  75. package/dist/migrate/parse-datasource.js.map +1 -1
  76. package/dist/migrate/types.d.ts +15 -1
  77. package/dist/migrate/types.d.ts.map +1 -1
  78. package/dist/schema/connection.d.ts +46 -1
  79. package/dist/schema/connection.d.ts.map +1 -1
  80. package/dist/schema/connection.js +39 -0
  81. package/dist/schema/connection.js.map +1 -1
  82. package/dist/schema/connection.test.js +46 -1
  83. package/dist/schema/connection.test.js.map +1 -1
  84. package/dist/schema/datasource.d.ts +14 -1
  85. package/dist/schema/datasource.d.ts.map +1 -1
  86. package/dist/schema/datasource.js +2 -2
  87. package/dist/schema/datasource.js.map +1 -1
  88. package/dist/schema/datasource.test.js +2 -2
  89. package/dist/schema/datasource.test.js.map +1 -1
  90. package/package.json +1 -1
  91. package/src/api/branches.test.ts +38 -1
  92. package/src/api/branches.ts +8 -6
  93. package/src/cli/commands/build.test.ts +176 -0
  94. package/src/cli/commands/build.ts +9 -2
  95. package/src/cli/commands/clear.ts +8 -1
  96. package/src/cli/commands/deploy.test.ts +1 -0
  97. package/src/cli/commands/dev.ts +9 -1
  98. package/src/cli/commands/generate.test.ts +3 -0
  99. package/src/cli/commands/migrate.test.ts +95 -0
  100. package/src/cli/commands/migrate.ts +17 -1
  101. package/src/cli/commands/preview.test.ts +133 -2
  102. package/src/cli/commands/preview.ts +6 -2
  103. package/src/cli/config-types.ts +4 -0
  104. package/src/cli/config.test.ts +123 -0
  105. package/src/cli/config.ts +41 -3
  106. package/src/cli/index.ts +8 -2
  107. package/src/client/base.test.ts +3 -1
  108. package/src/client/base.ts +7 -1
  109. package/src/generator/connection.test.ts +17 -0
  110. package/src/generator/connection.ts +18 -0
  111. package/src/generator/datasource.test.ts +54 -1
  112. package/src/generator/datasource.ts +24 -1
  113. package/src/index.ts +6 -1
  114. package/src/migrate/emit-ts.ts +44 -3
  115. package/src/migrate/parse-connection.test.ts +41 -0
  116. package/src/migrate/parse-connection.ts +86 -7
  117. package/src/migrate/parse-datasource.ts +35 -2
  118. package/src/migrate/types.ts +18 -1
  119. package/src/schema/connection.test.ts +65 -0
  120. package/src/schema/connection.ts +76 -1
  121. package/src/schema/datasource.test.ts +2 -2
  122. package/src/schema/datasource.ts +21 -2
package/src/index.ts CHANGED
@@ -116,6 +116,7 @@ export type {
116
116
  KafkaConfig,
117
117
  S3Config,
118
118
  GCSConfig,
119
+ DynamoDBConfig,
119
120
  DatasourceIndex,
120
121
  } from "./schema/datasource.js";
121
122
 
@@ -125,10 +126,12 @@ export {
125
126
  createKafkaConnection,
126
127
  defineS3Connection,
127
128
  defineGCSConnection,
129
+ defineDynamoDBConnection,
128
130
  isConnectionDefinition,
129
131
  isKafkaConnectionDefinition,
130
132
  isS3ConnectionDefinition,
131
133
  isGCSConnectionDefinition,
134
+ isDynamoDBConnectionDefinition,
132
135
  getConnectionType,
133
136
  } from "./schema/connection.js";
134
137
  export type {
@@ -142,6 +145,8 @@ export type {
142
145
  S3ConnectionOptions,
143
146
  GCSConnectionDefinition,
144
147
  GCSConnectionOptions,
148
+ DynamoDBConnectionDefinition,
149
+ DynamoDBConnectionOptions,
145
150
  } from "./schema/connection.js";
146
151
 
147
152
  // ============ Token ============
@@ -312,4 +317,4 @@ export type {
312
317
 
313
318
  // ============ Config Types ============
314
319
  // Import from config-types.ts to avoid bundling esbuild in client code
315
- export type { TinybirdConfig, DevMode } from "./cli/config-types.js";
320
+ export type { TinybirdConfig, DevMode, BranchDataMode } from "./cli/config-types.js";
@@ -4,6 +4,7 @@ import { parseLiteralFromDatafile, toTsLiteral } from "./parser-utils.js";
4
4
  import type {
5
5
  DatasourceModel,
6
6
  DatasourceEngineModel,
7
+ DynamoDBConnectionModel,
7
8
  GCSConnectionModel,
8
9
  KafkaConnectionModel,
9
10
  ParsedResource,
@@ -72,6 +73,9 @@ function hasSecretTemplate(resources: ParsedResource[]): boolean {
72
73
  if (resource.arn) values.push(resource.arn);
73
74
  if (resource.accessKey) values.push(resource.accessKey);
74
75
  if (resource.secret) values.push(resource.secret);
76
+ } else if (resource.connectionType === "dynamodb") {
77
+ values.push(resource.region);
78
+ values.push(resource.arn);
75
79
  } else {
76
80
  values.push(resource.serviceAccountCredentialsJson);
77
81
  }
@@ -95,6 +99,10 @@ function hasSecretTemplate(resources: ParsedResource[]): boolean {
95
99
  if (resource.gcs.schedule) values.push(resource.gcs.schedule);
96
100
  if (resource.gcs.fromTimestamp) values.push(resource.gcs.fromTimestamp);
97
101
  }
102
+ if (resource.dynamodb) {
103
+ values.push(resource.dynamodb.tableArn);
104
+ values.push(resource.dynamodb.exportBucket);
105
+ }
98
106
  continue;
99
107
  }
100
108
  }
@@ -388,6 +396,15 @@ function emitDatasource(ds: DatasourceModel): string {
388
396
  lines.push(" },");
389
397
  }
390
398
 
399
+ if (ds.dynamodb) {
400
+ const connectionVar = toCamelCase(ds.dynamodb.connectionName);
401
+ lines.push(" dynamodb: {");
402
+ lines.push(` connection: ${connectionVar},`);
403
+ lines.push(` tableArn: ${emitStringOrSecret(ds.dynamodb.tableArn)},`);
404
+ lines.push(` exportBucket: ${emitStringOrSecret(ds.dynamodb.exportBucket)},`);
405
+ lines.push(" },");
406
+ }
407
+
391
408
  if (ds.forwardQuery) {
392
409
  lines.push(" forwardQuery: `");
393
410
  lines.push(escapeTemplateLiteral(ds.forwardQuery));
@@ -416,7 +433,11 @@ function emitDatasource(ds: DatasourceModel): string {
416
433
  }
417
434
 
418
435
  function emitConnection(
419
- connection: KafkaConnectionModel | S3ConnectionModel | GCSConnectionModel
436
+ connection:
437
+ | KafkaConnectionModel
438
+ | S3ConnectionModel
439
+ | GCSConnectionModel
440
+ | DynamoDBConnectionModel
420
441
  ): string {
421
442
  const variableName = toCamelCase(connection.name);
422
443
  const lines: string[] = [];
@@ -486,6 +507,17 @@ function emitConnection(
486
507
  return lines.join("\n");
487
508
  }
488
509
 
510
+ if (connection.connectionType === "dynamodb") {
511
+ lines.push(
512
+ `export const ${variableName} = defineDynamoDBConnection(${escapeString(connection.name)}, {`
513
+ );
514
+ lines.push(` region: ${emitStringOrSecret(connection.region)},`);
515
+ lines.push(` arn: ${emitStringOrSecret(connection.arn)},`);
516
+ lines.push("});");
517
+ lines.push("");
518
+ return lines.join("\n");
519
+ }
520
+
489
521
  lines.push(
490
522
  `export const ${variableName} = defineGCSConnection(${escapeString(connection.name)}, {`
491
523
  );
@@ -625,8 +657,13 @@ function emitPipe(pipe: PipeModel): string {
625
657
 
626
658
  export function emitMigrationFileContent(resources: ParsedResource[]): string {
627
659
  const connections = resources.filter(
628
- (resource): resource is KafkaConnectionModel | S3ConnectionModel | GCSConnectionModel =>
629
- resource.kind === "connection"
660
+ (
661
+ resource
662
+ ): resource is
663
+ | KafkaConnectionModel
664
+ | S3ConnectionModel
665
+ | GCSConnectionModel
666
+ | DynamoDBConnectionModel => resource.kind === "connection"
630
667
  );
631
668
  const datasources = resources.filter(
632
669
  (resource): resource is DatasourceModel => resource.kind === "datasource"
@@ -655,6 +692,9 @@ export function emitMigrationFileContent(resources: ParsedResource[]): string {
655
692
  if (connections.some((connection) => connection.connectionType === "gcs")) {
656
693
  imports.add("defineGCSConnection");
657
694
  }
695
+ if (connections.some((connection) => connection.connectionType === "dynamodb")) {
696
+ imports.add("defineDynamoDBConnection");
697
+ }
658
698
  if (needsParams) {
659
699
  imports.add("p");
660
700
  }
@@ -672,6 +712,7 @@ export function emitMigrationFileContent(resources: ParsedResource[]): string {
672
712
  "defineKafkaConnection",
673
713
  "defineS3Connection",
674
714
  "defineGCSConnection",
715
+ "defineDynamoDBConnection",
675
716
  "defineDatasource",
676
717
  "definePipe",
677
718
  "defineMaterializedView",
@@ -122,4 +122,45 @@ KAFKA_SASL_OAUTHBEARER_AWS_EXTERNAL_ID {{ tb_secret("KAFKA_AWS_EXTERNAL_ID") }}`
122
122
  '{{ tb_secret("KAFKA_AWS_EXTERNAL_ID") }}'
123
123
  );
124
124
  });
125
+
126
+ it("parses basic DynamoDB connection", () => {
127
+ const result = parseConnectionFile(
128
+ resource(
129
+ "my_dynamo",
130
+ `TYPE dynamodb
131
+ DYNAMODB_ARN {{ tb_secret("DYNAMODB_ROLE_ARN") }}
132
+ DYNAMODB_REGION us-east-1`
133
+ )
134
+ );
135
+
136
+ expect(result.connectionType).toBe("dynamodb");
137
+ expect(result).toHaveProperty("arn", '{{ tb_secret("DYNAMODB_ROLE_ARN") }}');
138
+ expect(result).toHaveProperty("region", "us-east-1");
139
+ });
140
+
141
+ it("throws when DynamoDB connection is missing DYNAMODB_ARN", () => {
142
+ expect(() =>
143
+ parseConnectionFile(
144
+ resource(
145
+ "my_dynamo",
146
+ `TYPE dynamodb
147
+ DYNAMODB_REGION us-east-1`
148
+ )
149
+ )
150
+ ).toThrow("DYNAMODB_ARN is required");
151
+ });
152
+
153
+ it("throws when DynamoDB connection mixes S3 directives", () => {
154
+ expect(() =>
155
+ parseConnectionFile(
156
+ resource(
157
+ "my_dynamo",
158
+ `TYPE dynamodb
159
+ DYNAMODB_ARN {{ tb_secret("DYNAMODB_ROLE_ARN") }}
160
+ DYNAMODB_REGION us-east-1
161
+ S3_ARN arn:aws:iam::1:role/x`
162
+ )
163
+ )
164
+ ).toThrow("Kafka/S3/GCS directives are not valid for dynamodb connections.");
165
+ });
125
166
  });
@@ -1,4 +1,5 @@
1
1
  import type {
2
+ DynamoDBConnectionModel,
2
3
  GCSConnectionModel,
3
4
  KafkaConnectionModel,
4
5
  ResourceFile,
@@ -31,6 +32,8 @@ const CONNECTION_DIRECTIVES = new Set([
31
32
  "S3_ACCESS_KEY",
32
33
  "S3_SECRET",
33
34
  "GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON",
35
+ "DYNAMODB_ARN",
36
+ "DYNAMODB_REGION",
34
37
  ]);
35
38
 
36
39
  function isConnectionDirectiveLine(line: string): boolean {
@@ -43,7 +46,7 @@ function isConnectionDirectiveLine(line: string): boolean {
43
46
 
44
47
  export function parseConnectionFile(
45
48
  resource: ResourceFile
46
- ): KafkaConnectionModel | S3ConnectionModel | GCSConnectionModel {
49
+ ): KafkaConnectionModel | S3ConnectionModel | GCSConnectionModel | DynamoDBConnectionModel {
47
50
  const lines = splitLines(resource.content);
48
51
  let connectionType: string | undefined;
49
52
 
@@ -74,6 +77,9 @@ export function parseConnectionFile(
74
77
  let accessSecret: string | undefined;
75
78
  let serviceAccountCredentialsJson: string | undefined;
76
79
 
80
+ let dynamodbArn: string | undefined;
81
+ let dynamodbRegion: string | undefined;
82
+
77
83
  let i = 0;
78
84
  while (i < lines.length) {
79
85
  const rawLine = lines[i] ?? "";
@@ -177,6 +183,12 @@ export function parseConnectionFile(
177
183
  }
178
184
  serviceAccountCredentialsJson = parseQuotedValue(value);
179
185
  break;
186
+ case "DYNAMODB_ARN":
187
+ dynamodbArn = parseQuotedValue(value);
188
+ break;
189
+ case "DYNAMODB_REGION":
190
+ dynamodbRegion = parseQuotedValue(value);
191
+ break;
180
192
  default:
181
193
  throw new MigrationParseError(
182
194
  resource.filePath,
@@ -198,12 +210,20 @@ export function parseConnectionFile(
198
210
  }
199
211
 
200
212
  if (connectionType === "kafka") {
201
- if (region || arn || accessKey || accessSecret || serviceAccountCredentialsJson) {
213
+ if (
214
+ region ||
215
+ arn ||
216
+ accessKey ||
217
+ accessSecret ||
218
+ serviceAccountCredentialsJson ||
219
+ dynamodbArn ||
220
+ dynamodbRegion
221
+ ) {
202
222
  throw new MigrationParseError(
203
223
  resource.filePath,
204
224
  "connection",
205
225
  resource.name,
206
- "S3/GCS directives are not valid for kafka connections."
226
+ "S3/GCS/DynamoDB directives are not valid for kafka connections."
207
227
  );
208
228
  }
209
229
 
@@ -248,13 +268,15 @@ export function parseConnectionFile(
248
268
  secret ||
249
269
  schemaRegistryUrl ||
250
270
  sslCaPem ||
251
- serviceAccountCredentialsJson
271
+ serviceAccountCredentialsJson ||
272
+ dynamodbArn ||
273
+ dynamodbRegion
252
274
  ) {
253
275
  throw new MigrationParseError(
254
276
  resource.filePath,
255
277
  "connection",
256
278
  resource.name,
257
- "Kafka/GCS directives are not valid for s3 connections."
279
+ "Kafka/GCS/DynamoDB directives are not valid for s3 connections."
258
280
  );
259
281
  }
260
282
 
@@ -313,13 +335,15 @@ export function parseConnectionFile(
313
335
  region ||
314
336
  arn ||
315
337
  accessKey ||
316
- accessSecret
338
+ accessSecret ||
339
+ dynamodbArn ||
340
+ dynamodbRegion
317
341
  ) {
318
342
  throw new MigrationParseError(
319
343
  resource.filePath,
320
344
  "connection",
321
345
  resource.name,
322
- "Kafka/S3 directives are not valid for gcs connections."
346
+ "Kafka/S3/DynamoDB directives are not valid for gcs connections."
323
347
  );
324
348
  }
325
349
 
@@ -341,6 +365,61 @@ export function parseConnectionFile(
341
365
  };
342
366
  }
343
367
 
368
+ if (connectionType === "dynamodb") {
369
+ if (
370
+ bootstrapServers ||
371
+ securityProtocol ||
372
+ saslMechanism ||
373
+ saslOauthbearerMethod ||
374
+ saslOauthbearerAwsRegion ||
375
+ saslOauthbearerAwsRoleArn ||
376
+ saslOauthbearerAwsExternalId ||
377
+ key ||
378
+ secret ||
379
+ schemaRegistryUrl ||
380
+ sslCaPem ||
381
+ region ||
382
+ arn ||
383
+ accessKey ||
384
+ accessSecret ||
385
+ serviceAccountCredentialsJson
386
+ ) {
387
+ throw new MigrationParseError(
388
+ resource.filePath,
389
+ "connection",
390
+ resource.name,
391
+ "Kafka/S3/GCS directives are not valid for dynamodb connections."
392
+ );
393
+ }
394
+
395
+ if (!dynamodbArn) {
396
+ throw new MigrationParseError(
397
+ resource.filePath,
398
+ "connection",
399
+ resource.name,
400
+ "DYNAMODB_ARN is required for dynamodb connections."
401
+ );
402
+ }
403
+
404
+ if (!dynamodbRegion) {
405
+ throw new MigrationParseError(
406
+ resource.filePath,
407
+ "connection",
408
+ resource.name,
409
+ "DYNAMODB_REGION is required for dynamodb connections."
410
+ );
411
+ }
412
+
413
+ return {
414
+ kind: "connection",
415
+ name: resource.name,
416
+ filePath: resource.filePath,
417
+ connectionType: "dynamodb",
418
+ arn: dynamodbArn,
419
+ region: dynamodbRegion,
420
+ };
421
+ }
422
+
344
423
  throw new MigrationParseError(
345
424
  resource.filePath,
346
425
  "connection",
@@ -39,6 +39,8 @@ const DATASOURCE_DIRECTIVES = new Set([
39
39
  "IMPORT_BUCKET_URI",
40
40
  "IMPORT_SCHEDULE",
41
41
  "IMPORT_FROM_TIMESTAMP",
42
+ "IMPORT_TABLE_ARN",
43
+ "IMPORT_EXPORT_BUCKET",
42
44
  "TOKEN",
43
45
  ]);
44
46
 
@@ -321,6 +323,8 @@ export function parseDatasourceFile(resource: ResourceFile): DatasourceModel {
321
323
  let importBucketUri: string | undefined;
322
324
  let importSchedule: string | undefined;
323
325
  let importFromTimestamp: string | undefined;
326
+ let importTableArn: string | undefined;
327
+ let importExportBucket: string | undefined;
324
328
 
325
329
  let i = 0;
326
330
  while (i < lines.length) {
@@ -498,6 +502,12 @@ export function parseDatasourceFile(resource: ResourceFile): DatasourceModel {
498
502
  case "IMPORT_FROM_TIMESTAMP":
499
503
  importFromTimestamp = parseQuotedValue(value);
500
504
  break;
505
+ case "IMPORT_TABLE_ARN":
506
+ importTableArn = parseQuotedValue(value);
507
+ break;
508
+ case "IMPORT_EXPORT_BUCKET":
509
+ importExportBucket = parseQuotedValue(value);
510
+ break;
501
511
  case "TOKEN":
502
512
  tokens.push(parseToken(resource.filePath, resource.name, value));
503
513
  break;
@@ -573,8 +583,30 @@ export function parseDatasourceFile(resource: ResourceFile): DatasourceModel {
573
583
  );
574
584
  }
575
585
 
586
+ // DynamoDB import directives share IMPORT_CONNECTION_NAME with blob storage,
587
+ // so treat the import block as DynamoDB whenever a DynamoDB-only directive is present.
588
+ const isDynamoDB = importTableArn !== undefined || importExportBucket !== undefined;
589
+
590
+ const dynamodb = isDynamoDB
591
+ ? {
592
+ connectionName: importConnectionName ?? "",
593
+ tableArn: importTableArn ?? "",
594
+ exportBucket: importExportBucket ?? "",
595
+ }
596
+ : undefined;
597
+
598
+ if (dynamodb && (!dynamodb.connectionName || !dynamodb.tableArn || !dynamodb.exportBucket)) {
599
+ throw new MigrationParseError(
600
+ resource.filePath,
601
+ "datasource",
602
+ resource.name,
603
+ "IMPORT_CONNECTION_NAME, IMPORT_TABLE_ARN and IMPORT_EXPORT_BUCKET are required when DynamoDB directives are used."
604
+ );
605
+ }
606
+
576
607
  const s3 =
577
- importConnectionName || importBucketUri || importSchedule || importFromTimestamp
608
+ !isDynamoDB &&
609
+ (importConnectionName || importBucketUri || importSchedule || importFromTimestamp)
578
610
  ? {
579
611
  connectionName: importConnectionName ?? "",
580
612
  bucketUri: importBucketUri ?? "",
@@ -592,7 +624,7 @@ export function parseDatasourceFile(resource: ResourceFile): DatasourceModel {
592
624
  );
593
625
  }
594
626
 
595
- if (kafka && s3) {
627
+ if (kafka && (s3 || dynamodb)) {
596
628
  throw new MigrationParseError(
597
629
  resource.filePath,
598
630
  "datasource",
@@ -625,6 +657,7 @@ export function parseDatasourceFile(resource: ResourceFile): DatasourceModel {
625
657
  indexes,
626
658
  kafka,
627
659
  s3,
660
+ dynamodb,
628
661
  forwardQuery,
629
662
  tokens,
630
663
  sharedWith,
@@ -59,6 +59,12 @@ export interface DatasourceGCSModel {
59
59
  fromTimestamp?: string;
60
60
  }
61
61
 
62
+ export interface DatasourceDynamoDBModel {
63
+ connectionName: string;
64
+ tableArn: string;
65
+ exportBucket: string;
66
+ }
67
+
62
68
  export interface DatasourceTokenModel {
63
69
  name: string;
64
70
  scope: "READ" | "APPEND";
@@ -82,6 +88,7 @@ export interface DatasourceModel {
82
88
  kafka?: DatasourceKafkaModel;
83
89
  s3?: DatasourceS3Model;
84
90
  gcs?: DatasourceGCSModel;
91
+ dynamodb?: DatasourceDynamoDBModel;
85
92
  forwardQuery?: string;
86
93
  tokens: DatasourceTokenModel[];
87
94
  sharedWith: string[];
@@ -184,12 +191,22 @@ export interface GCSConnectionModel {
184
191
  serviceAccountCredentialsJson: string;
185
192
  }
186
193
 
194
+ export interface DynamoDBConnectionModel {
195
+ kind: "connection";
196
+ name: string;
197
+ filePath: string;
198
+ connectionType: "dynamodb";
199
+ region: string;
200
+ arn: string;
201
+ }
202
+
187
203
  export type ParsedResource =
188
204
  | DatasourceModel
189
205
  | PipeModel
190
206
  | KafkaConnectionModel
191
207
  | S3ConnectionModel
192
- | GCSConnectionModel;
208
+ | GCSConnectionModel
209
+ | DynamoDBConnectionModel;
193
210
 
194
211
  export interface MigrationResult {
195
212
  success: boolean;
@@ -3,10 +3,12 @@ import {
3
3
  defineKafkaConnection,
4
4
  defineS3Connection,
5
5
  defineGCSConnection,
6
+ defineDynamoDBConnection,
6
7
  isConnectionDefinition,
7
8
  isKafkaConnectionDefinition,
8
9
  isS3ConnectionDefinition,
9
10
  isGCSConnectionDefinition,
11
+ isDynamoDBConnectionDefinition,
10
12
  getConnectionType,
11
13
  } from "./connection.js";
12
14
 
@@ -199,6 +201,48 @@ describe("Connection Schema", () => {
199
201
  });
200
202
  });
201
203
 
204
+ describe("defineDynamoDBConnection", () => {
205
+ it("creates a DynamoDB connection with required fields", () => {
206
+ const conn = defineDynamoDBConnection("my_dynamo", {
207
+ region: "us-east-1",
208
+ arn: '{{ tb_secret("DYNAMODB_ROLE_ARN") }}',
209
+ });
210
+
211
+ expect(conn._name).toBe("my_dynamo");
212
+ expect(conn._type).toBe("connection");
213
+ expect(conn._connectionType).toBe("dynamodb");
214
+ expect(conn.options.region).toBe("us-east-1");
215
+ expect(conn.options.arn).toBe('{{ tb_secret("DYNAMODB_ROLE_ARN") }}');
216
+ });
217
+
218
+ it("throws when region is missing", () => {
219
+ expect(() =>
220
+ defineDynamoDBConnection("my_dynamo", {
221
+ region: " ",
222
+ arn: '{{ tb_secret("DYNAMODB_ROLE_ARN") }}',
223
+ })
224
+ ).toThrow("DynamoDB connection `region` is required.");
225
+ });
226
+
227
+ it("throws when arn is missing", () => {
228
+ expect(() =>
229
+ defineDynamoDBConnection("my_dynamo", {
230
+ region: "us-east-1",
231
+ arn: "",
232
+ })
233
+ ).toThrow("DynamoDB connection `arn` is required.");
234
+ });
235
+
236
+ it("throws for invalid connection name", () => {
237
+ expect(() =>
238
+ defineDynamoDBConnection("123-invalid", {
239
+ region: "us-east-1",
240
+ arn: '{{ tb_secret("DYNAMODB_ROLE_ARN") }}',
241
+ })
242
+ ).toThrow("Invalid connection name");
243
+ });
244
+ });
245
+
202
246
  describe("isConnectionDefinition", () => {
203
247
  it("returns true for valid connection", () => {
204
248
  const conn = defineKafkaConnection("my_kafka", {
@@ -264,6 +308,27 @@ describe("Connection Schema", () => {
264
308
  });
265
309
  });
266
310
 
311
+ describe("isDynamoDBConnectionDefinition", () => {
312
+ it("returns true for DynamoDB connection", () => {
313
+ const conn = defineDynamoDBConnection("my_dynamo", {
314
+ region: "us-east-1",
315
+ arn: '{{ tb_secret("DYNAMODB_ROLE_ARN") }}',
316
+ });
317
+
318
+ expect(isDynamoDBConnectionDefinition(conn)).toBe(true);
319
+ });
320
+
321
+ it("returns false for non-DynamoDB objects", () => {
322
+ expect(isDynamoDBConnectionDefinition({})).toBe(false);
323
+ expect(isDynamoDBConnectionDefinition(null)).toBe(false);
324
+ expect(
325
+ isDynamoDBConnectionDefinition(
326
+ defineS3Connection("my_s3", { region: "us-east-1", arn: "arn:aws:iam::1:role/x" })
327
+ )
328
+ ).toBe(false);
329
+ });
330
+ });
331
+
267
332
  describe("getConnectionType", () => {
268
333
  it("returns the connection type", () => {
269
334
  const conn = defineKafkaConnection("my_kafka", {
@@ -117,13 +117,39 @@ export interface GCSConnectionDefinition {
117
117
  readonly options: GCSConnectionOptions;
118
118
  }
119
119
 
120
+ /**
121
+ * Options for defining a DynamoDB connection
122
+ */
123
+ export interface DynamoDBConnectionOptions {
124
+ /** DynamoDB table region (for example: us-east-1) */
125
+ region: string;
126
+ /** IAM role ARN used by Tinybird to access the table - can use {{ tb_secret(...) }} */
127
+ arn: string;
128
+ }
129
+
130
+ /**
131
+ * DynamoDB-specific connection definition
132
+ */
133
+ export interface DynamoDBConnectionDefinition {
134
+ readonly [CONNECTION_BRAND]: true;
135
+ /** Connection name */
136
+ readonly _name: string;
137
+ /** Type marker for inference */
138
+ readonly _type: "connection";
139
+ /** Connection type */
140
+ readonly _connectionType: "dynamodb";
141
+ /** DynamoDB options */
142
+ readonly options: DynamoDBConnectionOptions;
143
+ }
144
+
120
145
  /**
121
146
  * A connection definition - union of all connection types
122
147
  */
123
148
  export type ConnectionDefinition =
124
149
  | KafkaConnectionDefinition
125
150
  | S3ConnectionDefinition
126
- | GCSConnectionDefinition;
151
+ | GCSConnectionDefinition
152
+ | DynamoDBConnectionDefinition;
127
153
 
128
154
  function validateConnectionName(name: string): void {
129
155
  if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
@@ -231,6 +257,46 @@ export function defineGCSConnection(
231
257
  };
232
258
  }
233
259
 
260
+ /**
261
+ * Define a DynamoDB connection
262
+ *
263
+ * @param name - The connection name (must be valid identifier)
264
+ * @param options - DynamoDB connection configuration
265
+ * @returns A connection definition that can be used in a project
266
+ *
267
+ * @example
268
+ * ```ts
269
+ * import { defineDynamoDBConnection } from '@tinybirdco/sdk';
270
+ *
271
+ * export const myDynamo = defineDynamoDBConnection('my_dynamo', {
272
+ * region: 'us-east-1',
273
+ * arn: '{{ tb_secret("DYNAMODB_ROLE_ARN") }}',
274
+ * });
275
+ * ```
276
+ */
277
+ export function defineDynamoDBConnection(
278
+ name: string,
279
+ options: DynamoDBConnectionOptions
280
+ ): DynamoDBConnectionDefinition {
281
+ validateConnectionName(name);
282
+
283
+ if (!options.region?.trim()) {
284
+ throw new Error("DynamoDB connection `region` is required.");
285
+ }
286
+
287
+ if (!options.arn?.trim()) {
288
+ throw new Error("DynamoDB connection `arn` is required.");
289
+ }
290
+
291
+ return {
292
+ [CONNECTION_BRAND]: true,
293
+ _name: name,
294
+ _type: "connection",
295
+ _connectionType: "dynamodb",
296
+ options,
297
+ };
298
+ }
299
+
234
300
  /**
235
301
  * Check if a value is a connection definition
236
302
  */
@@ -264,6 +330,15 @@ export function isGCSConnectionDefinition(value: unknown): value is GCSConnectio
264
330
  return isConnectionDefinition(value) && value._connectionType === "gcs";
265
331
  }
266
332
 
333
+ /**
334
+ * Check if a value is a DynamoDB connection definition
335
+ */
336
+ export function isDynamoDBConnectionDefinition(
337
+ value: unknown
338
+ ): value is DynamoDBConnectionDefinition {
339
+ return isConnectionDefinition(value) && value._connectionType === "dynamodb";
340
+ }
341
+
267
342
  /**
268
343
  * Get the connection type from a connection definition
269
344
  */
@@ -112,7 +112,7 @@ describe("Datasource Schema", () => {
112
112
  bucketUri: "s3://my-bucket/events/*.csv",
113
113
  },
114
114
  })
115
- ).toThrow("Datasource can only define one ingestion option: `kafka`, `s3`, or `gcs`.");
115
+ ).toThrow("Datasource can only define one ingestion option: `kafka`, `s3`, `gcs`, or `dynamodb`.");
116
116
 
117
117
  expect(() =>
118
118
  defineDatasource("events_gcs", {
@@ -126,7 +126,7 @@ describe("Datasource Schema", () => {
126
126
  bucketUri: "gs://my-bucket/events/*.csv",
127
127
  },
128
128
  })
129
- ).toThrow("Datasource can only define one ingestion option: `kafka`, `s3`, or `gcs`.");
129
+ ).toThrow("Datasource can only define one ingestion option: `kafka`, `s3`, `gcs`, or `dynamodb`.");
130
130
  });
131
131
 
132
132
  it("accepts gcs ingestion configuration", () => {