@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,832 @@
1
+ /**
2
+ * Pipe definition for Tinybird
3
+ * Define SQL transformations and endpoints as TypeScript with full type safety
4
+ */
5
+
6
+ import type { AnyTypeValidator } from "./types.js";
7
+ import type { AnyParamValidator } from "./params.js";
8
+ import type { DatasourceDefinition, SchemaDefinition, ColumnDefinition } from "./datasource.js";
9
+ import { getColumnType } from "./datasource.js";
10
+ import { getTinybirdType } from "./types.js";
11
+
12
+ /** Symbol for brand typing pipes */
13
+ export const PIPE_BRAND: unique symbol = Symbol("tinybird.pipe");
14
+ /** Symbol for brand typing nodes */
15
+ export const NODE_BRAND: unique symbol = Symbol("tinybird.node");
16
+
17
+ /**
18
+ * Parameter definition for a pipe
19
+ */
20
+ export type ParamsDefinition = Record<string, AnyParamValidator>;
21
+
22
+ /**
23
+ * Output schema definition for a pipe
24
+ */
25
+ export type OutputDefinition = Record<string, AnyTypeValidator>;
26
+
27
+ /**
28
+ * Node configuration options
29
+ */
30
+ export interface NodeOptions {
31
+ /** Node name (must be valid identifier) */
32
+ name: string;
33
+ /** SQL query for this node */
34
+ sql: string;
35
+ /** Human-readable description */
36
+ description?: string;
37
+ }
38
+
39
+ /**
40
+ * A node definition within a pipe
41
+ */
42
+ export interface NodeDefinition {
43
+ readonly [NODE_BRAND]: true;
44
+ /** Node name */
45
+ readonly _name: string;
46
+ /** Type marker for inference */
47
+ readonly _type: "node";
48
+ /** SQL query */
49
+ readonly sql: string;
50
+ /** Description */
51
+ readonly description?: string;
52
+ }
53
+
54
+ /**
55
+ * Create a node within a pipe
56
+ *
57
+ * @param options - Node configuration
58
+ * @returns A node definition
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * import { node } from '@tinybirdco/sdk';
63
+ *
64
+ * const filteredNode = node({
65
+ * name: 'filtered',
66
+ * sql: `
67
+ * SELECT *
68
+ * FROM events
69
+ * WHERE timestamp >= {{DateTime(start_date)}}
70
+ * `,
71
+ * });
72
+ * ```
73
+ */
74
+ export function node(options: NodeOptions): NodeDefinition {
75
+ // Validate name is a valid identifier
76
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(options.name)) {
77
+ throw new Error(
78
+ `Invalid node name: "${options.name}". Must start with a letter or underscore and contain only alphanumeric characters and underscores.`
79
+ );
80
+ }
81
+
82
+ return {
83
+ [NODE_BRAND]: true,
84
+ _name: options.name,
85
+ _type: "node",
86
+ sql: options.sql,
87
+ description: options.description,
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Check if a value is a node definition
93
+ */
94
+ export function isNodeDefinition(value: unknown): value is NodeDefinition {
95
+ return (
96
+ typeof value === "object" &&
97
+ value !== null &&
98
+ NODE_BRAND in value &&
99
+ (value as Record<symbol, unknown>)[NODE_BRAND] === true
100
+ );
101
+ }
102
+
103
+ /**
104
+ * Endpoint configuration for a pipe
105
+ */
106
+ export interface EndpointConfig {
107
+ /** Whether this pipe is exposed as an API endpoint */
108
+ enabled: boolean;
109
+ /** Cache configuration */
110
+ cache?: {
111
+ /** Whether caching is enabled */
112
+ enabled: boolean;
113
+ /** Cache TTL in seconds */
114
+ ttl?: number;
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Materialized view configuration for a pipe
120
+ */
121
+ export interface MaterializedConfig<
122
+ TDatasource extends DatasourceDefinition<SchemaDefinition> = DatasourceDefinition<SchemaDefinition>
123
+ > {
124
+ /** Target datasource where materialized data is written */
125
+ datasource: TDatasource;
126
+ /**
127
+ * Deployment method for materialized views.
128
+ * Use 'alter' to update existing materialized views using ALTER TABLE ... MODIFY QUERY
129
+ * instead of recreating the table. This preserves existing data and reduces deployment time.
130
+ */
131
+ deploymentMethod?: "alter";
132
+ }
133
+
134
+ /**
135
+ * Copy pipe configuration
136
+ */
137
+ export interface CopyConfig<
138
+ TDatasource extends DatasourceDefinition<SchemaDefinition> = DatasourceDefinition<SchemaDefinition>
139
+ > {
140
+ /** Target datasource where copied data is written */
141
+ datasource: TDatasource;
142
+ /**
143
+ * Copy mode: how data is ingested
144
+ * - 'append': Appends the result to the target data source (default)
145
+ * - 'replace': Every run completely replaces the destination Data Source content
146
+ */
147
+ copy_mode?: "append" | "replace";
148
+ /**
149
+ * Copy schedule: when the copy job runs
150
+ * - A cron expression (e.g., "0 * * * *" for hourly)
151
+ * - "@on-demand" for manual execution only
152
+ * Defaults to "@on-demand" if not specified
153
+ */
154
+ copy_schedule?: string;
155
+ }
156
+
157
+ /**
158
+ * Token configuration for pipe access
159
+ */
160
+ export interface PipeTokenConfig {
161
+ /** Token name */
162
+ name: string;
163
+ }
164
+
165
+ /**
166
+ * Options for defining a pipe (reusable SQL logic, no endpoint)
167
+ */
168
+ export interface PipeOptions<
169
+ TParams extends ParamsDefinition,
170
+ TOutput extends OutputDefinition
171
+ > {
172
+ /** Human-readable description of the pipe */
173
+ description?: string;
174
+ /** Parameter definitions for query inputs */
175
+ params?: TParams;
176
+ /** Nodes in the transformation pipeline */
177
+ nodes: readonly NodeDefinition[];
178
+ /** Output schema (optional for reusable pipes, required for endpoints) */
179
+ output?: TOutput;
180
+ /** Whether this pipe is an API endpoint (shorthand for { enabled: true }). Mutually exclusive with materialized and copy. */
181
+ endpoint?: boolean | EndpointConfig;
182
+ /** Materialized view configuration. Mutually exclusive with endpoint and copy. */
183
+ materialized?: MaterializedConfig;
184
+ /** Copy pipe configuration. Mutually exclusive with endpoint and materialized. */
185
+ copy?: CopyConfig;
186
+ /** Access tokens for this pipe */
187
+ tokens?: readonly PipeTokenConfig[];
188
+ }
189
+
190
+ /**
191
+ * Options for defining an endpoint (API-exposed pipe)
192
+ */
193
+ export interface EndpointOptions<
194
+ TParams extends ParamsDefinition,
195
+ TOutput extends OutputDefinition
196
+ > {
197
+ /** Human-readable description of the endpoint */
198
+ description?: string;
199
+ /** Parameter definitions for query inputs */
200
+ params?: TParams;
201
+ /** Nodes in the transformation pipeline */
202
+ nodes: readonly NodeDefinition[];
203
+ /** Output schema (required for type safety) */
204
+ output: TOutput;
205
+ /** Cache configuration */
206
+ cache?: {
207
+ /** Whether caching is enabled */
208
+ enabled: boolean;
209
+ /** Cache TTL in seconds */
210
+ ttl?: number;
211
+ };
212
+ /** Access tokens for this endpoint */
213
+ tokens?: readonly PipeTokenConfig[];
214
+ }
215
+
216
+ /**
217
+ * Options for defining a copy pipe
218
+ */
219
+ export interface CopyPipeOptions<
220
+ TSchema extends SchemaDefinition,
221
+ TDatasource extends DatasourceDefinition<TSchema>
222
+ > {
223
+ /** Human-readable description of the copy pipe */
224
+ description?: string;
225
+ /** Nodes in the transformation pipeline */
226
+ nodes: readonly NodeDefinition[];
227
+ /** Target datasource where copied data is written */
228
+ datasource: TDatasource;
229
+ /**
230
+ * Copy mode: how data is ingested
231
+ * - 'append': Appends the result to the target data source (default)
232
+ * - 'replace': Every run completely replaces the destination Data Source content
233
+ */
234
+ copy_mode?: "append" | "replace";
235
+ /**
236
+ * Copy schedule: when the copy job runs
237
+ * - A cron expression (e.g., "0 * * * *" for hourly)
238
+ * - "@on-demand" for manual execution only
239
+ * Defaults to "@on-demand" if not specified
240
+ */
241
+ copy_schedule?: string;
242
+ /** Access tokens for this copy pipe */
243
+ tokens?: readonly PipeTokenConfig[];
244
+ }
245
+
246
+ /**
247
+ * A pipe definition with full type information
248
+ */
249
+ export interface PipeDefinition<
250
+ TParams extends ParamsDefinition = ParamsDefinition,
251
+ TOutput extends OutputDefinition = OutputDefinition
252
+ > {
253
+ readonly [PIPE_BRAND]: true;
254
+ /** Pipe name */
255
+ readonly _name: string;
256
+ /** Type marker for inference */
257
+ readonly _type: "pipe";
258
+ /** Parameter definitions */
259
+ readonly _params: TParams;
260
+ /** Output schema (optional for reusable pipes) */
261
+ readonly _output?: TOutput;
262
+ /** Full options */
263
+ readonly options: PipeOptions<TParams, TOutput>;
264
+ }
265
+
266
+ /**
267
+ * Define a Tinybird pipe
268
+ *
269
+ * @param name - The pipe name (must be valid identifier)
270
+ * @param options - Pipe configuration including params, nodes, and output schema
271
+ * @returns A pipe definition that can be used in a project
272
+ *
273
+ * @example
274
+ * ```ts
275
+ * import { definePipe, node, p, t } from '@tinybirdco/sdk';
276
+ *
277
+ * export const topEvents = definePipe('top_events', {
278
+ * description: 'Get top events by count',
279
+ * params: {
280
+ * start_date: p.dateTime(),
281
+ * end_date: p.dateTime(),
282
+ * limit: p.int32().optional(10),
283
+ * },
284
+ * nodes: [
285
+ * node({
286
+ * name: 'filtered',
287
+ * sql: `
288
+ * SELECT *
289
+ * FROM events
290
+ * WHERE timestamp BETWEEN {{DateTime(start_date)}} AND {{DateTime(end_date)}}
291
+ * `,
292
+ * }),
293
+ * node({
294
+ * name: 'aggregated',
295
+ * sql: `
296
+ * SELECT
297
+ * event_type,
298
+ * count() as event_count,
299
+ * uniqExact(user_id) as unique_users
300
+ * FROM filtered
301
+ * GROUP BY event_type
302
+ * ORDER BY event_count DESC
303
+ * LIMIT {{Int32(limit, 10)}}
304
+ * `,
305
+ * }),
306
+ * ],
307
+ * output: {
308
+ * event_type: t.string(),
309
+ * event_count: t.uint64(),
310
+ * unique_users: t.uint64(),
311
+ * },
312
+ * endpoint: true,
313
+ * });
314
+ * ```
315
+ */
316
+ /**
317
+ * Normalize a Tinybird type for comparison by removing wrappers that don't affect compatibility
318
+ */
319
+ function normalizeTypeForComparison(type: string): string {
320
+ // Remove Nullable wrapper for comparison
321
+ let normalized = type.replace(/^Nullable\((.+)\)$/, "$1");
322
+ // Remove LowCardinality wrapper
323
+ normalized = normalized.replace(/^LowCardinality\((.+)\)$/, "$1");
324
+ // Handle LowCardinality(Nullable(...))
325
+ normalized = normalized.replace(/^LowCardinality\(Nullable\((.+)\)\)$/, "$1");
326
+ // Remove timezone from DateTime types
327
+ normalized = normalized.replace(/^DateTime\('[^']+'\)$/, "DateTime");
328
+ normalized = normalized.replace(/^DateTime64\((\d+),\s*'[^']+'\)$/, "DateTime64($1)");
329
+ return normalized;
330
+ }
331
+
332
+ /**
333
+ * Check if two Tinybird types are compatible
334
+ */
335
+ function typesAreCompatible(outputType: string, datasourceType: string): boolean {
336
+ const normalizedOutput = normalizeTypeForComparison(outputType);
337
+ const normalizedDatasource = normalizeTypeForComparison(datasourceType);
338
+
339
+ // Direct match
340
+ if (normalizedOutput === normalizedDatasource) {
341
+ return true;
342
+ }
343
+
344
+ // SimpleAggregateFunction compatibility: output can be the base type
345
+ // e.g., output UInt64 -> datasource SimpleAggregateFunction(sum, UInt64)
346
+ const simpleAggMatch = normalizedDatasource.match(
347
+ /^SimpleAggregateFunction\([^,]+,\s*(.+)\)$/
348
+ );
349
+ if (simpleAggMatch && normalizedOutput === simpleAggMatch[1]) {
350
+ return true;
351
+ }
352
+
353
+ // AggregateFunction compatibility
354
+ const aggMatch = normalizedDatasource.match(
355
+ /^AggregateFunction\([^,]+,\s*(.+)\)$/
356
+ );
357
+ if (aggMatch && normalizedOutput === aggMatch[1]) {
358
+ return true;
359
+ }
360
+
361
+ return false;
362
+ }
363
+
364
+ /**
365
+ * Validate that the pipe output schema matches the target datasource schema
366
+ */
367
+ function validateMaterializedSchema(
368
+ pipeName: string,
369
+ output: OutputDefinition,
370
+ datasource: DatasourceDefinition
371
+ ): void {
372
+ const outputColumns = Object.keys(output);
373
+ const datasourceSchema = datasource._schema;
374
+ const datasourceColumns = Object.keys(datasourceSchema);
375
+
376
+ // Check for missing columns in output
377
+ const missingInOutput = datasourceColumns.filter(
378
+ (col) => !outputColumns.includes(col)
379
+ );
380
+ if (missingInOutput.length > 0) {
381
+ throw new Error(
382
+ `Materialized view "${pipeName}" output schema is missing columns from target datasource "${datasource._name}": ${missingInOutput.join(", ")}`
383
+ );
384
+ }
385
+
386
+ // Check for extra columns in output
387
+ const extraInOutput = outputColumns.filter(
388
+ (col) => !datasourceColumns.includes(col)
389
+ );
390
+ if (extraInOutput.length > 0) {
391
+ throw new Error(
392
+ `Materialized view "${pipeName}" output schema has columns not in target datasource "${datasource._name}": ${extraInOutput.join(", ")}`
393
+ );
394
+ }
395
+
396
+ // Check type compatibility for each column
397
+ for (const columnName of outputColumns) {
398
+ const outputValidator = output[columnName];
399
+ const datasourceColumn = datasourceSchema[columnName];
400
+
401
+ const outputType = getTinybirdType(outputValidator);
402
+ const datasourceValidator = getColumnType(datasourceColumn);
403
+ const datasourceType = getTinybirdType(datasourceValidator);
404
+
405
+ if (!typesAreCompatible(outputType, datasourceType)) {
406
+ throw new Error(
407
+ `Materialized view "${pipeName}" column "${columnName}" type mismatch: ` +
408
+ `output has "${outputType}" but target datasource "${datasource._name}" expects "${datasourceType}"`
409
+ );
410
+ }
411
+ }
412
+ }
413
+
414
+ export function definePipe<
415
+ TParams extends ParamsDefinition,
416
+ TOutput extends OutputDefinition
417
+ >(
418
+ name: string,
419
+ options: PipeOptions<TParams, TOutput>
420
+ ): PipeDefinition<TParams, TOutput> {
421
+ // Validate name is a valid identifier
422
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
423
+ throw new Error(
424
+ `Invalid pipe name: "${name}". Must start with a letter or underscore and contain only alphanumeric characters and underscores.`
425
+ );
426
+ }
427
+
428
+ // Validate at least one node
429
+ if (!options.nodes || options.nodes.length === 0) {
430
+ throw new Error(`Pipe "${name}" must have at least one node.`);
431
+ }
432
+
433
+ // Validate output is provided for endpoints and materialized views
434
+ if ((options.endpoint || options.materialized) && (!options.output || Object.keys(options.output).length === 0)) {
435
+ throw new Error(
436
+ `Pipe "${name}" must have an output schema defined when used as an endpoint or materialized view.`
437
+ );
438
+ }
439
+
440
+ // Count how many types are configured
441
+ const typeCount = [options.endpoint, options.materialized, options.copy].filter(Boolean).length;
442
+ if (typeCount > 1) {
443
+ throw new Error(
444
+ `Pipe "${name}" can only have one of: endpoint, materialized, or copy configuration. ` +
445
+ `A pipe must be at most one type.`
446
+ );
447
+ }
448
+
449
+ // Validate materialized view schema compatibility
450
+ if (options.materialized) {
451
+ // output is guaranteed to be defined here because of the earlier validation
452
+ validateMaterializedSchema(name, options.output!, options.materialized.datasource);
453
+ }
454
+
455
+ const params = (options.params ?? {}) as TParams;
456
+
457
+ return {
458
+ [PIPE_BRAND]: true,
459
+ _name: name,
460
+ _type: "pipe",
461
+ _params: params,
462
+ _output: options.output,
463
+ options: {
464
+ ...options,
465
+ params,
466
+ },
467
+ };
468
+ }
469
+
470
+ /**
471
+ * Options for defining a materialized view
472
+ */
473
+ export interface MaterializedViewOptions<
474
+ TDatasource extends DatasourceDefinition<SchemaDefinition>
475
+ > {
476
+ /** Human-readable description of the materialized view */
477
+ description?: string;
478
+ /** Nodes in the transformation pipeline */
479
+ nodes: readonly NodeDefinition[];
480
+ /** Target datasource where materialized data is written */
481
+ datasource: TDatasource;
482
+ /**
483
+ * Deployment method for materialized views.
484
+ * Use 'alter' to update existing materialized views using ALTER TABLE ... MODIFY QUERY
485
+ * instead of recreating the table. This preserves existing data and reduces deployment time.
486
+ */
487
+ deploymentMethod?: "alter";
488
+ /** Access tokens for this pipe */
489
+ tokens?: readonly PipeTokenConfig[];
490
+ }
491
+
492
+ /**
493
+ * Helper type to extract the output definition from a datasource schema
494
+ */
495
+ type DatasourceSchemaToOutput<TSchema extends SchemaDefinition> = {
496
+ [K in keyof TSchema]: TSchema[K] extends ColumnDefinition<infer V>
497
+ ? V
498
+ : TSchema[K] extends AnyTypeValidator
499
+ ? TSchema[K]
500
+ : never;
501
+ };
502
+
503
+ /**
504
+ * Define a Tinybird materialized view
505
+ *
506
+ * This is a convenience function that simplifies creating materialized views.
507
+ * The output schema is automatically derived from the target datasource, ensuring
508
+ * type safety between the pipe output and the target.
509
+ *
510
+ * @param name - The pipe name (must be valid identifier)
511
+ * @param options - Materialized view configuration
512
+ * @returns A pipe definition configured as a materialized view
513
+ *
514
+ * @example
515
+ * ```ts
516
+ * import { defineDatasource, defineMaterializedView, node, t, engine } from '@tinybirdco/sdk';
517
+ *
518
+ * // Target datasource for aggregated data
519
+ * const salesByHour = defineDatasource('sales_by_hour', {
520
+ * schema: {
521
+ * day: t.date(),
522
+ * country: t.string().lowCardinality(),
523
+ * total_sales: t.simpleAggregateFunction('sum', t.uint64()),
524
+ * },
525
+ * engine: engine.aggregatingMergeTree({
526
+ * sortingKey: ['day', 'country'],
527
+ * }),
528
+ * });
529
+ *
530
+ * // Materialized view - output schema is inferred from datasource
531
+ * export const salesByHourMv = defineMaterializedView('sales_by_hour_mv', {
532
+ * description: 'Aggregate sales per hour',
533
+ * datasource: salesByHour,
534
+ * nodes: [
535
+ * node({
536
+ * name: 'daily_sales',
537
+ * sql: `
538
+ * SELECT
539
+ * toStartOfDay(starting_date) as day,
540
+ * country,
541
+ * sum(sales) as total_sales
542
+ * FROM teams
543
+ * GROUP BY day, country
544
+ * `,
545
+ * }),
546
+ * ],
547
+ * deploymentMethod: 'alter', // optional
548
+ * });
549
+ * ```
550
+ */
551
+ export function defineMaterializedView<
552
+ TSchema extends SchemaDefinition,
553
+ TDatasource extends DatasourceDefinition<TSchema>
554
+ >(
555
+ name: string,
556
+ options: MaterializedViewOptions<TDatasource>
557
+ ): PipeDefinition<Record<string, never>, DatasourceSchemaToOutput<TSchema>> {
558
+ // Extract the schema from the datasource to build the output
559
+ const datasourceSchema = options.datasource._schema as TSchema;
560
+ const output: Record<string, AnyTypeValidator> = {};
561
+
562
+ for (const [columnName, column] of Object.entries(datasourceSchema)) {
563
+ output[columnName] = getColumnType(column);
564
+ }
565
+
566
+ return definePipe(name, {
567
+ description: options.description,
568
+ nodes: options.nodes,
569
+ output: output as DatasourceSchemaToOutput<TSchema>,
570
+ materialized: {
571
+ datasource: options.datasource,
572
+ deploymentMethod: options.deploymentMethod,
573
+ },
574
+ tokens: options.tokens,
575
+ });
576
+ }
577
+
578
+ /**
579
+ * Define a Tinybird endpoint
580
+ *
581
+ * This is a convenience function for creating API endpoints.
582
+ * Endpoints are pipes that are exposed as HTTP API endpoints.
583
+ *
584
+ * @param name - The endpoint name (must be valid identifier)
585
+ * @param options - Endpoint configuration including params, nodes, and output schema
586
+ * @returns A pipe definition configured as an endpoint
587
+ *
588
+ * @example
589
+ * ```ts
590
+ * import { defineEndpoint, node, p, t } from '@tinybirdco/sdk';
591
+ *
592
+ * export const topEvents = defineEndpoint('top_events', {
593
+ * description: 'Get top events by count',
594
+ * params: {
595
+ * start_date: p.dateTime(),
596
+ * end_date: p.dateTime(),
597
+ * limit: p.int32().optional(10),
598
+ * },
599
+ * nodes: [
600
+ * node({
601
+ * name: 'aggregated',
602
+ * sql: `
603
+ * SELECT
604
+ * event_type,
605
+ * count() as event_count
606
+ * FROM events
607
+ * WHERE timestamp BETWEEN {{DateTime(start_date)}} AND {{DateTime(end_date)}}
608
+ * GROUP BY event_type
609
+ * ORDER BY event_count DESC
610
+ * LIMIT {{Int32(limit, 10)}}
611
+ * `,
612
+ * }),
613
+ * ],
614
+ * output: {
615
+ * event_type: t.string(),
616
+ * event_count: t.uint64(),
617
+ * },
618
+ * });
619
+ * ```
620
+ */
621
+ export function defineEndpoint<
622
+ TParams extends ParamsDefinition,
623
+ TOutput extends OutputDefinition
624
+ >(
625
+ name: string,
626
+ options: EndpointOptions<TParams, TOutput>
627
+ ): PipeDefinition<TParams, TOutput> {
628
+ return definePipe(name, {
629
+ description: options.description,
630
+ params: options.params,
631
+ nodes: options.nodes,
632
+ output: options.output,
633
+ endpoint: options.cache ? { enabled: true, cache: options.cache } : true,
634
+ tokens: options.tokens,
635
+ });
636
+ }
637
+
638
+ /**
639
+ * Define a Tinybird copy pipe
640
+ *
641
+ * Copy pipes capture the result of a pipe at a moment in time and write
642
+ * the result into a target data source. They can be run on a schedule,
643
+ * or executed on demand.
644
+ *
645
+ * Unlike materialized views which continuously update as new events are inserted,
646
+ * copy pipes generate a single snapshot at a specific point in time.
647
+ *
648
+ * @param name - The copy pipe name (must be valid identifier)
649
+ * @param options - Copy pipe configuration
650
+ * @returns A pipe definition configured as a copy pipe
651
+ *
652
+ * @example
653
+ * ```ts
654
+ * import { defineCopyPipe, defineDatasource, node, t, engine } from '@tinybirdco/sdk';
655
+ *
656
+ * // Target datasource for daily snapshots
657
+ * const dailySalesSnapshot = defineDatasource('daily_sales_snapshot', {
658
+ * schema: {
659
+ * snapshot_date: t.date(),
660
+ * country: t.string(),
661
+ * total_sales: t.uint64(),
662
+ * },
663
+ * engine: engine.mergeTree({
664
+ * sortingKey: ['snapshot_date', 'country'],
665
+ * }),
666
+ * });
667
+ *
668
+ * // Copy pipe that runs daily at midnight
669
+ * export const dailySalesCopy = defineCopyPipe('daily_sales_copy', {
670
+ * description: 'Daily snapshot of sales by country',
671
+ * datasource: dailySalesSnapshot,
672
+ * copy_schedule: '0 0 * * *', // Daily at midnight UTC
673
+ * copy_mode: 'append',
674
+ * nodes: [
675
+ * node({
676
+ * name: 'snapshot',
677
+ * sql: `
678
+ * SELECT
679
+ * today() AS snapshot_date,
680
+ * country,
681
+ * sum(sales) AS total_sales
682
+ * FROM sales
683
+ * WHERE date = today() - 1
684
+ * GROUP BY country
685
+ * `,
686
+ * }),
687
+ * ],
688
+ * });
689
+ * ```
690
+ */
691
+ export function defineCopyPipe<
692
+ TSchema extends SchemaDefinition,
693
+ TDatasource extends DatasourceDefinition<TSchema>
694
+ >(
695
+ name: string,
696
+ options: CopyPipeOptions<TSchema, TDatasource>
697
+ ): PipeDefinition<Record<string, never>, DatasourceSchemaToOutput<TSchema>> {
698
+ // Extract the schema from the datasource to build the output
699
+ const datasourceSchema = options.datasource._schema as TSchema;
700
+ const output: Record<string, AnyTypeValidator> = {};
701
+
702
+ for (const [columnName, column] of Object.entries(datasourceSchema)) {
703
+ output[columnName] = getColumnType(column);
704
+ }
705
+
706
+ return definePipe(name, {
707
+ description: options.description,
708
+ nodes: options.nodes,
709
+ output: output as DatasourceSchemaToOutput<TSchema>,
710
+ copy: {
711
+ datasource: options.datasource,
712
+ copy_mode: options.copy_mode,
713
+ copy_schedule: options.copy_schedule,
714
+ },
715
+ tokens: options.tokens,
716
+ });
717
+ }
718
+
719
+ /**
720
+ * Check if a value is a pipe definition
721
+ */
722
+ export function isPipeDefinition(value: unknown): value is PipeDefinition {
723
+ return (
724
+ typeof value === "object" &&
725
+ value !== null &&
726
+ PIPE_BRAND in value &&
727
+ (value as Record<symbol, unknown>)[PIPE_BRAND] === true
728
+ );
729
+ }
730
+
731
+ /**
732
+ * Get the endpoint configuration from a pipe
733
+ */
734
+ export function getEndpointConfig(pipe: PipeDefinition): EndpointConfig | null {
735
+ const { endpoint } = pipe.options;
736
+
737
+ if (!endpoint) {
738
+ return null;
739
+ }
740
+
741
+ if (typeof endpoint === "boolean") {
742
+ return endpoint ? { enabled: true } : null;
743
+ }
744
+
745
+ return endpoint.enabled ? endpoint : null;
746
+ }
747
+
748
+ /**
749
+ * Get the materialized view configuration from a pipe
750
+ */
751
+ export function getMaterializedConfig(pipe: PipeDefinition): MaterializedConfig | null {
752
+ return pipe.options.materialized ?? null;
753
+ }
754
+
755
+ /**
756
+ * Check if a pipe is a materialized view
757
+ */
758
+ export function isMaterializedView(pipe: PipeDefinition): boolean {
759
+ return pipe.options.materialized !== undefined;
760
+ }
761
+
762
+ /**
763
+ * Get the copy pipe configuration from a pipe
764
+ */
765
+ export function getCopyConfig(pipe: PipeDefinition): CopyConfig | null {
766
+ return pipe.options.copy ?? null;
767
+ }
768
+
769
+ /**
770
+ * Check if a pipe is a copy pipe
771
+ */
772
+ export function isCopyPipe(pipe: PipeDefinition): boolean {
773
+ return pipe.options.copy !== undefined;
774
+ }
775
+
776
+ /**
777
+ * Get all node names from a pipe
778
+ */
779
+ export function getNodeNames(pipe: PipeDefinition): string[] {
780
+ return pipe.options.nodes.map((n) => n._name);
781
+ }
782
+
783
+ /**
784
+ * Get a specific node by name
785
+ */
786
+ export function getNode(pipe: PipeDefinition, name: string): NodeDefinition | undefined {
787
+ return pipe.options.nodes.find((n) => n._name === name);
788
+ }
789
+
790
+ /**
791
+ * Helper type to extract params from a pipe definition
792
+ */
793
+ export type ExtractParams<T> = T extends PipeDefinition<infer P, OutputDefinition> ? P : never;
794
+
795
+ /**
796
+ * Helper type to extract output from a pipe definition
797
+ */
798
+ export type ExtractOutput<T> = T extends PipeDefinition<ParamsDefinition, infer O> ? O : never;
799
+
800
+ /**
801
+ * SQL template helper for referencing datasources and other nodes
802
+ * This is a simple helper - for complex templating, use raw strings
803
+ *
804
+ * @example
805
+ * ```ts
806
+ * import { sql, events } from './datasources/events';
807
+ *
808
+ * const query = sql`SELECT * FROM ${events} WHERE id = 1`;
809
+ * // Results in: "SELECT * FROM events WHERE id = 1"
810
+ * ```
811
+ */
812
+ export function sql(
813
+ strings: TemplateStringsArray,
814
+ ...values: (DatasourceDefinition | NodeDefinition | string | number)[]
815
+ ): string {
816
+ return strings.reduce((result, str, i) => {
817
+ const value = values[i];
818
+ if (value === undefined) {
819
+ return result + str;
820
+ }
821
+
822
+ if (typeof value === "string" || typeof value === "number") {
823
+ return result + str + String(value);
824
+ }
825
+
826
+ if ("_name" in value) {
827
+ return result + str + value._name;
828
+ }
829
+
830
+ return result + str;
831
+ }, "");
832
+ }