@tinybirdco/sdk 0.0.77 → 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.
- package/README.md +27 -2
- package/dist/cli/commands/migrate.d.ts.map +1 -1
- package/dist/cli/commands/migrate.js +14 -1
- package/dist/cli/commands/migrate.js.map +1 -1
- package/dist/cli/commands/migrate.test.js +60 -0
- package/dist/cli/commands/migrate.test.js.map +1 -1
- package/dist/generator/connection.d.ts.map +1 -1
- package/dist/generator/connection.js +15 -1
- package/dist/generator/connection.js.map +1 -1
- package/dist/generator/connection.test.js +10 -1
- package/dist/generator/connection.test.js.map +1 -1
- package/dist/generator/datasource.d.ts.map +1 -1
- package/dist/generator/datasource.js +17 -1
- package/dist/generator/datasource.js.map +1 -1
- package/dist/generator/datasource.test.js +44 -1
- package/dist/generator/datasource.test.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/migrate/emit-ts.d.ts.map +1 -1
- package/dist/migrate/emit-ts.js +28 -0
- package/dist/migrate/emit-ts.js.map +1 -1
- package/dist/migrate/parse-connection.d.ts +2 -2
- package/dist/migrate/parse-connection.d.ts.map +1 -1
- package/dist/migrate/parse-connection.js +60 -6
- package/dist/migrate/parse-connection.js.map +1 -1
- package/dist/migrate/parse-connection.test.js +18 -0
- package/dist/migrate/parse-connection.test.js.map +1 -1
- package/dist/migrate/parse-datasource.d.ts.map +1 -1
- package/dist/migrate/parse-datasource.js +27 -2
- package/dist/migrate/parse-datasource.js.map +1 -1
- package/dist/migrate/types.d.ts +15 -1
- package/dist/migrate/types.d.ts.map +1 -1
- package/dist/schema/connection.d.ts +46 -1
- package/dist/schema/connection.d.ts.map +1 -1
- package/dist/schema/connection.js +39 -0
- package/dist/schema/connection.js.map +1 -1
- package/dist/schema/connection.test.js +46 -1
- package/dist/schema/connection.test.js.map +1 -1
- package/dist/schema/datasource.d.ts +14 -1
- package/dist/schema/datasource.d.ts.map +1 -1
- package/dist/schema/datasource.js +2 -2
- package/dist/schema/datasource.js.map +1 -1
- package/dist/schema/datasource.test.js +2 -2
- package/dist/schema/datasource.test.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/migrate.test.ts +95 -0
- package/src/cli/commands/migrate.ts +17 -1
- package/src/generator/connection.test.ts +17 -0
- package/src/generator/connection.ts +18 -0
- package/src/generator/datasource.test.ts +54 -1
- package/src/generator/datasource.ts +24 -1
- package/src/index.ts +5 -0
- package/src/migrate/emit-ts.ts +44 -3
- package/src/migrate/parse-connection.test.ts +41 -0
- package/src/migrate/parse-connection.ts +86 -7
- package/src/migrate/parse-datasource.ts +35 -2
- package/src/migrate/types.ts +18 -1
- package/src/schema/connection.test.ts +65 -0
- package/src/schema/connection.ts +76 -1
- package/src/schema/datasource.test.ts +2 -2
- package/src/schema/datasource.ts +21 -2
|
@@ -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 (
|
|
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
|
-
|
|
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,
|
package/src/migrate/types.ts
CHANGED
|
@@ -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", {
|
package/src/schema/connection.ts
CHANGED
|
@@ -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 `
|
|
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 `
|
|
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", () => {
|
package/src/schema/datasource.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
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) {
|