@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.
- package/README.md +27 -2
- package/dist/api/deploy.d.ts.map +1 -1
- package/dist/api/deploy.js +26 -0
- package/dist/api/deploy.js.map +1 -1
- package/dist/api/deploy.test.js +26 -2
- package/dist/api/deploy.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/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/api/deploy.test.ts +42 -2
- package/src/api/deploy.ts +34 -0
- 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
|
@@ -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) {
|