@tinybirdco/sdk 0.0.76 → 0.0.78

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 (70) hide show
  1. package/README.md +27 -2
  2. package/dist/api/deploy.d.ts.map +1 -1
  3. package/dist/api/deploy.js +26 -0
  4. package/dist/api/deploy.js.map +1 -1
  5. package/dist/api/deploy.test.js +26 -2
  6. package/dist/api/deploy.test.js.map +1 -1
  7. package/dist/cli/commands/migrate.d.ts.map +1 -1
  8. package/dist/cli/commands/migrate.js +14 -1
  9. package/dist/cli/commands/migrate.js.map +1 -1
  10. package/dist/cli/commands/migrate.test.js +60 -0
  11. package/dist/cli/commands/migrate.test.js.map +1 -1
  12. package/dist/generator/connection.d.ts.map +1 -1
  13. package/dist/generator/connection.js +15 -1
  14. package/dist/generator/connection.js.map +1 -1
  15. package/dist/generator/connection.test.js +10 -1
  16. package/dist/generator/connection.test.js.map +1 -1
  17. package/dist/generator/datasource.d.ts.map +1 -1
  18. package/dist/generator/datasource.js +17 -1
  19. package/dist/generator/datasource.js.map +1 -1
  20. package/dist/generator/datasource.test.js +44 -1
  21. package/dist/generator/datasource.test.js.map +1 -1
  22. package/dist/index.d.ts +3 -3
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +1 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/migrate/emit-ts.d.ts.map +1 -1
  27. package/dist/migrate/emit-ts.js +28 -0
  28. package/dist/migrate/emit-ts.js.map +1 -1
  29. package/dist/migrate/parse-connection.d.ts +2 -2
  30. package/dist/migrate/parse-connection.d.ts.map +1 -1
  31. package/dist/migrate/parse-connection.js +60 -6
  32. package/dist/migrate/parse-connection.js.map +1 -1
  33. package/dist/migrate/parse-connection.test.js +18 -0
  34. package/dist/migrate/parse-connection.test.js.map +1 -1
  35. package/dist/migrate/parse-datasource.d.ts.map +1 -1
  36. package/dist/migrate/parse-datasource.js +27 -2
  37. package/dist/migrate/parse-datasource.js.map +1 -1
  38. package/dist/migrate/types.d.ts +15 -1
  39. package/dist/migrate/types.d.ts.map +1 -1
  40. package/dist/schema/connection.d.ts +46 -1
  41. package/dist/schema/connection.d.ts.map +1 -1
  42. package/dist/schema/connection.js +39 -0
  43. package/dist/schema/connection.js.map +1 -1
  44. package/dist/schema/connection.test.js +46 -1
  45. package/dist/schema/connection.test.js.map +1 -1
  46. package/dist/schema/datasource.d.ts +14 -1
  47. package/dist/schema/datasource.d.ts.map +1 -1
  48. package/dist/schema/datasource.js +2 -2
  49. package/dist/schema/datasource.js.map +1 -1
  50. package/dist/schema/datasource.test.js +2 -2
  51. package/dist/schema/datasource.test.js.map +1 -1
  52. package/package.json +1 -1
  53. package/src/api/deploy.test.ts +42 -2
  54. package/src/api/deploy.ts +34 -0
  55. package/src/cli/commands/migrate.test.ts +95 -0
  56. package/src/cli/commands/migrate.ts +17 -1
  57. package/src/generator/connection.test.ts +17 -0
  58. package/src/generator/connection.ts +18 -0
  59. package/src/generator/datasource.test.ts +54 -1
  60. package/src/generator/datasource.ts +24 -1
  61. package/src/index.ts +5 -0
  62. package/src/migrate/emit-ts.ts +44 -3
  63. package/src/migrate/parse-connection.test.ts +41 -0
  64. package/src/migrate/parse-connection.ts +86 -7
  65. package/src/migrate/parse-datasource.ts +35 -2
  66. package/src/migrate/types.ts +18 -1
  67. package/src/schema/connection.test.ts +65 -0
  68. package/src/schema/connection.ts +76 -1
  69. package/src/schema/datasource.test.ts +2 -2
  70. package/src/schema/datasource.ts +21 -2
@@ -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", () => {
@@ -9,6 +9,7 @@ import type {
9
9
  KafkaConnectionDefinition,
10
10
  S3ConnectionDefinition,
11
11
  GCSConnectionDefinition,
12
+ DynamoDBConnectionDefinition,
12
13
  } from "./connection.js";
13
14
  import type { TokenDefinition, DatasourceTokenScope } from "./token.js";
14
15
 
@@ -102,6 +103,18 @@ export interface GCSConfig {
102
103
  fromTimestamp?: string;
103
104
  }
104
105
 
106
+ /**
107
+ * DynamoDB import configuration for a datasource
108
+ */
109
+ export interface DynamoDBConfig {
110
+ /** DynamoDB connection to use */
111
+ connection: DynamoDBConnectionDefinition;
112
+ /** Source DynamoDB table ARN */
113
+ tableArn: string;
114
+ /** S3 bucket Tinybird uses to stage the initial table export */
115
+ exportBucket: string;
116
+ }
117
+
105
118
  /**
106
119
  * Datasource index configuration.
107
120
  * Emits as: `<name> <expr> TYPE <type> GRANULARITY <n>`
@@ -152,6 +165,8 @@ export interface DatasourceOptions<TSchema extends SchemaDefinition> {
152
165
  s3?: S3Config;
153
166
  /** GCS ingestion configuration */
154
167
  gcs?: GCSConfig;
168
+ /** DynamoDB ingestion configuration */
169
+ dynamodb?: DynamoDBConfig;
155
170
  }
156
171
 
157
172
  /**
@@ -209,9 +224,13 @@ export function defineDatasource<TSchema extends SchemaDefinition>(
209
224
  );
210
225
  }
211
226
 
212
- const ingestionConfigCount = [options.kafka, options.s3, options.gcs].filter(Boolean).length;
227
+ const ingestionConfigCount = [options.kafka, options.s3, options.gcs, options.dynamodb].filter(
228
+ Boolean
229
+ ).length;
213
230
  if (ingestionConfigCount > 1) {
214
- throw new Error("Datasource can only define one ingestion option: `kafka`, `s3`, or `gcs`.");
231
+ throw new Error(
232
+ "Datasource can only define one ingestion option: `kafka`, `s3`, `gcs`, or `dynamodb`."
233
+ );
215
234
  }
216
235
 
217
236
  if (options.indexes) {