@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,165 @@
1
+ /**
2
+ * Client types for Tinybird API interactions
3
+ */
4
+
5
+ /**
6
+ * Configuration for the Tinybird client
7
+ */
8
+ export interface ClientConfig {
9
+ /** Tinybird API base URL (e.g., 'https://api.tinybird.co' or 'https://api.us-east.tinybird.co') */
10
+ baseUrl: string;
11
+ /** API token for authentication */
12
+ token: string;
13
+ /** Custom fetch implementation (optional, defaults to global fetch) */
14
+ fetch?: typeof fetch;
15
+ /** Default timeout in milliseconds (optional) */
16
+ timeout?: number;
17
+ /**
18
+ * Enable dev mode to automatically use branch tokens when on a feature branch.
19
+ * When enabled, the client will detect the git branch and use the corresponding
20
+ * Tinybird branch token instead of the workspace token.
21
+ */
22
+ devMode?: boolean;
23
+ }
24
+
25
+ /**
26
+ * Column metadata from query response
27
+ */
28
+ export interface ColumnMeta {
29
+ /** Column name */
30
+ name: string;
31
+ /** Column type (Tinybird/ClickHouse type) */
32
+ type: string;
33
+ }
34
+
35
+ /**
36
+ * Query statistics from response
37
+ */
38
+ export interface QueryStatistics {
39
+ /** Time elapsed in seconds */
40
+ elapsed: number;
41
+ /** Number of rows read */
42
+ rows_read: number;
43
+ /** Number of bytes read */
44
+ bytes_read: number;
45
+ }
46
+
47
+ /**
48
+ * Result of a query operation
49
+ */
50
+ export interface QueryResult<T> {
51
+ /** Query result data */
52
+ data: T[];
53
+ /** Column metadata */
54
+ meta: ColumnMeta[];
55
+ /** Number of rows returned */
56
+ rows: number;
57
+ /** Query statistics */
58
+ statistics: QueryStatistics;
59
+ }
60
+
61
+ /**
62
+ * Result of an ingest operation
63
+ */
64
+ export interface IngestResult {
65
+ /** Number of rows successfully ingested */
66
+ successful_rows: number;
67
+ /** Number of rows that failed to ingest */
68
+ quarantined_rows: number;
69
+ }
70
+
71
+ /**
72
+ * Error response from Tinybird API
73
+ */
74
+ export interface TinybirdErrorResponse {
75
+ /** Error message */
76
+ error: string;
77
+ /** HTTP status code */
78
+ status?: number;
79
+ /** Additional error details */
80
+ documentation?: string;
81
+ }
82
+
83
+ /**
84
+ * Custom error class for Tinybird API errors
85
+ */
86
+ export class TinybirdError extends Error {
87
+ /** HTTP status code */
88
+ readonly statusCode: number;
89
+ /** Raw error response */
90
+ readonly response?: TinybirdErrorResponse;
91
+
92
+ constructor(message: string, statusCode: number, response?: TinybirdErrorResponse) {
93
+ super(message);
94
+ this.name = "TinybirdError";
95
+ this.statusCode = statusCode;
96
+ this.response = response;
97
+ }
98
+
99
+ /**
100
+ * Check if this is a rate limit error
101
+ */
102
+ isRateLimitError(): boolean {
103
+ return this.statusCode === 429;
104
+ }
105
+
106
+ /**
107
+ * Check if this is an authentication error
108
+ */
109
+ isAuthError(): boolean {
110
+ return this.statusCode === 401 || this.statusCode === 403;
111
+ }
112
+
113
+ /**
114
+ * Check if this is a not found error
115
+ */
116
+ isNotFoundError(): boolean {
117
+ return this.statusCode === 404;
118
+ }
119
+
120
+ /**
121
+ * Check if this is a server error
122
+ */
123
+ isServerError(): boolean {
124
+ return this.statusCode >= 500;
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Options for query requests
130
+ */
131
+ export interface QueryOptions {
132
+ /** Request timeout in milliseconds */
133
+ timeout?: number;
134
+ /** AbortController signal for cancellation */
135
+ signal?: AbortSignal;
136
+ }
137
+
138
+ /**
139
+ * Options for ingest requests
140
+ */
141
+ export interface IngestOptions {
142
+ /** Request timeout in milliseconds */
143
+ timeout?: number;
144
+ /** AbortController signal for cancellation */
145
+ signal?: AbortSignal;
146
+ /** Wait for the ingestion to complete before returning */
147
+ wait?: boolean;
148
+ }
149
+
150
+ /**
151
+ * Base interface for typed pipe endpoints
152
+ */
153
+ export interface TypedPipeEndpoint<TParams, TOutput> {
154
+ (params: TParams, options?: QueryOptions): Promise<QueryResult<TOutput>>;
155
+ }
156
+
157
+ /**
158
+ * Base interface for typed datasource ingestion
159
+ */
160
+ export interface TypedDatasourceIngest<TRow> {
161
+ /** Send a single event */
162
+ send(event: TRow, options?: IngestOptions): Promise<IngestResult>;
163
+ /** Send multiple events in a batch */
164
+ sendBatch(events: TRow[], options?: IngestOptions): Promise<IngestResult>;
165
+ }
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Client file generator
3
+ * Generates a typed tinybird.ts client file from discovered entities
4
+ */
5
+
6
+ import * as path from "path";
7
+ import type { LoadedEntities } from "./loader.js";
8
+
9
+ /**
10
+ * Options for generating the client file
11
+ */
12
+ export interface GenerateClientOptions {
13
+ /** Loaded entities from source files */
14
+ entities: LoadedEntities;
15
+ /** Output file path (relative to cwd) */
16
+ outputPath: string;
17
+ /** Working directory */
18
+ cwd: string;
19
+ }
20
+
21
+ /**
22
+ * Result of generating the client file
23
+ */
24
+ export interface GeneratedClient {
25
+ /** The generated file content */
26
+ content: string;
27
+ /** Absolute path to output file */
28
+ absolutePath: string;
29
+ }
30
+
31
+ /**
32
+ * Convert a file path to a relative import path
33
+ * e.g., "src/datasources.ts" -> "./datasources" when output is "src/tinybird.ts"
34
+ */
35
+ function toRelativeImport(fromPath: string, toPath: string): string {
36
+ // Get directory of the from file
37
+ const fromDir = path.dirname(fromPath);
38
+
39
+ // Get relative path from output dir to source file
40
+ let relativePath = path.relative(fromDir, toPath);
41
+
42
+ // Normalize Windows separators to forward slashes for TS imports
43
+ relativePath = relativePath.replace(/\\/g, "/");
44
+
45
+ // Remove .ts extension
46
+ relativePath = relativePath.replace(/\.tsx?$/, "");
47
+
48
+ // Ensure it starts with ./ or ../
49
+ if (!relativePath.startsWith(".") && !relativePath.startsWith("/")) {
50
+ relativePath = "./" + relativePath;
51
+ }
52
+
53
+ return relativePath;
54
+ }
55
+
56
+ /**
57
+ * Convert camelCase to PascalCase
58
+ */
59
+ function toPascalCase(str: string): string {
60
+ return str.charAt(0).toUpperCase() + str.slice(1);
61
+ }
62
+
63
+ /**
64
+ * Generate the client file content
65
+ *
66
+ * @param options - Generation options
67
+ * @returns Generated client file info
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * const result = generateClientFile({
72
+ * entities: loadedEntities,
73
+ * outputPath: 'src/tinybird.ts',
74
+ * cwd: '/path/to/project',
75
+ * });
76
+ *
77
+ * fs.writeFileSync(result.absolutePath, result.content);
78
+ * ```
79
+ */
80
+ export function generateClientFile(options: GenerateClientOptions): GeneratedClient {
81
+ const { entities, outputPath, cwd } = options;
82
+ const absolutePath = path.isAbsolute(outputPath) ? outputPath : path.join(cwd, outputPath);
83
+
84
+ // Group entities by source file for imports
85
+ const importsByFile = new Map<string, { datasources: string[]; pipes: string[] }>();
86
+
87
+ for (const [name, { info }] of Object.entries(entities.datasources)) {
88
+ if (!importsByFile.has(info.sourceFile)) {
89
+ importsByFile.set(info.sourceFile, { datasources: [], pipes: [] });
90
+ }
91
+ importsByFile.get(info.sourceFile)!.datasources.push(name);
92
+ }
93
+
94
+ for (const [name, { info }] of Object.entries(entities.pipes)) {
95
+ if (!importsByFile.has(info.sourceFile)) {
96
+ importsByFile.set(info.sourceFile, { datasources: [], pipes: [] });
97
+ }
98
+ importsByFile.get(info.sourceFile)!.pipes.push(name);
99
+ }
100
+
101
+ // Build import statements
102
+ const importLines: string[] = [];
103
+ const reexportLines: string[] = [];
104
+
105
+ // SDK imports
106
+ const sdkTypes = ["InferRow"];
107
+ if (Object.keys(entities.pipes).length > 0) {
108
+ sdkTypes.push("InferParams", "InferOutputRow");
109
+ }
110
+ importLines.push(
111
+ `import { createTinybirdClient, type ${sdkTypes.join(", type ")} } from "@tinybirdco/sdk";`
112
+ );
113
+ importLines.push("");
114
+
115
+ // Entity imports and re-exports
116
+ for (const [sourceFile, { datasources, pipes }] of importsByFile) {
117
+ const allExports = [...datasources, ...pipes];
118
+ if (allExports.length === 0) continue;
119
+
120
+ // Resolve to absolute paths for correct relative path calculation
121
+ const sourceAbsolute = path.isAbsolute(sourceFile) ? sourceFile : path.join(cwd, sourceFile);
122
+ const relativePath = toRelativeImport(absolutePath, sourceAbsolute);
123
+ importLines.push(`import { ${allExports.join(", ")} } from "${relativePath}";`);
124
+ reexportLines.push(`export { ${allExports.join(", ")} } from "${relativePath}";`);
125
+ }
126
+
127
+ // Build createTinybirdClient call
128
+ const datasourceNames = Object.keys(entities.datasources);
129
+ const pipeNames = Object.keys(entities.pipes);
130
+
131
+ const clientLines: string[] = [];
132
+ clientLines.push("export const tinybird = createTinybirdClient({");
133
+
134
+ if (datasourceNames.length > 0) {
135
+ clientLines.push(` datasources: { ${datasourceNames.join(", ")} },`);
136
+ } else {
137
+ clientLines.push(" datasources: {},");
138
+ }
139
+
140
+ if (pipeNames.length > 0) {
141
+ clientLines.push(` pipes: { ${pipeNames.join(", ")} },`);
142
+ } else {
143
+ clientLines.push(" pipes: {},");
144
+ }
145
+
146
+ clientLines.push("});");
147
+
148
+ // Build type exports
149
+ const typeLines: string[] = [];
150
+
151
+ for (const name of datasourceNames) {
152
+ const pascalName = toPascalCase(name);
153
+ typeLines.push(`export type ${pascalName}Row = InferRow<typeof ${name}>;`);
154
+ }
155
+
156
+ for (const name of pipeNames) {
157
+ const { definition } = entities.pipes[name];
158
+ const pascalName = toPascalCase(name);
159
+ typeLines.push(`export type ${pascalName}Params = InferParams<typeof ${name}>;`);
160
+ // Only generate Output type for pipes with output schema
161
+ if (definition._output) {
162
+ typeLines.push(`export type ${pascalName}Output = InferOutputRow<typeof ${name}>;`);
163
+ }
164
+ }
165
+
166
+ // Combine all sections
167
+ const sections: string[] = [
168
+ "// Auto-generated by @tinybirdco/sdk - DO NOT EDIT",
169
+ "// This file is regenerated on every build. Manual changes will be overwritten.",
170
+ "",
171
+ importLines.join("\n"),
172
+ "",
173
+ "// Typed Tinybird client",
174
+ clientLines.join("\n"),
175
+ "",
176
+ "// Re-export entities for convenience",
177
+ reexportLines.join("\n"),
178
+ ];
179
+
180
+ if (typeLines.length > 0) {
181
+ sections.push("");
182
+ sections.push("// Inferred types from entity definitions");
183
+ sections.push(typeLines.join("\n"));
184
+ }
185
+
186
+ sections.push(""); // Trailing newline
187
+
188
+ const content = sections.join("\n");
189
+
190
+ return {
191
+ content,
192
+ absolutePath,
193
+ };
194
+ }
@@ -0,0 +1,297 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { generateDatasource, generateAllDatasources } from './datasource.js';
3
+ import { defineDatasource } from '../schema/datasource.js';
4
+ import { t } from '../schema/types.js';
5
+ import { engine } from '../schema/engines.js';
6
+
7
+ describe('Datasource Generator', () => {
8
+ describe('generateDatasource', () => {
9
+ it('generates basic datasource with schema', () => {
10
+ const ds = defineDatasource('test_ds', {
11
+ schema: {
12
+ id: t.string(),
13
+ count: t.int32(),
14
+ },
15
+ });
16
+
17
+ const result = generateDatasource(ds);
18
+ expect(result.name).toBe('test_ds');
19
+ expect(result.content).toContain('SCHEMA >');
20
+ expect(result.content).toContain('id String');
21
+ expect(result.content).toContain('count Int32');
22
+ });
23
+
24
+ it('includes description when provided', () => {
25
+ const ds = defineDatasource('test_ds', {
26
+ description: 'Test datasource description',
27
+ schema: {
28
+ id: t.string(),
29
+ },
30
+ });
31
+
32
+ const result = generateDatasource(ds);
33
+ expect(result.content).toContain('DESCRIPTION >');
34
+ expect(result.content).toContain('Test datasource description');
35
+ });
36
+
37
+ it('includes engine configuration', () => {
38
+ const ds = defineDatasource('test_ds', {
39
+ schema: {
40
+ id: t.string(),
41
+ },
42
+ engine: engine.mergeTree({
43
+ sortingKey: ['id'],
44
+ }),
45
+ });
46
+
47
+ const result = generateDatasource(ds);
48
+ expect(result.content).toContain('ENGINE "MergeTree"');
49
+ expect(result.content).toContain('ENGINE_SORTING_KEY "id"');
50
+ });
51
+
52
+ it('includes partition key in engine config', () => {
53
+ const ds = defineDatasource('test_ds', {
54
+ schema: {
55
+ id: t.string(),
56
+ timestamp: t.dateTime(),
57
+ },
58
+ engine: engine.mergeTree({
59
+ sortingKey: ['id'],
60
+ partitionKey: 'toYYYYMM(timestamp)',
61
+ }),
62
+ });
63
+
64
+ const result = generateDatasource(ds);
65
+ expect(result.content).toContain('ENGINE_PARTITION_KEY "toYYYYMM(timestamp)"');
66
+ });
67
+
68
+ it('includes TTL in engine config', () => {
69
+ const ds = defineDatasource('test_ds', {
70
+ schema: {
71
+ id: t.string(),
72
+ timestamp: t.dateTime(),
73
+ },
74
+ engine: engine.mergeTree({
75
+ sortingKey: ['id'],
76
+ ttl: 'timestamp + INTERVAL 90 DAY',
77
+ }),
78
+ });
79
+
80
+ const result = generateDatasource(ds);
81
+ expect(result.content).toContain('ENGINE_TTL "timestamp + INTERVAL 90 DAY"');
82
+ });
83
+ });
84
+
85
+ describe('Column formatting', () => {
86
+ it('formats Nullable columns correctly', () => {
87
+ const ds = defineDatasource('test_ds', {
88
+ schema: {
89
+ name: t.string().nullable(),
90
+ },
91
+ });
92
+
93
+ const result = generateDatasource(ds);
94
+ expect(result.content).toContain('name Nullable(String)');
95
+ });
96
+
97
+ it('formats LowCardinality columns correctly', () => {
98
+ const ds = defineDatasource('test_ds', {
99
+ schema: {
100
+ country: t.string().lowCardinality(),
101
+ },
102
+ });
103
+
104
+ const result = generateDatasource(ds);
105
+ expect(result.content).toContain('country LowCardinality(String)');
106
+ });
107
+
108
+ it('formats LowCardinality(Nullable) correctly', () => {
109
+ const ds = defineDatasource('test_ds', {
110
+ schema: {
111
+ country: t.string().lowCardinality().nullable(),
112
+ },
113
+ });
114
+
115
+ const result = generateDatasource(ds);
116
+ expect(result.content).toContain('country LowCardinality(Nullable(String))');
117
+ });
118
+
119
+ it('includes default values', () => {
120
+ const ds = defineDatasource('test_ds', {
121
+ schema: {
122
+ status: t.string().default('pending'),
123
+ },
124
+ });
125
+
126
+ const result = generateDatasource(ds);
127
+ expect(result.content).toContain("status String `json:$.status` DEFAULT 'pending'");
128
+ });
129
+
130
+ it('formats null default values', () => {
131
+ const ds = defineDatasource('test_ds', {
132
+ schema: {
133
+ // Using nullable with explicit null default
134
+ status: t.string().nullable().default(null),
135
+ },
136
+ });
137
+
138
+ const result = generateDatasource(ds);
139
+ expect(result.content).toContain('DEFAULT NULL');
140
+ });
141
+
142
+ it('formats number default values', () => {
143
+ const ds = defineDatasource('test_ds', {
144
+ schema: {
145
+ count: t.int32().default(42),
146
+ score: t.float64().default(3.14),
147
+ },
148
+ });
149
+
150
+ const result = generateDatasource(ds);
151
+ expect(result.content).toContain('count Int32 `json:$.count` DEFAULT 42');
152
+ expect(result.content).toContain('score Float64 `json:$.score` DEFAULT 3.14');
153
+ });
154
+
155
+ it('formats boolean default values', () => {
156
+ const ds = defineDatasource('test_ds', {
157
+ schema: {
158
+ is_active: t.bool().default(true),
159
+ is_deleted: t.bool().default(false),
160
+ },
161
+ });
162
+
163
+ const result = generateDatasource(ds);
164
+ expect(result.content).toContain('is_active Bool `json:$.is_active` DEFAULT 1');
165
+ expect(result.content).toContain('is_deleted Bool `json:$.is_deleted` DEFAULT 0');
166
+ });
167
+
168
+ it('formats Date default values for DateTime type', () => {
169
+ const ds = defineDatasource('test_ds', {
170
+ schema: {
171
+ created_at: t.dateTime().default(new Date('2024-01-15T10:30:00Z')),
172
+ },
173
+ });
174
+
175
+ const result = generateDatasource(ds);
176
+ expect(result.content).toContain("created_at DateTime `json:$.created_at` DEFAULT '2024-01-15 10:30:00'");
177
+ });
178
+
179
+ it('formats Date default values for Date type', () => {
180
+ const ds = defineDatasource('test_ds', {
181
+ schema: {
182
+ birth_date: t.date().default(new Date('2024-01-15T10:30:00Z')),
183
+ },
184
+ });
185
+
186
+ const result = generateDatasource(ds);
187
+ expect(result.content).toContain("birth_date Date `json:$.birth_date` DEFAULT '2024-01-15'");
188
+ });
189
+
190
+ it('formats array default values', () => {
191
+ const ds = defineDatasource('test_ds', {
192
+ schema: {
193
+ tags: t.array(t.string()).default(['a', 'b']),
194
+ },
195
+ });
196
+
197
+ const result = generateDatasource(ds);
198
+ expect(result.content).toContain('tags Array(String) `json:$.tags` DEFAULT ["a","b"]');
199
+ });
200
+
201
+ it('formats object default values for JSON type', () => {
202
+ const ds = defineDatasource('test_ds', {
203
+ schema: {
204
+ metadata: t.json<{ key: string }>().default({ key: 'value' }),
205
+ },
206
+ });
207
+
208
+ const result = generateDatasource(ds);
209
+ expect(result.content).toContain('metadata JSON `json:$.metadata` DEFAULT {"key":"value"}');
210
+ });
211
+
212
+ it('escapes single quotes in string default values', () => {
213
+ const ds = defineDatasource('test_ds', {
214
+ schema: {
215
+ message: t.string().default("it's working"),
216
+ },
217
+ });
218
+
219
+ const result = generateDatasource(ds);
220
+ expect(result.content).toContain("message String `json:$.message` DEFAULT 'it\\'s working'");
221
+ });
222
+
223
+ it('includes codec', () => {
224
+ const ds = defineDatasource('test_ds', {
225
+ schema: {
226
+ data: t.string().codec('LZ4'),
227
+ },
228
+ });
229
+
230
+ const result = generateDatasource(ds);
231
+ expect(result.content).toContain('data String `json:$.data` CODEC(LZ4)');
232
+ });
233
+
234
+ it('adds commas between columns except last', () => {
235
+ const ds = defineDatasource('test_ds', {
236
+ schema: {
237
+ id: t.string(),
238
+ name: t.string(),
239
+ count: t.int32(),
240
+ },
241
+ });
242
+
243
+ const result = generateDatasource(ds);
244
+ const lines = result.content.split('\n');
245
+ const schemaLines = lines.filter(l => l.trim().startsWith('id') || l.trim().startsWith('name') || l.trim().startsWith('count'));
246
+
247
+ expect(schemaLines[0]).toContain(',');
248
+ expect(schemaLines[1]).toContain(',');
249
+ expect(schemaLines[2]).not.toContain(',');
250
+ });
251
+ });
252
+
253
+ describe('generateAllDatasources', () => {
254
+ it('generates all datasources', () => {
255
+ const ds1 = defineDatasource('ds1', { schema: { id: t.string() } });
256
+ const ds2 = defineDatasource('ds2', { schema: { id: t.int32() } });
257
+
258
+ const results = generateAllDatasources({ ds1, ds2 });
259
+ expect(results).toHaveLength(2);
260
+ expect(results.map(r => r.name).sort()).toEqual(['ds1', 'ds2']);
261
+ });
262
+ });
263
+
264
+ describe('Full integration', () => {
265
+ it('generates complete datasource file', () => {
266
+ const ds = defineDatasource('page_views', {
267
+ description: 'Page view tracking data',
268
+ schema: {
269
+ timestamp: t.dateTime(),
270
+ pathname: t.string(),
271
+ session_id: t.string(),
272
+ country: t.string().lowCardinality().nullable(),
273
+ },
274
+ engine: engine.mergeTree({
275
+ sortingKey: ['pathname', 'timestamp'],
276
+ partitionKey: 'toYYYYMM(timestamp)',
277
+ ttl: 'timestamp + INTERVAL 90 DAY',
278
+ }),
279
+ });
280
+
281
+ const result = generateDatasource(ds);
282
+
283
+ expect(result.name).toBe('page_views');
284
+ expect(result.content).toContain('DESCRIPTION >');
285
+ expect(result.content).toContain('Page view tracking data');
286
+ expect(result.content).toContain('SCHEMA >');
287
+ expect(result.content).toContain('timestamp DateTime');
288
+ expect(result.content).toContain('pathname String');
289
+ expect(result.content).toContain('session_id String');
290
+ expect(result.content).toContain('country LowCardinality(Nullable(String))');
291
+ expect(result.content).toContain('ENGINE "MergeTree"');
292
+ expect(result.content).toContain('ENGINE_PARTITION_KEY "toYYYYMM(timestamp)"');
293
+ expect(result.content).toContain('ENGINE_SORTING_KEY "pathname, timestamp"');
294
+ expect(result.content).toContain('ENGINE_TTL "timestamp + INTERVAL 90 DAY"');
295
+ });
296
+ });
297
+ });