@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.
Files changed (258) hide show
  1. package/README.md +518 -0
  2. package/bin/tinybird.js +7 -0
  3. package/dist/api/branches.d.ts +98 -0
  4. package/dist/api/branches.d.ts.map +1 -0
  5. package/dist/api/branches.js +203 -0
  6. package/dist/api/branches.js.map +1 -0
  7. package/dist/api/branches.test.d.ts +2 -0
  8. package/dist/api/branches.test.d.ts.map +1 -0
  9. package/dist/api/branches.test.js +286 -0
  10. package/dist/api/branches.test.js.map +1 -0
  11. package/dist/api/build.d.ts +130 -0
  12. package/dist/api/build.d.ts.map +1 -0
  13. package/dist/api/build.js +143 -0
  14. package/dist/api/build.js.map +1 -0
  15. package/dist/api/build.test.d.ts +2 -0
  16. package/dist/api/build.test.d.ts.map +1 -0
  17. package/dist/api/build.test.js +138 -0
  18. package/dist/api/build.test.js.map +1 -0
  19. package/dist/api/deploy.d.ts +39 -0
  20. package/dist/api/deploy.d.ts.map +1 -0
  21. package/dist/api/deploy.js +135 -0
  22. package/dist/api/deploy.js.map +1 -0
  23. package/dist/api/deploy.test.d.ts +2 -0
  24. package/dist/api/deploy.test.d.ts.map +1 -0
  25. package/dist/api/deploy.test.js +118 -0
  26. package/dist/api/deploy.test.js.map +1 -0
  27. package/dist/api/workspaces.d.ts +46 -0
  28. package/dist/api/workspaces.d.ts.map +1 -0
  29. package/dist/api/workspaces.js +39 -0
  30. package/dist/api/workspaces.js.map +1 -0
  31. package/dist/api/workspaces.test.d.ts +2 -0
  32. package/dist/api/workspaces.test.d.ts.map +1 -0
  33. package/dist/api/workspaces.test.js +65 -0
  34. package/dist/api/workspaces.test.js.map +1 -0
  35. package/dist/cli/auth.d.ts +86 -0
  36. package/dist/cli/auth.d.ts.map +1 -0
  37. package/dist/cli/auth.js +284 -0
  38. package/dist/cli/auth.js.map +1 -0
  39. package/dist/cli/branch-store.d.ts +53 -0
  40. package/dist/cli/branch-store.d.ts.map +1 -0
  41. package/dist/cli/branch-store.js +91 -0
  42. package/dist/cli/branch-store.js.map +1 -0
  43. package/dist/cli/branch-store.test.d.ts +2 -0
  44. package/dist/cli/branch-store.test.d.ts.map +1 -0
  45. package/dist/cli/branch-store.test.js +115 -0
  46. package/dist/cli/branch-store.test.js.map +1 -0
  47. package/dist/cli/commands/branch.d.ts +82 -0
  48. package/dist/cli/commands/branch.d.ts.map +1 -0
  49. package/dist/cli/commands/branch.js +215 -0
  50. package/dist/cli/commands/branch.js.map +1 -0
  51. package/dist/cli/commands/build.d.ts +43 -0
  52. package/dist/cli/commands/build.d.ts.map +1 -0
  53. package/dist/cli/commands/build.js +138 -0
  54. package/dist/cli/commands/build.js.map +1 -0
  55. package/dist/cli/commands/dev.d.ts +78 -0
  56. package/dist/cli/commands/dev.d.ts.map +1 -0
  57. package/dist/cli/commands/dev.js +226 -0
  58. package/dist/cli/commands/dev.js.map +1 -0
  59. package/dist/cli/commands/init.d.ts +45 -0
  60. package/dist/cli/commands/init.d.ts.map +1 -0
  61. package/dist/cli/commands/init.js +277 -0
  62. package/dist/cli/commands/init.js.map +1 -0
  63. package/dist/cli/commands/init.test.d.ts +2 -0
  64. package/dist/cli/commands/init.test.d.ts.map +1 -0
  65. package/dist/cli/commands/init.test.js +158 -0
  66. package/dist/cli/commands/init.test.js.map +1 -0
  67. package/dist/cli/commands/login.d.ts +37 -0
  68. package/dist/cli/commands/login.d.ts.map +1 -0
  69. package/dist/cli/commands/login.js +64 -0
  70. package/dist/cli/commands/login.js.map +1 -0
  71. package/dist/cli/config.d.ts +114 -0
  72. package/dist/cli/config.d.ts.map +1 -0
  73. package/dist/cli/config.js +258 -0
  74. package/dist/cli/config.js.map +1 -0
  75. package/dist/cli/config.test.d.ts +2 -0
  76. package/dist/cli/config.test.d.ts.map +1 -0
  77. package/dist/cli/config.test.js +243 -0
  78. package/dist/cli/config.test.js.map +1 -0
  79. package/dist/cli/env.d.ts +29 -0
  80. package/dist/cli/env.d.ts.map +1 -0
  81. package/dist/cli/env.js +66 -0
  82. package/dist/cli/env.js.map +1 -0
  83. package/dist/cli/git.d.ts +29 -0
  84. package/dist/cli/git.d.ts.map +1 -0
  85. package/dist/cli/git.js +114 -0
  86. package/dist/cli/git.js.map +1 -0
  87. package/dist/cli/git.test.d.ts +2 -0
  88. package/dist/cli/git.test.d.ts.map +1 -0
  89. package/dist/cli/git.test.js +125 -0
  90. package/dist/cli/git.test.js.map +1 -0
  91. package/dist/cli/index.d.ts +7 -0
  92. package/dist/cli/index.d.ts.map +1 -0
  93. package/dist/cli/index.js +337 -0
  94. package/dist/cli/index.js.map +1 -0
  95. package/dist/cli/utils/schema-validation.d.ts +95 -0
  96. package/dist/cli/utils/schema-validation.d.ts.map +1 -0
  97. package/dist/cli/utils/schema-validation.js +175 -0
  98. package/dist/cli/utils/schema-validation.js.map +1 -0
  99. package/dist/cli/utils/schema-validation.test.d.ts +5 -0
  100. package/dist/cli/utils/schema-validation.test.d.ts.map +1 -0
  101. package/dist/cli/utils/schema-validation.test.js +173 -0
  102. package/dist/cli/utils/schema-validation.test.js.map +1 -0
  103. package/dist/client/base.d.ts +116 -0
  104. package/dist/client/base.d.ts.map +1 -0
  105. package/dist/client/base.js +328 -0
  106. package/dist/client/base.js.map +1 -0
  107. package/dist/client/types.d.ts +137 -0
  108. package/dist/client/types.d.ts.map +1 -0
  109. package/dist/client/types.js +43 -0
  110. package/dist/client/types.js.map +1 -0
  111. package/dist/generator/client.d.ts +44 -0
  112. package/dist/generator/client.d.ts.map +1 -0
  113. package/dist/generator/client.js +144 -0
  114. package/dist/generator/client.js.map +1 -0
  115. package/dist/generator/datasource.d.ts +57 -0
  116. package/dist/generator/datasource.d.ts.map +1 -0
  117. package/dist/generator/datasource.js +169 -0
  118. package/dist/generator/datasource.js.map +1 -0
  119. package/dist/generator/datasource.test.d.ts +2 -0
  120. package/dist/generator/datasource.test.d.ts.map +1 -0
  121. package/dist/generator/datasource.test.js +254 -0
  122. package/dist/generator/datasource.test.js.map +1 -0
  123. package/dist/generator/index.d.ts +131 -0
  124. package/dist/generator/index.d.ts.map +1 -0
  125. package/dist/generator/index.js +121 -0
  126. package/dist/generator/index.js.map +1 -0
  127. package/dist/generator/index.test.d.ts +2 -0
  128. package/dist/generator/index.test.d.ts.map +1 -0
  129. package/dist/generator/index.test.js +175 -0
  130. package/dist/generator/index.test.js.map +1 -0
  131. package/dist/generator/loader.d.ts +156 -0
  132. package/dist/generator/loader.d.ts.map +1 -0
  133. package/dist/generator/loader.js +295 -0
  134. package/dist/generator/loader.js.map +1 -0
  135. package/dist/generator/pipe.d.ts +72 -0
  136. package/dist/generator/pipe.d.ts.map +1 -0
  137. package/dist/generator/pipe.js +174 -0
  138. package/dist/generator/pipe.js.map +1 -0
  139. package/dist/generator/pipe.test.d.ts +2 -0
  140. package/dist/generator/pipe.test.d.ts.map +1 -0
  141. package/dist/generator/pipe.test.js +393 -0
  142. package/dist/generator/pipe.test.js.map +1 -0
  143. package/dist/index.d.ts +74 -0
  144. package/dist/index.d.ts.map +1 -0
  145. package/dist/index.js +73 -0
  146. package/dist/index.js.map +1 -0
  147. package/dist/infer/index.d.ts +202 -0
  148. package/dist/infer/index.d.ts.map +1 -0
  149. package/dist/infer/index.js +5 -0
  150. package/dist/infer/index.js.map +1 -0
  151. package/dist/schema/datasource.d.ts +135 -0
  152. package/dist/schema/datasource.d.ts.map +1 -0
  153. package/dist/schema/datasource.js +105 -0
  154. package/dist/schema/datasource.js.map +1 -0
  155. package/dist/schema/datasource.test.d.ts +2 -0
  156. package/dist/schema/datasource.test.d.ts.map +1 -0
  157. package/dist/schema/datasource.test.js +142 -0
  158. package/dist/schema/datasource.test.js.map +1 -0
  159. package/dist/schema/engines.d.ts +157 -0
  160. package/dist/schema/engines.d.ts.map +1 -0
  161. package/dist/schema/engines.js +155 -0
  162. package/dist/schema/engines.js.map +1 -0
  163. package/dist/schema/engines.test.d.ts +2 -0
  164. package/dist/schema/engines.test.d.ts.map +1 -0
  165. package/dist/schema/engines.test.js +221 -0
  166. package/dist/schema/engines.test.js.map +1 -0
  167. package/dist/schema/params.d.ts +106 -0
  168. package/dist/schema/params.d.ts.map +1 -0
  169. package/dist/schema/params.js +138 -0
  170. package/dist/schema/params.js.map +1 -0
  171. package/dist/schema/params.test.d.ts +2 -0
  172. package/dist/schema/params.test.d.ts.map +1 -0
  173. package/dist/schema/params.test.js +175 -0
  174. package/dist/schema/params.test.js.map +1 -0
  175. package/dist/schema/pipe.d.ts +436 -0
  176. package/dist/schema/pipe.d.ts.map +1 -0
  177. package/dist/schema/pipe.js +484 -0
  178. package/dist/schema/pipe.js.map +1 -0
  179. package/dist/schema/pipe.test.d.ts +2 -0
  180. package/dist/schema/pipe.test.d.ts.map +1 -0
  181. package/dist/schema/pipe.test.js +488 -0
  182. package/dist/schema/pipe.test.js.map +1 -0
  183. package/dist/schema/project.d.ts +202 -0
  184. package/dist/schema/project.d.ts.map +1 -0
  185. package/dist/schema/project.js +188 -0
  186. package/dist/schema/project.js.map +1 -0
  187. package/dist/schema/project.test.d.ts +2 -0
  188. package/dist/schema/project.test.d.ts.map +1 -0
  189. package/dist/schema/project.test.js +180 -0
  190. package/dist/schema/project.test.js.map +1 -0
  191. package/dist/schema/types.d.ts +140 -0
  192. package/dist/schema/types.d.ts.map +1 -0
  193. package/dist/schema/types.js +174 -0
  194. package/dist/schema/types.js.map +1 -0
  195. package/dist/schema/types.test.d.ts +2 -0
  196. package/dist/schema/types.test.d.ts.map +1 -0
  197. package/dist/schema/types.test.js +176 -0
  198. package/dist/schema/types.test.js.map +1 -0
  199. package/dist/test/handlers.d.ts +58 -0
  200. package/dist/test/handlers.d.ts.map +1 -0
  201. package/dist/test/handlers.js +62 -0
  202. package/dist/test/handlers.js.map +1 -0
  203. package/dist/test/setup.d.ts +5 -0
  204. package/dist/test/setup.d.ts.map +1 -0
  205. package/dist/test/setup.js +11 -0
  206. package/dist/test/setup.js.map +1 -0
  207. package/package.json +57 -0
  208. package/src/api/branches.test.ts +377 -0
  209. package/src/api/branches.ts +334 -0
  210. package/src/api/build.test.ts +216 -0
  211. package/src/api/build.ts +266 -0
  212. package/src/api/deploy.test.ts +193 -0
  213. package/src/api/deploy.ts +163 -0
  214. package/src/api/workspaces.test.ts +81 -0
  215. package/src/api/workspaces.ts +77 -0
  216. package/src/cli/auth.ts +358 -0
  217. package/src/cli/branch-store.test.ts +139 -0
  218. package/src/cli/branch-store.ts +137 -0
  219. package/src/cli/commands/branch.ts +306 -0
  220. package/src/cli/commands/build.ts +183 -0
  221. package/src/cli/commands/dev.ts +334 -0
  222. package/src/cli/commands/init.test.ts +249 -0
  223. package/src/cli/commands/init.ts +323 -0
  224. package/src/cli/commands/login.ts +98 -0
  225. package/src/cli/config.test.ts +359 -0
  226. package/src/cli/config.ts +335 -0
  227. package/src/cli/env.ts +86 -0
  228. package/src/cli/git.test.ts +147 -0
  229. package/src/cli/git.ts +125 -0
  230. package/src/cli/index.ts +382 -0
  231. package/src/cli/utils/schema-validation.test.ts +222 -0
  232. package/src/cli/utils/schema-validation.ts +272 -0
  233. package/src/client/base.ts +414 -0
  234. package/src/client/types.ts +165 -0
  235. package/src/generator/client.ts +194 -0
  236. package/src/generator/datasource.test.ts +297 -0
  237. package/src/generator/datasource.ts +217 -0
  238. package/src/generator/index.test.ts +209 -0
  239. package/src/generator/index.ts +203 -0
  240. package/src/generator/loader.ts +406 -0
  241. package/src/generator/pipe.test.ts +441 -0
  242. package/src/generator/pipe.ts +220 -0
  243. package/src/index.ts +191 -0
  244. package/src/infer/index.ts +247 -0
  245. package/src/schema/datasource.test.ts +187 -0
  246. package/src/schema/datasource.ts +195 -0
  247. package/src/schema/engines.test.ts +247 -0
  248. package/src/schema/engines.ts +271 -0
  249. package/src/schema/params.test.ts +208 -0
  250. package/src/schema/params.ts +249 -0
  251. package/src/schema/pipe.test.ts +588 -0
  252. package/src/schema/pipe.ts +832 -0
  253. package/src/schema/project.test.ts +236 -0
  254. package/src/schema/project.ts +394 -0
  255. package/src/schema/types.test.ts +212 -0
  256. package/src/schema/types.ts +366 -0
  257. package/src/test/handlers.ts +79 -0
  258. package/src/test/setup.ts +13 -0
@@ -0,0 +1,382 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Tinybird CLI
4
+ * Commands for building and deploying Tinybird projects
5
+ */
6
+
7
+ import { readFileSync } from "node:fs";
8
+ import { fileURLToPath } from "node:url";
9
+ import { dirname, resolve } from "node:path";
10
+ import { Command } from "commander";
11
+ import { runInit } from "./commands/init.js";
12
+ import { runBuild } from "./commands/build.js";
13
+ import { runDev } from "./commands/dev.js";
14
+ import { runLogin } from "./commands/login.js";
15
+ import {
16
+ runBranchList,
17
+ runBranchStatus,
18
+ runBranchDelete,
19
+ } from "./commands/branch.js";
20
+
21
+ const __dirname = dirname(fileURLToPath(import.meta.url));
22
+ const packageJson = JSON.parse(
23
+ readFileSync(resolve(__dirname, "../../package.json"), "utf-8")
24
+ ) as { version: string };
25
+ const VERSION = packageJson.version;
26
+
27
+ /**
28
+ * Format timestamp for console output
29
+ */
30
+ function formatTime(): string {
31
+ return new Date().toLocaleTimeString("en-US", { hour12: false });
32
+ }
33
+
34
+ /**
35
+ * Create and configure the CLI
36
+ */
37
+ function createCli(): Command {
38
+ const program = new Command();
39
+
40
+ program
41
+ .name("tinybird")
42
+ .description("Tinybird TypeScript SDK CLI")
43
+ .version(VERSION);
44
+
45
+ // Init command
46
+ program
47
+ .command("init")
48
+ .description("Initialize a new Tinybird TypeScript project")
49
+ .option("-f, --force", "Overwrite existing files")
50
+ .option("--skip-login", "Skip browser login flow")
51
+ .action(async (options) => {
52
+ console.log("Initializing Tinybird project...\n");
53
+
54
+ const result = await runInit({
55
+ force: options.force,
56
+ skipLogin: options.skipLogin,
57
+ });
58
+
59
+ if (!result.success) {
60
+ console.error(`Error: ${result.error}`);
61
+ process.exit(1);
62
+ }
63
+
64
+ if (result.created.length > 0) {
65
+ console.log("Created:");
66
+ result.created.forEach((file) => {
67
+ console.log(` - ${file}`);
68
+ });
69
+ }
70
+
71
+ if (result.skipped.length > 0) {
72
+ console.log("\nSkipped (already exists):");
73
+ result.skipped.forEach((file) => {
74
+ console.log(` - ${file}`);
75
+ });
76
+ }
77
+
78
+ if (result.loggedIn) {
79
+ console.log(`\nLogged in successfully!`);
80
+ if (result.workspaceName) {
81
+ console.log(` Workspace: ${result.workspaceName}`);
82
+ }
83
+ if (result.userEmail) {
84
+ console.log(` User: ${result.userEmail}`);
85
+ }
86
+ console.log("\nDone! Next steps:");
87
+ console.log(" 1. Edit src/tinybird/schema.ts with your schema");
88
+ console.log(" 2. Run 'npx tinybird dev' to start development");
89
+ } else if (result.loggedIn === false) {
90
+ console.log("\nLogin was skipped or failed.");
91
+ console.log("\nDone! Next steps:");
92
+ console.log(" 1. Run 'npx tinybird login' to authenticate");
93
+ console.log(" 2. Edit src/tinybird/schema.ts with your schema");
94
+ console.log(" 3. Run 'npx tinybird dev' to start development");
95
+ } else {
96
+ console.log("\nDone! Next steps:");
97
+ console.log(" 1. Edit src/tinybird/schema.ts with your schema");
98
+ console.log(" 2. Run 'npx tinybird dev' to start development");
99
+ }
100
+ });
101
+
102
+ // Login command
103
+ program
104
+ .command("login")
105
+ .description("Authenticate with Tinybird via browser")
106
+ .action(async () => {
107
+ console.log("Starting authentication...\n");
108
+
109
+ const result = await runLogin();
110
+
111
+ if (!result.success) {
112
+ console.error(`Error: ${result.error}`);
113
+ process.exit(1);
114
+ }
115
+
116
+ console.log("\nAuthentication successful!");
117
+ if (result.workspaceName) {
118
+ console.log(` Workspace: ${result.workspaceName}`);
119
+ }
120
+ if (result.userEmail) {
121
+ console.log(` User: ${result.userEmail}`);
122
+ }
123
+ if (result.baseUrl) {
124
+ console.log(` API Host: ${result.baseUrl}`);
125
+ }
126
+ });
127
+
128
+ // Build command
129
+ program
130
+ .command("build")
131
+ .description("Build and push resources to Tinybird")
132
+ .option("--dry-run", "Generate without pushing to API")
133
+ .option("--debug", "Show debug output including API requests/responses")
134
+ .action(async (options) => {
135
+ if (options.debug) {
136
+ process.env.TINYBIRD_DEBUG = "1";
137
+ }
138
+ console.log(`[${formatTime()}] Building...\n`);
139
+
140
+ const result = await runBuild({
141
+ dryRun: options.dryRun,
142
+ });
143
+
144
+ if (!result.success) {
145
+ console.error(`Error: ${result.error}`);
146
+ process.exit(1);
147
+ }
148
+
149
+ const { build, deploy } = result;
150
+
151
+ if (build) {
152
+ console.log(`Generated ${build.stats.datasourceCount} datasource(s), ${build.stats.pipeCount} pipe(s)`);
153
+ }
154
+
155
+ if (options.dryRun) {
156
+ console.log("\n[Dry run] Resources not deployed to API");
157
+
158
+ // Show generated content
159
+ if (build) {
160
+ console.log("\n--- Generated Datasources ---");
161
+ build.resources.datasources.forEach((ds) => {
162
+ console.log(`\n${ds.name}.datasource:`);
163
+ console.log(ds.content);
164
+ });
165
+
166
+ console.log("\n--- Generated Pipes ---");
167
+ build.resources.pipes.forEach((pipe) => {
168
+ console.log(`\n${pipe.name}.pipe:`);
169
+ console.log(pipe.content);
170
+ });
171
+ }
172
+ } else if (deploy) {
173
+ if (deploy.result === "no_changes") {
174
+ console.log("No changes detected - already up to date");
175
+ } else {
176
+ console.log(`Deployed to Tinybird successfully`);
177
+ }
178
+ }
179
+
180
+ console.log(`\n[${formatTime()}] Done in ${result.durationMs}ms`);
181
+ });
182
+
183
+ // Dev command
184
+ program
185
+ .command("dev")
186
+ .description("Watch for changes and sync with Tinybird")
187
+ .action(async () => {
188
+ console.log(`tinybird dev v${VERSION}`);
189
+ console.log("Loading config from tinybird.json...\n");
190
+
191
+ try {
192
+ const controller = await runDev({
193
+ onLoginComplete: (info) => {
194
+ console.log("\nAuthentication successful!");
195
+ if (info.workspaceName) {
196
+ console.log(` Workspace: ${info.workspaceName}`);
197
+ }
198
+ if (info.userEmail) {
199
+ console.log(` User: ${info.userEmail}`);
200
+ }
201
+ console.log("");
202
+ },
203
+ onBranchReady: (info) => {
204
+ if (info.isMainBranch) {
205
+ console.log("On main branch - deploying to workspace\n");
206
+ } else if (info.gitBranch) {
207
+ const tinybirdName = info.tinybirdBranch?.name ?? info.gitBranch;
208
+ if (info.wasCreated) {
209
+ console.log(`Detected git branch: ${info.gitBranch}`);
210
+ console.log(`Creating Tinybird branch '${tinybirdName}'...`);
211
+ console.log("Branch created and token cached.\n");
212
+ } else {
213
+ console.log(`Detected git branch: ${info.gitBranch}`);
214
+ console.log(`Using existing Tinybird branch '${tinybirdName}'\n`);
215
+ }
216
+ } else {
217
+ console.log("Not in a git repository - deploying to workspace\n");
218
+ }
219
+ },
220
+ onBuildStart: () => {
221
+ console.log(`[${formatTime()}] Building...`);
222
+ },
223
+ onBuildComplete: (result) => {
224
+ if (!result.success) {
225
+ console.error(`[${formatTime()}] Build failed: ${result.error}`);
226
+ return;
227
+ }
228
+
229
+ const { deploy } = result;
230
+
231
+ if (deploy) {
232
+ if (deploy.result === "no_changes") {
233
+ console.log(`[${formatTime()}] No changes detected`);
234
+ } else {
235
+ console.log(`[${formatTime()}] Built in ${result.durationMs}ms`);
236
+
237
+ // Show datasource changes
238
+ if (deploy.datasources) {
239
+ for (const name of deploy.datasources.created) {
240
+ console.log(` + datasource ${name} (created)`);
241
+ }
242
+ for (const name of deploy.datasources.changed) {
243
+ console.log(` ~ datasource ${name} (changed)`);
244
+ }
245
+ for (const name of deploy.datasources.deleted) {
246
+ console.log(` - datasource ${name} (deleted)`);
247
+ }
248
+ }
249
+
250
+ // Show pipe changes
251
+ if (deploy.pipes) {
252
+ for (const name of deploy.pipes.created) {
253
+ console.log(` + pipe ${name} (created)`);
254
+ }
255
+ for (const name of deploy.pipes.changed) {
256
+ console.log(` ~ pipe ${name} (changed)`);
257
+ }
258
+ for (const name of deploy.pipes.deleted) {
259
+ console.log(` - pipe ${name} (deleted)`);
260
+ }
261
+ }
262
+ }
263
+ }
264
+ },
265
+ onSchemaValidation: (validation) => {
266
+ if (validation.issues.length > 0) {
267
+ console.log(`[${formatTime()}] Schema validation:`);
268
+ for (const issue of validation.issues) {
269
+ if (issue.type === "error") {
270
+ console.error(` ERROR [${issue.pipeName}]: ${issue.message}`);
271
+ } else {
272
+ console.warn(` WARN [${issue.pipeName}]: ${issue.message}`);
273
+ }
274
+ }
275
+ }
276
+ },
277
+ onError: (error) => {
278
+ console.error(`[${formatTime()}] Error: ${error.message}`);
279
+ },
280
+ });
281
+
282
+ console.log("Watching for changes... (Ctrl+C to stop)\n");
283
+
284
+ // Handle shutdown
285
+ const shutdown = async () => {
286
+ console.log("\nShutting down...");
287
+ await controller.stop();
288
+ process.exit(0);
289
+ };
290
+
291
+ process.on("SIGINT", shutdown);
292
+ process.on("SIGTERM", shutdown);
293
+
294
+ // Keep process alive
295
+ await new Promise(() => {});
296
+ } catch (error) {
297
+ console.error(`Error: ${(error as Error).message}`);
298
+ process.exit(1);
299
+ }
300
+ });
301
+
302
+ // Branch command
303
+ const branchCommand = new Command("branch")
304
+ .description("Manage Tinybird branches");
305
+
306
+ branchCommand
307
+ .command("list")
308
+ .description("List all Tinybird branches")
309
+ .action(async () => {
310
+ const result = await runBranchList();
311
+
312
+ if (!result.success) {
313
+ console.error(`Error: ${result.error}`);
314
+ process.exit(1);
315
+ }
316
+
317
+ if (!result.branches || result.branches.length === 0) {
318
+ console.log("No branches found.");
319
+ return;
320
+ }
321
+
322
+ console.log("Branches:");
323
+ result.branches.forEach((branch) => {
324
+ console.log(` - ${branch.name} (created: ${branch.created_at})`);
325
+ });
326
+ });
327
+
328
+ branchCommand
329
+ .command("status")
330
+ .description("Show current branch status")
331
+ .action(async () => {
332
+ const result = await runBranchStatus();
333
+
334
+ if (!result.success) {
335
+ console.error(`Error: ${result.error}`);
336
+ process.exit(1);
337
+ }
338
+
339
+ console.log("Branch Status:");
340
+ console.log(` Git branch: ${result.gitBranch ?? "(not in git repo)"}`);
341
+ if (result.tinybirdBranchName && result.tinybirdBranchName !== result.gitBranch) {
342
+ console.log(` Tinybird branch name: ${result.tinybirdBranchName} (sanitized)`);
343
+ }
344
+ console.log(` Main branch: ${result.isMainBranch ? "yes" : "no"}`);
345
+
346
+ if (result.tinybirdBranch) {
347
+ console.log(` Tinybird branch: ${result.tinybirdBranch.name}`);
348
+ console.log(` Branch ID: ${result.tinybirdBranch.id}`);
349
+ console.log(` Created: ${result.tinybirdBranch.created_at}`);
350
+ } else if (!result.isMainBranch && result.tinybirdBranchName) {
351
+ console.log(" Tinybird branch: not created yet");
352
+ console.log(" (Run 'npx tinybird dev' to create it)");
353
+ }
354
+
355
+ console.log(` Cached token: ${result.hasCachedToken ? "yes" : "no"}`);
356
+ });
357
+
358
+ branchCommand
359
+ .command("delete")
360
+ .description("Delete a Tinybird branch")
361
+ .argument("<name>", "Branch name to delete")
362
+ .action(async (name) => {
363
+ console.log(`Deleting branch '${name}'...`);
364
+
365
+ const result = await runBranchDelete(name);
366
+
367
+ if (!result.success) {
368
+ console.error(`Error: ${result.error}`);
369
+ process.exit(1);
370
+ }
371
+
372
+ console.log(`Branch '${name}' deleted successfully.`);
373
+ });
374
+
375
+ program.addCommand(branchCommand);
376
+
377
+ return program;
378
+ }
379
+
380
+ // Run CLI
381
+ const program = createCli();
382
+ program.parse();
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Tests for schema validation utility
3
+ */
4
+
5
+ import { describe, it, expect } from "vitest";
6
+ import {
7
+ _typesAreCompatible,
8
+ _validateOutputSchema,
9
+ _hasRequiredParams,
10
+ _buildDefaultParams,
11
+ } from "./schema-validation.js";
12
+ import type { PipeDefinition } from "../../schema/pipe.js";
13
+ import type { ColumnMeta } from "../../client/types.js";
14
+
15
+ describe("typesAreCompatible", () => {
16
+ it("matches identical types", () => {
17
+ expect(_typesAreCompatible("String", "String")).toBe(true);
18
+ expect(_typesAreCompatible("Int64", "Int64")).toBe(true);
19
+ expect(_typesAreCompatible("UInt64", "UInt64")).toBe(true);
20
+ });
21
+
22
+ it("matches Nullable wrapped types", () => {
23
+ expect(_typesAreCompatible("Nullable(String)", "String")).toBe(true);
24
+ expect(_typesAreCompatible("Nullable(Int64)", "Int64")).toBe(true);
25
+ });
26
+
27
+ it("matches LowCardinality wrapped types", () => {
28
+ expect(_typesAreCompatible("LowCardinality(String)", "String")).toBe(true);
29
+ });
30
+
31
+ it("matches LowCardinality(Nullable(...)) wrapped types", () => {
32
+ expect(
33
+ _typesAreCompatible("LowCardinality(Nullable(String))", "String")
34
+ ).toBe(true);
35
+ });
36
+
37
+ it("matches DateTime with timezone to base DateTime", () => {
38
+ expect(_typesAreCompatible("DateTime('UTC')", "DateTime")).toBe(true);
39
+ expect(_typesAreCompatible("DateTime('Europe/Madrid')", "DateTime")).toBe(
40
+ true
41
+ );
42
+ });
43
+
44
+ it("matches DateTime64 with precision", () => {
45
+ expect(_typesAreCompatible("DateTime64(3)", "DateTime64")).toBe(true);
46
+ expect(_typesAreCompatible("DateTime64(6, 'UTC')", "DateTime64")).toBe(
47
+ true
48
+ );
49
+ });
50
+
51
+ it("rejects mismatched types", () => {
52
+ expect(_typesAreCompatible("String", "Int64")).toBe(false);
53
+ expect(_typesAreCompatible("UInt64", "Int64")).toBe(false);
54
+ expect(_typesAreCompatible("DateTime", "Date")).toBe(false);
55
+ });
56
+ });
57
+
58
+ describe("validateOutputSchema", () => {
59
+ it("validates matching schema", () => {
60
+ const responseMeta: ColumnMeta[] = [
61
+ { name: "id", type: "UInt64" },
62
+ { name: "name", type: "String" },
63
+ ];
64
+
65
+ const outputSchema = {
66
+ id: { _tinybirdType: "UInt64" },
67
+ name: { _tinybirdType: "String" },
68
+ };
69
+
70
+ const result = _validateOutputSchema(responseMeta, outputSchema as any);
71
+
72
+ expect(result.valid).toBe(true);
73
+ expect(result.missingColumns).toHaveLength(0);
74
+ expect(result.extraColumns).toHaveLength(0);
75
+ expect(result.typeMismatches).toHaveLength(0);
76
+ });
77
+
78
+ it("detects missing columns", () => {
79
+ const responseMeta: ColumnMeta[] = [{ name: "id", type: "UInt64" }];
80
+
81
+ const outputSchema = {
82
+ id: { _tinybirdType: "UInt64" },
83
+ name: { _tinybirdType: "String" },
84
+ };
85
+
86
+ const result = _validateOutputSchema(responseMeta, outputSchema as any);
87
+
88
+ expect(result.valid).toBe(false);
89
+ expect(result.missingColumns).toHaveLength(1);
90
+ expect(result.missingColumns[0]).toEqual({
91
+ name: "name",
92
+ expectedType: "String",
93
+ });
94
+ });
95
+
96
+ it("detects extra columns", () => {
97
+ const responseMeta: ColumnMeta[] = [
98
+ { name: "id", type: "UInt64" },
99
+ { name: "extra", type: "String" },
100
+ ];
101
+
102
+ const outputSchema = {
103
+ id: { _tinybirdType: "UInt64" },
104
+ };
105
+
106
+ const result = _validateOutputSchema(responseMeta, outputSchema as any);
107
+
108
+ // Extra columns are warnings, not errors
109
+ expect(result.valid).toBe(true);
110
+ expect(result.extraColumns).toHaveLength(1);
111
+ expect(result.extraColumns[0]).toEqual({
112
+ name: "extra",
113
+ actualType: "String",
114
+ });
115
+ });
116
+
117
+ it("detects type mismatches", () => {
118
+ const responseMeta: ColumnMeta[] = [{ name: "count", type: "Int64" }];
119
+
120
+ const outputSchema = {
121
+ count: { _tinybirdType: "UInt64" },
122
+ };
123
+
124
+ const result = _validateOutputSchema(responseMeta, outputSchema as any);
125
+
126
+ expect(result.valid).toBe(false);
127
+ expect(result.typeMismatches).toHaveLength(1);
128
+ expect(result.typeMismatches[0]).toEqual({
129
+ name: "count",
130
+ expectedType: "UInt64",
131
+ actualType: "Int64",
132
+ });
133
+ });
134
+
135
+ it("handles Nullable types as compatible", () => {
136
+ const responseMeta: ColumnMeta[] = [
137
+ { name: "value", type: "Nullable(String)" },
138
+ ];
139
+
140
+ const outputSchema = {
141
+ value: { _tinybirdType: "String" },
142
+ };
143
+
144
+ const result = _validateOutputSchema(responseMeta, outputSchema as any);
145
+
146
+ expect(result.valid).toBe(true);
147
+ expect(result.typeMismatches).toHaveLength(0);
148
+ });
149
+ });
150
+
151
+ describe("hasRequiredParams", () => {
152
+ it("returns false for pipe with no params", () => {
153
+ const pipe = { _name: "test" } as PipeDefinition;
154
+ expect(_hasRequiredParams(pipe)).toBe(false);
155
+ });
156
+
157
+ it("returns false for pipe with only optional params", () => {
158
+ const pipe = {
159
+ _name: "test",
160
+ _params: {
161
+ limit: { _required: false, _default: 10 },
162
+ },
163
+ } as unknown as PipeDefinition;
164
+ expect(_hasRequiredParams(pipe)).toBe(false);
165
+ });
166
+
167
+ it("returns true for pipe with required param without default", () => {
168
+ const pipe = {
169
+ _name: "test",
170
+ _params: {
171
+ start_date: { _required: true, _default: undefined },
172
+ },
173
+ } as unknown as PipeDefinition;
174
+ expect(_hasRequiredParams(pipe)).toBe(true);
175
+ });
176
+
177
+ it("returns false for required param with default value", () => {
178
+ const pipe = {
179
+ _name: "test",
180
+ _params: {
181
+ start_date: { _required: true, _default: "2024-01-01" },
182
+ },
183
+ } as unknown as PipeDefinition;
184
+ expect(_hasRequiredParams(pipe)).toBe(false);
185
+ });
186
+ });
187
+
188
+ describe("buildDefaultParams", () => {
189
+ it("returns empty object for pipe with no params", () => {
190
+ const pipe = { _name: "test" } as PipeDefinition;
191
+ expect(_buildDefaultParams(pipe)).toEqual({});
192
+ });
193
+
194
+ it("includes params with default values", () => {
195
+ const pipe = {
196
+ _name: "test",
197
+ _params: {
198
+ limit: { _required: false, _default: 10 },
199
+ offset: { _required: false, _default: 0 },
200
+ },
201
+ } as unknown as PipeDefinition;
202
+
203
+ expect(_buildDefaultParams(pipe)).toEqual({
204
+ limit: 10,
205
+ offset: 0,
206
+ });
207
+ });
208
+
209
+ it("excludes params without default values", () => {
210
+ const pipe = {
211
+ _name: "test",
212
+ _params: {
213
+ limit: { _required: false, _default: 10 },
214
+ start_date: { _required: true, _default: undefined },
215
+ },
216
+ } as unknown as PipeDefinition;
217
+
218
+ expect(_buildDefaultParams(pipe)).toEqual({
219
+ limit: 10,
220
+ });
221
+ });
222
+ });