@tinybirdco/sdk 0.0.2 → 0.0.3
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/dist/api/build.d.ts +2 -0
- package/dist/api/build.d.ts.map +1 -1
- package/dist/api/build.js +13 -0
- package/dist/api/build.js.map +1 -1
- package/dist/api/build.test.js +1 -0
- package/dist/api/build.test.js.map +1 -1
- package/dist/api/deploy.d.ts.map +1 -1
- package/dist/api/deploy.js +3 -0
- package/dist/api/deploy.js.map +1 -1
- package/dist/api/deploy.test.js +1 -0
- package/dist/api/deploy.test.js.map +1 -1
- package/dist/generator/connection.d.ts +49 -0
- package/dist/generator/connection.d.ts.map +1 -0
- package/dist/generator/connection.js +78 -0
- package/dist/generator/connection.js.map +1 -0
- package/dist/generator/connection.test.d.ts +2 -0
- package/dist/generator/connection.test.d.ts.map +1 -0
- package/dist/generator/connection.test.js +106 -0
- package/dist/generator/connection.test.js.map +1 -0
- package/dist/generator/datasource.d.ts.map +1 -1
- package/dist/generator/datasource.js +20 -0
- package/dist/generator/datasource.js.map +1 -1
- package/dist/generator/datasource.test.js +92 -0
- package/dist/generator/datasource.test.js.map +1 -1
- package/dist/generator/index.d.ts +8 -2
- package/dist/generator/index.d.ts.map +1 -1
- package/dist/generator/index.js +10 -3
- package/dist/generator/index.js.map +1 -1
- package/dist/generator/loader.d.ts +8 -1
- package/dist/generator/loader.d.ts.map +1 -1
- package/dist/generator/loader.js +17 -2
- package/dist/generator/loader.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/schema/connection.d.ts +83 -0
- package/dist/schema/connection.d.ts.map +1 -0
- package/dist/schema/connection.js +61 -0
- package/dist/schema/connection.js.map +1 -0
- package/dist/schema/connection.test.d.ts +2 -0
- package/dist/schema/connection.test.d.ts.map +1 -0
- package/dist/schema/connection.test.js +117 -0
- package/dist/schema/connection.test.js.map +1 -0
- package/dist/schema/datasource.d.ts +16 -0
- package/dist/schema/datasource.d.ts.map +1 -1
- package/dist/schema/datasource.js.map +1 -1
- package/dist/schema/project.d.ts +12 -3
- package/dist/schema/project.d.ts.map +1 -1
- package/dist/schema/project.js +2 -0
- package/dist/schema/project.js.map +1 -1
- package/package.json +1 -1
- package/src/api/build.test.ts +1 -0
- package/src/api/build.ts +20 -0
- package/src/api/deploy.test.ts +1 -0
- package/src/api/deploy.ts +3 -0
- package/src/generator/connection.test.ts +135 -0
- package/src/generator/connection.ts +104 -0
- package/src/generator/datasource.test.ts +108 -0
- package/src/generator/datasource.ts +27 -1
- package/src/generator/index.ts +16 -4
- package/src/generator/loader.ts +21 -3
- package/src/index.ts +12 -0
- package/src/schema/connection.test.ts +149 -0
- package/src/schema/connection.ts +123 -0
- package/src/schema/datasource.ts +17 -0
- package/src/schema/project.ts +20 -5
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connection content generator
|
|
3
|
+
* Converts ConnectionDefinition to native .connection file format
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ConnectionDefinition, KafkaConnectionDefinition } from "../schema/connection.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Generated connection content
|
|
10
|
+
*/
|
|
11
|
+
export interface GeneratedConnection {
|
|
12
|
+
/** Connection name */
|
|
13
|
+
name: string;
|
|
14
|
+
/** The generated .connection file content */
|
|
15
|
+
content: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Generate a Kafka connection content
|
|
20
|
+
*/
|
|
21
|
+
function generateKafkaConnection(connection: KafkaConnectionDefinition): string {
|
|
22
|
+
const parts: string[] = [];
|
|
23
|
+
const options = connection.options;
|
|
24
|
+
|
|
25
|
+
parts.push("TYPE kafka");
|
|
26
|
+
parts.push(`KAFKA_BOOTSTRAP_SERVERS ${options.bootstrapServers}`);
|
|
27
|
+
|
|
28
|
+
if (options.securityProtocol) {
|
|
29
|
+
parts.push(`KAFKA_SECURITY_PROTOCOL ${options.securityProtocol}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (options.saslMechanism) {
|
|
33
|
+
parts.push(`KAFKA_SASL_MECHANISM ${options.saslMechanism}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (options.key) {
|
|
37
|
+
parts.push(`KAFKA_KEY ${options.key}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (options.secret) {
|
|
41
|
+
parts.push(`KAFKA_SECRET ${options.secret}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (options.sslCaPem) {
|
|
45
|
+
parts.push(`KAFKA_SSL_CA_PEM ${options.sslCaPem}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return parts.join("\n");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Generate a .connection file content from a ConnectionDefinition
|
|
53
|
+
*
|
|
54
|
+
* @param connection - The connection definition
|
|
55
|
+
* @returns Generated connection content
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* const myKafka = createKafkaConnection('my_kafka', {
|
|
60
|
+
* bootstrapServers: 'kafka.example.com:9092',
|
|
61
|
+
* securityProtocol: 'SASL_SSL',
|
|
62
|
+
* saslMechanism: 'PLAIN',
|
|
63
|
+
* key: '{{ tb_secret("KAFKA_KEY") }}',
|
|
64
|
+
* secret: '{{ tb_secret("KAFKA_SECRET") }}',
|
|
65
|
+
* });
|
|
66
|
+
*
|
|
67
|
+
* const { content } = generateConnection(myKafka);
|
|
68
|
+
* // Returns:
|
|
69
|
+
* // TYPE kafka
|
|
70
|
+
* // KAFKA_BOOTSTRAP_SERVERS kafka.example.com:9092
|
|
71
|
+
* // KAFKA_SECURITY_PROTOCOL SASL_SSL
|
|
72
|
+
* // KAFKA_SASL_MECHANISM PLAIN
|
|
73
|
+
* // KAFKA_KEY {{ tb_secret("KAFKA_KEY") }}
|
|
74
|
+
* // KAFKA_SECRET {{ tb_secret("KAFKA_SECRET") }}
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export function generateConnection(
|
|
78
|
+
connection: ConnectionDefinition
|
|
79
|
+
): GeneratedConnection {
|
|
80
|
+
let content: string;
|
|
81
|
+
|
|
82
|
+
if (connection._connectionType === "kafka") {
|
|
83
|
+
content = generateKafkaConnection(connection as KafkaConnectionDefinition);
|
|
84
|
+
} else {
|
|
85
|
+
throw new Error(`Unsupported connection type: ${connection._connectionType}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
name: connection._name,
|
|
90
|
+
content,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Generate .connection files for all connections in a project
|
|
96
|
+
*
|
|
97
|
+
* @param connections - Record of connection definitions
|
|
98
|
+
* @returns Array of generated connection content
|
|
99
|
+
*/
|
|
100
|
+
export function generateAllConnections(
|
|
101
|
+
connections: Record<string, ConnectionDefinition>
|
|
102
|
+
): GeneratedConnection[] {
|
|
103
|
+
return Object.values(connections).map(generateConnection);
|
|
104
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import { generateDatasource, generateAllDatasources } from './datasource.js';
|
|
3
3
|
import { defineDatasource } from '../schema/datasource.js';
|
|
4
|
+
import { createKafkaConnection } from '../schema/connection.js';
|
|
4
5
|
import { t } from '../schema/types.js';
|
|
5
6
|
import { engine } from '../schema/engines.js';
|
|
6
7
|
|
|
@@ -294,4 +295,111 @@ describe('Datasource Generator', () => {
|
|
|
294
295
|
expect(result.content).toContain('ENGINE_TTL "timestamp + INTERVAL 90 DAY"');
|
|
295
296
|
});
|
|
296
297
|
});
|
|
298
|
+
|
|
299
|
+
describe('Kafka configuration', () => {
|
|
300
|
+
it('includes Kafka connection name and topic', () => {
|
|
301
|
+
const kafkaConn = createKafkaConnection('my_kafka', {
|
|
302
|
+
bootstrapServers: 'kafka.example.com:9092',
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const ds = defineDatasource('kafka_events', {
|
|
306
|
+
schema: {
|
|
307
|
+
timestamp: t.dateTime(),
|
|
308
|
+
event: t.string(),
|
|
309
|
+
},
|
|
310
|
+
engine: engine.mergeTree({ sortingKey: ['timestamp'] }),
|
|
311
|
+
kafka: {
|
|
312
|
+
connection: kafkaConn,
|
|
313
|
+
topic: 'events',
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const result = generateDatasource(ds);
|
|
318
|
+
|
|
319
|
+
expect(result.content).toContain('KAFKA_CONNECTION_NAME my_kafka');
|
|
320
|
+
expect(result.content).toContain('KAFKA_TOPIC events');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('includes Kafka group ID when provided', () => {
|
|
324
|
+
const kafkaConn = createKafkaConnection('my_kafka', {
|
|
325
|
+
bootstrapServers: 'kafka.example.com:9092',
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const ds = defineDatasource('kafka_events', {
|
|
329
|
+
schema: {
|
|
330
|
+
timestamp: t.dateTime(),
|
|
331
|
+
event: t.string(),
|
|
332
|
+
},
|
|
333
|
+
engine: engine.mergeTree({ sortingKey: ['timestamp'] }),
|
|
334
|
+
kafka: {
|
|
335
|
+
connection: kafkaConn,
|
|
336
|
+
topic: 'events',
|
|
337
|
+
groupId: 'my-consumer-group',
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const result = generateDatasource(ds);
|
|
342
|
+
|
|
343
|
+
expect(result.content).toContain('KAFKA_GROUP_ID my-consumer-group');
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('includes auto offset reset when provided', () => {
|
|
347
|
+
const kafkaConn = createKafkaConnection('my_kafka', {
|
|
348
|
+
bootstrapServers: 'kafka.example.com:9092',
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const ds = defineDatasource('kafka_events', {
|
|
352
|
+
schema: {
|
|
353
|
+
timestamp: t.dateTime(),
|
|
354
|
+
event: t.string(),
|
|
355
|
+
},
|
|
356
|
+
engine: engine.mergeTree({ sortingKey: ['timestamp'] }),
|
|
357
|
+
kafka: {
|
|
358
|
+
connection: kafkaConn,
|
|
359
|
+
topic: 'events',
|
|
360
|
+
autoOffsetReset: 'earliest',
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
const result = generateDatasource(ds);
|
|
365
|
+
|
|
366
|
+
expect(result.content).toContain('KAFKA_AUTO_OFFSET_RESET earliest');
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('generates complete Kafka datasource with all options', () => {
|
|
370
|
+
const kafkaConn = createKafkaConnection('my_kafka', {
|
|
371
|
+
bootstrapServers: 'kafka.example.com:9092',
|
|
372
|
+
securityProtocol: 'SASL_SSL',
|
|
373
|
+
saslMechanism: 'PLAIN',
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const ds = defineDatasource('kafka_events', {
|
|
377
|
+
description: 'Events from Kafka',
|
|
378
|
+
schema: {
|
|
379
|
+
timestamp: t.dateTime(),
|
|
380
|
+
event_type: t.string(),
|
|
381
|
+
payload: t.string(),
|
|
382
|
+
},
|
|
383
|
+
engine: engine.mergeTree({ sortingKey: ['timestamp'] }),
|
|
384
|
+
kafka: {
|
|
385
|
+
connection: kafkaConn,
|
|
386
|
+
topic: 'events',
|
|
387
|
+
groupId: 'my-consumer-group',
|
|
388
|
+
autoOffsetReset: 'earliest',
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const result = generateDatasource(ds);
|
|
393
|
+
|
|
394
|
+
expect(result.name).toBe('kafka_events');
|
|
395
|
+
expect(result.content).toContain('DESCRIPTION >');
|
|
396
|
+
expect(result.content).toContain('Events from Kafka');
|
|
397
|
+
expect(result.content).toContain('SCHEMA >');
|
|
398
|
+
expect(result.content).toContain('ENGINE "MergeTree"');
|
|
399
|
+
expect(result.content).toContain('KAFKA_CONNECTION_NAME my_kafka');
|
|
400
|
+
expect(result.content).toContain('KAFKA_TOPIC events');
|
|
401
|
+
expect(result.content).toContain('KAFKA_GROUP_ID my-consumer-group');
|
|
402
|
+
expect(result.content).toContain('KAFKA_AUTO_OFFSET_RESET earliest');
|
|
403
|
+
});
|
|
404
|
+
});
|
|
297
405
|
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Converts DatasourceDefinition to native .datasource file format
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { DatasourceDefinition, SchemaDefinition, ColumnDefinition } from "../schema/datasource.js";
|
|
6
|
+
import type { DatasourceDefinition, SchemaDefinition, ColumnDefinition, KafkaConfig } from "../schema/datasource.js";
|
|
7
7
|
import type { AnyTypeValidator, TypeModifiers } from "../schema/types.js";
|
|
8
8
|
import { getColumnType, getColumnJsonPath } from "../schema/datasource.js";
|
|
9
9
|
import { getEngineClause, type EngineConfig } from "../schema/engines.js";
|
|
@@ -143,6 +143,26 @@ function generateEngineConfig(engine?: EngineConfig): string {
|
|
|
143
143
|
return getEngineClause(engine);
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Generate Kafka configuration lines
|
|
148
|
+
*/
|
|
149
|
+
function generateKafkaConfig(kafka: KafkaConfig): string {
|
|
150
|
+
const parts: string[] = [];
|
|
151
|
+
|
|
152
|
+
parts.push(`KAFKA_CONNECTION_NAME ${kafka.connection._name}`);
|
|
153
|
+
parts.push(`KAFKA_TOPIC ${kafka.topic}`);
|
|
154
|
+
|
|
155
|
+
if (kafka.groupId) {
|
|
156
|
+
parts.push(`KAFKA_GROUP_ID ${kafka.groupId}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (kafka.autoOffsetReset) {
|
|
160
|
+
parts.push(`KAFKA_AUTO_OFFSET_RESET ${kafka.autoOffsetReset}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return parts.join("\n");
|
|
164
|
+
}
|
|
165
|
+
|
|
146
166
|
/**
|
|
147
167
|
* Generate a .datasource file content from a DatasourceDefinition
|
|
148
168
|
*
|
|
@@ -198,6 +218,12 @@ export function generateDatasource(
|
|
|
198
218
|
// Add engine configuration
|
|
199
219
|
parts.push(generateEngineConfig(datasource.options.engine));
|
|
200
220
|
|
|
221
|
+
// Add Kafka configuration if present
|
|
222
|
+
if (datasource.options.kafka) {
|
|
223
|
+
parts.push("");
|
|
224
|
+
parts.push(generateKafkaConfig(datasource.options.kafka));
|
|
225
|
+
}
|
|
226
|
+
|
|
201
227
|
return {
|
|
202
228
|
name: datasource._name,
|
|
203
229
|
content: parts.join("\n"),
|
package/src/generator/index.ts
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
import { loadSchema, loadEntities, entitiesToProject, type LoadedEntities } from "./loader.js";
|
|
7
7
|
import { generateAllDatasources, type GeneratedDatasource } from "./datasource.js";
|
|
8
8
|
import { generateAllPipes, type GeneratedPipe } from "./pipe.js";
|
|
9
|
-
import
|
|
9
|
+
import { generateAllConnections, type GeneratedConnection } from "./connection.js";
|
|
10
|
+
import type { ProjectDefinition, DatasourcesDefinition, PipesDefinition, ConnectionsDefinition } from "../schema/project.js";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Generated resources ready for API push
|
|
@@ -16,6 +17,8 @@ export interface GeneratedResources {
|
|
|
16
17
|
datasources: GeneratedDatasource[];
|
|
17
18
|
/** Generated pipe files */
|
|
18
19
|
pipes: GeneratedPipe[];
|
|
20
|
+
/** Generated connection files */
|
|
21
|
+
connections: GeneratedConnection[];
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
/**
|
|
@@ -34,6 +37,7 @@ export interface BuildResult {
|
|
|
34
37
|
stats: {
|
|
35
38
|
datasourceCount: number;
|
|
36
39
|
pipeCount: number;
|
|
40
|
+
connectionCount: number;
|
|
37
41
|
};
|
|
38
42
|
}
|
|
39
43
|
|
|
@@ -46,10 +50,12 @@ export interface BuildResult {
|
|
|
46
50
|
export function generateResources(project: ProjectDefinition): GeneratedResources {
|
|
47
51
|
const datasources = generateAllDatasources(project.datasources);
|
|
48
52
|
const pipes = generateAllPipes(project.pipes);
|
|
53
|
+
const connections = generateAllConnections(project.connections);
|
|
49
54
|
|
|
50
55
|
return {
|
|
51
56
|
datasources,
|
|
52
57
|
pipes,
|
|
58
|
+
connections,
|
|
53
59
|
};
|
|
54
60
|
}
|
|
55
61
|
|
|
@@ -107,6 +113,7 @@ export async function build(options: BuildOptions): Promise<BuildResult> {
|
|
|
107
113
|
stats: {
|
|
108
114
|
datasourceCount: resources.datasources.length,
|
|
109
115
|
pipeCount: resources.pipes.length,
|
|
116
|
+
connectionCount: resources.connections.length,
|
|
110
117
|
},
|
|
111
118
|
};
|
|
112
119
|
}
|
|
@@ -133,6 +140,7 @@ export interface BuildFromIncludeResult {
|
|
|
133
140
|
stats: {
|
|
134
141
|
datasourceCount: number;
|
|
135
142
|
pipeCount: number;
|
|
143
|
+
connectionCount: number;
|
|
136
144
|
};
|
|
137
145
|
}
|
|
138
146
|
|
|
@@ -141,11 +149,13 @@ export interface BuildFromIncludeResult {
|
|
|
141
149
|
*/
|
|
142
150
|
export function generateResourcesFromEntities(
|
|
143
151
|
datasources: DatasourcesDefinition,
|
|
144
|
-
pipes: PipesDefinition
|
|
152
|
+
pipes: PipesDefinition,
|
|
153
|
+
connections: ConnectionsDefinition = {}
|
|
145
154
|
): GeneratedResources {
|
|
146
155
|
return {
|
|
147
156
|
datasources: generateAllDatasources(datasources),
|
|
148
157
|
pipes: generateAllPipes(pipes),
|
|
158
|
+
connections: generateAllConnections(connections),
|
|
149
159
|
};
|
|
150
160
|
}
|
|
151
161
|
|
|
@@ -181,10 +191,10 @@ export async function buildFromInclude(
|
|
|
181
191
|
});
|
|
182
192
|
|
|
183
193
|
// Convert to format for generators
|
|
184
|
-
const { datasources, pipes } = entitiesToProject(entities);
|
|
194
|
+
const { datasources, pipes, connections } = entitiesToProject(entities);
|
|
185
195
|
|
|
186
196
|
// Generate resources
|
|
187
|
-
const resources = generateResourcesFromEntities(datasources, pipes);
|
|
197
|
+
const resources = generateResourcesFromEntities(datasources, pipes, connections);
|
|
188
198
|
|
|
189
199
|
return {
|
|
190
200
|
resources,
|
|
@@ -192,6 +202,7 @@ export async function buildFromInclude(
|
|
|
192
202
|
stats: {
|
|
193
203
|
datasourceCount: resources.datasources.length,
|
|
194
204
|
pipeCount: resources.pipes.length,
|
|
205
|
+
connectionCount: resources.connections.length,
|
|
195
206
|
},
|
|
196
207
|
};
|
|
197
208
|
}
|
|
@@ -200,4 +211,5 @@ export async function buildFromInclude(
|
|
|
200
211
|
export { loadSchema, loadEntities, entitiesToProject, type LoaderOptions, type LoadedSchema, type LoadedEntities, type LoadEntitiesOptions } from "./loader.js";
|
|
201
212
|
export { generateDatasource, generateAllDatasources, type GeneratedDatasource } from "./datasource.js";
|
|
202
213
|
export { generatePipe, generateAllPipes, type GeneratedPipe } from "./pipe.js";
|
|
214
|
+
export { generateConnection, generateAllConnections, type GeneratedConnection } from "./connection.js";
|
|
203
215
|
export { generateClientFile, type GenerateClientOptions, type GeneratedClient } from "./client.js";
|
package/src/generator/loader.ts
CHANGED
|
@@ -7,9 +7,10 @@ import * as esbuild from "esbuild";
|
|
|
7
7
|
import * as path from "path";
|
|
8
8
|
import * as fs from "fs";
|
|
9
9
|
import { watch as chokidarWatch, type FSWatcher } from "chokidar";
|
|
10
|
-
import { isProjectDefinition, type ProjectDefinition, type DatasourcesDefinition, type PipesDefinition } from "../schema/project.js";
|
|
10
|
+
import { isProjectDefinition, type ProjectDefinition, type DatasourcesDefinition, type PipesDefinition, type ConnectionsDefinition } from "../schema/project.js";
|
|
11
11
|
import { isDatasourceDefinition, type DatasourceDefinition } from "../schema/datasource.js";
|
|
12
12
|
import { isPipeDefinition, type PipeDefinition } from "../schema/pipe.js";
|
|
13
|
+
import { isConnectionDefinition, type ConnectionDefinition } from "../schema/connection.js";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Result of loading a schema file
|
|
@@ -157,6 +158,8 @@ export interface LoadedEntities {
|
|
|
157
158
|
datasources: Record<string, { definition: DatasourceDefinition; info: EntityInfo }>;
|
|
158
159
|
/** Discovered pipes with their metadata */
|
|
159
160
|
pipes: Record<string, { definition: PipeDefinition; info: EntityInfo }>;
|
|
161
|
+
/** Discovered connections with their metadata */
|
|
162
|
+
connections: Record<string, { definition: ConnectionDefinition; info: EntityInfo }>;
|
|
160
163
|
/** All source files that were scanned */
|
|
161
164
|
sourceFiles: string[];
|
|
162
165
|
}
|
|
@@ -195,6 +198,7 @@ export async function loadEntities(options: LoadEntitiesOptions): Promise<Loaded
|
|
|
195
198
|
const result: LoadedEntities = {
|
|
196
199
|
datasources: {},
|
|
197
200
|
pipes: {},
|
|
201
|
+
connections: {},
|
|
198
202
|
sourceFiles: [],
|
|
199
203
|
};
|
|
200
204
|
|
|
@@ -237,7 +241,7 @@ export async function loadEntities(options: LoadEntitiesOptions): Promise<Loaded
|
|
|
237
241
|
const moduleUrl = `file://${outfile}`;
|
|
238
242
|
const module = await import(moduleUrl);
|
|
239
243
|
|
|
240
|
-
// Scan all exports for datasources and
|
|
244
|
+
// Scan all exports for datasources, pipes, and connections
|
|
241
245
|
for (const [exportName, value] of Object.entries(module)) {
|
|
242
246
|
if (isDatasourceDefinition(value)) {
|
|
243
247
|
result.datasources[exportName] = {
|
|
@@ -255,6 +259,14 @@ export async function loadEntities(options: LoadEntitiesOptions): Promise<Loaded
|
|
|
255
259
|
sourceFile: includePath,
|
|
256
260
|
},
|
|
257
261
|
};
|
|
262
|
+
} else if (isConnectionDefinition(value)) {
|
|
263
|
+
result.connections[exportName] = {
|
|
264
|
+
definition: value,
|
|
265
|
+
info: {
|
|
266
|
+
exportName,
|
|
267
|
+
sourceFile: includePath,
|
|
268
|
+
},
|
|
269
|
+
};
|
|
258
270
|
}
|
|
259
271
|
}
|
|
260
272
|
} finally {
|
|
@@ -282,9 +294,11 @@ export async function loadEntities(options: LoadEntitiesOptions): Promise<Loaded
|
|
|
282
294
|
export function entitiesToProject(entities: LoadedEntities): {
|
|
283
295
|
datasources: DatasourcesDefinition;
|
|
284
296
|
pipes: PipesDefinition;
|
|
297
|
+
connections: ConnectionsDefinition;
|
|
285
298
|
} {
|
|
286
299
|
const datasources: DatasourcesDefinition = {};
|
|
287
300
|
const pipes: PipesDefinition = {};
|
|
301
|
+
const connections: ConnectionsDefinition = {};
|
|
288
302
|
|
|
289
303
|
for (const [name, { definition }] of Object.entries(entities.datasources)) {
|
|
290
304
|
datasources[name] = definition;
|
|
@@ -294,7 +308,11 @@ export function entitiesToProject(entities: LoadedEntities): {
|
|
|
294
308
|
pipes[name] = definition;
|
|
295
309
|
}
|
|
296
310
|
|
|
297
|
-
|
|
311
|
+
for (const [name, { definition }] of Object.entries(entities.connections)) {
|
|
312
|
+
connections[name] = definition;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return { datasources, pipes, connections };
|
|
298
316
|
}
|
|
299
317
|
|
|
300
318
|
/**
|
package/src/index.ts
CHANGED
|
@@ -106,8 +106,19 @@ export type {
|
|
|
106
106
|
ColumnDefinition,
|
|
107
107
|
TokenConfig,
|
|
108
108
|
ExtractSchema,
|
|
109
|
+
KafkaConfig,
|
|
109
110
|
} from "./schema/datasource.js";
|
|
110
111
|
|
|
112
|
+
// ============ Connection ============
|
|
113
|
+
export { createKafkaConnection, isConnectionDefinition, isKafkaConnectionDefinition, getConnectionType } from "./schema/connection.js";
|
|
114
|
+
export type {
|
|
115
|
+
ConnectionDefinition,
|
|
116
|
+
KafkaConnectionDefinition,
|
|
117
|
+
KafkaConnectionOptions,
|
|
118
|
+
KafkaSecurityProtocol,
|
|
119
|
+
KafkaSaslMechanism,
|
|
120
|
+
} from "./schema/connection.js";
|
|
121
|
+
|
|
111
122
|
// ============ Pipe ============
|
|
112
123
|
export {
|
|
113
124
|
definePipe,
|
|
@@ -153,6 +164,7 @@ export type {
|
|
|
153
164
|
TinybirdClientConfig,
|
|
154
165
|
DatasourcesDefinition,
|
|
155
166
|
PipesDefinition,
|
|
167
|
+
ConnectionsDefinition,
|
|
156
168
|
ExtractDatasources,
|
|
157
169
|
ExtractPipes,
|
|
158
170
|
DataModel,
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
createKafkaConnection,
|
|
4
|
+
isConnectionDefinition,
|
|
5
|
+
isKafkaConnectionDefinition,
|
|
6
|
+
getConnectionType,
|
|
7
|
+
} from "./connection.js";
|
|
8
|
+
|
|
9
|
+
describe("Connection Schema", () => {
|
|
10
|
+
describe("createKafkaConnection", () => {
|
|
11
|
+
it("creates a Kafka connection with required fields", () => {
|
|
12
|
+
const conn = createKafkaConnection("my_kafka", {
|
|
13
|
+
bootstrapServers: "kafka.example.com:9092",
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
expect(conn._name).toBe("my_kafka");
|
|
17
|
+
expect(conn._type).toBe("connection");
|
|
18
|
+
expect(conn._connectionType).toBe("kafka");
|
|
19
|
+
expect(conn.options.bootstrapServers).toBe("kafka.example.com:9092");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("creates a Kafka connection with all options", () => {
|
|
23
|
+
const conn = createKafkaConnection("my_kafka", {
|
|
24
|
+
bootstrapServers: "kafka.example.com:9092",
|
|
25
|
+
securityProtocol: "SASL_SSL",
|
|
26
|
+
saslMechanism: "PLAIN",
|
|
27
|
+
key: '{{ tb_secret("KAFKA_KEY") }}',
|
|
28
|
+
secret: '{{ tb_secret("KAFKA_SECRET") }}',
|
|
29
|
+
sslCaPem: '{{ tb_secret("KAFKA_CA_CERT") }}',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(conn.options.securityProtocol).toBe("SASL_SSL");
|
|
33
|
+
expect(conn.options.saslMechanism).toBe("PLAIN");
|
|
34
|
+
expect(conn.options.key).toBe('{{ tb_secret("KAFKA_KEY") }}');
|
|
35
|
+
expect(conn.options.secret).toBe('{{ tb_secret("KAFKA_SECRET") }}');
|
|
36
|
+
expect(conn.options.sslCaPem).toBe('{{ tb_secret("KAFKA_CA_CERT") }}');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("supports different SASL mechanisms", () => {
|
|
40
|
+
const scramConn = createKafkaConnection("scram_kafka", {
|
|
41
|
+
bootstrapServers: "kafka.example.com:9092",
|
|
42
|
+
saslMechanism: "SCRAM-SHA-256",
|
|
43
|
+
});
|
|
44
|
+
expect(scramConn.options.saslMechanism).toBe("SCRAM-SHA-256");
|
|
45
|
+
|
|
46
|
+
const scram512Conn = createKafkaConnection("scram512_kafka", {
|
|
47
|
+
bootstrapServers: "kafka.example.com:9092",
|
|
48
|
+
saslMechanism: "SCRAM-SHA-512",
|
|
49
|
+
});
|
|
50
|
+
expect(scram512Conn.options.saslMechanism).toBe("SCRAM-SHA-512");
|
|
51
|
+
|
|
52
|
+
const oauthConn = createKafkaConnection("oauth_kafka", {
|
|
53
|
+
bootstrapServers: "kafka.example.com:9092",
|
|
54
|
+
saslMechanism: "OAUTHBEARER",
|
|
55
|
+
});
|
|
56
|
+
expect(oauthConn.options.saslMechanism).toBe("OAUTHBEARER");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("supports different security protocols", () => {
|
|
60
|
+
const plaintext = createKafkaConnection("plaintext_kafka", {
|
|
61
|
+
bootstrapServers: "localhost:9092",
|
|
62
|
+
securityProtocol: "PLAINTEXT",
|
|
63
|
+
});
|
|
64
|
+
expect(plaintext.options.securityProtocol).toBe("PLAINTEXT");
|
|
65
|
+
|
|
66
|
+
const saslPlaintext = createKafkaConnection("sasl_plaintext_kafka", {
|
|
67
|
+
bootstrapServers: "localhost:9092",
|
|
68
|
+
securityProtocol: "SASL_PLAINTEXT",
|
|
69
|
+
});
|
|
70
|
+
expect(saslPlaintext.options.securityProtocol).toBe("SASL_PLAINTEXT");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("throws error for invalid connection name", () => {
|
|
74
|
+
expect(() =>
|
|
75
|
+
createKafkaConnection("123invalid", {
|
|
76
|
+
bootstrapServers: "kafka.example.com:9092",
|
|
77
|
+
})
|
|
78
|
+
).toThrow("Invalid connection name");
|
|
79
|
+
|
|
80
|
+
expect(() =>
|
|
81
|
+
createKafkaConnection("my-connection", {
|
|
82
|
+
bootstrapServers: "kafka.example.com:9092",
|
|
83
|
+
})
|
|
84
|
+
).toThrow("Invalid connection name");
|
|
85
|
+
|
|
86
|
+
expect(() =>
|
|
87
|
+
createKafkaConnection("", {
|
|
88
|
+
bootstrapServers: "kafka.example.com:9092",
|
|
89
|
+
})
|
|
90
|
+
).toThrow("Invalid connection name");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("allows valid naming patterns", () => {
|
|
94
|
+
const conn1 = createKafkaConnection("_private_kafka", {
|
|
95
|
+
bootstrapServers: "kafka.example.com:9092",
|
|
96
|
+
});
|
|
97
|
+
expect(conn1._name).toBe("_private_kafka");
|
|
98
|
+
|
|
99
|
+
const conn2 = createKafkaConnection("kafka_v2", {
|
|
100
|
+
bootstrapServers: "kafka.example.com:9092",
|
|
101
|
+
});
|
|
102
|
+
expect(conn2._name).toBe("kafka_v2");
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("isConnectionDefinition", () => {
|
|
107
|
+
it("returns true for valid connection", () => {
|
|
108
|
+
const conn = createKafkaConnection("my_kafka", {
|
|
109
|
+
bootstrapServers: "kafka.example.com:9092",
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(isConnectionDefinition(conn)).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("returns false for non-connection objects", () => {
|
|
116
|
+
expect(isConnectionDefinition({})).toBe(false);
|
|
117
|
+
expect(isConnectionDefinition(null)).toBe(false);
|
|
118
|
+
expect(isConnectionDefinition(undefined)).toBe(false);
|
|
119
|
+
expect(isConnectionDefinition("string")).toBe(false);
|
|
120
|
+
expect(isConnectionDefinition(123)).toBe(false);
|
|
121
|
+
expect(isConnectionDefinition({ _name: "test" })).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe("isKafkaConnectionDefinition", () => {
|
|
126
|
+
it("returns true for Kafka connection", () => {
|
|
127
|
+
const conn = createKafkaConnection("my_kafka", {
|
|
128
|
+
bootstrapServers: "kafka.example.com:9092",
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
expect(isKafkaConnectionDefinition(conn)).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("returns false for non-Kafka objects", () => {
|
|
135
|
+
expect(isKafkaConnectionDefinition({})).toBe(false);
|
|
136
|
+
expect(isKafkaConnectionDefinition(null)).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe("getConnectionType", () => {
|
|
141
|
+
it("returns the connection type", () => {
|
|
142
|
+
const conn = createKafkaConnection("my_kafka", {
|
|
143
|
+
bootstrapServers: "kafka.example.com:9092",
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
expect(getConnectionType(conn)).toBe("kafka");
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|