@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.
- package/README.md +27 -2
- package/dist/api/branches.d.ts +4 -3
- package/dist/api/branches.d.ts.map +1 -1
- package/dist/api/branches.js +4 -4
- package/dist/api/branches.js.map +1 -1
- package/dist/api/branches.test.js +34 -1
- package/dist/api/branches.test.js.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +7 -1
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/build.test.js +159 -0
- package/dist/cli/commands/build.test.js.map +1 -1
- package/dist/cli/commands/clear.d.ts.map +1 -1
- package/dist/cli/commands/clear.js +4 -1
- package/dist/cli/commands/clear.js.map +1 -1
- package/dist/cli/commands/deploy.test.js +1 -0
- package/dist/cli/commands/deploy.test.js.map +1 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +7 -1
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/generate.test.js +3 -0
- package/dist/cli/commands/generate.test.js.map +1 -1
- 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/cli/commands/preview.d.ts.map +1 -1
- package/dist/cli/commands/preview.js +4 -1
- package/dist/cli/commands/preview.js.map +1 -1
- package/dist/cli/commands/preview.test.js +116 -2
- package/dist/cli/commands/preview.test.js.map +1 -1
- package/dist/cli/config-types.d.ts +4 -0
- package/dist/cli/config-types.d.ts.map +1 -1
- package/dist/cli/config-types.js +1 -1
- package/dist/cli/config-types.js.map +1 -1
- package/dist/cli/config.d.ts +4 -2
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +27 -0
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/config.test.js +79 -0
- package/dist/cli/config.test.js.map +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/client/base.d.ts.map +1 -1
- package/dist/client/base.js +4 -1
- package/dist/client/base.js.map +1 -1
- package/dist/client/base.test.js +2 -1
- package/dist/client/base.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 +4 -4
- 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/api/branches.test.ts +38 -1
- package/src/api/branches.ts +8 -6
- package/src/cli/commands/build.test.ts +176 -0
- package/src/cli/commands/build.ts +9 -2
- package/src/cli/commands/clear.ts +8 -1
- package/src/cli/commands/deploy.test.ts +1 -0
- package/src/cli/commands/dev.ts +9 -1
- package/src/cli/commands/generate.test.ts +3 -0
- package/src/cli/commands/migrate.test.ts +95 -0
- package/src/cli/commands/migrate.ts +17 -1
- package/src/cli/commands/preview.test.ts +133 -2
- package/src/cli/commands/preview.ts +6 -2
- package/src/cli/config-types.ts +4 -0
- package/src/cli/config.test.ts +123 -0
- package/src/cli/config.ts +41 -3
- package/src/cli/index.ts +8 -2
- package/src/client/base.test.ts +3 -1
- package/src/client/base.ts +7 -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 +6 -1
- 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
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";
|
package/src/migrate/emit-ts.ts
CHANGED
|
@@ -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:
|
|
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
|
-
(
|
|
629
|
-
resource
|
|
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 (
|
|
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", () => {
|