@tinybirdco/sdk 0.0.48 → 0.0.50
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 +71 -4
- package/dist/cli/commands/migrate.d.ts.map +1 -1
- package/dist/cli/commands/migrate.js +68 -1
- package/dist/cli/commands/migrate.js.map +1 -1
- package/dist/cli/commands/migrate.test.js +458 -1
- package/dist/cli/commands/migrate.test.js.map +1 -1
- package/dist/generator/connection.d.ts.map +1 -1
- package/dist/generator/connection.js +14 -1
- package/dist/generator/connection.js.map +1 -1
- package/dist/generator/connection.test.js +20 -4
- package/dist/generator/connection.test.js.map +1 -1
- package/dist/generator/datasource.d.ts.map +1 -1
- package/dist/generator/datasource.js +20 -10
- package/dist/generator/datasource.js.map +1 -1
- package/dist/generator/datasource.test.js +26 -1
- package/dist/generator/datasource.test.js.map +1 -1
- package/dist/generator/pipe.d.ts.map +1 -1
- package/dist/generator/pipe.js +31 -1
- package/dist/generator/pipe.js.map +1 -1
- package/dist/generator/pipe.test.js +50 -1
- package/dist/generator/pipe.test.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/migrate/emit-ts.d.ts.map +1 -1
- package/dist/migrate/emit-ts.js +95 -20
- 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 +34 -4
- package/dist/migrate/parse-connection.js.map +1 -1
- package/dist/migrate/parse-datasource.d.ts.map +1 -1
- package/dist/migrate/parse-datasource.js +79 -51
- package/dist/migrate/parse-datasource.js.map +1 -1
- package/dist/migrate/parse-pipe.d.ts.map +1 -1
- package/dist/migrate/parse-pipe.js +254 -44
- package/dist/migrate/parse-pipe.js.map +1 -1
- package/dist/migrate/parser-utils.d.ts +5 -0
- package/dist/migrate/parser-utils.d.ts.map +1 -1
- package/dist/migrate/parser-utils.js +22 -0
- package/dist/migrate/parser-utils.js.map +1 -1
- package/dist/migrate/types.d.ts +37 -4
- package/dist/migrate/types.d.ts.map +1 -1
- package/dist/schema/connection.d.ts +34 -1
- package/dist/schema/connection.d.ts.map +1 -1
- package/dist/schema/connection.js +26 -0
- package/dist/schema/connection.js.map +1 -1
- package/dist/schema/connection.test.js +35 -1
- package/dist/schema/connection.test.js.map +1 -1
- package/dist/schema/datasource.d.ts +16 -1
- package/dist/schema/datasource.d.ts.map +1 -1
- package/dist/schema/datasource.js +3 -2
- package/dist/schema/datasource.js.map +1 -1
- package/dist/schema/datasource.test.js +33 -3
- package/dist/schema/datasource.test.js.map +1 -1
- package/dist/schema/pipe.d.ts +90 -3
- package/dist/schema/pipe.d.ts.map +1 -1
- package/dist/schema/pipe.js +84 -0
- package/dist/schema/pipe.js.map +1 -1
- package/dist/schema/pipe.test.js +70 -1
- package/dist/schema/pipe.test.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/migrate.test.ts +671 -1
- package/src/cli/commands/migrate.ts +74 -1
- package/src/generator/connection.test.ts +29 -4
- package/src/generator/connection.ts +25 -2
- package/src/generator/datasource.test.ts +30 -1
- package/src/generator/datasource.ts +22 -10
- package/src/generator/pipe.test.ts +56 -1
- package/src/generator/pipe.ts +41 -1
- package/src/index.ts +14 -0
- package/src/migrate/emit-ts.ts +106 -24
- package/src/migrate/parse-connection.ts +56 -6
- package/src/migrate/parse-datasource.ts +84 -70
- package/src/migrate/parse-pipe.ts +359 -66
- package/src/migrate/parser-utils.ts +36 -1
- package/src/migrate/types.ts +43 -4
- package/src/schema/connection.test.ts +48 -0
- package/src/schema/connection.ts +60 -1
- package/src/schema/datasource.test.ts +39 -3
- package/src/schema/datasource.ts +24 -3
- package/src/schema/pipe.test.ts +89 -0
- package/src/schema/pipe.ts +188 -4
package/src/migrate/emit-ts.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { toCamelCase } from "../codegen/utils.js";
|
|
|
3
3
|
import { parseLiteralFromDatafile, toTsLiteral } from "./parser-utils.js";
|
|
4
4
|
import type {
|
|
5
5
|
DatasourceModel,
|
|
6
|
+
DatasourceEngineModel,
|
|
7
|
+
GCSConnectionModel,
|
|
6
8
|
KafkaConnectionModel,
|
|
7
9
|
ParsedResource,
|
|
8
10
|
PipeModel,
|
|
@@ -58,11 +60,13 @@ function hasSecretTemplate(resources: ParsedResource[]): boolean {
|
|
|
58
60
|
if (resource.secret) values.push(resource.secret);
|
|
59
61
|
if (resource.sslCaPem) values.push(resource.sslCaPem);
|
|
60
62
|
if (resource.schemaRegistryUrl) values.push(resource.schemaRegistryUrl);
|
|
61
|
-
} else {
|
|
63
|
+
} else if (resource.connectionType === "s3") {
|
|
62
64
|
values.push(resource.region);
|
|
63
65
|
if (resource.arn) values.push(resource.arn);
|
|
64
66
|
if (resource.accessKey) values.push(resource.accessKey);
|
|
65
67
|
if (resource.secret) values.push(resource.secret);
|
|
68
|
+
} else {
|
|
69
|
+
values.push(resource.serviceAccountCredentialsJson);
|
|
66
70
|
}
|
|
67
71
|
continue;
|
|
68
72
|
}
|
|
@@ -79,6 +83,11 @@ function hasSecretTemplate(resources: ParsedResource[]): boolean {
|
|
|
79
83
|
if (resource.s3.schedule) values.push(resource.s3.schedule);
|
|
80
84
|
if (resource.s3.fromTimestamp) values.push(resource.s3.fromTimestamp);
|
|
81
85
|
}
|
|
86
|
+
if (resource.gcs) {
|
|
87
|
+
values.push(resource.gcs.bucketUri);
|
|
88
|
+
if (resource.gcs.schedule) values.push(resource.gcs.schedule);
|
|
89
|
+
if (resource.gcs.fromTimestamp) values.push(resource.gcs.fromTimestamp);
|
|
90
|
+
}
|
|
82
91
|
continue;
|
|
83
92
|
}
|
|
84
93
|
}
|
|
@@ -151,7 +160,7 @@ function strictParamBaseValidator(type: string): string {
|
|
|
151
160
|
function applyParamOptional(
|
|
152
161
|
baseValidator: string,
|
|
153
162
|
required: boolean,
|
|
154
|
-
defaultValue: string | number | undefined
|
|
163
|
+
defaultValue: string | number | boolean | undefined
|
|
155
164
|
): string {
|
|
156
165
|
const withDefault = defaultValue !== undefined;
|
|
157
166
|
if (!withDefault && required) {
|
|
@@ -168,6 +177,13 @@ function applyParamOptional(
|
|
|
168
177
|
return `${baseValidator}${optionalSuffix}`;
|
|
169
178
|
}
|
|
170
179
|
|
|
180
|
+
function applyParamDescription(validator: string, description: string | undefined): string {
|
|
181
|
+
if (description === undefined) {
|
|
182
|
+
return validator;
|
|
183
|
+
}
|
|
184
|
+
return `${validator}.describe(${JSON.stringify(description)})`;
|
|
185
|
+
}
|
|
186
|
+
|
|
171
187
|
function engineFunctionName(type: string): string {
|
|
172
188
|
const map: Record<string, string> = {
|
|
173
189
|
MergeTree: "mergeTree",
|
|
@@ -184,9 +200,8 @@ function engineFunctionName(type: string): string {
|
|
|
184
200
|
return functionName;
|
|
185
201
|
}
|
|
186
202
|
|
|
187
|
-
function emitEngineOptions(
|
|
203
|
+
function emitEngineOptions(engine: DatasourceEngineModel): string {
|
|
188
204
|
const options: string[] = [];
|
|
189
|
-
const { engine } = ds;
|
|
190
205
|
|
|
191
206
|
if (engine.sortingKey.length === 1) {
|
|
192
207
|
options.push(`sortingKey: ${escapeString(engine.sortingKey[0]!)}`);
|
|
@@ -256,7 +271,7 @@ function emitDatasource(ds: DatasourceModel): string {
|
|
|
256
271
|
}
|
|
257
272
|
|
|
258
273
|
lines.push(`export const ${variableName} = defineDatasource(${escapeString(ds.name)}, {`);
|
|
259
|
-
if (ds.description) {
|
|
274
|
+
if (ds.description !== undefined) {
|
|
260
275
|
lines.push(` description: ${emitStringOrSecret(ds.description)},`);
|
|
261
276
|
}
|
|
262
277
|
if (!hasJsonPath) {
|
|
@@ -295,7 +310,9 @@ function emitDatasource(ds: DatasourceModel): string {
|
|
|
295
310
|
lines.push(` ${columnKey}: ${validator},`);
|
|
296
311
|
}
|
|
297
312
|
lines.push(" },");
|
|
298
|
-
|
|
313
|
+
if (ds.engine) {
|
|
314
|
+
lines.push(` engine: ${emitEngineOptions(ds.engine)},`);
|
|
315
|
+
}
|
|
299
316
|
|
|
300
317
|
if (ds.kafka) {
|
|
301
318
|
const connectionVar = toCamelCase(ds.kafka.connectionName);
|
|
@@ -328,6 +345,20 @@ function emitDatasource(ds: DatasourceModel): string {
|
|
|
328
345
|
lines.push(" },");
|
|
329
346
|
}
|
|
330
347
|
|
|
348
|
+
if (ds.gcs) {
|
|
349
|
+
const connectionVar = toCamelCase(ds.gcs.connectionName);
|
|
350
|
+
lines.push(" gcs: {");
|
|
351
|
+
lines.push(` connection: ${connectionVar},`);
|
|
352
|
+
lines.push(` bucketUri: ${emitStringOrSecret(ds.gcs.bucketUri)},`);
|
|
353
|
+
if (ds.gcs.schedule) {
|
|
354
|
+
lines.push(` schedule: ${emitStringOrSecret(ds.gcs.schedule)},`);
|
|
355
|
+
}
|
|
356
|
+
if (ds.gcs.fromTimestamp) {
|
|
357
|
+
lines.push(` fromTimestamp: ${emitStringOrSecret(ds.gcs.fromTimestamp)},`);
|
|
358
|
+
}
|
|
359
|
+
lines.push(" },");
|
|
360
|
+
}
|
|
361
|
+
|
|
331
362
|
if (ds.forwardQuery) {
|
|
332
363
|
lines.push(" forwardQuery: `");
|
|
333
364
|
lines.push(ds.forwardQuery.replace(/`/g, "\\`").replace(/\${/g, "\\${"));
|
|
@@ -355,7 +386,9 @@ function emitDatasource(ds: DatasourceModel): string {
|
|
|
355
386
|
return lines.join("\n");
|
|
356
387
|
}
|
|
357
388
|
|
|
358
|
-
function emitConnection(
|
|
389
|
+
function emitConnection(
|
|
390
|
+
connection: KafkaConnectionModel | S3ConnectionModel | GCSConnectionModel
|
|
391
|
+
): string {
|
|
359
392
|
const variableName = toCamelCase(connection.name);
|
|
360
393
|
const lines: string[] = [];
|
|
361
394
|
|
|
@@ -387,19 +420,31 @@ function emitConnection(connection: KafkaConnectionModel | S3ConnectionModel): s
|
|
|
387
420
|
return lines.join("\n");
|
|
388
421
|
}
|
|
389
422
|
|
|
423
|
+
if (connection.connectionType === "s3") {
|
|
424
|
+
lines.push(
|
|
425
|
+
`export const ${variableName} = defineS3Connection(${escapeString(connection.name)}, {`
|
|
426
|
+
);
|
|
427
|
+
lines.push(` region: ${emitStringOrSecret(connection.region)},`);
|
|
428
|
+
if (connection.arn) {
|
|
429
|
+
lines.push(` arn: ${emitStringOrSecret(connection.arn)},`);
|
|
430
|
+
}
|
|
431
|
+
if (connection.accessKey) {
|
|
432
|
+
lines.push(` accessKey: ${emitStringOrSecret(connection.accessKey)},`);
|
|
433
|
+
}
|
|
434
|
+
if (connection.secret) {
|
|
435
|
+
lines.push(` secret: ${emitStringOrSecret(connection.secret)},`);
|
|
436
|
+
}
|
|
437
|
+
lines.push("});");
|
|
438
|
+
lines.push("");
|
|
439
|
+
return lines.join("\n");
|
|
440
|
+
}
|
|
441
|
+
|
|
390
442
|
lines.push(
|
|
391
|
-
`export const ${variableName} =
|
|
443
|
+
`export const ${variableName} = defineGCSConnection(${escapeString(connection.name)}, {`
|
|
444
|
+
);
|
|
445
|
+
lines.push(
|
|
446
|
+
` serviceAccountCredentialsJson: ${emitStringOrSecret(connection.serviceAccountCredentialsJson)},`
|
|
392
447
|
);
|
|
393
|
-
lines.push(` region: ${emitStringOrSecret(connection.region)},`);
|
|
394
|
-
if (connection.arn) {
|
|
395
|
-
lines.push(` arn: ${emitStringOrSecret(connection.arn)},`);
|
|
396
|
-
}
|
|
397
|
-
if (connection.accessKey) {
|
|
398
|
-
lines.push(` accessKey: ${emitStringOrSecret(connection.accessKey)},`);
|
|
399
|
-
}
|
|
400
|
-
if (connection.secret) {
|
|
401
|
-
lines.push(` secret: ${emitStringOrSecret(connection.secret)},`);
|
|
402
|
-
}
|
|
403
448
|
lines.push("});");
|
|
404
449
|
lines.push("");
|
|
405
450
|
return lines.join("\n");
|
|
@@ -423,24 +468,27 @@ function emitPipe(pipe: PipeModel): string {
|
|
|
423
468
|
lines.push(`export const ${variableName} = defineMaterializedView(${escapeString(pipe.name)}, {`);
|
|
424
469
|
} else if (pipe.type === "copy") {
|
|
425
470
|
lines.push(`export const ${variableName} = defineCopyPipe(${escapeString(pipe.name)}, {`);
|
|
471
|
+
} else if (pipe.type === "sink") {
|
|
472
|
+
lines.push(`export const ${variableName} = defineSinkPipe(${escapeString(pipe.name)}, {`);
|
|
426
473
|
} else {
|
|
427
474
|
lines.push(`export const ${variableName} = definePipe(${escapeString(pipe.name)}, {`);
|
|
428
475
|
}
|
|
429
476
|
|
|
430
|
-
if (pipe.description) {
|
|
477
|
+
if (pipe.description !== undefined) {
|
|
431
478
|
lines.push(` description: ${escapeString(pipe.description)},`);
|
|
432
479
|
}
|
|
433
480
|
|
|
434
|
-
if (pipe.type === "pipe" || pipe.type === "endpoint") {
|
|
481
|
+
if (pipe.type === "pipe" || pipe.type === "endpoint" || pipe.type === "sink") {
|
|
435
482
|
if (pipe.params.length > 0) {
|
|
436
483
|
lines.push(" params: {");
|
|
437
484
|
for (const param of pipe.params) {
|
|
438
485
|
const baseValidator = strictParamBaseValidator(param.type);
|
|
439
|
-
const
|
|
486
|
+
const validatorWithOptional = applyParamOptional(
|
|
440
487
|
baseValidator,
|
|
441
488
|
param.required,
|
|
442
489
|
param.defaultValue
|
|
443
490
|
);
|
|
491
|
+
const validator = applyParamDescription(validatorWithOptional, param.description);
|
|
444
492
|
lines.push(` ${emitObjectKey(param.name)}: ${validator},`);
|
|
445
493
|
}
|
|
446
494
|
lines.push(" },");
|
|
@@ -464,11 +512,35 @@ function emitPipe(pipe: PipeModel): string {
|
|
|
464
512
|
}
|
|
465
513
|
}
|
|
466
514
|
|
|
515
|
+
if (pipe.type === "sink") {
|
|
516
|
+
if (!pipe.sink) {
|
|
517
|
+
throw new Error(`Sink pipe "${pipe.name}" is missing sink configuration.`);
|
|
518
|
+
}
|
|
519
|
+
lines.push(" sink: {");
|
|
520
|
+
lines.push(` connection: ${toCamelCase(pipe.sink.connectionName)},`);
|
|
521
|
+
if (pipe.sink.service === "kafka") {
|
|
522
|
+
lines.push(` topic: ${escapeString(pipe.sink.topic)},`);
|
|
523
|
+
lines.push(` schedule: ${escapeString(pipe.sink.schedule)},`);
|
|
524
|
+
} else {
|
|
525
|
+
lines.push(` bucketUri: ${escapeString(pipe.sink.bucketUri)},`);
|
|
526
|
+
lines.push(` fileTemplate: ${escapeString(pipe.sink.fileTemplate)},`);
|
|
527
|
+
lines.push(` schedule: ${escapeString(pipe.sink.schedule)},`);
|
|
528
|
+
lines.push(` format: ${escapeString(pipe.sink.format)},`);
|
|
529
|
+
if (pipe.sink.strategy) {
|
|
530
|
+
lines.push(` strategy: ${escapeString(pipe.sink.strategy)},`);
|
|
531
|
+
}
|
|
532
|
+
if (pipe.sink.compression) {
|
|
533
|
+
lines.push(` compression: ${escapeString(pipe.sink.compression)},`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
lines.push(" },");
|
|
537
|
+
}
|
|
538
|
+
|
|
467
539
|
lines.push(" nodes: [");
|
|
468
540
|
for (const node of pipe.nodes) {
|
|
469
541
|
lines.push(" node({");
|
|
470
542
|
lines.push(` name: ${escapeString(node.name)},`);
|
|
471
|
-
if (node.description) {
|
|
543
|
+
if (node.description !== undefined) {
|
|
472
544
|
lines.push(` description: ${escapeString(node.description)},`);
|
|
473
545
|
}
|
|
474
546
|
lines.push(" sql: `");
|
|
@@ -506,7 +578,7 @@ function emitPipe(pipe: PipeModel): string {
|
|
|
506
578
|
|
|
507
579
|
export function emitMigrationFileContent(resources: ParsedResource[]): string {
|
|
508
580
|
const connections = resources.filter(
|
|
509
|
-
(resource): resource is KafkaConnectionModel | S3ConnectionModel =>
|
|
581
|
+
(resource): resource is KafkaConnectionModel | S3ConnectionModel | GCSConnectionModel =>
|
|
510
582
|
resource.kind === "connection"
|
|
511
583
|
);
|
|
512
584
|
const datasources = resources.filter(
|
|
@@ -526,7 +598,6 @@ export function emitMigrationFileContent(resources: ParsedResource[]): string {
|
|
|
526
598
|
"defineCopyPipe",
|
|
527
599
|
"node",
|
|
528
600
|
"t",
|
|
529
|
-
"engine",
|
|
530
601
|
]);
|
|
531
602
|
if (connections.some((connection) => connection.connectionType === "kafka")) {
|
|
532
603
|
imports.add("defineKafkaConnection");
|
|
@@ -534,9 +605,18 @@ export function emitMigrationFileContent(resources: ParsedResource[]): string {
|
|
|
534
605
|
if (connections.some((connection) => connection.connectionType === "s3")) {
|
|
535
606
|
imports.add("defineS3Connection");
|
|
536
607
|
}
|
|
608
|
+
if (connections.some((connection) => connection.connectionType === "gcs")) {
|
|
609
|
+
imports.add("defineGCSConnection");
|
|
610
|
+
}
|
|
537
611
|
if (needsParams) {
|
|
538
612
|
imports.add("p");
|
|
539
613
|
}
|
|
614
|
+
if (pipes.some((pipe) => pipe.type === "sink")) {
|
|
615
|
+
imports.add("defineSinkPipe");
|
|
616
|
+
}
|
|
617
|
+
if (datasources.some((datasource) => datasource.engine !== undefined)) {
|
|
618
|
+
imports.add("engine");
|
|
619
|
+
}
|
|
540
620
|
if (needsSecret) {
|
|
541
621
|
imports.add("secret");
|
|
542
622
|
}
|
|
@@ -544,10 +624,12 @@ export function emitMigrationFileContent(resources: ParsedResource[]): string {
|
|
|
544
624
|
const orderedImports = [
|
|
545
625
|
"defineKafkaConnection",
|
|
546
626
|
"defineS3Connection",
|
|
627
|
+
"defineGCSConnection",
|
|
547
628
|
"defineDatasource",
|
|
548
629
|
"definePipe",
|
|
549
630
|
"defineMaterializedView",
|
|
550
631
|
"defineCopyPipe",
|
|
632
|
+
"defineSinkPipe",
|
|
551
633
|
"node",
|
|
552
634
|
"t",
|
|
553
635
|
"engine",
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
GCSConnectionModel,
|
|
3
|
+
KafkaConnectionModel,
|
|
4
|
+
ResourceFile,
|
|
5
|
+
S3ConnectionModel,
|
|
6
|
+
} from "./types.js";
|
|
2
7
|
import {
|
|
3
8
|
MigrationParseError,
|
|
4
9
|
isBlank,
|
|
@@ -9,7 +14,7 @@ import {
|
|
|
9
14
|
|
|
10
15
|
export function parseConnectionFile(
|
|
11
16
|
resource: ResourceFile
|
|
12
|
-
): KafkaConnectionModel | S3ConnectionModel {
|
|
17
|
+
): KafkaConnectionModel | S3ConnectionModel | GCSConnectionModel {
|
|
13
18
|
const lines = splitLines(resource.content);
|
|
14
19
|
let connectionType: string | undefined;
|
|
15
20
|
|
|
@@ -34,6 +39,7 @@ export function parseConnectionFile(
|
|
|
34
39
|
let arn: string | undefined;
|
|
35
40
|
let accessKey: string | undefined;
|
|
36
41
|
let accessSecret: string | undefined;
|
|
42
|
+
let serviceAccountCredentialsJson: string | undefined;
|
|
37
43
|
|
|
38
44
|
for (const rawLine of lines) {
|
|
39
45
|
const line = rawLine.trim();
|
|
@@ -100,6 +106,9 @@ export function parseConnectionFile(
|
|
|
100
106
|
case "S3_SECRET":
|
|
101
107
|
accessSecret = parseQuotedValue(value);
|
|
102
108
|
break;
|
|
109
|
+
case "GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON":
|
|
110
|
+
serviceAccountCredentialsJson = parseQuotedValue(value);
|
|
111
|
+
break;
|
|
103
112
|
default:
|
|
104
113
|
throw new MigrationParseError(
|
|
105
114
|
resource.filePath,
|
|
@@ -120,12 +129,12 @@ export function parseConnectionFile(
|
|
|
120
129
|
}
|
|
121
130
|
|
|
122
131
|
if (connectionType === "kafka") {
|
|
123
|
-
if (region || arn || accessKey || accessSecret) {
|
|
132
|
+
if (region || arn || accessKey || accessSecret || serviceAccountCredentialsJson) {
|
|
124
133
|
throw new MigrationParseError(
|
|
125
134
|
resource.filePath,
|
|
126
135
|
"connection",
|
|
127
136
|
resource.name,
|
|
128
|
-
"S3 directives are not valid for kafka connections."
|
|
137
|
+
"S3/GCS directives are not valid for kafka connections."
|
|
129
138
|
);
|
|
130
139
|
}
|
|
131
140
|
|
|
@@ -161,13 +170,14 @@ export function parseConnectionFile(
|
|
|
161
170
|
key ||
|
|
162
171
|
secret ||
|
|
163
172
|
schemaRegistryUrl ||
|
|
164
|
-
sslCaPem
|
|
173
|
+
sslCaPem ||
|
|
174
|
+
serviceAccountCredentialsJson
|
|
165
175
|
) {
|
|
166
176
|
throw new MigrationParseError(
|
|
167
177
|
resource.filePath,
|
|
168
178
|
"connection",
|
|
169
179
|
resource.name,
|
|
170
|
-
"Kafka directives are not valid for s3 connections."
|
|
180
|
+
"Kafka/GCS directives are not valid for s3 connections."
|
|
171
181
|
);
|
|
172
182
|
}
|
|
173
183
|
|
|
@@ -210,6 +220,46 @@ export function parseConnectionFile(
|
|
|
210
220
|
};
|
|
211
221
|
}
|
|
212
222
|
|
|
223
|
+
if (connectionType === "gcs") {
|
|
224
|
+
if (
|
|
225
|
+
bootstrapServers ||
|
|
226
|
+
securityProtocol ||
|
|
227
|
+
saslMechanism ||
|
|
228
|
+
key ||
|
|
229
|
+
secret ||
|
|
230
|
+
schemaRegistryUrl ||
|
|
231
|
+
sslCaPem ||
|
|
232
|
+
region ||
|
|
233
|
+
arn ||
|
|
234
|
+
accessKey ||
|
|
235
|
+
accessSecret
|
|
236
|
+
) {
|
|
237
|
+
throw new MigrationParseError(
|
|
238
|
+
resource.filePath,
|
|
239
|
+
"connection",
|
|
240
|
+
resource.name,
|
|
241
|
+
"Kafka/S3 directives are not valid for gcs connections."
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!serviceAccountCredentialsJson) {
|
|
246
|
+
throw new MigrationParseError(
|
|
247
|
+
resource.filePath,
|
|
248
|
+
"connection",
|
|
249
|
+
resource.name,
|
|
250
|
+
"GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON is required for gcs connections."
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
kind: "connection",
|
|
256
|
+
name: resource.name,
|
|
257
|
+
filePath: resource.filePath,
|
|
258
|
+
connectionType: "gcs",
|
|
259
|
+
serviceAccountCredentialsJson,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
213
263
|
throw new MigrationParseError(
|
|
214
264
|
resource.filePath,
|
|
215
265
|
"connection",
|
|
@@ -4,45 +4,44 @@ import {
|
|
|
4
4
|
isBlank,
|
|
5
5
|
parseDirectiveLine,
|
|
6
6
|
parseQuotedValue,
|
|
7
|
+
readDirectiveBlock,
|
|
7
8
|
splitCommaSeparated,
|
|
8
9
|
splitLines,
|
|
9
10
|
splitTopLevelComma,
|
|
10
|
-
stripIndent,
|
|
11
11
|
} from "./parser-utils.js";
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
break;
|
|
13
|
+
const DATASOURCE_DIRECTIVES = new Set([
|
|
14
|
+
"DESCRIPTION",
|
|
15
|
+
"SCHEMA",
|
|
16
|
+
"FORWARD_QUERY",
|
|
17
|
+
"SHARED_WITH",
|
|
18
|
+
"ENGINE",
|
|
19
|
+
"ENGINE_SORTING_KEY",
|
|
20
|
+
"ENGINE_PARTITION_KEY",
|
|
21
|
+
"ENGINE_PRIMARY_KEY",
|
|
22
|
+
"ENGINE_TTL",
|
|
23
|
+
"ENGINE_VER",
|
|
24
|
+
"ENGINE_SIGN",
|
|
25
|
+
"ENGINE_VERSION",
|
|
26
|
+
"ENGINE_SUMMING_COLUMNS",
|
|
27
|
+
"ENGINE_SETTINGS",
|
|
28
|
+
"KAFKA_CONNECTION_NAME",
|
|
29
|
+
"KAFKA_TOPIC",
|
|
30
|
+
"KAFKA_GROUP_ID",
|
|
31
|
+
"KAFKA_AUTO_OFFSET_RESET",
|
|
32
|
+
"IMPORT_CONNECTION_NAME",
|
|
33
|
+
"IMPORT_BUCKET_URI",
|
|
34
|
+
"IMPORT_SCHEDULE",
|
|
35
|
+
"IMPORT_FROM_TIMESTAMP",
|
|
36
|
+
"TOKEN",
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
function isDatasourceDirectiveLine(line: string): boolean {
|
|
40
|
+
if (!line) {
|
|
41
|
+
return false;
|
|
43
42
|
}
|
|
44
|
-
|
|
45
|
-
return
|
|
43
|
+
const { key } = parseDirectiveLine(line);
|
|
44
|
+
return DATASOURCE_DIRECTIVES.has(key);
|
|
46
45
|
}
|
|
47
46
|
|
|
48
47
|
function findTokenOutsideContexts(input: string, token: string): number {
|
|
@@ -197,7 +196,15 @@ function parseEngineSettings(value: string): Record<string, string | number | bo
|
|
|
197
196
|
}
|
|
198
197
|
|
|
199
198
|
function parseToken(filePath: string, resourceName: string, value: string): DatasourceTokenModel {
|
|
200
|
-
const
|
|
199
|
+
const trimmed = value.trim();
|
|
200
|
+
const quotedMatch = trimmed.match(/^"([^"]+)"\s+(READ|APPEND)$/);
|
|
201
|
+
if (quotedMatch) {
|
|
202
|
+
const name = quotedMatch[1];
|
|
203
|
+
const scope = quotedMatch[2] as "READ" | "APPEND";
|
|
204
|
+
return { name, scope };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const parts = trimmed.split(/\s+/).filter(Boolean);
|
|
201
208
|
if (parts.length < 2) {
|
|
202
209
|
throw new MigrationParseError(
|
|
203
210
|
filePath,
|
|
@@ -216,7 +223,11 @@ function parseToken(filePath: string, resourceName: string, value: string): Data
|
|
|
216
223
|
);
|
|
217
224
|
}
|
|
218
225
|
|
|
219
|
-
const
|
|
226
|
+
const rawName = parts[0] ?? "";
|
|
227
|
+
const name =
|
|
228
|
+
rawName.startsWith('"') && rawName.endsWith('"') && rawName.length >= 2
|
|
229
|
+
? rawName.slice(1, -1)
|
|
230
|
+
: rawName;
|
|
220
231
|
const scope = parts[1];
|
|
221
232
|
if (scope !== "READ" && scope !== "APPEND") {
|
|
222
233
|
throw new MigrationParseError(
|
|
@@ -271,22 +282,14 @@ export function parseDatasourceFile(resource: ResourceFile): DatasourceModel {
|
|
|
271
282
|
}
|
|
272
283
|
|
|
273
284
|
if (line === "DESCRIPTION >") {
|
|
274
|
-
const block =
|
|
275
|
-
if (block.lines.length === 0) {
|
|
276
|
-
throw new MigrationParseError(
|
|
277
|
-
resource.filePath,
|
|
278
|
-
"datasource",
|
|
279
|
-
resource.name,
|
|
280
|
-
"DESCRIPTION block is empty."
|
|
281
|
-
);
|
|
282
|
-
}
|
|
285
|
+
const block = readDirectiveBlock(lines, i + 1, isDatasourceDirectiveLine);
|
|
283
286
|
description = block.lines.join("\n");
|
|
284
287
|
i = block.nextIndex;
|
|
285
288
|
continue;
|
|
286
289
|
}
|
|
287
290
|
|
|
288
291
|
if (line === "SCHEMA >") {
|
|
289
|
-
const block =
|
|
292
|
+
const block = readDirectiveBlock(lines, i + 1, isDatasourceDirectiveLine);
|
|
290
293
|
if (block.lines.length === 0) {
|
|
291
294
|
throw new MigrationParseError(
|
|
292
295
|
resource.filePath,
|
|
@@ -306,7 +309,7 @@ export function parseDatasourceFile(resource: ResourceFile): DatasourceModel {
|
|
|
306
309
|
}
|
|
307
310
|
|
|
308
311
|
if (line === "FORWARD_QUERY >") {
|
|
309
|
-
const block =
|
|
312
|
+
const block = readDirectiveBlock(lines, i + 1, isDatasourceDirectiveLine);
|
|
310
313
|
if (block.lines.length === 0) {
|
|
311
314
|
throw new MigrationParseError(
|
|
312
315
|
resource.filePath,
|
|
@@ -321,7 +324,7 @@ export function parseDatasourceFile(resource: ResourceFile): DatasourceModel {
|
|
|
321
324
|
}
|
|
322
325
|
|
|
323
326
|
if (line === "SHARED_WITH >") {
|
|
324
|
-
const block =
|
|
327
|
+
const block = readDirectiveBlock(lines, i + 1, isDatasourceDirectiveLine);
|
|
325
328
|
for (const sharedLine of block.lines) {
|
|
326
329
|
const normalized = sharedLine.trim().replace(/,$/, "");
|
|
327
330
|
if (normalized) {
|
|
@@ -449,16 +452,25 @@ export function parseDatasourceFile(resource: ResourceFile): DatasourceModel {
|
|
|
449
452
|
);
|
|
450
453
|
}
|
|
451
454
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
455
|
+
const hasEngineDirectives =
|
|
456
|
+
sortingKey.length > 0 ||
|
|
457
|
+
partitionKey !== undefined ||
|
|
458
|
+
(primaryKey !== undefined && primaryKey.length > 0) ||
|
|
459
|
+
ttl !== undefined ||
|
|
460
|
+
ver !== undefined ||
|
|
461
|
+
isDeleted !== undefined ||
|
|
462
|
+
sign !== undefined ||
|
|
463
|
+
version !== undefined ||
|
|
464
|
+
(summingColumns !== undefined && summingColumns.length > 0) ||
|
|
465
|
+
settings !== undefined;
|
|
466
|
+
|
|
467
|
+
if (!engineType && hasEngineDirectives) {
|
|
468
|
+
// Tinybird defaults to MergeTree when ENGINE is omitted.
|
|
469
|
+
// If engine-specific options are present, preserve them by inferring MergeTree.
|
|
470
|
+
engineType = "MergeTree";
|
|
459
471
|
}
|
|
460
472
|
|
|
461
|
-
if (sortingKey.length === 0) {
|
|
473
|
+
if (engineType && sortingKey.length === 0) {
|
|
462
474
|
throw new MigrationParseError(
|
|
463
475
|
resource.filePath,
|
|
464
476
|
"datasource",
|
|
@@ -506,7 +518,7 @@ export function parseDatasourceFile(resource: ResourceFile): DatasourceModel {
|
|
|
506
518
|
resource.filePath,
|
|
507
519
|
"datasource",
|
|
508
520
|
resource.name,
|
|
509
|
-
"IMPORT_CONNECTION_NAME and IMPORT_BUCKET_URI are required when
|
|
521
|
+
"IMPORT_CONNECTION_NAME and IMPORT_BUCKET_URI are required when import directives are used."
|
|
510
522
|
);
|
|
511
523
|
}
|
|
512
524
|
|
|
@@ -515,7 +527,7 @@ export function parseDatasourceFile(resource: ResourceFile): DatasourceModel {
|
|
|
515
527
|
resource.filePath,
|
|
516
528
|
"datasource",
|
|
517
529
|
resource.name,
|
|
518
|
-
"Datasource cannot mix Kafka directives with
|
|
530
|
+
"Datasource cannot mix Kafka directives with import directives."
|
|
519
531
|
);
|
|
520
532
|
}
|
|
521
533
|
|
|
@@ -525,19 +537,21 @@ export function parseDatasourceFile(resource: ResourceFile): DatasourceModel {
|
|
|
525
537
|
filePath: resource.filePath,
|
|
526
538
|
description,
|
|
527
539
|
columns,
|
|
528
|
-
engine:
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
540
|
+
engine: engineType
|
|
541
|
+
? {
|
|
542
|
+
type: engineType,
|
|
543
|
+
sortingKey,
|
|
544
|
+
partitionKey,
|
|
545
|
+
primaryKey,
|
|
546
|
+
ttl,
|
|
547
|
+
ver,
|
|
548
|
+
isDeleted,
|
|
549
|
+
sign,
|
|
550
|
+
version,
|
|
551
|
+
summingColumns,
|
|
552
|
+
settings,
|
|
553
|
+
}
|
|
554
|
+
: undefined,
|
|
541
555
|
kafka,
|
|
542
556
|
s3,
|
|
543
557
|
forwardQuery,
|