@tinybirdco/sdk 0.0.4 → 0.0.7

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 (165) hide show
  1. package/README.md +52 -13
  2. package/dist/api/branches.d.ts.map +1 -1
  3. package/dist/api/branches.js +6 -5
  4. package/dist/api/branches.js.map +1 -1
  5. package/dist/api/branches.test.js +32 -6
  6. package/dist/api/branches.test.js.map +1 -1
  7. package/dist/api/build.d.ts.map +1 -1
  8. package/dist/api/build.js +2 -1
  9. package/dist/api/build.js.map +1 -1
  10. package/dist/api/deploy.d.ts +42 -3
  11. package/dist/api/deploy.d.ts.map +1 -1
  12. package/dist/api/deploy.js +162 -19
  13. package/dist/api/deploy.js.map +1 -1
  14. package/dist/api/deploy.test.js +83 -31
  15. package/dist/api/deploy.test.js.map +1 -1
  16. package/dist/api/fetcher.d.ts +6 -0
  17. package/dist/api/fetcher.d.ts.map +1 -0
  18. package/dist/api/fetcher.js +13 -0
  19. package/dist/api/fetcher.js.map +1 -0
  20. package/dist/api/local.d.ts.map +1 -1
  21. package/dist/api/local.js +5 -4
  22. package/dist/api/local.js.map +1 -1
  23. package/dist/api/local.test.js.map +1 -1
  24. package/dist/api/resources.d.ts +178 -0
  25. package/dist/api/resources.d.ts.map +1 -0
  26. package/dist/api/resources.js +245 -0
  27. package/dist/api/resources.js.map +1 -0
  28. package/dist/api/resources.test.d.ts +2 -0
  29. package/dist/api/resources.test.d.ts.map +1 -0
  30. package/dist/api/resources.test.js +255 -0
  31. package/dist/api/resources.test.js.map +1 -0
  32. package/dist/api/workspaces.d.ts.map +1 -1
  33. package/dist/api/workspaces.js +2 -1
  34. package/dist/api/workspaces.js.map +1 -1
  35. package/dist/api/workspaces.test.js +9 -1
  36. package/dist/api/workspaces.test.js.map +1 -1
  37. package/dist/cli/auth.d.ts.map +1 -1
  38. package/dist/cli/auth.js +2 -1
  39. package/dist/cli/auth.js.map +1 -1
  40. package/dist/cli/commands/build.d.ts +3 -4
  41. package/dist/cli/commands/build.d.ts.map +1 -1
  42. package/dist/cli/commands/build.js +23 -25
  43. package/dist/cli/commands/build.js.map +1 -1
  44. package/dist/cli/commands/deploy.d.ts +41 -0
  45. package/dist/cli/commands/deploy.d.ts.map +1 -0
  46. package/dist/cli/commands/deploy.js +92 -0
  47. package/dist/cli/commands/deploy.js.map +1 -0
  48. package/dist/cli/commands/dev.d.ts.map +1 -1
  49. package/dist/cli/commands/dev.js +7 -3
  50. package/dist/cli/commands/dev.js.map +1 -1
  51. package/dist/cli/commands/init.d.ts +38 -1
  52. package/dist/cli/commands/init.d.ts.map +1 -1
  53. package/dist/cli/commands/init.js +434 -23
  54. package/dist/cli/commands/init.js.map +1 -1
  55. package/dist/cli/commands/init.test.js +190 -30
  56. package/dist/cli/commands/init.test.js.map +1 -1
  57. package/dist/cli/index.js +80 -15
  58. package/dist/cli/index.js.map +1 -1
  59. package/dist/cli/utils/package-manager.d.ts +8 -0
  60. package/dist/cli/utils/package-manager.d.ts.map +1 -0
  61. package/dist/cli/utils/package-manager.js +45 -0
  62. package/dist/cli/utils/package-manager.js.map +1 -0
  63. package/dist/cli/utils/package-manager.test.d.ts +2 -0
  64. package/dist/cli/utils/package-manager.test.d.ts.map +1 -0
  65. package/dist/cli/utils/package-manager.test.js +85 -0
  66. package/dist/cli/utils/package-manager.test.js.map +1 -0
  67. package/dist/client/base.d.ts.map +1 -1
  68. package/dist/client/base.js +2 -1
  69. package/dist/client/base.js.map +1 -1
  70. package/dist/codegen/index.d.ts +39 -0
  71. package/dist/codegen/index.d.ts.map +1 -0
  72. package/dist/codegen/index.js +300 -0
  73. package/dist/codegen/index.js.map +1 -0
  74. package/dist/codegen/index.test.d.ts +2 -0
  75. package/dist/codegen/index.test.d.ts.map +1 -0
  76. package/dist/codegen/index.test.js +310 -0
  77. package/dist/codegen/index.test.js.map +1 -0
  78. package/dist/codegen/type-mapper.d.ts +20 -0
  79. package/dist/codegen/type-mapper.d.ts.map +1 -0
  80. package/dist/codegen/type-mapper.js +238 -0
  81. package/dist/codegen/type-mapper.js.map +1 -0
  82. package/dist/codegen/type-mapper.test.d.ts +2 -0
  83. package/dist/codegen/type-mapper.test.d.ts.map +1 -0
  84. package/dist/codegen/type-mapper.test.js +167 -0
  85. package/dist/codegen/type-mapper.test.js.map +1 -0
  86. package/dist/codegen/utils.d.ts +46 -0
  87. package/dist/codegen/utils.d.ts.map +1 -0
  88. package/dist/codegen/utils.js +141 -0
  89. package/dist/codegen/utils.js.map +1 -0
  90. package/dist/codegen/utils.test.d.ts +2 -0
  91. package/dist/codegen/utils.test.d.ts.map +1 -0
  92. package/dist/codegen/utils.test.js +178 -0
  93. package/dist/codegen/utils.test.js.map +1 -0
  94. package/dist/generator/index.d.ts +3 -0
  95. package/dist/generator/index.d.ts.map +1 -1
  96. package/dist/generator/index.js +17 -1
  97. package/dist/generator/index.js.map +1 -1
  98. package/dist/generator/index.test.js +104 -1
  99. package/dist/generator/index.test.js.map +1 -1
  100. package/dist/generator/loader.d.ts +15 -0
  101. package/dist/generator/loader.d.ts.map +1 -1
  102. package/dist/generator/loader.js +24 -0
  103. package/dist/generator/loader.js.map +1 -1
  104. package/dist/schema/connection.d.ts.map +1 -1
  105. package/dist/schema/connection.js +3 -2
  106. package/dist/schema/connection.js.map +1 -1
  107. package/dist/schema/datasource.d.ts.map +1 -1
  108. package/dist/schema/datasource.js +3 -2
  109. package/dist/schema/datasource.js.map +1 -1
  110. package/dist/schema/params.d.ts.map +1 -1
  111. package/dist/schema/params.js +3 -2
  112. package/dist/schema/params.js.map +1 -1
  113. package/dist/schema/pipe.d.ts +2 -2
  114. package/dist/schema/pipe.d.ts.map +1 -1
  115. package/dist/schema/pipe.js +4 -4
  116. package/dist/schema/pipe.js.map +1 -1
  117. package/dist/schema/project.d.ts.map +1 -1
  118. package/dist/schema/project.js +3 -2
  119. package/dist/schema/project.js.map +1 -1
  120. package/dist/schema/types.d.ts.map +1 -1
  121. package/dist/schema/types.js +3 -2
  122. package/dist/schema/types.js.map +1 -1
  123. package/dist/test/handlers.d.ts +49 -0
  124. package/dist/test/handlers.d.ts.map +1 -1
  125. package/dist/test/handlers.js +45 -0
  126. package/dist/test/handlers.js.map +1 -1
  127. package/package.json +4 -2
  128. package/src/api/branches.test.ts +65 -57
  129. package/src/api/branches.ts +7 -5
  130. package/src/api/build.ts +2 -1
  131. package/src/api/deploy.test.ts +141 -36
  132. package/src/api/deploy.ts +231 -23
  133. package/src/api/fetcher.ts +17 -0
  134. package/src/api/local.test.ts +43 -31
  135. package/src/api/local.ts +5 -4
  136. package/src/api/resources.test.ts +332 -0
  137. package/src/api/resources.ts +555 -0
  138. package/src/api/workspaces.test.ts +15 -9
  139. package/src/api/workspaces.ts +3 -1
  140. package/src/cli/auth.ts +2 -1
  141. package/src/cli/commands/build.ts +29 -33
  142. package/src/cli/commands/deploy.ts +131 -0
  143. package/src/cli/commands/dev.ts +10 -3
  144. package/src/cli/commands/init.test.ts +239 -30
  145. package/src/cli/commands/init.ts +548 -26
  146. package/src/cli/index.ts +117 -20
  147. package/src/cli/utils/package-manager.test.ts +118 -0
  148. package/src/cli/utils/package-manager.ts +44 -0
  149. package/src/client/base.ts +3 -2
  150. package/src/codegen/index.test.ts +367 -0
  151. package/src/codegen/index.ts +379 -0
  152. package/src/codegen/type-mapper.test.ts +224 -0
  153. package/src/codegen/type-mapper.ts +265 -0
  154. package/src/codegen/utils.test.ts +221 -0
  155. package/src/codegen/utils.ts +174 -0
  156. package/src/generator/index.test.ts +121 -1
  157. package/src/generator/index.ts +19 -1
  158. package/src/generator/loader.ts +43 -0
  159. package/src/schema/connection.ts +3 -2
  160. package/src/schema/datasource.ts +3 -2
  161. package/src/schema/params.ts +3 -2
  162. package/src/schema/pipe.ts +4 -4
  163. package/src/schema/project.ts +3 -2
  164. package/src/schema/types.ts +3 -2
  165. package/src/test/handlers.ts +58 -0
@@ -0,0 +1,379 @@
1
+ /**
2
+ * Code generator for converting Tinybird API resources to TypeScript SDK code
3
+ */
4
+
5
+ import type { DatasourceInfo, PipeInfo } from "../api/resources.js";
6
+ import { clickhouseTypeToValidator, paramTypeToValidator } from "./type-mapper.js";
7
+ import {
8
+ toCamelCase,
9
+ toPascalCase,
10
+ escapeString,
11
+ generateEngineCode,
12
+ formatSqlForTemplate,
13
+ } from "./utils.js";
14
+
15
+ /**
16
+ * Generate TypeScript code for a single datasource
17
+ */
18
+ export function generateDatasourceCode(ds: DatasourceInfo): string {
19
+ const varName = toCamelCase(ds.name);
20
+ const typeName = toPascalCase(ds.name);
21
+ const lines: string[] = [];
22
+
23
+ // JSDoc comment
24
+ if (ds.description) {
25
+ lines.push("/**");
26
+ lines.push(` * ${ds.description}`);
27
+ lines.push(" */");
28
+ }
29
+
30
+ lines.push(`export const ${varName} = defineDatasource("${ds.name}", {`);
31
+
32
+ if (ds.description) {
33
+ lines.push(` description: "${escapeString(ds.description)}",`);
34
+ }
35
+
36
+ // Schema
37
+ lines.push(" schema: {");
38
+ for (const col of ds.columns) {
39
+ const validator = clickhouseTypeToValidator(col.type);
40
+ lines.push(` ${col.name}: ${validator},`);
41
+ }
42
+ lines.push(" },");
43
+
44
+ // Engine
45
+ const engineCode = generateEngineCode(ds.engine);
46
+ lines.push(` engine: ${engineCode},`);
47
+
48
+ lines.push("});");
49
+ lines.push("");
50
+ lines.push(`export type ${typeName}Row = InferRow<typeof ${varName}>;`);
51
+
52
+ return lines.join("\n");
53
+ }
54
+
55
+ /**
56
+ * Generate TypeScript code for a single pipe
57
+ */
58
+ export function generatePipeCode(pipe: PipeInfo): string {
59
+ const varName = toCamelCase(pipe.name);
60
+ const typeName = toPascalCase(pipe.name);
61
+ const lines: string[] = [];
62
+
63
+ // Determine which define function to use
64
+ let defineFunc = "definePipe";
65
+ if (pipe.type === "endpoint") {
66
+ defineFunc = "defineEndpoint";
67
+ } else if (pipe.type === "materialized") {
68
+ defineFunc = "defineMaterializedView";
69
+ } else if (pipe.type === "copy") {
70
+ defineFunc = "defineCopyPipe";
71
+ }
72
+
73
+ // JSDoc comment
74
+ if (pipe.description) {
75
+ lines.push("/**");
76
+ lines.push(` * ${pipe.description}`);
77
+ lines.push(" */");
78
+ }
79
+
80
+ lines.push(`export const ${varName} = ${defineFunc}("${pipe.name}", {`);
81
+
82
+ if (pipe.description) {
83
+ lines.push(` description: "${escapeString(pipe.description)}",`);
84
+ }
85
+
86
+ // For materialized views and copy pipes, add datasource first
87
+ if (pipe.type === "materialized" && pipe.materialized) {
88
+ const dsVarName = toCamelCase(pipe.materialized.datasource);
89
+ lines.push(` datasource: ${dsVarName},`);
90
+ } else if (pipe.type === "copy" && pipe.copy) {
91
+ const dsVarName = toCamelCase(pipe.copy.target_datasource);
92
+ lines.push(` datasource: ${dsVarName},`);
93
+ if (pipe.copy.copy_schedule) {
94
+ lines.push(` copy_schedule: "${pipe.copy.copy_schedule}",`);
95
+ }
96
+ if (pipe.copy.copy_mode) {
97
+ lines.push(` copy_mode: "${pipe.copy.copy_mode}",`);
98
+ }
99
+ }
100
+
101
+ // Params (for endpoints and regular pipes with params)
102
+ if (pipe.params.length > 0 && pipe.type !== "materialized" && pipe.type !== "copy") {
103
+ lines.push(" params: {");
104
+ for (const param of pipe.params) {
105
+ const validator = paramTypeToValidator(param.type, param.default, param.required);
106
+ if (param.description) {
107
+ lines.push(` ${param.name}: ${validator}.describe("${escapeString(param.description)}"),`);
108
+ } else {
109
+ lines.push(` ${param.name}: ${validator},`);
110
+ }
111
+ }
112
+ lines.push(" },");
113
+ }
114
+
115
+ // Nodes
116
+ lines.push(" nodes: [");
117
+ for (const node of pipe.nodes) {
118
+ lines.push(" node({");
119
+ lines.push(` name: "${node.name}",`);
120
+ const formattedSql = formatSqlForTemplate(node.sql);
121
+ lines.push(` sql: \`${formattedSql}\`,`);
122
+ lines.push(" }),");
123
+ }
124
+ lines.push(" ],");
125
+
126
+ // Output (for endpoints)
127
+ if (pipe.type === "endpoint" && pipe.output_columns.length > 0) {
128
+ lines.push(" output: {");
129
+ for (const col of pipe.output_columns) {
130
+ const validator = clickhouseTypeToValidator(col.type);
131
+ lines.push(` ${col.name}: ${validator},`);
132
+ }
133
+ lines.push(" },");
134
+ }
135
+
136
+ lines.push("});");
137
+
138
+ // Type exports for endpoints
139
+ if (pipe.type === "endpoint") {
140
+ lines.push("");
141
+ lines.push(`export type ${typeName}Params = InferParams<typeof ${varName}>;`);
142
+ lines.push(`export type ${typeName}Output = InferOutputRow<typeof ${varName}>;`);
143
+ }
144
+
145
+ return lines.join("\n");
146
+ }
147
+
148
+ /**
149
+ * Generate the complete datasources.ts file content
150
+ */
151
+ export function generateDatasourcesFile(datasources: DatasourceInfo[]): string {
152
+ if (datasources.length === 0) {
153
+ return `import { defineDatasource, t, engine, type InferRow } from "@tinybirdco/sdk";
154
+
155
+ // No datasources found in workspace
156
+ `;
157
+ }
158
+
159
+ const imports = [
160
+ 'import { defineDatasource, t, engine, type InferRow } from "@tinybirdco/sdk";',
161
+ "",
162
+ ];
163
+
164
+ const code = datasources.map((ds) => generateDatasourceCode(ds)).join("\n\n");
165
+
166
+ return imports.join("\n") + "\n" + code + "\n";
167
+ }
168
+
169
+ /**
170
+ * Generate the complete pipes.ts file content
171
+ */
172
+ export function generatePipesFile(
173
+ pipes: PipeInfo[],
174
+ datasources: DatasourceInfo[]
175
+ ): string {
176
+ if (pipes.length === 0) {
177
+ return `import { defineEndpoint, node, t, p, type InferParams, type InferOutputRow } from "@tinybirdco/sdk";
178
+
179
+ // No pipes found in workspace
180
+ `;
181
+ }
182
+
183
+ // Determine which imports are needed
184
+ const hasMaterialized = pipes.some((p) => p.type === "materialized");
185
+ const hasCopy = pipes.some((p) => p.type === "copy");
186
+ const hasEndpoint = pipes.some((p) => p.type === "endpoint");
187
+ const hasPlainPipe = pipes.some((p) => p.type === "pipe");
188
+ const hasParams = pipes.some((p) => p.params.length > 0 && p.type !== "materialized" && p.type !== "copy");
189
+
190
+ const sdkImports: string[] = ["node", "t"];
191
+ if (hasParams) {
192
+ sdkImports.push("p");
193
+ }
194
+ if (hasEndpoint) {
195
+ sdkImports.push("defineEndpoint", "type InferParams", "type InferOutputRow");
196
+ }
197
+ if (hasMaterialized) {
198
+ sdkImports.push("defineMaterializedView");
199
+ }
200
+ if (hasCopy) {
201
+ sdkImports.push("defineCopyPipe");
202
+ }
203
+ if (hasPlainPipe) {
204
+ sdkImports.push("definePipe");
205
+ }
206
+
207
+ const lines: string[] = [
208
+ `import { ${sdkImports.join(", ")} } from "@tinybirdco/sdk";`,
209
+ ];
210
+
211
+ // Import datasources referenced by materialized/copy pipes
212
+ const referencedDatasources = new Set<string>();
213
+ for (const pipe of pipes) {
214
+ if (pipe.materialized?.datasource) {
215
+ referencedDatasources.add(pipe.materialized.datasource);
216
+ }
217
+ if (pipe.copy?.target_datasource) {
218
+ referencedDatasources.add(pipe.copy.target_datasource);
219
+ }
220
+ }
221
+
222
+ if (referencedDatasources.size > 0) {
223
+ // Verify datasources exist
224
+ const existingDatasourceNames = new Set(datasources.map((ds) => ds.name));
225
+ const validReferences = Array.from(referencedDatasources).filter((name) =>
226
+ existingDatasourceNames.has(name)
227
+ );
228
+
229
+ if (validReferences.length > 0) {
230
+ const dsImports = validReferences.map((name) => toCamelCase(name)).join(", ");
231
+ lines.push(`import { ${dsImports} } from "./datasources.js";`);
232
+ }
233
+ }
234
+
235
+ lines.push("");
236
+
237
+ const code = pipes.map((p) => generatePipeCode(p)).join("\n\n");
238
+
239
+ return lines.join("\n") + code + "\n";
240
+ }
241
+
242
+ /**
243
+ * Generate the complete client.ts file content
244
+ */
245
+ export function generateClientFile(
246
+ datasources: DatasourceInfo[],
247
+ pipes: PipeInfo[]
248
+ ): string {
249
+ const lines: string[] = [
250
+ "/**",
251
+ " * Tinybird Client",
252
+ " *",
253
+ " * This file defines the typed Tinybird client for your project.",
254
+ " * Generated from existing workspace resources.",
255
+ " */",
256
+ "",
257
+ 'import { createTinybirdClient } from "@tinybirdco/sdk";',
258
+ "",
259
+ ];
260
+
261
+ // Import datasources
262
+ if (datasources.length > 0) {
263
+ const dsImports: string[] = [];
264
+ const typeImports: string[] = [];
265
+
266
+ for (const ds of datasources) {
267
+ const varName = toCamelCase(ds.name);
268
+ const typeName = toPascalCase(ds.name);
269
+ dsImports.push(varName);
270
+ typeImports.push(`type ${typeName}Row`);
271
+ }
272
+
273
+ lines.push(`import { ${dsImports.join(", ")}, ${typeImports.join(", ")} } from "./datasources.js";`);
274
+ }
275
+
276
+ // Import pipes (only endpoints are useful for the client)
277
+ const endpoints = pipes.filter((p) => p.type === "endpoint");
278
+ if (endpoints.length > 0) {
279
+ const pipeImports: string[] = [];
280
+ const typeImports: string[] = [];
281
+
282
+ for (const pipe of endpoints) {
283
+ const varName = toCamelCase(pipe.name);
284
+ const typeName = toPascalCase(pipe.name);
285
+ pipeImports.push(varName);
286
+ typeImports.push(`type ${typeName}Params`, `type ${typeName}Output`);
287
+ }
288
+
289
+ lines.push(`import { ${pipeImports.join(", ")}, ${typeImports.join(", ")} } from "./pipes.js";`);
290
+ }
291
+
292
+ lines.push("");
293
+
294
+ // Create client
295
+ lines.push("// Create the typed Tinybird client");
296
+ lines.push("export const tinybird = createTinybirdClient({");
297
+
298
+ if (datasources.length > 0) {
299
+ const dsNames = datasources.map((ds) => toCamelCase(ds.name)).join(", ");
300
+ lines.push(` datasources: { ${dsNames} },`);
301
+ } else {
302
+ lines.push(" datasources: {},");
303
+ }
304
+
305
+ if (endpoints.length > 0) {
306
+ const pipeNames = endpoints.map((p) => toCamelCase(p.name)).join(", ");
307
+ lines.push(` pipes: { ${pipeNames} },`);
308
+ } else {
309
+ lines.push(" pipes: {},");
310
+ }
311
+
312
+ lines.push("});");
313
+ lines.push("");
314
+
315
+ // Re-export types
316
+ if (datasources.length > 0 || endpoints.length > 0) {
317
+ lines.push("// Re-export types for convenience");
318
+ const typeExports: string[] = [];
319
+
320
+ for (const ds of datasources) {
321
+ const typeName = toPascalCase(ds.name);
322
+ typeExports.push(`${typeName}Row`);
323
+ }
324
+
325
+ for (const pipe of endpoints) {
326
+ const typeName = toPascalCase(pipe.name);
327
+ typeExports.push(`${typeName}Params`, `${typeName}Output`);
328
+ }
329
+
330
+ lines.push(`export type { ${typeExports.join(", ")} };`);
331
+ lines.push("");
332
+ }
333
+
334
+ // Re-export entities
335
+ if (datasources.length > 0 || endpoints.length > 0) {
336
+ lines.push("// Re-export entities");
337
+ const entityExports: string[] = [];
338
+
339
+ for (const ds of datasources) {
340
+ entityExports.push(toCamelCase(ds.name));
341
+ }
342
+
343
+ for (const pipe of endpoints) {
344
+ entityExports.push(toCamelCase(pipe.name));
345
+ }
346
+
347
+ lines.push(`export { ${entityExports.join(", ")} };`);
348
+ lines.push("");
349
+ }
350
+
351
+ return lines.join("\n");
352
+ }
353
+
354
+ /**
355
+ * Result of generating all files
356
+ */
357
+ export interface GeneratedFiles {
358
+ datasourcesContent: string;
359
+ pipesContent: string;
360
+ clientContent: string;
361
+ datasourceCount: number;
362
+ pipeCount: number;
363
+ }
364
+
365
+ /**
366
+ * Generate all TypeScript files from resources
367
+ */
368
+ export function generateAllFiles(
369
+ datasources: DatasourceInfo[],
370
+ pipes: PipeInfo[]
371
+ ): GeneratedFiles {
372
+ return {
373
+ datasourcesContent: generateDatasourcesFile(datasources),
374
+ pipesContent: generatePipesFile(pipes, datasources),
375
+ clientContent: generateClientFile(datasources, pipes),
376
+ datasourceCount: datasources.length,
377
+ pipeCount: pipes.length,
378
+ };
379
+ }
@@ -0,0 +1,224 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { clickhouseTypeToValidator, paramTypeToValidator } from "./type-mapper.js";
3
+
4
+ describe("clickhouseTypeToValidator", () => {
5
+ describe("simple types", () => {
6
+ it("maps String to t.string()", () => {
7
+ expect(clickhouseTypeToValidator("String")).toBe("t.string()");
8
+ });
9
+
10
+ it("maps UUID to t.uuid()", () => {
11
+ expect(clickhouseTypeToValidator("UUID")).toBe("t.uuid()");
12
+ });
13
+
14
+ it("maps integer types", () => {
15
+ expect(clickhouseTypeToValidator("Int8")).toBe("t.int8()");
16
+ expect(clickhouseTypeToValidator("Int16")).toBe("t.int16()");
17
+ expect(clickhouseTypeToValidator("Int32")).toBe("t.int32()");
18
+ expect(clickhouseTypeToValidator("Int64")).toBe("t.int64()");
19
+ expect(clickhouseTypeToValidator("Int128")).toBe("t.int128()");
20
+ expect(clickhouseTypeToValidator("Int256")).toBe("t.int256()");
21
+ });
22
+
23
+ it("maps unsigned integer types", () => {
24
+ expect(clickhouseTypeToValidator("UInt8")).toBe("t.uint8()");
25
+ expect(clickhouseTypeToValidator("UInt16")).toBe("t.uint16()");
26
+ expect(clickhouseTypeToValidator("UInt32")).toBe("t.uint32()");
27
+ expect(clickhouseTypeToValidator("UInt64")).toBe("t.uint64()");
28
+ expect(clickhouseTypeToValidator("UInt128")).toBe("t.uint128()");
29
+ expect(clickhouseTypeToValidator("UInt256")).toBe("t.uint256()");
30
+ });
31
+
32
+ it("maps float types", () => {
33
+ expect(clickhouseTypeToValidator("Float32")).toBe("t.float32()");
34
+ expect(clickhouseTypeToValidator("Float64")).toBe("t.float64()");
35
+ });
36
+
37
+ it("maps Bool to t.bool()", () => {
38
+ expect(clickhouseTypeToValidator("Bool")).toBe("t.bool()");
39
+ expect(clickhouseTypeToValidator("Boolean")).toBe("t.bool()");
40
+ });
41
+
42
+ it("maps date types", () => {
43
+ expect(clickhouseTypeToValidator("Date")).toBe("t.date()");
44
+ expect(clickhouseTypeToValidator("Date32")).toBe("t.date32()");
45
+ expect(clickhouseTypeToValidator("DateTime")).toBe("t.dateTime()");
46
+ });
47
+
48
+ it("maps JSON to t.json()", () => {
49
+ expect(clickhouseTypeToValidator("JSON")).toBe("t.json()");
50
+ });
51
+
52
+ it("maps IP types", () => {
53
+ expect(clickhouseTypeToValidator("IPv4")).toBe("t.ipv4()");
54
+ expect(clickhouseTypeToValidator("IPv6")).toBe("t.ipv6()");
55
+ });
56
+ });
57
+
58
+ describe("Nullable wrapper", () => {
59
+ it("adds .nullable() for Nullable(String)", () => {
60
+ expect(clickhouseTypeToValidator("Nullable(String)")).toBe("t.string().nullable()");
61
+ });
62
+
63
+ it("adds .nullable() for Nullable(Int32)", () => {
64
+ expect(clickhouseTypeToValidator("Nullable(Int32)")).toBe("t.int32().nullable()");
65
+ });
66
+
67
+ it("adds .nullable() for Nullable(DateTime)", () => {
68
+ expect(clickhouseTypeToValidator("Nullable(DateTime)")).toBe("t.dateTime().nullable()");
69
+ });
70
+ });
71
+
72
+ describe("LowCardinality wrapper", () => {
73
+ it("adds .lowCardinality() for LowCardinality(String)", () => {
74
+ expect(clickhouseTypeToValidator("LowCardinality(String)")).toBe("t.string().lowCardinality()");
75
+ });
76
+
77
+ it("handles LowCardinality(Nullable(String))", () => {
78
+ expect(clickhouseTypeToValidator("LowCardinality(Nullable(String))")).toBe(
79
+ "t.string().nullable().lowCardinality()"
80
+ );
81
+ });
82
+ });
83
+
84
+ describe("parameterized types", () => {
85
+ it("handles DateTime with timezone", () => {
86
+ expect(clickhouseTypeToValidator("DateTime('UTC')")).toBe('t.dateTime("UTC")');
87
+ expect(clickhouseTypeToValidator("DateTime('America/New_York')")).toBe(
88
+ 't.dateTime("America/New_York")'
89
+ );
90
+ });
91
+
92
+ it("handles DateTime64 with precision", () => {
93
+ expect(clickhouseTypeToValidator("DateTime64(3)")).toBe("t.dateTime64(3)");
94
+ expect(clickhouseTypeToValidator("DateTime64(6)")).toBe("t.dateTime64(6)");
95
+ });
96
+
97
+ it("handles DateTime64 with precision and timezone", () => {
98
+ expect(clickhouseTypeToValidator("DateTime64(3, 'UTC')")).toBe('t.dateTime64(3, "UTC")');
99
+ });
100
+
101
+ it("handles FixedString(N)", () => {
102
+ expect(clickhouseTypeToValidator("FixedString(10)")).toBe("t.fixedString(10)");
103
+ expect(clickhouseTypeToValidator("FixedString(255)")).toBe("t.fixedString(255)");
104
+ });
105
+
106
+ it("handles Decimal(P, S)", () => {
107
+ expect(clickhouseTypeToValidator("Decimal(10, 2)")).toBe("t.decimal(10, 2)");
108
+ expect(clickhouseTypeToValidator("Decimal(18, 4)")).toBe("t.decimal(18, 4)");
109
+ });
110
+ });
111
+
112
+ describe("complex types", () => {
113
+ it("handles Array(T)", () => {
114
+ expect(clickhouseTypeToValidator("Array(String)")).toBe("t.array(t.string())");
115
+ expect(clickhouseTypeToValidator("Array(Int32)")).toBe("t.array(t.int32())");
116
+ });
117
+
118
+ it("handles nested Array types", () => {
119
+ expect(clickhouseTypeToValidator("Array(Array(String))")).toBe(
120
+ "t.array(t.array(t.string()))"
121
+ );
122
+ });
123
+
124
+ it("handles Array with Nullable elements", () => {
125
+ expect(clickhouseTypeToValidator("Array(Nullable(String))")).toBe(
126
+ "t.array(t.string().nullable())"
127
+ );
128
+ });
129
+
130
+ it("handles Map(K, V)", () => {
131
+ expect(clickhouseTypeToValidator("Map(String, Int32)")).toBe(
132
+ "t.map(t.string(), t.int32())"
133
+ );
134
+ });
135
+ });
136
+
137
+ describe("enum types", () => {
138
+ it("handles Enum8", () => {
139
+ expect(clickhouseTypeToValidator("Enum8('a' = 1, 'b' = 2)")).toBe(
140
+ 't.enum8("a", "b")'
141
+ );
142
+ });
143
+
144
+ it("handles Enum16", () => {
145
+ expect(clickhouseTypeToValidator("Enum16('pending' = 1, 'active' = 2, 'done' = 3)")).toBe(
146
+ 't.enum16("pending", "active", "done")'
147
+ );
148
+ });
149
+ });
150
+
151
+ describe("aggregate function types", () => {
152
+ it("handles SimpleAggregateFunction", () => {
153
+ expect(clickhouseTypeToValidator("SimpleAggregateFunction(sum, UInt64)")).toBe(
154
+ 't.simpleAggregateFunction("sum", t.uint64())'
155
+ );
156
+ });
157
+
158
+ it("handles AggregateFunction", () => {
159
+ expect(clickhouseTypeToValidator("AggregateFunction(uniq, String)")).toBe(
160
+ 't.aggregateFunction("uniq", t.string())'
161
+ );
162
+ });
163
+ });
164
+
165
+ describe("unknown types", () => {
166
+ it("returns string with TODO comment for unknown types", () => {
167
+ expect(clickhouseTypeToValidator("UnknownType")).toBe(
168
+ "t.string() /* TODO: Unknown type: UnknownType */"
169
+ );
170
+ });
171
+ });
172
+ });
173
+
174
+ describe("paramTypeToValidator", () => {
175
+ describe("simple types", () => {
176
+ it("maps String to p.string()", () => {
177
+ expect(paramTypeToValidator("String")).toBe("p.string()");
178
+ });
179
+
180
+ it("maps Int32 to p.int32()", () => {
181
+ expect(paramTypeToValidator("Int32")).toBe("p.int32()");
182
+ });
183
+
184
+ it("maps DateTime to p.dateTime()", () => {
185
+ expect(paramTypeToValidator("DateTime")).toBe("p.dateTime()");
186
+ });
187
+
188
+ it("maps Boolean to p.boolean()", () => {
189
+ expect(paramTypeToValidator("Boolean")).toBe("p.boolean()");
190
+ expect(paramTypeToValidator("Bool")).toBe("p.boolean()");
191
+ });
192
+ });
193
+
194
+ describe("optional parameters", () => {
195
+ it("adds .optional() for non-required params", () => {
196
+ expect(paramTypeToValidator("String", undefined, false)).toBe("p.string().optional()");
197
+ });
198
+
199
+ it("adds .optional(default) for params with defaults", () => {
200
+ expect(paramTypeToValidator("Int32", 10)).toBe("p.int32().optional(10)");
201
+ expect(paramTypeToValidator("String", "test")).toBe('p.string().optional("test")');
202
+ });
203
+
204
+ it("adds .optional(default) even for required params with defaults", () => {
205
+ expect(paramTypeToValidator("Int32", 5, true)).toBe("p.int32().optional(5)");
206
+ });
207
+ });
208
+
209
+ describe("DateTime variants", () => {
210
+ it("handles DateTime64", () => {
211
+ expect(paramTypeToValidator("DateTime64")).toBe("p.dateTime64()");
212
+ });
213
+
214
+ it("handles DateTime with timezone", () => {
215
+ expect(paramTypeToValidator("DateTime('UTC')")).toBe("p.dateTime()");
216
+ });
217
+ });
218
+
219
+ describe("unknown types", () => {
220
+ it("defaults to p.string() for unknown types", () => {
221
+ expect(paramTypeToValidator("UnknownType")).toBe("p.string()");
222
+ });
223
+ });
224
+ });