@tinybirdco/sdk 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/api/branches.d.ts.map +1 -1
  2. package/dist/api/branches.js +6 -5
  3. package/dist/api/branches.js.map +1 -1
  4. package/dist/api/branches.test.js +32 -6
  5. package/dist/api/branches.test.js.map +1 -1
  6. package/dist/api/build.d.ts.map +1 -1
  7. package/dist/api/build.js +2 -1
  8. package/dist/api/build.js.map +1 -1
  9. package/dist/api/deploy.d.ts +1 -0
  10. package/dist/api/deploy.d.ts.map +1 -1
  11. package/dist/api/deploy.js +27 -6
  12. package/dist/api/deploy.js.map +1 -1
  13. package/dist/api/deploy.test.js +6 -2
  14. package/dist/api/deploy.test.js.map +1 -1
  15. package/dist/api/fetcher.d.ts +6 -0
  16. package/dist/api/fetcher.d.ts.map +1 -0
  17. package/dist/api/fetcher.js +13 -0
  18. package/dist/api/fetcher.js.map +1 -0
  19. package/dist/api/local.d.ts.map +1 -1
  20. package/dist/api/local.js +5 -4
  21. package/dist/api/local.js.map +1 -1
  22. package/dist/api/local.test.js.map +1 -1
  23. package/dist/api/resources.d.ts.map +1 -1
  24. package/dist/api/resources.js +5 -4
  25. package/dist/api/resources.js.map +1 -1
  26. package/dist/api/workspaces.d.ts.map +1 -1
  27. package/dist/api/workspaces.js +2 -1
  28. package/dist/api/workspaces.js.map +1 -1
  29. package/dist/api/workspaces.test.js +9 -1
  30. package/dist/api/workspaces.test.js.map +1 -1
  31. package/dist/cli/auth.d.ts.map +1 -1
  32. package/dist/cli/auth.js +2 -1
  33. package/dist/cli/auth.js.map +1 -1
  34. package/dist/cli/commands/deploy.d.ts +2 -0
  35. package/dist/cli/commands/deploy.d.ts.map +1 -1
  36. package/dist/cli/commands/deploy.js +3 -1
  37. package/dist/cli/commands/deploy.js.map +1 -1
  38. package/dist/cli/commands/init.d.ts +14 -0
  39. package/dist/cli/commands/init.d.ts.map +1 -1
  40. package/dist/cli/commands/init.js +264 -4
  41. package/dist/cli/commands/init.js.map +1 -1
  42. package/dist/cli/index.js +8 -3
  43. package/dist/cli/index.js.map +1 -1
  44. package/dist/client/base.d.ts.map +1 -1
  45. package/dist/client/base.js +2 -1
  46. package/dist/client/base.js.map +1 -1
  47. package/dist/schema/connection.d.ts.map +1 -1
  48. package/dist/schema/connection.js +3 -2
  49. package/dist/schema/connection.js.map +1 -1
  50. package/dist/schema/datasource.d.ts.map +1 -1
  51. package/dist/schema/datasource.js +3 -2
  52. package/dist/schema/datasource.js.map +1 -1
  53. package/dist/schema/params.d.ts.map +1 -1
  54. package/dist/schema/params.js +3 -2
  55. package/dist/schema/params.js.map +1 -1
  56. package/dist/schema/pipe.d.ts +2 -2
  57. package/dist/schema/pipe.d.ts.map +1 -1
  58. package/dist/schema/pipe.js +4 -4
  59. package/dist/schema/pipe.js.map +1 -1
  60. package/dist/schema/project.d.ts.map +1 -1
  61. package/dist/schema/project.js +3 -2
  62. package/dist/schema/project.js.map +1 -1
  63. package/dist/schema/types.d.ts.map +1 -1
  64. package/dist/schema/types.js +3 -2
  65. package/dist/schema/types.js.map +1 -1
  66. package/package.json +1 -1
  67. package/src/api/branches.test.ts +65 -57
  68. package/src/api/branches.ts +7 -5
  69. package/src/api/build.ts +2 -1
  70. package/src/api/deploy.test.ts +6 -2
  71. package/src/api/deploy.ts +35 -7
  72. package/src/api/fetcher.ts +17 -0
  73. package/src/api/local.test.ts +43 -31
  74. package/src/api/local.ts +5 -4
  75. package/src/api/resources.ts +5 -4
  76. package/src/api/workspaces.test.ts +15 -9
  77. package/src/api/workspaces.ts +3 -1
  78. package/src/cli/auth.ts +2 -1
  79. package/src/cli/commands/deploy.ts +6 -1
  80. package/src/cli/commands/init.ts +311 -6
  81. package/src/cli/index.ts +35 -11
  82. package/src/client/base.ts +3 -2
  83. package/src/schema/connection.ts +3 -2
  84. package/src/schema/datasource.ts +3 -2
  85. package/src/schema/params.ts +3 -2
  86. package/src/schema/pipe.ts +4 -4
  87. package/src/schema/project.ts +3 -2
  88. package/src/schema/types.ts +3 -2
@@ -114,6 +114,64 @@ export type { PageViewsRow, TopPagesParams, TopPagesOutput };
114
114
  export { pageViews, topPages };
115
115
  `;
116
116
 
117
+ const TINYBIRD_CI_WORKFLOW = `name: Tinybird CI
118
+
119
+ on:
120
+ pull_request:
121
+ paths:
122
+ - "tinybird.json"
123
+ - "src/tinybird/**"
124
+ - "tinybird/**"
125
+ - "**/*.datasource"
126
+ - "**/*.pipe"
127
+
128
+ jobs:
129
+ tinybird:
130
+ runs-on: ubuntu-latest
131
+ steps:
132
+ - uses: actions/checkout@v4
133
+ - uses: pnpm/action-setup@v4
134
+ - uses: actions/setup-node@v4
135
+ with:
136
+ node-version: "20"
137
+ cache: "pnpm"
138
+ - run: pnpm install --frozen-lockfile
139
+ - run: pnpm run tinybird:deploy -- --check
140
+ env:
141
+ TINYBIRD_TOKEN: \${{ secrets.TINYBIRD_TOKEN }}
142
+ `;
143
+
144
+ const TINYBIRD_CD_WORKFLOW = `name: Tinybird CD
145
+
146
+ on:
147
+ push:
148
+ branches:
149
+ - main
150
+ paths:
151
+ - "tinybird.json"
152
+ - "src/tinybird/**"
153
+ - "tinybird/**"
154
+ - "**/*.datasource"
155
+ - "**/*.pipe"
156
+
157
+ jobs:
158
+ tinybird:
159
+ runs-on: ubuntu-latest
160
+ steps:
161
+ - uses: actions/checkout@v4
162
+ - uses: pnpm/action-setup@v4
163
+ - uses: actions/setup-node@v4
164
+ with:
165
+ node-version: "20"
166
+ cache: "pnpm"
167
+ - run: pnpm install --frozen-lockfile
168
+ - run: pnpm run tinybird:deploy
169
+ env:
170
+ TINYBIRD_TOKEN: \${{ secrets.TINYBIRD_TOKEN }}
171
+ `;
172
+
173
+ const TINYBIRD_GITLAB_CI = `stages:\n - tinybird\n\ntinybird_ci:\n stage: tinybird\n image: node:20\n rules:\n - changes:\n - tinybird.json\n - src/tinybird/**\n - tinybird/**\n - **/*.datasource\n - **/*.pipe\n script:\n - corepack enable\n - pnpm install --frozen-lockfile\n - pnpm run tinybird:deploy -- --check\n variables:\n TINYBIRD_TOKEN: \${TINYBIRD_TOKEN}\n\ntinybird_cd:\n stage: tinybird\n image: node:20\n rules:\n - if: '$CI_COMMIT_BRANCH == \"main\"'\n changes:\n - tinybird.json\n - src/tinybird/**\n - tinybird/**\n - **/*.datasource\n - **/*.pipe\n script:\n - corepack enable\n - pnpm install --frozen-lockfile\n - pnpm run tinybird:deploy\n variables:\n TINYBIRD_TOKEN: \${TINYBIRD_TOKEN}\n`;
174
+
117
175
  /**
118
176
  * Default config content generator
119
177
  */
@@ -153,6 +211,14 @@ export interface InitOptions {
153
211
  skipDatafilePrompt?: boolean;
154
212
  /** Auto-include existing datafiles without prompting - for testing */
155
213
  includeExistingDatafiles?: boolean;
214
+ /** Skip GitHub Actions workflow prompts */
215
+ skipWorkflowPrompt?: boolean;
216
+ /** Include Tinybird CI workflow */
217
+ includeCiWorkflow?: boolean;
218
+ /** Include Tinybird CD workflow */
219
+ includeCdWorkflow?: boolean;
220
+ /** Git provider for workflow templates */
221
+ workflowProvider?: "github" | "gitlab";
156
222
  }
157
223
 
158
224
  /**
@@ -179,6 +245,12 @@ export interface InitResult {
179
245
  clientPath?: string;
180
246
  /** Existing datafiles that were added to config */
181
247
  existingDatafiles?: string[];
248
+ /** Whether a Tinybird CI workflow was created */
249
+ ciWorkflowCreated?: boolean;
250
+ /** Whether a Tinybird CD workflow was created */
251
+ cdWorkflowCreated?: boolean;
252
+ /** Git provider used for workflow templates */
253
+ workflowProvider?: "github" | "gitlab";
182
254
  }
183
255
 
184
256
  /**
@@ -252,7 +324,11 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
252
324
 
253
325
  const created: string[] = [];
254
326
  const skipped: string[] = [];
327
+ let didPrompt = false;
255
328
  let existingDatafiles: string[] = [];
329
+ let ciWorkflowCreated = false;
330
+ let cdWorkflowCreated = false;
331
+ let workflowProvider = options.workflowProvider;
256
332
 
257
333
  // Check for existing .datasource and .pipe files
258
334
  const foundDatafiles = findExistingDatafiles(cwd);
@@ -281,7 +357,7 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
281
357
  });
282
358
 
283
359
  if (p.isCancel(workflowChoice)) {
284
- p.cancel("Init cancelled.");
360
+ p.cancel("Operation cancelled");
285
361
  return {
286
362
  success: false,
287
363
  created: [],
@@ -290,6 +366,7 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
290
366
  };
291
367
  }
292
368
 
369
+ didPrompt = true;
293
370
  devMode = workflowChoice as DevMode;
294
371
  }
295
372
 
@@ -300,13 +377,13 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
300
377
  if (!options.clientPath && !options.devMode) {
301
378
  // Ask user to confirm or change the client path
302
379
  const clientPathChoice = await p.text({
303
- message: "Where should we generate the Tinybird client?",
380
+ message: "Where should we create Tinybird definitions?",
304
381
  placeholder: defaultRelativePath,
305
382
  defaultValue: defaultRelativePath,
306
383
  });
307
384
 
308
385
  if (p.isCancel(clientPathChoice)) {
309
- p.cancel("Init cancelled.");
386
+ p.cancel("Operation cancelled");
310
387
  return {
311
388
  success: false,
312
389
  created: [],
@@ -315,14 +392,97 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
315
392
  };
316
393
  }
317
394
 
395
+ didPrompt = true;
318
396
  relativeTinybirdDir = clientPathChoice || defaultRelativePath;
319
397
  }
320
398
 
399
+ const skipWorkflowPrompt =
400
+ options.skipWorkflowPrompt ??
401
+ (options.devMode !== undefined || options.clientPath !== undefined);
402
+ let includeCiWorkflow = options.includeCiWorkflow ?? false;
403
+ let includeCdWorkflow = options.includeCdWorkflow ?? false;
404
+ const shouldPromptWorkflows =
405
+ !skipWorkflowPrompt && options.includeCiWorkflow === undefined;
406
+ const shouldPromptProvider = !skipWorkflowPrompt && !workflowProvider;
407
+
408
+ if (shouldPromptProvider && shouldPromptWorkflows) {
409
+ const providerChoice = await p.select({
410
+ message: "Which git provider are you using?",
411
+ options: [
412
+ { value: "github", label: "GitHub" },
413
+ { value: "gitlab", label: "GitLab" },
414
+ ],
415
+ });
416
+
417
+ if (p.isCancel(providerChoice)) {
418
+ p.cancel("Operation cancelled");
419
+ return {
420
+ success: false,
421
+ created: [],
422
+ skipped: [],
423
+ error: "Cancelled by user",
424
+ };
425
+ }
426
+
427
+ didPrompt = true;
428
+ workflowProvider = providerChoice as "github" | "gitlab";
429
+ }
430
+
431
+ if (shouldPromptWorkflows) {
432
+ const confirmWorkflows = await p.confirm({
433
+ message: "Create CI/CD workflows for Tinybird?",
434
+ initialValue: true,
435
+ });
436
+
437
+ if (p.isCancel(confirmWorkflows)) {
438
+ p.cancel("Operation cancelled");
439
+ return {
440
+ success: false,
441
+ created: [],
442
+ skipped: [],
443
+ error: "Cancelled by user",
444
+ };
445
+ }
446
+
447
+ didPrompt = true;
448
+ includeCiWorkflow = confirmWorkflows;
449
+ includeCdWorkflow = confirmWorkflows;
450
+ }
451
+
452
+ if (shouldPromptProvider && !shouldPromptWorkflows && !workflowProvider) {
453
+ const providerChoice = await p.select({
454
+ message: "Which git provider are you using?",
455
+ options: [
456
+ { value: "github", label: "GitHub" },
457
+ { value: "gitlab", label: "GitLab" },
458
+ ],
459
+ });
460
+
461
+ if (p.isCancel(providerChoice)) {
462
+ p.cancel("Operation cancelled");
463
+ return {
464
+ success: false,
465
+ created: [],
466
+ skipped: [],
467
+ error: "Cancelled by user",
468
+ };
469
+ }
470
+
471
+ didPrompt = true;
472
+ workflowProvider = providerChoice as "github" | "gitlab";
473
+ }
474
+ if ((includeCiWorkflow || includeCdWorkflow) && !workflowProvider) {
475
+ workflowProvider = "github";
476
+ }
477
+
321
478
  // Ask about existing datafiles if found
322
479
  if (foundDatafiles.length > 0 && !options.skipDatafilePrompt) {
323
480
  const includeDatafiles =
324
481
  options.includeExistingDatafiles ??
325
- (await promptForExistingDatafiles(foundDatafiles));
482
+ (await (async () => {
483
+ didPrompt = true;
484
+ return promptForExistingDatafiles(foundDatafiles);
485
+ })());
326
486
 
327
487
  if (includeDatafiles) {
328
488
  existingDatafiles = foundDatafiles;
@@ -331,6 +491,47 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
331
491
  existingDatafiles = foundDatafiles;
332
492
  }
333
493
 
494
+ if (didPrompt) {
495
+ const devModeLabel = devMode === "local" ? "Tinybird Local" : "Branches";
496
+ const datafileSummary =
497
+ foundDatafiles.length > 0
498
+ ? existingDatafiles.length > 0
499
+ ? `${existingDatafiles.length} included`
500
+ : "none included"
501
+ : "none found";
502
+ let workflowSummary = "none";
503
+ if (includeCiWorkflow || includeCdWorkflow) {
504
+ workflowSummary =
505
+ workflowProvider === "gitlab"
506
+ ? "GitLab (.gitlab-ci.yml)"
507
+ : "GitHub Actions (tinybird-ci.yaml, tinybird-cd.yaml)";
508
+ }
509
+
510
+ const summaryLines = [
511
+ `Mode: ${devModeLabel}`,
512
+ `Definitions: ${relativeTinybirdDir}/`,
513
+ `Existing datafiles: ${datafileSummary}`,
514
+ `Workflows: ${workflowSummary}`,
515
+ ];
516
+
517
+ p.note(summaryLines.join("\n"), "Installation Summary");
518
+
519
+ const confirmInit = await p.confirm({
520
+ message: "Proceed with initialization?",
521
+ initialValue: true,
522
+ });
523
+
524
+ if (p.isCancel(confirmInit) || !confirmInit) {
525
+ p.cancel("Init cancelled.");
526
+ return {
527
+ success: false,
528
+ created: [],
529
+ skipped: [],
530
+ error: "Cancelled by user",
531
+ };
532
+ }
533
+ }
534
+
334
535
  const tinybirdDir = path.join(cwd, relativeTinybirdDir);
335
536
 
336
537
  // File paths
@@ -464,6 +665,94 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
464
665
  }
465
666
  }
466
667
 
668
+ const workflowsDir = path.join(cwd, ".github", "workflows");
669
+ const ciWorkflowPath = path.join(workflowsDir, "tinybird-ci.yaml");
670
+ const cdWorkflowPath = path.join(workflowsDir, "tinybird-cd.yaml");
671
+ const gitlabWorkflowPath = path.join(cwd, ".gitlab-ci.yml");
672
+
673
+ if (includeCiWorkflow || includeCdWorkflow) {
674
+ if (workflowProvider === "github") {
675
+ try {
676
+ fs.mkdirSync(workflowsDir, { recursive: true });
677
+ } catch (error) {
678
+ return {
679
+ success: false,
680
+ created,
681
+ skipped,
682
+ error: `Failed to create .github/workflows folder: ${
683
+ (error as Error).message
684
+ }`,
685
+ };
686
+ }
687
+ }
688
+
689
+ if (workflowProvider === "gitlab") {
690
+ if (fs.existsSync(gitlabWorkflowPath) && !force) {
691
+ skipped.push(".gitlab-ci.yml");
692
+ } else {
693
+ try {
694
+ fs.writeFileSync(gitlabWorkflowPath, TINYBIRD_GITLAB_CI);
695
+ created.push(".gitlab-ci.yml");
696
+ ciWorkflowCreated = includeCiWorkflow;
697
+ cdWorkflowCreated = includeCdWorkflow;
698
+ } catch (error) {
699
+ return {
700
+ success: false,
701
+ created,
702
+ skipped,
703
+ error: `Failed to create .gitlab-ci.yml: ${
704
+ (error as Error).message
705
+ }`,
706
+ };
707
+ }
708
+ }
709
+ }
710
+ }
711
+
712
+ if (workflowProvider === "github") {
713
+ if (includeCiWorkflow) {
714
+ if (fs.existsSync(ciWorkflowPath) && !force) {
715
+ skipped.push(".github/workflows/tinybird-ci.yaml");
716
+ } else {
717
+ try {
718
+ fs.writeFileSync(ciWorkflowPath, TINYBIRD_CI_WORKFLOW);
719
+ created.push(".github/workflows/tinybird-ci.yaml");
720
+ ciWorkflowCreated = true;
721
+ } catch (error) {
722
+ return {
723
+ success: false,
724
+ created,
725
+ skipped,
726
+ error: `Failed to create tinybird-ci.yaml: ${
727
+ (error as Error).message
728
+ }`,
729
+ };
730
+ }
731
+ }
732
+ }
733
+
734
+ if (includeCdWorkflow) {
735
+ if (fs.existsSync(cdWorkflowPath) && !force) {
736
+ skipped.push(".github/workflows/tinybird-cd.yaml");
737
+ } else {
738
+ try {
739
+ fs.writeFileSync(cdWorkflowPath, TINYBIRD_CD_WORKFLOW);
740
+ created.push(".github/workflows/tinybird-cd.yaml");
741
+ cdWorkflowCreated = true;
742
+ } catch (error) {
743
+ return {
744
+ success: false,
745
+ created,
746
+ skipped,
747
+ error: `Failed to create tinybird-cd.yaml: ${
748
+ (error as Error).message
749
+ }`,
750
+ };
751
+ }
752
+ }
753
+ }
754
+ }
755
+
467
756
  // Check if login is needed
468
757
  if (!skipLogin && !hasValidToken(cwd)) {
469
758
  console.log("\nNo authentication found. Starting login flow...\n");
@@ -495,6 +784,9 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
495
784
  clientPath: relativeTinybirdDir,
496
785
  existingDatafiles:
497
786
  existingDatafiles.length > 0 ? existingDatafiles : undefined,
787
+ ciWorkflowCreated,
788
+ cdWorkflowCreated,
789
+ workflowProvider,
498
790
  };
499
791
  } catch (error) {
500
792
  // Login succeeded but saving credentials failed
@@ -510,6 +802,9 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
510
802
  clientPath: relativeTinybirdDir,
511
803
  existingDatafiles:
512
804
  existingDatafiles.length > 0 ? existingDatafiles : undefined,
805
+ ciWorkflowCreated,
806
+ cdWorkflowCreated,
807
+ workflowProvider,
513
808
  };
514
809
  }
515
810
  } else {
@@ -523,6 +818,9 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
523
818
  clientPath: relativeTinybirdDir,
524
819
  existingDatafiles:
525
820
  existingDatafiles.length > 0 ? existingDatafiles : undefined,
821
+ ciWorkflowCreated,
822
+ cdWorkflowCreated,
823
+ workflowProvider,
526
824
  };
527
825
  }
528
826
  }
@@ -535,6 +833,9 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
535
833
  clientPath: relativeTinybirdDir,
536
834
  existingDatafiles:
537
835
  existingDatafiles.length > 0 ? existingDatafiles : undefined,
836
+ ciWorkflowCreated,
837
+ cdWorkflowCreated,
838
+ workflowProvider,
538
839
  };
539
840
  }
540
841
 
@@ -551,14 +852,18 @@ async function promptForExistingDatafiles(
551
852
 
552
853
  const parts: string[] = [];
553
854
  if (datasourceCount > 0) {
554
- parts.push(`${datasourceCount} .datasource file${datasourceCount > 1 ? "s" : ""}`);
855
+ parts.push(
856
+ `${datasourceCount} .datasource file${datasourceCount > 1 ? "s" : ""}`
857
+ );
555
858
  }
556
859
  if (pipeCount > 0) {
557
860
  parts.push(`${pipeCount} .pipe file${pipeCount > 1 ? "s" : ""}`);
558
861
  }
559
862
 
560
863
  const confirmInclude = await p.confirm({
561
- message: `Found ${parts.join(" and ")} in your project. Include them in tinybird.json?`,
864
+ message: `Found ${parts.join(
865
+ " and "
866
+ )} in your project. Include them in tinybird.json?`,
562
867
  initialValue: true,
563
868
  });
564
869
 
package/src/cli/index.ts CHANGED
@@ -62,7 +62,9 @@ function createCli(): Command {
62
62
  .action(async (options) => {
63
63
  // Validate mode if provided
64
64
  if (options.mode && !["branch", "local"].includes(options.mode)) {
65
- console.error(`Error: Invalid mode '${options.mode}'. Use 'branch' or 'local'.`);
65
+ console.error(
66
+ `Error: Invalid mode '${options.mode}'. Use 'branch' or 'local'.`
67
+ );
66
68
  process.exit(1);
67
69
  }
68
70
 
@@ -186,7 +188,9 @@ function createCli(): Command {
186
188
  const { build, deploy } = result;
187
189
 
188
190
  if (build) {
189
- console.log(`Generated ${build.stats.datasourceCount} datasource(s), ${build.stats.pipeCount} pipe(s)`);
191
+ console.log(
192
+ `Generated ${build.stats.datasourceCount} datasource(s), ${build.stats.pipeCount} pipe(s)`
193
+ );
190
194
  }
191
195
 
192
196
  if (options.dryRun) {
@@ -222,6 +226,7 @@ function createCli(): Command {
222
226
  .command("deploy")
223
227
  .description("Deploy resources to main Tinybird workspace (production)")
224
228
  .option("--dry-run", "Generate without pushing to API")
229
+ .option("--check", "Validate deploy with Tinybird API without applying")
225
230
  .option("--debug", "Show debug output including API requests/responses")
226
231
  .action(async (options) => {
227
232
  if (options.debug) {
@@ -232,6 +237,7 @@ function createCli(): Command {
232
237
 
233
238
  const result = await runDeploy({
234
239
  dryRun: options.dryRun,
240
+ check: options.check,
235
241
  });
236
242
 
237
243
  if (!result.success) {
@@ -242,7 +248,9 @@ function createCli(): Command {
242
248
  const { build, deploy } = result;
243
249
 
244
250
  if (build) {
245
- console.log(`Generated ${build.stats.datasourceCount} datasource(s), ${build.stats.pipeCount} pipe(s)`);
251
+ console.log(
252
+ `Generated ${build.stats.datasourceCount} datasource(s), ${build.stats.pipeCount} pipe(s)`
253
+ );
246
254
  }
247
255
 
248
256
  if (options.dryRun) {
@@ -262,6 +270,8 @@ function createCli(): Command {
262
270
  console.log(pipe.content);
263
271
  });
264
272
  }
273
+ } else if (options.check) {
274
+ console.log("\n[Check] Resources validated with Tinybird API");
265
275
  } else if (deploy) {
266
276
  if (deploy.result === "no_changes") {
267
277
  console.log("No changes detected - already up to date");
@@ -314,7 +324,9 @@ function createCli(): Command {
314
324
  console.log("Workspace created.\n");
315
325
  } else {
316
326
  console.log(`Using local Tinybird container`);
317
- console.log(`Using existing local workspace '${workspaceName}'\n`);
327
+ console.log(
328
+ `Using existing local workspace '${workspaceName}'\n`
329
+ );
318
330
  }
319
331
  } else if (info.isMainBranch) {
320
332
  console.log("On main branch - deploying to workspace\n");
@@ -326,7 +338,9 @@ function createCli(): Command {
326
338
  console.log("Branch created and token cached.\n");
327
339
  } else {
328
340
  console.log(`Detected git branch: ${info.gitBranch}`);
329
- console.log(`Using existing Tinybird branch '${tinybirdName}'\n`);
341
+ console.log(
342
+ `Using existing Tinybird branch '${tinybirdName}'\n`
343
+ );
330
344
  }
331
345
  } else {
332
346
  console.log("Not in a git repository - deploying to workspace\n");
@@ -347,7 +361,9 @@ function createCli(): Command {
347
361
  if (deploy.result === "no_changes") {
348
362
  console.log(`[${formatTime()}] No changes detected`);
349
363
  } else {
350
- console.log(`[${formatTime()}] Built in ${result.durationMs}ms`);
364
+ console.log(
365
+ `[${formatTime()}] Built in ${result.durationMs}ms`
366
+ );
351
367
 
352
368
  // Show datasource changes
353
369
  if (deploy.datasources) {
@@ -382,7 +398,9 @@ function createCli(): Command {
382
398
  console.log(`[${formatTime()}] Schema validation:`);
383
399
  for (const issue of validation.issues) {
384
400
  if (issue.type === "error") {
385
- console.error(` ERROR [${issue.pipeName}]: ${issue.message}`);
401
+ console.error(
402
+ ` ERROR [${issue.pipeName}]: ${issue.message}`
403
+ );
386
404
  } else {
387
405
  console.warn(` WARN [${issue.pipeName}]: ${issue.message}`);
388
406
  }
@@ -415,8 +433,9 @@ function createCli(): Command {
415
433
  });
416
434
 
417
435
  // Branch command
418
- const branchCommand = new Command("branch")
419
- .description("Manage Tinybird branches");
436
+ const branchCommand = new Command("branch").description(
437
+ "Manage Tinybird branches"
438
+ );
420
439
 
421
440
  branchCommand
422
441
  .command("list")
@@ -453,8 +472,13 @@ function createCli(): Command {
453
472
 
454
473
  console.log("Branch Status:");
455
474
  console.log(` Git branch: ${result.gitBranch ?? "(not in git repo)"}`);
456
- if (result.tinybirdBranchName && result.tinybirdBranchName !== result.gitBranch) {
457
- console.log(` Tinybird branch name: ${result.tinybirdBranchName} (sanitized)`);
475
+ if (
476
+ result.tinybirdBranchName &&
477
+ result.tinybirdBranchName !== result.gitBranch
478
+ ) {
479
+ console.log(
480
+ ` Tinybird branch name: ${result.tinybirdBranchName} (sanitized)`
481
+ );
458
482
  }
459
483
  console.log(` Main branch: ${result.isMainBranch ? "yes" : "no"}`);
460
484
 
@@ -11,6 +11,7 @@ import type {
11
11
  TinybirdErrorResponse,
12
12
  } from "./types.js";
13
13
  import { TinybirdError } from "./types.js";
14
+ import { createTinybirdFetcher, type TinybirdFetch } from "../api/fetcher.js";
14
15
 
15
16
  /**
16
17
  * Default timeout for requests (30 seconds)
@@ -56,7 +57,7 @@ interface ResolvedTokenInfo {
56
57
  */
57
58
  export class TinybirdClient {
58
59
  private readonly config: ClientConfig;
59
- private readonly fetchFn: typeof fetch;
60
+ private readonly fetchFn: TinybirdFetch;
60
61
  private tokenPromise: Promise<ResolvedTokenInfo> | null = null;
61
62
  private resolvedToken: string | null = null;
62
63
 
@@ -75,7 +76,7 @@ export class TinybirdClient {
75
76
  baseUrl: config.baseUrl.replace(/\/$/, ""),
76
77
  };
77
78
 
78
- this.fetchFn = config.fetch ?? globalThis.fetch;
79
+ this.fetchFn = createTinybirdFetcher(config.fetch ?? globalThis.fetch);
79
80
  }
80
81
 
81
82
  /**
@@ -3,8 +3,9 @@
3
3
  * Define external connections (Kafka, etc.) as TypeScript with full type safety
4
4
  */
5
5
 
6
- // Symbol for brand typing
7
- const CONNECTION_BRAND = Symbol("tinybird.connection");
6
+ // Symbol for brand typing - use Symbol.for() for global registry
7
+ // This ensures the same symbol is used across module instances
8
+ const CONNECTION_BRAND = Symbol.for("tinybird.connection");
8
9
 
9
10
  /**
10
11
  * Kafka security protocol options
@@ -7,8 +7,9 @@ import type { AnyTypeValidator } from "./types.js";
7
7
  import type { EngineConfig } from "./engines.js";
8
8
  import type { KafkaConnectionDefinition } from "./connection.js";
9
9
 
10
- // Symbol for brand typing
11
- const DATASOURCE_BRAND = Symbol("tinybird.datasource");
10
+ // Symbol for brand typing - use Symbol.for() for global registry
11
+ // This ensures the same symbol is used across module instances
12
+ const DATASOURCE_BRAND = Symbol.for("tinybird.datasource");
12
13
 
13
14
  /**
14
15
  * A column can be defined as just a type validator,
@@ -3,8 +3,9 @@
3
3
  * Similar to the column type validators but for query parameters
4
4
  */
5
5
 
6
- // Symbol for brand typing
7
- const PARAM_BRAND = Symbol("tinybird.param");
6
+ // Symbol for brand typing - use Symbol.for() for global registry
7
+ // This ensures the same symbol is used across module instances
8
+ const PARAM_BRAND = Symbol.for("tinybird.param");
8
9
 
9
10
  /**
10
11
  * Base interface for parameter validators
@@ -9,10 +9,10 @@ import type { DatasourceDefinition, SchemaDefinition, ColumnDefinition } from ".
9
9
  import { getColumnType } from "./datasource.js";
10
10
  import { getTinybirdType } from "./types.js";
11
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");
12
+ /** Symbol for brand typing pipes - use Symbol.for() for global registry */
13
+ export const PIPE_BRAND = Symbol.for("tinybird.pipe");
14
+ /** Symbol for brand typing nodes - use Symbol.for() for global registry */
15
+ export const NODE_BRAND = Symbol.for("tinybird.node");
16
16
 
17
17
  /**
18
18
  * Parameter definition for a pipe
@@ -11,8 +11,9 @@ import type { TinybirdClient } from "../client/base.js";
11
11
  import type { QueryResult } from "../client/types.js";
12
12
  import type { InferRow, InferParams, InferOutputRow } from "../infer/index.js";
13
13
 
14
- // Symbol for brand typing
15
- const PROJECT_BRAND = Symbol("tinybird.project");
14
+ // Symbol for brand typing - use Symbol.for() for global registry
15
+ // This ensures the same symbol is used across module instances
16
+ const PROJECT_BRAND = Symbol.for("tinybird.project");
16
17
 
17
18
  /**
18
19
  * Collection of datasource definitions
@@ -3,8 +3,9 @@
3
3
  * Similar to Convex's `v.*` pattern, but for ClickHouse types
4
4
  */
5
5
 
6
- // Symbol for brand typing
7
- const VALIDATOR_BRAND = Symbol("tinybird.validator");
6
+ // Symbol for brand typing - use Symbol.for() for global registry
7
+ // This ensures the same symbol is used across module instances
8
+ const VALIDATOR_BRAND = Symbol.for("tinybird.validator");
8
9
 
9
10
  /**
10
11
  * Base interface for all type validators