@tinybirdco/sdk 0.0.40 → 0.0.42

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 (100) hide show
  1. package/README.md +42 -1
  2. package/dist/api/api.d.ts +2 -0
  3. package/dist/api/api.d.ts.map +1 -1
  4. package/dist/api/api.js +1 -1
  5. package/dist/api/api.js.map +1 -1
  6. package/dist/api/api.test.js +14 -0
  7. package/dist/api/api.test.js.map +1 -1
  8. package/dist/api/resources.d.ts +72 -1
  9. package/dist/api/resources.d.ts.map +1 -1
  10. package/dist/api/resources.js +197 -1
  11. package/dist/api/resources.js.map +1 -1
  12. package/dist/api/resources.test.js +82 -1
  13. package/dist/api/resources.test.js.map +1 -1
  14. package/dist/cli/commands/migrate.d.ts +11 -0
  15. package/dist/cli/commands/migrate.d.ts.map +1 -0
  16. package/dist/cli/commands/migrate.js +196 -0
  17. package/dist/cli/commands/migrate.js.map +1 -0
  18. package/dist/cli/commands/migrate.test.d.ts +2 -0
  19. package/dist/cli/commands/migrate.test.d.ts.map +1 -0
  20. package/dist/cli/commands/migrate.test.js +473 -0
  21. package/dist/cli/commands/migrate.test.js.map +1 -0
  22. package/dist/cli/commands/pull.d.ts +59 -0
  23. package/dist/cli/commands/pull.d.ts.map +1 -0
  24. package/dist/cli/commands/pull.js +104 -0
  25. package/dist/cli/commands/pull.js.map +1 -0
  26. package/dist/cli/commands/pull.test.d.ts +2 -0
  27. package/dist/cli/commands/pull.test.d.ts.map +1 -0
  28. package/dist/cli/commands/pull.test.js +140 -0
  29. package/dist/cli/commands/pull.test.js.map +1 -0
  30. package/dist/cli/index.js +77 -0
  31. package/dist/cli/index.js.map +1 -1
  32. package/dist/client/base.d.ts +18 -1
  33. package/dist/client/base.d.ts.map +1 -1
  34. package/dist/client/base.js +43 -2
  35. package/dist/client/base.js.map +1 -1
  36. package/dist/client/base.test.js +5 -1
  37. package/dist/client/base.test.js.map +1 -1
  38. package/dist/client/types.d.ts +4 -0
  39. package/dist/client/types.d.ts.map +1 -1
  40. package/dist/migrate/discovery.d.ts +7 -0
  41. package/dist/migrate/discovery.d.ts.map +1 -0
  42. package/dist/migrate/discovery.js +125 -0
  43. package/dist/migrate/discovery.js.map +1 -0
  44. package/dist/migrate/emit-ts.d.ts +4 -0
  45. package/dist/migrate/emit-ts.d.ts.map +1 -0
  46. package/dist/migrate/emit-ts.js +387 -0
  47. package/dist/migrate/emit-ts.js.map +1 -0
  48. package/dist/migrate/parse-connection.d.ts +3 -0
  49. package/dist/migrate/parse-connection.d.ts.map +1 -0
  50. package/dist/migrate/parse-connection.js +74 -0
  51. package/dist/migrate/parse-connection.js.map +1 -0
  52. package/dist/migrate/parse-datasource.d.ts +3 -0
  53. package/dist/migrate/parse-datasource.d.ts.map +1 -0
  54. package/dist/migrate/parse-datasource.js +324 -0
  55. package/dist/migrate/parse-datasource.js.map +1 -0
  56. package/dist/migrate/parse-pipe.d.ts +3 -0
  57. package/dist/migrate/parse-pipe.d.ts.map +1 -0
  58. package/dist/migrate/parse-pipe.js +332 -0
  59. package/dist/migrate/parse-pipe.js.map +1 -0
  60. package/dist/migrate/parse.d.ts +3 -0
  61. package/dist/migrate/parse.d.ts.map +1 -0
  62. package/dist/migrate/parse.js +18 -0
  63. package/dist/migrate/parse.js.map +1 -0
  64. package/dist/migrate/parser-utils.d.ts +20 -0
  65. package/dist/migrate/parser-utils.d.ts.map +1 -0
  66. package/dist/migrate/parser-utils.js +130 -0
  67. package/dist/migrate/parser-utils.js.map +1 -0
  68. package/dist/migrate/types.d.ts +110 -0
  69. package/dist/migrate/types.d.ts.map +1 -0
  70. package/dist/migrate/types.js +2 -0
  71. package/dist/migrate/types.js.map +1 -0
  72. package/dist/schema/project.d.ts +13 -27
  73. package/dist/schema/project.d.ts.map +1 -1
  74. package/dist/schema/project.js +14 -24
  75. package/dist/schema/project.js.map +1 -1
  76. package/dist/schema/project.test.js +25 -13
  77. package/dist/schema/project.test.js.map +1 -1
  78. package/package.json +1 -1
  79. package/src/api/api.test.ts +25 -0
  80. package/src/api/api.ts +3 -1
  81. package/src/api/resources.test.ts +121 -0
  82. package/src/api/resources.ts +292 -1
  83. package/src/cli/commands/migrate.test.ts +564 -0
  84. package/src/cli/commands/migrate.ts +240 -0
  85. package/src/cli/commands/pull.test.ts +173 -0
  86. package/src/cli/commands/pull.ts +177 -0
  87. package/src/cli/index.ts +112 -0
  88. package/src/client/base.test.ts +5 -1
  89. package/src/client/base.ts +56 -2
  90. package/src/client/types.ts +8 -0
  91. package/src/migrate/discovery.ts +151 -0
  92. package/src/migrate/emit-ts.ts +469 -0
  93. package/src/migrate/parse-connection.ts +128 -0
  94. package/src/migrate/parse-datasource.ts +453 -0
  95. package/src/migrate/parse-pipe.ts +518 -0
  96. package/src/migrate/parse.ts +20 -0
  97. package/src/migrate/parser-utils.ts +160 -0
  98. package/src/migrate/types.ts +125 -0
  99. package/src/schema/project.test.ts +25 -13
  100. package/src/schema/project.ts +33 -57
package/src/cli/index.ts CHANGED
@@ -20,6 +20,7 @@ import { runBuild } from "./commands/build.js";
20
20
  import { runDeploy } from "./commands/deploy.js";
21
21
  import { runPreview } from "./commands/preview.js";
22
22
  import { runDev } from "./commands/dev.js";
23
+ import { runMigrate } from "./commands/migrate.js";
23
24
  import { runLogin } from "./commands/login.js";
24
25
  import {
25
26
  runBranchList,
@@ -28,6 +29,7 @@ import {
28
29
  } from "./commands/branch.js";
29
30
  import { runClear } from "./commands/clear.js";
30
31
  import { runInfo } from "./commands/info.js";
32
+ import { runPull } from "./commands/pull.js";
31
33
  import {
32
34
  runOpenDashboard,
33
35
  type Environment,
@@ -179,6 +181,50 @@ function createCli(): Command {
179
181
  }
180
182
  });
181
183
 
184
+ // Pull command
185
+ program
186
+ .command("pull")
187
+ .description(
188
+ "Download datasources, pipes, and connections from Tinybird as datafiles"
189
+ )
190
+ .option(
191
+ "-o, --output-dir <path>",
192
+ "Directory where pulled files are written",
193
+ "."
194
+ )
195
+ .option("-f, --force", "Overwrite existing files")
196
+ .action(async (options) => {
197
+ output.highlight("Pulling resources from Tinybird...");
198
+
199
+ const result = await runPull({
200
+ outputDir: options.outputDir,
201
+ overwrite: options.force,
202
+ });
203
+
204
+ if (!result.success) {
205
+ output.error(result.error ?? "Pull failed");
206
+ process.exit(1);
207
+ }
208
+
209
+ const files = result.files ?? [];
210
+ const stats = result.stats ?? {
211
+ datasources: 0,
212
+ pipes: 0,
213
+ connections: 0,
214
+ total: files.length,
215
+ };
216
+
217
+ for (const file of files) {
218
+ const status = file.status === "created" ? "created" : "changed";
219
+ output.showResourceChange(file.relativePath, status);
220
+ }
221
+
222
+ output.success(
223
+ `\nPulled ${stats.total} file(s): ${stats.datasources} datasource(s), ${stats.pipes} pipe(s), ${stats.connections} connection(s)`
224
+ );
225
+ output.success(`Completed in ${output.formatDuration(result.durationMs)}`);
226
+ });
227
+
182
228
  // Open command
183
229
  program
184
230
  .command("open")
@@ -314,6 +360,72 @@ function createCli(): Command {
314
360
  }
315
361
  });
316
362
 
363
+ // Migrate command
364
+ program
365
+ .command("migrate")
366
+ .description(
367
+ "Migrate .datasource/.pipe/.connection files into TypeScript definitions"
368
+ )
369
+ .argument(
370
+ "<patterns...>",
371
+ "One or more file paths, directories, or glob patterns"
372
+ )
373
+ .option(
374
+ "-o, --out <path>",
375
+ "Output TypeScript path (default: ./tinybird.migration.ts)"
376
+ )
377
+ .option("--dry-run", "Preview migration without writing files")
378
+ .option("--force", "Overwrite output file if it already exists")
379
+ .option(
380
+ "--no-strict",
381
+ "Do not fail command when some resources cannot be migrated"
382
+ )
383
+ .action(async (patterns: string[], options) => {
384
+ const result = await runMigrate({
385
+ patterns,
386
+ out: options.out,
387
+ strict: options.strict,
388
+ dryRun: options.dryRun,
389
+ force: options.force,
390
+ });
391
+
392
+ const migratedCounts = {
393
+ datasources: result.migrated.filter((resource) => resource.kind === "datasource")
394
+ .length,
395
+ pipes: result.migrated.filter((resource) => resource.kind === "pipe").length,
396
+ connections: result.migrated.filter(
397
+ (resource) => resource.kind === "connection"
398
+ ).length,
399
+ };
400
+
401
+ console.log(
402
+ `Migrated: ${migratedCounts.datasources} datasource(s), ${migratedCounts.pipes} pipe(s), ${migratedCounts.connections} connection(s)`
403
+ );
404
+
405
+ if (result.migrated.length > 0) {
406
+ if (result.dryRun) {
407
+ console.log(`[Dry run] Output would be written to: ${result.outputPath}`);
408
+ } else {
409
+ console.log(`Output written to: ${result.outputPath}`);
410
+ }
411
+ } else {
412
+ console.log("No resources were migrated.");
413
+ }
414
+
415
+ if (result.errors.length > 0) {
416
+ console.log("\nErrors:");
417
+ for (const error of result.errors) {
418
+ console.log(
419
+ `- ${error.filePath} (${error.resourceKind}:${error.resourceName}): ${error.message}`
420
+ );
421
+ }
422
+ }
423
+
424
+ if (!result.success) {
425
+ process.exit(1);
426
+ }
427
+ });
428
+
317
429
  // Deploy command
318
430
  program
319
431
  .command("deploy")
@@ -149,13 +149,15 @@ describe("TinybirdClient", () => {
149
149
  expect(client.datasources).toBeDefined();
150
150
  });
151
151
 
152
- it("datasources namespace has append method", () => {
152
+ it("datasources namespace has ingest/append/replace/delete/truncate methods", () => {
153
153
  const client = createClient({
154
154
  baseUrl: "https://api.tinybird.co",
155
155
  token: "test-token",
156
156
  });
157
157
 
158
+ expect(typeof client.datasources.ingest).toBe("function");
158
159
  expect(typeof client.datasources.append).toBe("function");
160
+ expect(typeof client.datasources.replace).toBe("function");
159
161
  expect(typeof client.datasources.delete).toBe("function");
160
162
  expect(typeof client.datasources.truncate).toBe("function");
161
163
  });
@@ -168,7 +170,9 @@ describe("TinybirdClient", () => {
168
170
 
169
171
  const datasources: DatasourcesNamespace = client.datasources;
170
172
  expect(datasources).toBeDefined();
173
+ expect(typeof datasources.ingest).toBe("function");
171
174
  expect(typeof datasources.append).toBe("function");
175
+ expect(typeof datasources.replace).toBe("function");
172
176
  expect(typeof datasources.delete).toBe("function");
173
177
  expect(typeof datasources.truncate).toBe("function");
174
178
  });
@@ -66,7 +66,7 @@ export class TinybirdClient {
66
66
  private resolvedContext: ClientContext | null = null;
67
67
 
68
68
  /**
69
- * Datasources namespace for import operations
69
+ * Datasources namespace for ingest and import operations
70
70
  */
71
71
  readonly datasources: DatasourcesNamespace;
72
72
 
@@ -90,9 +90,19 @@ export class TinybirdClient {
90
90
 
91
91
  // Initialize datasources namespace
92
92
  this.datasources = {
93
+ ingest: <T extends Record<string, unknown>>(
94
+ datasourceName: string,
95
+ event: T,
96
+ options: IngestOptions = {}
97
+ ): Promise<IngestResult> => {
98
+ return this.ingestDatasource(datasourceName, event, options);
99
+ },
93
100
  append: (datasourceName: string, options: AppendOptions): Promise<AppendResult> => {
94
101
  return this.appendDatasource(datasourceName, options);
95
102
  },
103
+ replace: (datasourceName: string, options: AppendOptions): Promise<AppendResult> => {
104
+ return this.replaceDatasource(datasourceName, options);
105
+ },
96
106
  delete: (datasourceName: string, options: DeleteOptions): Promise<DeleteResult> => {
97
107
  return this.deleteDatasource(datasourceName, options);
98
108
  },
@@ -146,6 +156,28 @@ export class TinybirdClient {
146
156
  }
147
157
  }
148
158
 
159
+ /**
160
+ * Replace datasource rows from a URL or local file
161
+ *
162
+ * @param datasourceName - Name of the datasource
163
+ * @param options - Replace options including url or file source
164
+ * @returns Append-style result
165
+ */
166
+ private async replaceDatasource(
167
+ datasourceName: string,
168
+ options: AppendOptions
169
+ ): Promise<AppendResult> {
170
+ const token = await this.getToken();
171
+
172
+ try {
173
+ return await this.getApi(token).appendDatasource(datasourceName, options, {
174
+ mode: "replace",
175
+ });
176
+ } catch (error) {
177
+ this.rethrowApiError(error);
178
+ }
179
+ }
180
+
149
181
  /**
150
182
  * Delete rows from a datasource using a SQL condition
151
183
  *
@@ -186,6 +218,28 @@ export class TinybirdClient {
186
218
  }
187
219
  }
188
220
 
221
+ /**
222
+ * Ingest a single event to a datasource
223
+ *
224
+ * @param datasourceName - Name of the datasource
225
+ * @param event - Event data to ingest
226
+ * @param options - Additional request options
227
+ * @returns Ingest result
228
+ */
229
+ private async ingestDatasource<T extends Record<string, unknown>>(
230
+ datasourceName: string,
231
+ event: T,
232
+ options: IngestOptions = {}
233
+ ): Promise<IngestResult> {
234
+ const token = await this.getToken();
235
+
236
+ try {
237
+ return await this.getApi(token).ingestBatch(datasourceName, [event], options);
238
+ } catch (error) {
239
+ this.rethrowApiError(error);
240
+ }
241
+ }
242
+
189
243
  /**
190
244
  * Get the effective token, resolving branch token in dev mode if needed
191
245
  */
@@ -351,7 +405,7 @@ export class TinybirdClient {
351
405
  event: T,
352
406
  options: IngestOptions = {}
353
407
  ): Promise<IngestResult> {
354
- return this.ingestBatch(datasourceName, [event], options);
408
+ return this.datasources.ingest(datasourceName, event, options);
355
409
  }
356
410
 
357
411
  /**
@@ -290,8 +290,16 @@ export interface TruncateResult {
290
290
  * Datasources namespace interface for raw client
291
291
  */
292
292
  export interface DatasourcesNamespace {
293
+ /** Ingest a single event into a datasource */
294
+ ingest<T extends Record<string, unknown>>(
295
+ datasourceName: string,
296
+ event: T,
297
+ options?: IngestOptions
298
+ ): Promise<IngestResult>;
293
299
  /** Append data to a datasource from a URL or file */
294
300
  append(datasourceName: string, options: AppendOptions): Promise<AppendResult>;
301
+ /** Replace all datasource rows with data from a URL or file */
302
+ replace(datasourceName: string, options: AppendOptions): Promise<AppendResult>;
295
303
  /** Delete rows from a datasource using a SQL condition */
296
304
  delete(datasourceName: string, options: DeleteOptions): Promise<DeleteResult>;
297
305
  /** Truncate all rows from a datasource */
@@ -0,0 +1,151 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { resolveIncludeFiles } from "../generator/include-paths.js";
4
+ import type { MigrationError, ResourceFile, ResourceKind } from "./types.js";
5
+
6
+ const SUPPORTED_EXTENSIONS = new Map<string, ResourceKind>([
7
+ [".datasource", "datasource"],
8
+ [".pipe", "pipe"],
9
+ [".connection", "connection"],
10
+ ]);
11
+
12
+ function normalizePath(filePath: string): string {
13
+ return filePath.replace(/\\/g, "/");
14
+ }
15
+
16
+ function getKindFromPath(filePath: string): ResourceKind | null {
17
+ const ext = path.extname(filePath).toLowerCase();
18
+ return SUPPORTED_EXTENSIONS.get(ext) ?? null;
19
+ }
20
+
21
+ function collectDirectoryFiles(directory: string): string[] {
22
+ const entries = fs.readdirSync(directory, { withFileTypes: true });
23
+ const files: string[] = [];
24
+
25
+ for (const entry of entries) {
26
+ const fullPath = path.join(directory, entry.name);
27
+
28
+ if (entry.isDirectory()) {
29
+ if (entry.name === "node_modules" || entry.name === ".git") {
30
+ continue;
31
+ }
32
+ files.push(...collectDirectoryFiles(fullPath));
33
+ continue;
34
+ }
35
+
36
+ if (entry.isFile()) {
37
+ files.push(fullPath);
38
+ }
39
+ }
40
+
41
+ return files;
42
+ }
43
+
44
+ export interface DiscoverResourcesResult {
45
+ resources: ResourceFile[];
46
+ errors: MigrationError[];
47
+ }
48
+
49
+ export function discoverResourceFiles(
50
+ patterns: string[],
51
+ cwd: string
52
+ ): DiscoverResourcesResult {
53
+ const resources: ResourceFile[] = [];
54
+ const errors: MigrationError[] = [];
55
+ const seen = new Set<string>();
56
+
57
+ for (const pattern of patterns) {
58
+ const absolutePattern = path.isAbsolute(pattern)
59
+ ? pattern
60
+ : path.resolve(cwd, pattern);
61
+
62
+ if (fs.existsSync(absolutePattern)) {
63
+ const stat = fs.statSync(absolutePattern);
64
+
65
+ if (stat.isDirectory()) {
66
+ const directoryFiles = collectDirectoryFiles(absolutePattern);
67
+ for (const absoluteFilePath of directoryFiles) {
68
+ const kind = getKindFromPath(absoluteFilePath);
69
+ if (!kind) {
70
+ continue;
71
+ }
72
+
73
+ const key = normalizePath(absoluteFilePath);
74
+ if (seen.has(key)) {
75
+ continue;
76
+ }
77
+ seen.add(key);
78
+
79
+ const relativePath = normalizePath(path.relative(cwd, absoluteFilePath));
80
+ const name = path.basename(absoluteFilePath, path.extname(absoluteFilePath));
81
+ resources.push({
82
+ kind,
83
+ filePath: relativePath,
84
+ absolutePath: absoluteFilePath,
85
+ name,
86
+ content: fs.readFileSync(absoluteFilePath, "utf-8"),
87
+ });
88
+ }
89
+ continue;
90
+ }
91
+
92
+ const kind = getKindFromPath(absolutePattern);
93
+ if (!kind) {
94
+ errors.push({
95
+ filePath: pattern,
96
+ resourceName: path.basename(absolutePattern),
97
+ resourceKind: "datasource",
98
+ message: `Unsupported file extension: ${path.extname(absolutePattern) || "(none)"}. Use .datasource, .pipe, or .connection.`,
99
+ });
100
+ continue;
101
+ }
102
+
103
+ const key = normalizePath(absolutePattern);
104
+ if (seen.has(key)) {
105
+ continue;
106
+ }
107
+ seen.add(key);
108
+ resources.push({
109
+ kind,
110
+ filePath: normalizePath(path.relative(cwd, absolutePattern)),
111
+ absolutePath: absolutePattern,
112
+ name: path.basename(absolutePattern, path.extname(absolutePattern)),
113
+ content: fs.readFileSync(absolutePattern, "utf-8"),
114
+ });
115
+ continue;
116
+ }
117
+
118
+ try {
119
+ const matched = resolveIncludeFiles([pattern], cwd);
120
+ for (const entry of matched) {
121
+ const kind = getKindFromPath(entry.absolutePath);
122
+ if (!kind) {
123
+ continue;
124
+ }
125
+ const key = normalizePath(entry.absolutePath);
126
+ if (seen.has(key)) {
127
+ continue;
128
+ }
129
+ seen.add(key);
130
+ resources.push({
131
+ kind,
132
+ filePath: normalizePath(entry.sourcePath),
133
+ absolutePath: entry.absolutePath,
134
+ name: path.basename(entry.absolutePath, path.extname(entry.absolutePath)),
135
+ content: fs.readFileSync(entry.absolutePath, "utf-8"),
136
+ });
137
+ }
138
+ } catch (error) {
139
+ errors.push({
140
+ filePath: pattern,
141
+ resourceName: path.basename(pattern),
142
+ resourceKind: "datasource",
143
+ message: (error as Error).message,
144
+ });
145
+ }
146
+ }
147
+
148
+ resources.sort((a, b) => a.filePath.localeCompare(b.filePath));
149
+ return { resources, errors };
150
+ }
151
+