@tinybirdco/sdk 0.0.50 → 0.0.52
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 +5 -0
- package/dist/api/deploy.d.ts.map +1 -1
- package/dist/api/deploy.js +13 -3
- package/dist/api/deploy.js.map +1 -1
- package/dist/api/deploy.test.js +32 -0
- package/dist/api/deploy.test.js.map +1 -1
- package/dist/cli/commands/deploy.d.ts +2 -0
- package/dist/cli/commands/deploy.d.ts.map +1 -1
- package/dist/cli/commands/deploy.js +1 -0
- package/dist/cli/commands/deploy.js.map +1 -1
- package/dist/cli/commands/deploy.test.d.ts +2 -0
- package/dist/cli/commands/deploy.test.d.ts.map +1 -0
- package/dist/cli/commands/deploy.test.js +68 -0
- package/dist/cli/commands/deploy.test.js.map +1 -0
- package/dist/cli/commands/migrate.test.js +247 -2
- package/dist/cli/commands/migrate.test.js.map +1 -1
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/codegen/type-mapper.d.ts.map +1 -1
- package/dist/codegen/type-mapper.js +70 -7
- package/dist/codegen/type-mapper.js.map +1 -1
- package/dist/codegen/type-mapper.test.js +9 -0
- package/dist/codegen/type-mapper.test.js.map +1 -1
- package/dist/generator/datasource.d.ts.map +1 -1
- package/dist/generator/datasource.js +19 -0
- package/dist/generator/datasource.js.map +1 -1
- package/dist/generator/datasource.test.js +16 -0
- package/dist/generator/datasource.test.js.map +1 -1
- package/dist/generator/pipe.d.ts.map +1 -1
- package/dist/generator/pipe.js +92 -3
- package/dist/generator/pipe.js.map +1 -1
- package/dist/generator/pipe.test.js +19 -0
- package/dist/generator/pipe.test.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +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 +11 -0
- package/dist/migrate/emit-ts.js.map +1 -1
- package/dist/migrate/parse-datasource.d.ts.map +1 -1
- package/dist/migrate/parse-datasource.js +37 -0
- 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 +212 -93
- package/dist/migrate/parse-pipe.js.map +1 -1
- package/dist/migrate/parser-utils.d.ts.map +1 -1
- package/dist/migrate/parser-utils.js +3 -1
- package/dist/migrate/parser-utils.js.map +1 -1
- package/dist/migrate/types.d.ts +7 -0
- package/dist/migrate/types.d.ts.map +1 -1
- package/dist/schema/datasource.d.ts +16 -0
- package/dist/schema/datasource.d.ts.map +1 -1
- package/dist/schema/datasource.js +16 -0
- package/dist/schema/datasource.js.map +1 -1
- package/dist/schema/datasource.test.js +39 -0
- package/dist/schema/datasource.test.js.map +1 -1
- package/package.json +1 -1
- package/src/api/deploy.test.ts +55 -0
- package/src/api/deploy.ts +19 -3
- package/src/cli/commands/deploy.test.ts +82 -0
- package/src/cli/commands/deploy.ts +3 -0
- package/src/cli/commands/migrate.test.ts +357 -2
- package/src/cli/index.ts +5 -0
- package/src/codegen/type-mapper.test.ts +18 -0
- package/src/codegen/type-mapper.ts +79 -7
- package/src/generator/datasource.test.ts +22 -0
- package/src/generator/datasource.ts +25 -0
- package/src/generator/pipe.test.ts +21 -0
- package/src/generator/pipe.ts +119 -3
- package/src/index.ts +1 -0
- package/src/migrate/emit-ts.ts +13 -0
- package/src/migrate/parse-datasource.ts +72 -1
- package/src/migrate/parse-pipe.ts +250 -111
- package/src/migrate/parser-utils.ts +5 -1
- package/src/migrate/types.ts +8 -0
- package/src/schema/datasource.test.ts +53 -0
- package/src/schema/datasource.ts +38 -0
package/src/generator/pipe.ts
CHANGED
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
getCopyConfig,
|
|
19
19
|
getSinkConfig,
|
|
20
20
|
} from "../schema/pipe.js";
|
|
21
|
+
import type { AnyParamValidator } from "../schema/params.js";
|
|
22
|
+
import { getParamDefault } from "../schema/params.js";
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
25
|
* Generated pipe content
|
|
@@ -36,10 +38,123 @@ function hasDynamicParameters(sql: string): boolean {
|
|
|
36
38
|
return /\{\{[^}]+\}\}/.test(sql) || /\{%[^%]+%\}/.test(sql);
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
function splitTopLevelComma(input: string): string[] {
|
|
42
|
+
const parts: string[] = [];
|
|
43
|
+
let current = "";
|
|
44
|
+
let depth = 0;
|
|
45
|
+
let inSingleQuote = false;
|
|
46
|
+
let inDoubleQuote = false;
|
|
47
|
+
|
|
48
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
49
|
+
const char = input[i];
|
|
50
|
+
const prev = i > 0 ? input[i - 1] : "";
|
|
51
|
+
|
|
52
|
+
if (char === "'" && !inDoubleQuote && prev !== "\\") {
|
|
53
|
+
inSingleQuote = !inSingleQuote;
|
|
54
|
+
current += char;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (char === '"' && !inSingleQuote && prev !== "\\") {
|
|
59
|
+
inDoubleQuote = !inDoubleQuote;
|
|
60
|
+
current += char;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!inSingleQuote && !inDoubleQuote) {
|
|
65
|
+
if (char === "(") {
|
|
66
|
+
depth += 1;
|
|
67
|
+
current += char;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (char === ")" && depth > 0) {
|
|
71
|
+
depth -= 1;
|
|
72
|
+
current += char;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (char === "," && depth === 0) {
|
|
76
|
+
parts.push(current.trim());
|
|
77
|
+
current = "";
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
current += char;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (current.trim()) {
|
|
86
|
+
parts.push(current.trim());
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return parts;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function toTemplateDefaultLiteral(value: string | number | boolean): string {
|
|
93
|
+
if (typeof value === "string") {
|
|
94
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
95
|
+
}
|
|
96
|
+
return String(value);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function applyParamDefaultsToSql(
|
|
100
|
+
sql: string,
|
|
101
|
+
params?: Record<string, AnyParamValidator>
|
|
102
|
+
): string {
|
|
103
|
+
if (!params) {
|
|
104
|
+
return sql;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const defaults = new Map<string, string>();
|
|
108
|
+
for (const [name, validator] of Object.entries(params)) {
|
|
109
|
+
const defaultValue = getParamDefault(validator);
|
|
110
|
+
if (defaultValue !== undefined) {
|
|
111
|
+
defaults.set(name, toTemplateDefaultLiteral(defaultValue as string | number | boolean));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (defaults.size === 0) {
|
|
116
|
+
return sql;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const placeholderRegex = /\{\{\s*([^{}]+?)\s*\}\}/g;
|
|
120
|
+
return sql.replace(placeholderRegex, (fullMatch, rawExpression) => {
|
|
121
|
+
const expression = String(rawExpression);
|
|
122
|
+
const rewritten = expression.replace(
|
|
123
|
+
/([a-zA-Z_][a-zA-Z0-9_]*)\s*\(([^()]*)\)/g,
|
|
124
|
+
(call, _functionName, rawArgs) => {
|
|
125
|
+
const args = splitTopLevelComma(String(rawArgs));
|
|
126
|
+
if (args.length !== 1) {
|
|
127
|
+
return call;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const paramName = args[0]?.trim() ?? "";
|
|
131
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(paramName)) {
|
|
132
|
+
return call;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const defaultLiteral = defaults.get(paramName);
|
|
136
|
+
if (!defaultLiteral) {
|
|
137
|
+
return call;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return call.replace(/\)\s*$/, `, ${defaultLiteral})`);
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
if (rewritten === expression) {
|
|
145
|
+
return fullMatch;
|
|
146
|
+
}
|
|
147
|
+
return `{{ ${rewritten.trim()} }}`;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
39
151
|
/**
|
|
40
152
|
* Generate a NODE section for the pipe
|
|
41
153
|
*/
|
|
42
|
-
function generateNode(
|
|
154
|
+
function generateNode(
|
|
155
|
+
node: NodeDefinition,
|
|
156
|
+
params?: Record<string, AnyParamValidator>
|
|
157
|
+
): string {
|
|
43
158
|
const parts: string[] = [];
|
|
44
159
|
|
|
45
160
|
parts.push(`NODE ${node._name}`);
|
|
@@ -57,7 +172,8 @@ function generateNode(node: NodeDefinition): string {
|
|
|
57
172
|
parts.push(` %`);
|
|
58
173
|
}
|
|
59
174
|
|
|
60
|
-
const
|
|
175
|
+
const sqlWithDefaults = applyParamDefaultsToSql(node.sql, params);
|
|
176
|
+
const sqlLines = sqlWithDefaults.trim().split("\n");
|
|
61
177
|
sqlLines.forEach((line) => {
|
|
62
178
|
parts.push(` ${line}`);
|
|
63
179
|
});
|
|
@@ -225,7 +341,7 @@ export function generatePipe(pipe: PipeDefinition): GeneratedPipe {
|
|
|
225
341
|
|
|
226
342
|
// Add all nodes
|
|
227
343
|
pipe.options.nodes.forEach((node, index) => {
|
|
228
|
-
parts.push(generateNode(node));
|
|
344
|
+
parts.push(generateNode(node, pipe.options.params as Record<string, AnyParamValidator> | undefined));
|
|
229
345
|
// Add empty line between nodes
|
|
230
346
|
if (index < pipe.options.nodes.length - 1) {
|
|
231
347
|
parts.push("");
|
package/src/index.ts
CHANGED
package/src/migrate/emit-ts.ts
CHANGED
|
@@ -133,6 +133,8 @@ function strictParamBaseValidator(type: string): string {
|
|
|
133
133
|
const map: Record<string, string> = {
|
|
134
134
|
String: "p.string()",
|
|
135
135
|
UUID: "p.uuid()",
|
|
136
|
+
Int: "p.int32()",
|
|
137
|
+
Integer: "p.int32()",
|
|
136
138
|
Int8: "p.int8()",
|
|
137
139
|
Int16: "p.int16()",
|
|
138
140
|
Int32: "p.int32()",
|
|
@@ -149,6 +151,8 @@ function strictParamBaseValidator(type: string): string {
|
|
|
149
151
|
DateTime: "p.dateTime()",
|
|
150
152
|
DateTime64: "p.dateTime64()",
|
|
151
153
|
Array: "p.array(p.string())",
|
|
154
|
+
column: "p.column()",
|
|
155
|
+
JSON: "p.json()",
|
|
152
156
|
};
|
|
153
157
|
const validator = map[type];
|
|
154
158
|
if (!validator) {
|
|
@@ -313,6 +317,15 @@ function emitDatasource(ds: DatasourceModel): string {
|
|
|
313
317
|
if (ds.engine) {
|
|
314
318
|
lines.push(` engine: ${emitEngineOptions(ds.engine)},`);
|
|
315
319
|
}
|
|
320
|
+
if (ds.indexes.length > 0) {
|
|
321
|
+
lines.push(" indexes: [");
|
|
322
|
+
for (const index of ds.indexes) {
|
|
323
|
+
lines.push(
|
|
324
|
+
` { name: ${escapeString(index.name)}, expr: ${escapeString(index.expr)}, type: ${escapeString(index.type)}, granularity: ${index.granularity} },`
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
lines.push(" ],");
|
|
328
|
+
}
|
|
316
329
|
|
|
317
330
|
if (ds.kafka) {
|
|
318
331
|
const connectionVar = toCamelCase(ds.kafka.connectionName);
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
DatasourceIndexModel,
|
|
3
|
+
DatasourceModel,
|
|
4
|
+
DatasourceTokenModel,
|
|
5
|
+
ResourceFile,
|
|
6
|
+
} from "./types.js";
|
|
2
7
|
import {
|
|
3
8
|
MigrationParseError,
|
|
4
9
|
isBlank,
|
|
@@ -14,6 +19,7 @@ const DATASOURCE_DIRECTIVES = new Set([
|
|
|
14
19
|
"DESCRIPTION",
|
|
15
20
|
"SCHEMA",
|
|
16
21
|
"FORWARD_QUERY",
|
|
22
|
+
"INDEXES",
|
|
17
23
|
"SHARED_WITH",
|
|
18
24
|
"ENGINE",
|
|
19
25
|
"ENGINE_SORTING_KEY",
|
|
@@ -241,9 +247,53 @@ function parseToken(filePath: string, resourceName: string, value: string): Data
|
|
|
241
247
|
return { name, scope };
|
|
242
248
|
}
|
|
243
249
|
|
|
250
|
+
function parseIndexLine(
|
|
251
|
+
filePath: string,
|
|
252
|
+
resourceName: string,
|
|
253
|
+
rawLine: string
|
|
254
|
+
): DatasourceIndexModel {
|
|
255
|
+
const line = rawLine.trim().replace(/,$/, "");
|
|
256
|
+
if (!line) {
|
|
257
|
+
throw new MigrationParseError(
|
|
258
|
+
filePath,
|
|
259
|
+
"datasource",
|
|
260
|
+
resourceName,
|
|
261
|
+
"Empty INDEXES line."
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const match = line.match(/^(\S+)\s+(.+?)\s+TYPE\s+(.+?)\s+GRANULARITY\s+(\d+)$/i);
|
|
266
|
+
if (!match) {
|
|
267
|
+
throw new MigrationParseError(
|
|
268
|
+
filePath,
|
|
269
|
+
"datasource",
|
|
270
|
+
resourceName,
|
|
271
|
+
`Invalid INDEXES definition: "${rawLine}"`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const granularity = Number(match[4]);
|
|
276
|
+
if (!Number.isInteger(granularity) || granularity <= 0) {
|
|
277
|
+
throw new MigrationParseError(
|
|
278
|
+
filePath,
|
|
279
|
+
"datasource",
|
|
280
|
+
resourceName,
|
|
281
|
+
`Invalid INDEXES GRANULARITY value: "${match[4]}"`
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
name: match[1],
|
|
287
|
+
expr: match[2].trim(),
|
|
288
|
+
type: match[3].trim(),
|
|
289
|
+
granularity,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
244
293
|
export function parseDatasourceFile(resource: ResourceFile): DatasourceModel {
|
|
245
294
|
const lines = splitLines(resource.content);
|
|
246
295
|
const columns = [];
|
|
296
|
+
const indexes: DatasourceIndexModel[] = [];
|
|
247
297
|
const tokens: DatasourceTokenModel[] = [];
|
|
248
298
|
const sharedWith: string[] = [];
|
|
249
299
|
let description: string | undefined;
|
|
@@ -323,6 +373,26 @@ export function parseDatasourceFile(resource: ResourceFile): DatasourceModel {
|
|
|
323
373
|
continue;
|
|
324
374
|
}
|
|
325
375
|
|
|
376
|
+
if (line === "INDEXES >") {
|
|
377
|
+
const block = readDirectiveBlock(lines, i + 1, isDatasourceDirectiveLine);
|
|
378
|
+
if (block.lines.length === 0) {
|
|
379
|
+
throw new MigrationParseError(
|
|
380
|
+
resource.filePath,
|
|
381
|
+
"datasource",
|
|
382
|
+
resource.name,
|
|
383
|
+
"INDEXES block is empty."
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
for (const indexLine of block.lines) {
|
|
387
|
+
if (isBlank(indexLine) || indexLine.trim().startsWith("#")) {
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
indexes.push(parseIndexLine(resource.filePath, resource.name, indexLine));
|
|
391
|
+
}
|
|
392
|
+
i = block.nextIndex;
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
|
|
326
396
|
if (line === "SHARED_WITH >") {
|
|
327
397
|
const block = readDirectiveBlock(lines, i + 1, isDatasourceDirectiveLine);
|
|
328
398
|
for (const sharedLine of block.lines) {
|
|
@@ -552,6 +622,7 @@ export function parseDatasourceFile(resource: ResourceFile): DatasourceModel {
|
|
|
552
622
|
settings,
|
|
553
623
|
}
|
|
554
624
|
: undefined,
|
|
625
|
+
indexes,
|
|
555
626
|
kafka,
|
|
556
627
|
s3,
|
|
557
628
|
forwardQuery,
|