@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,406 @@
1
+ /**
2
+ * Schema loader using esbuild
3
+ * Bundles and executes TypeScript schema files at runtime
4
+ */
5
+
6
+ import * as esbuild from "esbuild";
7
+ import * as path from "path";
8
+ import * as fs from "fs";
9
+ import { watch as chokidarWatch, type FSWatcher } from "chokidar";
10
+ import { isProjectDefinition, type ProjectDefinition, type DatasourcesDefinition, type PipesDefinition } from "../schema/project.js";
11
+ import { isDatasourceDefinition, type DatasourceDefinition } from "../schema/datasource.js";
12
+ import { isPipeDefinition, type PipeDefinition } from "../schema/pipe.js";
13
+
14
+ /**
15
+ * Result of loading a schema file
16
+ */
17
+ export interface LoadedSchema {
18
+ /** The loaded project definition */
19
+ project: ProjectDefinition;
20
+ /** The resolved path to the schema file */
21
+ schemaPath: string;
22
+ /** The directory containing the schema */
23
+ schemaDir: string;
24
+ }
25
+
26
+ /**
27
+ * Options for the schema loader
28
+ */
29
+ export interface LoaderOptions {
30
+ /** The path to the schema file (can be relative or absolute) */
31
+ schemaPath: string;
32
+ /** The working directory for resolution (defaults to cwd) */
33
+ cwd?: string;
34
+ }
35
+
36
+ /**
37
+ * Load and execute a TypeScript schema file
38
+ *
39
+ * Uses esbuild to bundle the schema and its dependencies,
40
+ * then dynamically imports the bundle to get the ProjectDefinition.
41
+ *
42
+ * @param options - Loader options
43
+ * @returns The loaded project definition
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * const { project } = await loadSchema({
48
+ * schemaPath: 'src/tinybird/schema.ts',
49
+ * });
50
+ *
51
+ * console.log(project.datasources);
52
+ * console.log(project.pipes);
53
+ * ```
54
+ */
55
+ export async function loadSchema(options: LoaderOptions): Promise<LoadedSchema> {
56
+ const cwd = options.cwd ?? process.cwd();
57
+ const schemaPath = path.isAbsolute(options.schemaPath)
58
+ ? options.schemaPath
59
+ : path.resolve(cwd, options.schemaPath);
60
+
61
+ // Verify the file exists
62
+ if (!fs.existsSync(schemaPath)) {
63
+ throw new Error(`Schema file not found: ${schemaPath}`);
64
+ }
65
+
66
+ const schemaDir = path.dirname(schemaPath);
67
+
68
+ // Create a temporary output file for the bundle
69
+ const outfile = path.join(
70
+ schemaDir,
71
+ `.tinybird-schema-${Date.now()}.mjs`
72
+ );
73
+
74
+ try {
75
+ // Bundle the schema with esbuild
76
+ await esbuild.build({
77
+ entryPoints: [schemaPath],
78
+ outfile,
79
+ bundle: true,
80
+ platform: "node",
81
+ format: "esm",
82
+ target: "node18",
83
+ // Mark @tinybirdco/sdk as external - it should already be installed
84
+ external: ["@tinybirdco/sdk"],
85
+ // Enable source maps for better error messages
86
+ sourcemap: "inline",
87
+ // Minify is off for debugging
88
+ minify: false,
89
+ });
90
+
91
+ // Import the bundled module
92
+ const moduleUrl = `file://${outfile}`;
93
+ const module = await import(moduleUrl);
94
+
95
+ // Look for the project definition
96
+ // It can be the default export or a named 'project' export
97
+ let project: ProjectDefinition | undefined;
98
+
99
+ if (module.default && isProjectDefinition(module.default)) {
100
+ project = module.default;
101
+ } else if (module.project && isProjectDefinition(module.project)) {
102
+ project = module.project;
103
+ } else {
104
+ // Check all exports for a project definition
105
+ for (const key of Object.keys(module)) {
106
+ if (isProjectDefinition(module[key])) {
107
+ project = module[key];
108
+ break;
109
+ }
110
+ }
111
+ }
112
+
113
+ if (!project) {
114
+ throw new Error(
115
+ `No ProjectDefinition found in ${schemaPath}. ` +
116
+ `Make sure to export a project created with defineProject().`
117
+ );
118
+ }
119
+
120
+ return {
121
+ project,
122
+ schemaPath,
123
+ schemaDir,
124
+ };
125
+ } finally {
126
+ // Clean up the temporary bundle file
127
+ try {
128
+ if (fs.existsSync(outfile)) {
129
+ fs.unlinkSync(outfile);
130
+ }
131
+ // Also clean up the source map if it was created separately
132
+ const sourcemapFile = outfile + ".map";
133
+ if (fs.existsSync(sourcemapFile)) {
134
+ fs.unlinkSync(sourcemapFile);
135
+ }
136
+ } catch {
137
+ // Ignore cleanup errors
138
+ }
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Information about an entity discovered from a source file
144
+ */
145
+ export interface EntityInfo {
146
+ /** The export name used in the source file */
147
+ exportName: string;
148
+ /** The source file path (relative to cwd) */
149
+ sourceFile: string;
150
+ }
151
+
152
+ /**
153
+ * Result of loading entities from multiple files
154
+ */
155
+ export interface LoadedEntities {
156
+ /** Discovered datasources with their metadata */
157
+ datasources: Record<string, { definition: DatasourceDefinition; info: EntityInfo }>;
158
+ /** Discovered pipes with their metadata */
159
+ pipes: Record<string, { definition: PipeDefinition; info: EntityInfo }>;
160
+ /** All source files that were scanned */
161
+ sourceFiles: string[];
162
+ }
163
+
164
+ /**
165
+ * Options for loading entities
166
+ */
167
+ export interface LoadEntitiesOptions {
168
+ /** Array of file paths to scan (can be relative or absolute) */
169
+ includePaths: string[];
170
+ /** The working directory for resolution (defaults to cwd) */
171
+ cwd?: string;
172
+ }
173
+
174
+ /**
175
+ * Load datasources and pipes from multiple TypeScript files
176
+ *
177
+ * Uses esbuild to bundle each file and scans exports for datasource
178
+ * and pipe definitions.
179
+ *
180
+ * @param options - Loader options
181
+ * @returns Discovered entities with metadata
182
+ *
183
+ * @example
184
+ * ```ts
185
+ * const entities = await loadEntities({
186
+ * includePaths: ['src/datasources.ts', 'src/pipes.ts'],
187
+ * });
188
+ *
189
+ * console.log(Object.keys(entities.datasources)); // ['pageViews', 'events']
190
+ * console.log(Object.keys(entities.pipes)); // ['topPages', 'topEvents']
191
+ * ```
192
+ */
193
+ export async function loadEntities(options: LoadEntitiesOptions): Promise<LoadedEntities> {
194
+ const cwd = options.cwd ?? process.cwd();
195
+ const result: LoadedEntities = {
196
+ datasources: {},
197
+ pipes: {},
198
+ sourceFiles: [],
199
+ };
200
+
201
+ for (const includePath of options.includePaths) {
202
+ const absolutePath = path.isAbsolute(includePath)
203
+ ? includePath
204
+ : path.resolve(cwd, includePath);
205
+
206
+ // Verify the file exists
207
+ if (!fs.existsSync(absolutePath)) {
208
+ throw new Error(`Include file not found: ${absolutePath}`);
209
+ }
210
+
211
+ result.sourceFiles.push(includePath);
212
+ const fileDir = path.dirname(absolutePath);
213
+
214
+ // Create a temporary output file for the bundle
215
+ const outfile = path.join(
216
+ fileDir,
217
+ `.tinybird-entities-${Date.now()}.mjs`
218
+ );
219
+
220
+ try {
221
+ // Bundle the file with esbuild
222
+ await esbuild.build({
223
+ entryPoints: [absolutePath],
224
+ outfile,
225
+ bundle: true,
226
+ platform: "node",
227
+ format: "esm",
228
+ target: "node18",
229
+ // Mark @tinybirdco/sdk as external - it should already be installed
230
+ external: ["@tinybirdco/sdk"],
231
+ // Enable source maps for better error messages
232
+ sourcemap: "inline",
233
+ minify: false,
234
+ });
235
+
236
+ // Import the bundled module
237
+ const moduleUrl = `file://${outfile}`;
238
+ const module = await import(moduleUrl);
239
+
240
+ // Scan all exports for datasources and pipes
241
+ for (const [exportName, value] of Object.entries(module)) {
242
+ if (isDatasourceDefinition(value)) {
243
+ result.datasources[exportName] = {
244
+ definition: value,
245
+ info: {
246
+ exportName,
247
+ sourceFile: includePath,
248
+ },
249
+ };
250
+ } else if (isPipeDefinition(value)) {
251
+ result.pipes[exportName] = {
252
+ definition: value,
253
+ info: {
254
+ exportName,
255
+ sourceFile: includePath,
256
+ },
257
+ };
258
+ }
259
+ }
260
+ } finally {
261
+ // Clean up the temporary bundle file
262
+ try {
263
+ if (fs.existsSync(outfile)) {
264
+ fs.unlinkSync(outfile);
265
+ }
266
+ const sourcemapFile = outfile + ".map";
267
+ if (fs.existsSync(sourcemapFile)) {
268
+ fs.unlinkSync(sourcemapFile);
269
+ }
270
+ } catch {
271
+ // Ignore cleanup errors
272
+ }
273
+ }
274
+ }
275
+
276
+ return result;
277
+ }
278
+
279
+ /**
280
+ * Convert loaded entities to a format compatible with generators
281
+ */
282
+ export function entitiesToProject(entities: LoadedEntities): {
283
+ datasources: DatasourcesDefinition;
284
+ pipes: PipesDefinition;
285
+ } {
286
+ const datasources: DatasourcesDefinition = {};
287
+ const pipes: PipesDefinition = {};
288
+
289
+ for (const [name, { definition }] of Object.entries(entities.datasources)) {
290
+ datasources[name] = definition;
291
+ }
292
+
293
+ for (const [name, { definition }] of Object.entries(entities.pipes)) {
294
+ pipes[name] = definition;
295
+ }
296
+
297
+ return { datasources, pipes };
298
+ }
299
+
300
+ /**
301
+ * Watch options for the schema loader
302
+ */
303
+ export interface WatchOptions extends LoaderOptions {
304
+ /** Callback when the schema changes */
305
+ onChange: (result: LoadedSchema) => void | Promise<void>;
306
+ /** Callback when there's an error loading the schema */
307
+ onError?: (error: Error) => void;
308
+ /** Debounce delay in milliseconds (default: 100) */
309
+ debounce?: number;
310
+ }
311
+
312
+ /**
313
+ * Schema watcher controller
314
+ */
315
+ export interface SchemaWatcher {
316
+ /** Stop watching for changes */
317
+ close: () => Promise<void>;
318
+ /** The initial loaded schema */
319
+ initialSchema: LoadedSchema;
320
+ }
321
+
322
+ /**
323
+ * Watch a TypeScript schema file for changes
324
+ *
325
+ * Performs an initial load, then watches for file changes and reloads.
326
+ * Uses debouncing to coalesce rapid file system events.
327
+ *
328
+ * @param options - Watch options
329
+ * @returns A controller to stop watching
330
+ *
331
+ * @example
332
+ * ```ts
333
+ * const watcher = await watchSchema({
334
+ * schemaPath: 'src/tinybird/schema.ts',
335
+ * onChange: (schema) => {
336
+ * console.log('Schema updated:', schema.project);
337
+ * },
338
+ * onError: (err) => {
339
+ * console.error('Load error:', err.message);
340
+ * },
341
+ * });
342
+ *
343
+ * // Later, stop watching
344
+ * await watcher.close();
345
+ * ```
346
+ */
347
+ export async function watchSchema(options: WatchOptions): Promise<SchemaWatcher> {
348
+ const debounceMs = options.debounce ?? 100;
349
+
350
+ // Perform initial load
351
+ const initialSchema = await loadSchema(options);
352
+
353
+ const schemaPath = initialSchema.schemaPath;
354
+ const schemaDir = initialSchema.schemaDir;
355
+
356
+ // Set up debounced reload
357
+ let debounceTimer: ReturnType<typeof setTimeout> | null = null;
358
+
359
+ const reload = async () => {
360
+ try {
361
+ const result = await loadSchema(options);
362
+ await options.onChange(result);
363
+ } catch (error) {
364
+ if (options.onError) {
365
+ options.onError(error as Error);
366
+ }
367
+ }
368
+ };
369
+
370
+ const debouncedReload = () => {
371
+ if (debounceTimer) {
372
+ clearTimeout(debounceTimer);
373
+ }
374
+ debounceTimer = setTimeout(() => {
375
+ debounceTimer = null;
376
+ reload().catch((error) => {
377
+ if (options.onError) {
378
+ options.onError(error as Error);
379
+ }
380
+ });
381
+ }, debounceMs);
382
+ };
383
+
384
+ // Watch the schema file and its directory for TypeScript files
385
+ const watcher: FSWatcher = chokidarWatch([schemaPath, path.join(schemaDir, "**/*.ts")], {
386
+ ignoreInitial: true,
387
+ ignored: [
388
+ /node_modules/,
389
+ /\.tinybird-schema-.*\.mjs$/,
390
+ ],
391
+ });
392
+
393
+ watcher.on("change", debouncedReload);
394
+ watcher.on("add", debouncedReload);
395
+ watcher.on("unlink", debouncedReload);
396
+
397
+ return {
398
+ close: async () => {
399
+ if (debounceTimer) {
400
+ clearTimeout(debounceTimer);
401
+ }
402
+ await watcher.close();
403
+ },
404
+ initialSchema,
405
+ };
406
+ }