@tinybirdco/sdk 0.0.1
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 +518 -0
- package/bin/tinybird.js +7 -0
- package/dist/api/branches.d.ts +98 -0
- package/dist/api/branches.d.ts.map +1 -0
- package/dist/api/branches.js +203 -0
- package/dist/api/branches.js.map +1 -0
- package/dist/api/branches.test.d.ts +2 -0
- package/dist/api/branches.test.d.ts.map +1 -0
- package/dist/api/branches.test.js +286 -0
- package/dist/api/branches.test.js.map +1 -0
- package/dist/api/build.d.ts +130 -0
- package/dist/api/build.d.ts.map +1 -0
- package/dist/api/build.js +143 -0
- package/dist/api/build.js.map +1 -0
- package/dist/api/build.test.d.ts +2 -0
- package/dist/api/build.test.d.ts.map +1 -0
- package/dist/api/build.test.js +138 -0
- package/dist/api/build.test.js.map +1 -0
- package/dist/api/deploy.d.ts +39 -0
- package/dist/api/deploy.d.ts.map +1 -0
- package/dist/api/deploy.js +135 -0
- package/dist/api/deploy.js.map +1 -0
- package/dist/api/deploy.test.d.ts +2 -0
- package/dist/api/deploy.test.d.ts.map +1 -0
- package/dist/api/deploy.test.js +118 -0
- package/dist/api/deploy.test.js.map +1 -0
- package/dist/api/workspaces.d.ts +46 -0
- package/dist/api/workspaces.d.ts.map +1 -0
- package/dist/api/workspaces.js +39 -0
- package/dist/api/workspaces.js.map +1 -0
- package/dist/api/workspaces.test.d.ts +2 -0
- package/dist/api/workspaces.test.d.ts.map +1 -0
- package/dist/api/workspaces.test.js +65 -0
- package/dist/api/workspaces.test.js.map +1 -0
- package/dist/cli/auth.d.ts +86 -0
- package/dist/cli/auth.d.ts.map +1 -0
- package/dist/cli/auth.js +284 -0
- package/dist/cli/auth.js.map +1 -0
- package/dist/cli/branch-store.d.ts +53 -0
- package/dist/cli/branch-store.d.ts.map +1 -0
- package/dist/cli/branch-store.js +91 -0
- package/dist/cli/branch-store.js.map +1 -0
- package/dist/cli/branch-store.test.d.ts +2 -0
- package/dist/cli/branch-store.test.d.ts.map +1 -0
- package/dist/cli/branch-store.test.js +115 -0
- package/dist/cli/branch-store.test.js.map +1 -0
- package/dist/cli/commands/branch.d.ts +82 -0
- package/dist/cli/commands/branch.d.ts.map +1 -0
- package/dist/cli/commands/branch.js +215 -0
- package/dist/cli/commands/branch.js.map +1 -0
- package/dist/cli/commands/build.d.ts +43 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/build.js +138 -0
- package/dist/cli/commands/build.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +78 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/dev.js +226 -0
- package/dist/cli/commands/dev.js.map +1 -0
- package/dist/cli/commands/init.d.ts +45 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +277 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/init.test.d.ts +2 -0
- package/dist/cli/commands/init.test.d.ts.map +1 -0
- package/dist/cli/commands/init.test.js +158 -0
- package/dist/cli/commands/init.test.js.map +1 -0
- package/dist/cli/commands/login.d.ts +37 -0
- package/dist/cli/commands/login.d.ts.map +1 -0
- package/dist/cli/commands/login.js +64 -0
- package/dist/cli/commands/login.js.map +1 -0
- package/dist/cli/config.d.ts +114 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +258 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/config.test.d.ts +2 -0
- package/dist/cli/config.test.d.ts.map +1 -0
- package/dist/cli/config.test.js +243 -0
- package/dist/cli/config.test.js.map +1 -0
- package/dist/cli/env.d.ts +29 -0
- package/dist/cli/env.d.ts.map +1 -0
- package/dist/cli/env.js +66 -0
- package/dist/cli/env.js.map +1 -0
- package/dist/cli/git.d.ts +29 -0
- package/dist/cli/git.d.ts.map +1 -0
- package/dist/cli/git.js +114 -0
- package/dist/cli/git.js.map +1 -0
- package/dist/cli/git.test.d.ts +2 -0
- package/dist/cli/git.test.d.ts.map +1 -0
- package/dist/cli/git.test.js +125 -0
- package/dist/cli/git.test.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +337 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/utils/schema-validation.d.ts +95 -0
- package/dist/cli/utils/schema-validation.d.ts.map +1 -0
- package/dist/cli/utils/schema-validation.js +175 -0
- package/dist/cli/utils/schema-validation.js.map +1 -0
- package/dist/cli/utils/schema-validation.test.d.ts +5 -0
- package/dist/cli/utils/schema-validation.test.d.ts.map +1 -0
- package/dist/cli/utils/schema-validation.test.js +173 -0
- package/dist/cli/utils/schema-validation.test.js.map +1 -0
- package/dist/client/base.d.ts +116 -0
- package/dist/client/base.d.ts.map +1 -0
- package/dist/client/base.js +328 -0
- package/dist/client/base.js.map +1 -0
- package/dist/client/types.d.ts +137 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +43 -0
- package/dist/client/types.js.map +1 -0
- package/dist/generator/client.d.ts +44 -0
- package/dist/generator/client.d.ts.map +1 -0
- package/dist/generator/client.js +144 -0
- package/dist/generator/client.js.map +1 -0
- package/dist/generator/datasource.d.ts +57 -0
- package/dist/generator/datasource.d.ts.map +1 -0
- package/dist/generator/datasource.js +169 -0
- package/dist/generator/datasource.js.map +1 -0
- package/dist/generator/datasource.test.d.ts +2 -0
- package/dist/generator/datasource.test.d.ts.map +1 -0
- package/dist/generator/datasource.test.js +254 -0
- package/dist/generator/datasource.test.js.map +1 -0
- package/dist/generator/index.d.ts +131 -0
- package/dist/generator/index.d.ts.map +1 -0
- package/dist/generator/index.js +121 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/generator/index.test.d.ts +2 -0
- package/dist/generator/index.test.d.ts.map +1 -0
- package/dist/generator/index.test.js +175 -0
- package/dist/generator/index.test.js.map +1 -0
- package/dist/generator/loader.d.ts +156 -0
- package/dist/generator/loader.d.ts.map +1 -0
- package/dist/generator/loader.js +295 -0
- package/dist/generator/loader.js.map +1 -0
- package/dist/generator/pipe.d.ts +72 -0
- package/dist/generator/pipe.d.ts.map +1 -0
- package/dist/generator/pipe.js +174 -0
- package/dist/generator/pipe.js.map +1 -0
- package/dist/generator/pipe.test.d.ts +2 -0
- package/dist/generator/pipe.test.d.ts.map +1 -0
- package/dist/generator/pipe.test.js +393 -0
- package/dist/generator/pipe.test.js.map +1 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -0
- package/dist/infer/index.d.ts +202 -0
- package/dist/infer/index.d.ts.map +1 -0
- package/dist/infer/index.js +5 -0
- package/dist/infer/index.js.map +1 -0
- package/dist/schema/datasource.d.ts +135 -0
- package/dist/schema/datasource.d.ts.map +1 -0
- package/dist/schema/datasource.js +105 -0
- package/dist/schema/datasource.js.map +1 -0
- package/dist/schema/datasource.test.d.ts +2 -0
- package/dist/schema/datasource.test.d.ts.map +1 -0
- package/dist/schema/datasource.test.js +142 -0
- package/dist/schema/datasource.test.js.map +1 -0
- package/dist/schema/engines.d.ts +157 -0
- package/dist/schema/engines.d.ts.map +1 -0
- package/dist/schema/engines.js +155 -0
- package/dist/schema/engines.js.map +1 -0
- package/dist/schema/engines.test.d.ts +2 -0
- package/dist/schema/engines.test.d.ts.map +1 -0
- package/dist/schema/engines.test.js +221 -0
- package/dist/schema/engines.test.js.map +1 -0
- package/dist/schema/params.d.ts +106 -0
- package/dist/schema/params.d.ts.map +1 -0
- package/dist/schema/params.js +138 -0
- package/dist/schema/params.js.map +1 -0
- package/dist/schema/params.test.d.ts +2 -0
- package/dist/schema/params.test.d.ts.map +1 -0
- package/dist/schema/params.test.js +175 -0
- package/dist/schema/params.test.js.map +1 -0
- package/dist/schema/pipe.d.ts +436 -0
- package/dist/schema/pipe.d.ts.map +1 -0
- package/dist/schema/pipe.js +484 -0
- package/dist/schema/pipe.js.map +1 -0
- package/dist/schema/pipe.test.d.ts +2 -0
- package/dist/schema/pipe.test.d.ts.map +1 -0
- package/dist/schema/pipe.test.js +488 -0
- package/dist/schema/pipe.test.js.map +1 -0
- package/dist/schema/project.d.ts +202 -0
- package/dist/schema/project.d.ts.map +1 -0
- package/dist/schema/project.js +188 -0
- package/dist/schema/project.js.map +1 -0
- package/dist/schema/project.test.d.ts +2 -0
- package/dist/schema/project.test.d.ts.map +1 -0
- package/dist/schema/project.test.js +180 -0
- package/dist/schema/project.test.js.map +1 -0
- package/dist/schema/types.d.ts +140 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/types.js +174 -0
- package/dist/schema/types.js.map +1 -0
- package/dist/schema/types.test.d.ts +2 -0
- package/dist/schema/types.test.d.ts.map +1 -0
- package/dist/schema/types.test.js +176 -0
- package/dist/schema/types.test.js.map +1 -0
- package/dist/test/handlers.d.ts +58 -0
- package/dist/test/handlers.d.ts.map +1 -0
- package/dist/test/handlers.js +62 -0
- package/dist/test/handlers.js.map +1 -0
- package/dist/test/setup.d.ts +5 -0
- package/dist/test/setup.d.ts.map +1 -0
- package/dist/test/setup.js +11 -0
- package/dist/test/setup.js.map +1 -0
- package/package.json +57 -0
- package/src/api/branches.test.ts +377 -0
- package/src/api/branches.ts +334 -0
- package/src/api/build.test.ts +216 -0
- package/src/api/build.ts +266 -0
- package/src/api/deploy.test.ts +193 -0
- package/src/api/deploy.ts +163 -0
- package/src/api/workspaces.test.ts +81 -0
- package/src/api/workspaces.ts +77 -0
- package/src/cli/auth.ts +358 -0
- package/src/cli/branch-store.test.ts +139 -0
- package/src/cli/branch-store.ts +137 -0
- package/src/cli/commands/branch.ts +306 -0
- package/src/cli/commands/build.ts +183 -0
- package/src/cli/commands/dev.ts +334 -0
- package/src/cli/commands/init.test.ts +249 -0
- package/src/cli/commands/init.ts +323 -0
- package/src/cli/commands/login.ts +98 -0
- package/src/cli/config.test.ts +359 -0
- package/src/cli/config.ts +335 -0
- package/src/cli/env.ts +86 -0
- package/src/cli/git.test.ts +147 -0
- package/src/cli/git.ts +125 -0
- package/src/cli/index.ts +382 -0
- package/src/cli/utils/schema-validation.test.ts +222 -0
- package/src/cli/utils/schema-validation.ts +272 -0
- package/src/client/base.ts +414 -0
- package/src/client/types.ts +165 -0
- package/src/generator/client.ts +194 -0
- package/src/generator/datasource.test.ts +297 -0
- package/src/generator/datasource.ts +217 -0
- package/src/generator/index.test.ts +209 -0
- package/src/generator/index.ts +203 -0
- package/src/generator/loader.ts +406 -0
- package/src/generator/pipe.test.ts +441 -0
- package/src/generator/pipe.ts +220 -0
- package/src/index.ts +191 -0
- package/src/infer/index.ts +247 -0
- package/src/schema/datasource.test.ts +187 -0
- package/src/schema/datasource.ts +195 -0
- package/src/schema/engines.test.ts +247 -0
- package/src/schema/engines.ts +271 -0
- package/src/schema/params.test.ts +208 -0
- package/src/schema/params.ts +249 -0
- package/src/schema/pipe.test.ts +588 -0
- package/src/schema/pipe.ts +832 -0
- package/src/schema/project.test.ts +236 -0
- package/src/schema/project.ts +394 -0
- package/src/schema/types.test.ts +212 -0
- package/src/schema/types.ts +366 -0
- package/src/test/handlers.ts +79 -0
- package/src/test/setup.ts +13 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Datasource content generator
|
|
3
|
+
* Converts DatasourceDefinition to native .datasource file format
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { DatasourceDefinition, SchemaDefinition, ColumnDefinition } from "../schema/datasource.js";
|
|
7
|
+
import type { AnyTypeValidator, TypeModifiers } from "../schema/types.js";
|
|
8
|
+
import { getColumnType, getColumnJsonPath } from "../schema/datasource.js";
|
|
9
|
+
import { getEngineClause, type EngineConfig } from "../schema/engines.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generated datasource content
|
|
13
|
+
*/
|
|
14
|
+
export interface GeneratedDatasource {
|
|
15
|
+
/** Datasource name */
|
|
16
|
+
name: string;
|
|
17
|
+
/** The generated .datasource file content */
|
|
18
|
+
content: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get the Tinybird type string from a type validator
|
|
23
|
+
* Handles the internal structure of validators
|
|
24
|
+
*/
|
|
25
|
+
function getTinybirdTypeFromValidator(validator: AnyTypeValidator): string {
|
|
26
|
+
// The validator has _tinybirdType as the type string
|
|
27
|
+
return validator._tinybirdType;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get modifiers from a validator
|
|
32
|
+
*/
|
|
33
|
+
function getModifiersFromValidator(validator: AnyTypeValidator): TypeModifiers {
|
|
34
|
+
return validator._modifiers;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Format a default value for the datasource file
|
|
39
|
+
*/
|
|
40
|
+
function formatDefaultValue(value: unknown, tinybirdType: string): string {
|
|
41
|
+
if (value === null) {
|
|
42
|
+
return "NULL";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (typeof value === "string") {
|
|
46
|
+
// Escape single quotes
|
|
47
|
+
return `'${value.replace(/'/g, "\\'")}'`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (typeof value === "number" || typeof value === "bigint") {
|
|
51
|
+
return String(value);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (typeof value === "boolean") {
|
|
55
|
+
return value ? "1" : "0";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (value instanceof Date) {
|
|
59
|
+
// Format based on type
|
|
60
|
+
if (tinybirdType.startsWith("Date") && !tinybirdType.includes("Time")) {
|
|
61
|
+
return `'${value.toISOString().split("T")[0]}'`;
|
|
62
|
+
}
|
|
63
|
+
return `'${value.toISOString().replace("T", " ").slice(0, 19)}'`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// For arrays and objects, use raw JSON (no quotes)
|
|
67
|
+
if (Array.isArray(value)) {
|
|
68
|
+
return JSON.stringify(value);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (typeof value === "object" && value !== null) {
|
|
72
|
+
return JSON.stringify(value);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Fallback for other types - stringify as string literal
|
|
76
|
+
return `'${String(value).replace(/'/g, "\\'")}'`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Generate a column definition line for the schema
|
|
81
|
+
*/
|
|
82
|
+
function generateColumnLine(
|
|
83
|
+
columnName: string,
|
|
84
|
+
column: AnyTypeValidator | ColumnDefinition,
|
|
85
|
+
includeJsonPaths: boolean
|
|
86
|
+
): string {
|
|
87
|
+
const validator = getColumnType(column);
|
|
88
|
+
const jsonPath = getColumnJsonPath(column);
|
|
89
|
+
const tinybirdType = getTinybirdTypeFromValidator(validator);
|
|
90
|
+
const modifiers = getModifiersFromValidator(validator);
|
|
91
|
+
|
|
92
|
+
const parts: string[] = [` ${columnName} ${tinybirdType}`];
|
|
93
|
+
|
|
94
|
+
// Add JSON path for Events API ingestion support if enabled
|
|
95
|
+
// Use explicit jsonPath if defined, otherwise default to $.columnName
|
|
96
|
+
if (includeJsonPaths) {
|
|
97
|
+
const effectiveJsonPath = jsonPath ?? `$.${columnName}`;
|
|
98
|
+
parts.push(`\`json:${effectiveJsonPath}\``);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Add default value if defined
|
|
102
|
+
if (modifiers.hasDefault && modifiers.defaultValue !== undefined) {
|
|
103
|
+
const defaultStr = formatDefaultValue(modifiers.defaultValue, tinybirdType);
|
|
104
|
+
parts.push(`DEFAULT ${defaultStr}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Add codec if defined
|
|
108
|
+
if (modifiers.codec) {
|
|
109
|
+
parts.push(`CODEC(${modifiers.codec})`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return parts.join(" ");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Generate the SCHEMA section
|
|
117
|
+
*/
|
|
118
|
+
function generateSchema(schema: SchemaDefinition, includeJsonPaths: boolean): string {
|
|
119
|
+
const lines = ["SCHEMA >"];
|
|
120
|
+
|
|
121
|
+
const columnNames = Object.keys(schema);
|
|
122
|
+
columnNames.forEach((name, index) => {
|
|
123
|
+
const column = schema[name];
|
|
124
|
+
const line = generateColumnLine(name, column, includeJsonPaths);
|
|
125
|
+
// Add comma if not the last column
|
|
126
|
+
const suffix = index < columnNames.length - 1 ? "," : "";
|
|
127
|
+
lines.push(line + suffix);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return lines.join("\n");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Generate the engine configuration
|
|
135
|
+
* Uses the helper from engines.ts if an engine is provided
|
|
136
|
+
*/
|
|
137
|
+
function generateEngineConfig(engine?: EngineConfig): string {
|
|
138
|
+
if (!engine) {
|
|
139
|
+
// Default to MergeTree with first column as sorting key
|
|
140
|
+
return 'ENGINE "MergeTree"';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return getEngineClause(engine);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Generate a .datasource file content from a DatasourceDefinition
|
|
148
|
+
*
|
|
149
|
+
* @param datasource - The datasource definition
|
|
150
|
+
* @returns Generated datasource content
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```ts
|
|
154
|
+
* const events = defineDatasource('events', {
|
|
155
|
+
* description: 'User events',
|
|
156
|
+
* schema: {
|
|
157
|
+
* timestamp: t.dateTime(),
|
|
158
|
+
* user_id: t.string(),
|
|
159
|
+
* event: t.string(),
|
|
160
|
+
* },
|
|
161
|
+
* engine: engine.mergeTree({
|
|
162
|
+
* sortingKey: ['user_id', 'timestamp'],
|
|
163
|
+
* }),
|
|
164
|
+
* });
|
|
165
|
+
*
|
|
166
|
+
* const { content } = generateDatasource(events);
|
|
167
|
+
* // Returns:
|
|
168
|
+
* // DESCRIPTION >
|
|
169
|
+
* // User events
|
|
170
|
+
* //
|
|
171
|
+
* // SCHEMA >
|
|
172
|
+
* // timestamp DateTime,
|
|
173
|
+
* // user_id String,
|
|
174
|
+
* // event String
|
|
175
|
+
* //
|
|
176
|
+
* // ENGINE "MergeTree"
|
|
177
|
+
* // ENGINE_SORTING_KEY "user_id, timestamp"
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
export function generateDatasource(
|
|
181
|
+
datasource: DatasourceDefinition
|
|
182
|
+
): GeneratedDatasource {
|
|
183
|
+
const parts: string[] = [];
|
|
184
|
+
|
|
185
|
+
// Add description if present
|
|
186
|
+
if (datasource.options.description) {
|
|
187
|
+
parts.push(`DESCRIPTION >\n ${datasource.options.description}`);
|
|
188
|
+
parts.push("");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Check if JSON paths should be included (defaults to true)
|
|
192
|
+
const includeJsonPaths = datasource.options.jsonPaths !== false;
|
|
193
|
+
|
|
194
|
+
// Add schema
|
|
195
|
+
parts.push(generateSchema(datasource._schema, includeJsonPaths));
|
|
196
|
+
parts.push("");
|
|
197
|
+
|
|
198
|
+
// Add engine configuration
|
|
199
|
+
parts.push(generateEngineConfig(datasource.options.engine));
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
name: datasource._name,
|
|
203
|
+
content: parts.join("\n"),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Generate .datasource files for all datasources in a project
|
|
209
|
+
*
|
|
210
|
+
* @param datasources - Record of datasource definitions
|
|
211
|
+
* @returns Array of generated datasource content
|
|
212
|
+
*/
|
|
213
|
+
export function generateAllDatasources(
|
|
214
|
+
datasources: Record<string, DatasourceDefinition>
|
|
215
|
+
): GeneratedDatasource[] {
|
|
216
|
+
return Object.values(datasources).map(generateDatasource);
|
|
217
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { generateResources, build } from "./index.js";
|
|
3
|
+
import { defineProject } from "../schema/project.js";
|
|
4
|
+
import { defineDatasource } from "../schema/datasource.js";
|
|
5
|
+
import { definePipe, node } from "../schema/pipe.js";
|
|
6
|
+
import { t } from "../schema/types.js";
|
|
7
|
+
import { engine } from "../schema/engines.js";
|
|
8
|
+
import * as fs from "fs";
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
import * as os from "os";
|
|
11
|
+
|
|
12
|
+
describe("Generator Index", () => {
|
|
13
|
+
describe("generateResources", () => {
|
|
14
|
+
it("generates resources from a project definition", () => {
|
|
15
|
+
const events = defineDatasource("events", {
|
|
16
|
+
schema: {
|
|
17
|
+
timestamp: t.dateTime(),
|
|
18
|
+
event_name: t.string(),
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const topEvents = definePipe("top_events", {
|
|
23
|
+
nodes: [
|
|
24
|
+
node({
|
|
25
|
+
name: "endpoint",
|
|
26
|
+
sql: "SELECT event_name, count() as cnt FROM events GROUP BY event_name",
|
|
27
|
+
}),
|
|
28
|
+
],
|
|
29
|
+
output: {
|
|
30
|
+
event_name: t.string(),
|
|
31
|
+
cnt: t.uint64(),
|
|
32
|
+
},
|
|
33
|
+
endpoint: true,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const project = defineProject({
|
|
37
|
+
datasources: { events },
|
|
38
|
+
pipes: { topEvents },
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const result = generateResources(project);
|
|
42
|
+
|
|
43
|
+
expect(result.datasources).toHaveLength(1);
|
|
44
|
+
expect(result.datasources[0].name).toBe("events");
|
|
45
|
+
expect(result.datasources[0].content).toContain("timestamp DateTime");
|
|
46
|
+
|
|
47
|
+
expect(result.pipes).toHaveLength(1);
|
|
48
|
+
expect(result.pipes[0].name).toBe("top_events");
|
|
49
|
+
expect(result.pipes[0].content).toContain("SELECT event_name");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("handles empty project", () => {
|
|
53
|
+
const project = defineProject({
|
|
54
|
+
datasources: {},
|
|
55
|
+
pipes: {},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const result = generateResources(project);
|
|
59
|
+
|
|
60
|
+
expect(result.datasources).toHaveLength(0);
|
|
61
|
+
expect(result.pipes).toHaveLength(0);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("generates multiple datasources and pipes", () => {
|
|
65
|
+
const ds1 = defineDatasource("ds1", { schema: { id: t.string() } });
|
|
66
|
+
const ds2 = defineDatasource("ds2", { schema: { name: t.string() } });
|
|
67
|
+
const ds3 = defineDatasource("ds3", { schema: { count: t.int32() } });
|
|
68
|
+
|
|
69
|
+
const pipe1 = definePipe("pipe1", {
|
|
70
|
+
nodes: [node({ name: "n", sql: "SELECT * FROM ds1" })],
|
|
71
|
+
output: { id: t.string() },
|
|
72
|
+
endpoint: true,
|
|
73
|
+
});
|
|
74
|
+
const pipe2 = definePipe("pipe2", {
|
|
75
|
+
nodes: [node({ name: "n", sql: "SELECT * FROM ds2" })],
|
|
76
|
+
output: { name: t.string() },
|
|
77
|
+
endpoint: true,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const project = defineProject({
|
|
81
|
+
datasources: { ds1, ds2, ds3 },
|
|
82
|
+
pipes: { pipe1, pipe2 },
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const result = generateResources(project);
|
|
86
|
+
|
|
87
|
+
expect(result.datasources).toHaveLength(3);
|
|
88
|
+
expect(result.pipes).toHaveLength(2);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("generates datasources with full options", () => {
|
|
92
|
+
const events = defineDatasource("events", {
|
|
93
|
+
description: "Event tracking data",
|
|
94
|
+
schema: {
|
|
95
|
+
timestamp: t.dateTime(),
|
|
96
|
+
event_name: t.string(),
|
|
97
|
+
user_id: t.string().nullable(),
|
|
98
|
+
metadata: t.string().default("{}"),
|
|
99
|
+
},
|
|
100
|
+
engine: engine.mergeTree({
|
|
101
|
+
sortingKey: ["timestamp", "event_name"],
|
|
102
|
+
partitionKey: "toYYYYMM(timestamp)",
|
|
103
|
+
}),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const project = defineProject({
|
|
107
|
+
datasources: { events },
|
|
108
|
+
pipes: {},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const result = generateResources(project);
|
|
112
|
+
|
|
113
|
+
expect(result.datasources).toHaveLength(1);
|
|
114
|
+
expect(result.datasources[0].content).toContain("DESCRIPTION >");
|
|
115
|
+
expect(result.datasources[0].content).toContain("Event tracking data");
|
|
116
|
+
expect(result.datasources[0].content).toContain("ENGINE_SORTING_KEY");
|
|
117
|
+
expect(result.datasources[0].content).toContain("ENGINE_PARTITION_KEY");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("generates pipes with endpoint config", () => {
|
|
121
|
+
const stats = definePipe("stats", {
|
|
122
|
+
description: "Get stats",
|
|
123
|
+
nodes: [
|
|
124
|
+
node({
|
|
125
|
+
name: "calc",
|
|
126
|
+
description: "Calculate statistics",
|
|
127
|
+
sql: "SELECT count() as total FROM events",
|
|
128
|
+
}),
|
|
129
|
+
],
|
|
130
|
+
output: { total: t.uint64() },
|
|
131
|
+
endpoint: {
|
|
132
|
+
enabled: true,
|
|
133
|
+
cache: { enabled: true, ttl: 120 },
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const project = defineProject({
|
|
138
|
+
datasources: {},
|
|
139
|
+
pipes: { stats },
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const result = generateResources(project);
|
|
143
|
+
|
|
144
|
+
expect(result.pipes).toHaveLength(1);
|
|
145
|
+
expect(result.pipes[0].content).toContain("DESCRIPTION >");
|
|
146
|
+
expect(result.pipes[0].content).toContain("Get stats");
|
|
147
|
+
expect(result.pipes[0].content).toContain("TYPE endpoint");
|
|
148
|
+
expect(result.pipes[0].content).toContain("CACHE 120");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("generates non-endpoint pipes", () => {
|
|
152
|
+
const materialize = definePipe("materialize", {
|
|
153
|
+
nodes: [
|
|
154
|
+
node({
|
|
155
|
+
name: "aggregate",
|
|
156
|
+
sql: "SELECT event_name, count() FROM events GROUP BY event_name",
|
|
157
|
+
}),
|
|
158
|
+
],
|
|
159
|
+
output: { event_name: t.string() },
|
|
160
|
+
endpoint: false,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const project = defineProject({
|
|
164
|
+
datasources: {},
|
|
165
|
+
pipes: { materialize },
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const result = generateResources(project);
|
|
169
|
+
|
|
170
|
+
expect(result.pipes).toHaveLength(1);
|
|
171
|
+
expect(result.pipes[0].content).not.toContain("TYPE endpoint");
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe("build", () => {
|
|
176
|
+
let tempDir: string;
|
|
177
|
+
|
|
178
|
+
beforeEach(() => {
|
|
179
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-test-"));
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
afterEach(() => {
|
|
183
|
+
try {
|
|
184
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
185
|
+
} catch {
|
|
186
|
+
// Ignore cleanup errors
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("throws error for non-existent schema file", async () => {
|
|
191
|
+
const schemaPath = path.join(tempDir, "nonexistent.ts");
|
|
192
|
+
|
|
193
|
+
await expect(build({ schemaPath })).rejects.toThrow("Schema file not found");
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("throws error when no project definition is exported", async () => {
|
|
197
|
+
const schemaContent = `
|
|
198
|
+
export const notAProject = { foo: "bar" };
|
|
199
|
+
`;
|
|
200
|
+
|
|
201
|
+
const schemaPath = path.join(tempDir, "invalid-schema.ts");
|
|
202
|
+
fs.writeFileSync(schemaPath, schemaContent);
|
|
203
|
+
|
|
204
|
+
await expect(build({ schemaPath })).rejects.toThrow(
|
|
205
|
+
"No ProjectDefinition found"
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
});
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main generator module
|
|
3
|
+
* Orchestrates loading schema and generating all resources
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { loadSchema, loadEntities, entitiesToProject, type LoadedEntities } from "./loader.js";
|
|
7
|
+
import { generateAllDatasources, type GeneratedDatasource } from "./datasource.js";
|
|
8
|
+
import { generateAllPipes, type GeneratedPipe } from "./pipe.js";
|
|
9
|
+
import type { ProjectDefinition, DatasourcesDefinition, PipesDefinition } from "../schema/project.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generated resources ready for API push
|
|
13
|
+
*/
|
|
14
|
+
export interface GeneratedResources {
|
|
15
|
+
/** Generated datasource files */
|
|
16
|
+
datasources: GeneratedDatasource[];
|
|
17
|
+
/** Generated pipe files */
|
|
18
|
+
pipes: GeneratedPipe[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Build result with metadata
|
|
23
|
+
*/
|
|
24
|
+
export interface BuildResult {
|
|
25
|
+
/** The generated resources */
|
|
26
|
+
resources: GeneratedResources;
|
|
27
|
+
/** The loaded project definition (for validation) */
|
|
28
|
+
project: ProjectDefinition;
|
|
29
|
+
/** Path to the schema file */
|
|
30
|
+
schemaPath: string;
|
|
31
|
+
/** Directory containing the schema */
|
|
32
|
+
schemaDir: string;
|
|
33
|
+
/** Statistics about the build */
|
|
34
|
+
stats: {
|
|
35
|
+
datasourceCount: number;
|
|
36
|
+
pipeCount: number;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Generate resources from a loaded project definition
|
|
42
|
+
*
|
|
43
|
+
* @param project - The project definition
|
|
44
|
+
* @returns Generated resources
|
|
45
|
+
*/
|
|
46
|
+
export function generateResources(project: ProjectDefinition): GeneratedResources {
|
|
47
|
+
const datasources = generateAllDatasources(project.datasources);
|
|
48
|
+
const pipes = generateAllPipes(project.pipes);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
datasources,
|
|
52
|
+
pipes,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build options
|
|
58
|
+
*/
|
|
59
|
+
export interface BuildOptions {
|
|
60
|
+
/** Path to the schema file */
|
|
61
|
+
schemaPath: string;
|
|
62
|
+
/** Working directory (defaults to cwd) */
|
|
63
|
+
cwd?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Build all resources from a TypeScript schema
|
|
68
|
+
*
|
|
69
|
+
* This is the main entry point for the generator.
|
|
70
|
+
* It loads the schema, generates all resources, and returns them
|
|
71
|
+
* ready for API push.
|
|
72
|
+
*
|
|
73
|
+
* @param options - Build options
|
|
74
|
+
* @returns Build result with generated resources
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* const result = await build({
|
|
79
|
+
* schemaPath: 'src/tinybird/schema.ts',
|
|
80
|
+
* });
|
|
81
|
+
*
|
|
82
|
+
* console.log(`Generated ${result.stats.datasourceCount} datasources`);
|
|
83
|
+
* console.log(`Generated ${result.stats.pipeCount} pipes`);
|
|
84
|
+
*
|
|
85
|
+
* // Resources are ready to push to API
|
|
86
|
+
* result.resources.datasources.forEach(ds => {
|
|
87
|
+
* console.log(`${ds.name}.datasource:`);
|
|
88
|
+
* console.log(ds.content);
|
|
89
|
+
* });
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export async function build(options: BuildOptions): Promise<BuildResult> {
|
|
93
|
+
// Load the schema
|
|
94
|
+
const loaded = await loadSchema({
|
|
95
|
+
schemaPath: options.schemaPath,
|
|
96
|
+
cwd: options.cwd,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Generate resources
|
|
100
|
+
const resources = generateResources(loaded.project);
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
resources,
|
|
104
|
+
project: loaded.project,
|
|
105
|
+
schemaPath: loaded.schemaPath,
|
|
106
|
+
schemaDir: loaded.schemaDir,
|
|
107
|
+
stats: {
|
|
108
|
+
datasourceCount: resources.datasources.length,
|
|
109
|
+
pipeCount: resources.pipes.length,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Build options using include paths
|
|
116
|
+
*/
|
|
117
|
+
export interface BuildFromIncludeOptions {
|
|
118
|
+
/** Array of file paths to scan for datasources and pipes */
|
|
119
|
+
includePaths: string[];
|
|
120
|
+
/** Working directory (defaults to cwd) */
|
|
121
|
+
cwd?: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Build result from include paths
|
|
126
|
+
*/
|
|
127
|
+
export interface BuildFromIncludeResult {
|
|
128
|
+
/** The generated resources */
|
|
129
|
+
resources: GeneratedResources;
|
|
130
|
+
/** Loaded entities from source files */
|
|
131
|
+
entities: LoadedEntities;
|
|
132
|
+
/** Statistics about the build */
|
|
133
|
+
stats: {
|
|
134
|
+
datasourceCount: number;
|
|
135
|
+
pipeCount: number;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Generate resources from entities
|
|
141
|
+
*/
|
|
142
|
+
export function generateResourcesFromEntities(
|
|
143
|
+
datasources: DatasourcesDefinition,
|
|
144
|
+
pipes: PipesDefinition
|
|
145
|
+
): GeneratedResources {
|
|
146
|
+
return {
|
|
147
|
+
datasources: generateAllDatasources(datasources),
|
|
148
|
+
pipes: generateAllPipes(pipes),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Build all resources from include paths
|
|
154
|
+
*
|
|
155
|
+
* This is the main entry point for the generator that works with
|
|
156
|
+
* auto-discovered entities. It loads datasources and pipes from the
|
|
157
|
+
* include paths and generates Tinybird resources ready to deploy.
|
|
158
|
+
*
|
|
159
|
+
* @param options - Build options with include paths
|
|
160
|
+
* @returns Build result with generated resources
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* ```ts
|
|
164
|
+
* const result = await buildFromInclude({
|
|
165
|
+
* includePaths: ['src/tinybird/datasources.ts', 'src/tinybird/pipes.ts'],
|
|
166
|
+
* });
|
|
167
|
+
*
|
|
168
|
+
* // Push resources to Tinybird
|
|
169
|
+
* await deploy(result.resources);
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
export async function buildFromInclude(
|
|
173
|
+
options: BuildFromIncludeOptions
|
|
174
|
+
): Promise<BuildFromIncludeResult> {
|
|
175
|
+
const cwd = options.cwd ?? process.cwd();
|
|
176
|
+
|
|
177
|
+
// Load entities from include paths
|
|
178
|
+
const entities = await loadEntities({
|
|
179
|
+
includePaths: options.includePaths,
|
|
180
|
+
cwd,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Convert to format for generators
|
|
184
|
+
const { datasources, pipes } = entitiesToProject(entities);
|
|
185
|
+
|
|
186
|
+
// Generate resources
|
|
187
|
+
const resources = generateResourcesFromEntities(datasources, pipes);
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
resources,
|
|
191
|
+
entities,
|
|
192
|
+
stats: {
|
|
193
|
+
datasourceCount: resources.datasources.length,
|
|
194
|
+
pipeCount: resources.pipes.length,
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Re-export types and utilities
|
|
200
|
+
export { loadSchema, loadEntities, entitiesToProject, type LoaderOptions, type LoadedSchema, type LoadedEntities, type LoadEntitiesOptions } from "./loader.js";
|
|
201
|
+
export { generateDatasource, generateAllDatasources, type GeneratedDatasource } from "./datasource.js";
|
|
202
|
+
export { generatePipe, generateAllPipes, type GeneratedPipe } from "./pipe.js";
|
|
203
|
+
export { generateClientFile, type GenerateClientOptions, type GeneratedClient } from "./client.js";
|