@tinybirdco/sdk 0.0.78 → 0.0.80

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 (63) hide show
  1. package/dist/api/branches.d.ts +4 -3
  2. package/dist/api/branches.d.ts.map +1 -1
  3. package/dist/api/branches.js +4 -4
  4. package/dist/api/branches.js.map +1 -1
  5. package/dist/api/branches.test.js +34 -1
  6. package/dist/api/branches.test.js.map +1 -1
  7. package/dist/cli/commands/build.d.ts.map +1 -1
  8. package/dist/cli/commands/build.js +7 -1
  9. package/dist/cli/commands/build.js.map +1 -1
  10. package/dist/cli/commands/build.test.js +159 -0
  11. package/dist/cli/commands/build.test.js.map +1 -1
  12. package/dist/cli/commands/clear.d.ts.map +1 -1
  13. package/dist/cli/commands/clear.js +4 -1
  14. package/dist/cli/commands/clear.js.map +1 -1
  15. package/dist/cli/commands/deploy.test.js +1 -0
  16. package/dist/cli/commands/deploy.test.js.map +1 -1
  17. package/dist/cli/commands/dev.d.ts.map +1 -1
  18. package/dist/cli/commands/dev.js +7 -1
  19. package/dist/cli/commands/dev.js.map +1 -1
  20. package/dist/cli/commands/generate.test.js +3 -0
  21. package/dist/cli/commands/generate.test.js.map +1 -1
  22. package/dist/cli/commands/preview.d.ts.map +1 -1
  23. package/dist/cli/commands/preview.js +4 -1
  24. package/dist/cli/commands/preview.js.map +1 -1
  25. package/dist/cli/commands/preview.test.js +116 -2
  26. package/dist/cli/commands/preview.test.js.map +1 -1
  27. package/dist/cli/config-types.d.ts +4 -0
  28. package/dist/cli/config-types.d.ts.map +1 -1
  29. package/dist/cli/config-types.js +1 -1
  30. package/dist/cli/config-types.js.map +1 -1
  31. package/dist/cli/config.d.ts +4 -2
  32. package/dist/cli/config.d.ts.map +1 -1
  33. package/dist/cli/config.js +27 -0
  34. package/dist/cli/config.js.map +1 -1
  35. package/dist/cli/config.test.js +79 -0
  36. package/dist/cli/config.test.js.map +1 -1
  37. package/dist/cli/index.js +2 -2
  38. package/dist/cli/index.js.map +1 -1
  39. package/dist/client/base.d.ts.map +1 -1
  40. package/dist/client/base.js +4 -1
  41. package/dist/client/base.js.map +1 -1
  42. package/dist/client/base.test.js +2 -1
  43. package/dist/client/base.test.js.map +1 -1
  44. package/dist/index.d.ts +1 -1
  45. package/dist/index.d.ts.map +1 -1
  46. package/package.json +2 -2
  47. package/src/api/branches.test.ts +38 -1
  48. package/src/api/branches.ts +8 -6
  49. package/src/cli/commands/build.test.ts +176 -0
  50. package/src/cli/commands/build.ts +9 -2
  51. package/src/cli/commands/clear.ts +8 -1
  52. package/src/cli/commands/deploy.test.ts +1 -0
  53. package/src/cli/commands/dev.ts +9 -1
  54. package/src/cli/commands/generate.test.ts +3 -0
  55. package/src/cli/commands/preview.test.ts +133 -2
  56. package/src/cli/commands/preview.ts +6 -2
  57. package/src/cli/config-types.ts +4 -0
  58. package/src/cli/config.test.ts +123 -0
  59. package/src/cli/config.ts +41 -3
  60. package/src/cli/index.ts +8 -2
  61. package/src/client/base.test.ts +3 -1
  62. package/src/client/base.ts +7 -1
  63. package/src/index.ts +1 -1
@@ -64,6 +64,7 @@ describe("Build Command", () => {
64
64
  gitBranch: "feature-test",
65
65
  tinybirdBranch: "feature_test",
66
66
  isMainBranch: false,
67
+ branchDataMode: null,
67
68
  });
68
69
 
69
70
  vi.mocked(buildFromInclude).mockResolvedValue({
@@ -156,6 +157,7 @@ describe("Build Command", () => {
156
157
  gitBranch: "feature-test",
157
158
  tinybirdBranch: "feature_test",
158
159
  isMainBranch: false,
160
+ branchDataMode: null,
159
161
  });
160
162
 
161
163
  vi.mocked(buildFromInclude).mockResolvedValue({
@@ -242,6 +244,7 @@ describe("Build Command", () => {
242
244
  gitBranch: "feature-test",
243
245
  tinybirdBranch: "feature_test",
244
246
  isMainBranch: false,
247
+ branchDataMode: null,
245
248
  });
246
249
 
247
250
  vi.mocked(buildFromInclude).mockResolvedValue({
@@ -307,4 +310,177 @@ describe("Build Command", () => {
307
310
  expect(getLocalTokens).toHaveBeenCalled();
308
311
  });
309
312
  });
313
+
314
+ describe("branch_data_mode wiring", () => {
315
+ it("uses config-only last_partition when flag is absent", async () => {
316
+ const { loadConfigAsync } = await import("../config.js");
317
+ const { buildFromInclude } = await import("../../generator/index.js");
318
+ const { getOrCreateBranch } = await import("../../api/branches.js");
319
+ const { buildToTinybird } = await import("../../api/build.js");
320
+ const { getWorkspace } = await import("../../api/workspaces.js");
321
+ const { getBranchDashboardUrl } = await import("../../api/dashboard.js");
322
+
323
+ vi.mocked(loadConfigAsync).mockResolvedValue({
324
+ include: ["test.ts"],
325
+ token: "p.test-token",
326
+ baseUrl: "https://api.tinybird.co",
327
+ configPath: "/test/tinybird.config.json",
328
+ devMode: "branch",
329
+ cwd: "/test",
330
+ gitBranch: "feature-test",
331
+ tinybirdBranch: "feature_test",
332
+ isMainBranch: false,
333
+ branchDataMode: "last_partition",
334
+ });
335
+ vi.mocked(buildFromInclude).mockResolvedValue({
336
+ resources: { datasources: [], pipes: [], connections: [] },
337
+ entities: { datasources: {}, pipes: {}, connections: {}, rawDatasources: [], rawPipes: [], sourceFiles: [] },
338
+ stats: { datasourceCount: 0, pipeCount: 0, connectionCount: 0 },
339
+ });
340
+ vi.mocked(getOrCreateBranch).mockResolvedValue({
341
+ id: "branch-id",
342
+ name: "feature_test",
343
+ token: "branch-token",
344
+ wasCreated: false,
345
+ created_at: "2024-01-01",
346
+ });
347
+ vi.mocked(getWorkspace).mockResolvedValue({
348
+ id: "ws-id",
349
+ name: "test-workspace",
350
+ user_id: "user-id",
351
+ user_email: "user@test.com",
352
+ scope: "USER",
353
+ main: null,
354
+ });
355
+ vi.mocked(getBranchDashboardUrl).mockReturnValue("https://app.tinybird.co/dashboard");
356
+ vi.mocked(buildToTinybird).mockResolvedValue({
357
+ success: true,
358
+ result: "success",
359
+ datasourceCount: 0,
360
+ pipeCount: 0,
361
+ connectionCount: 0,
362
+ });
363
+
364
+ await runBuild();
365
+ expect(getOrCreateBranch).toHaveBeenCalledWith(
366
+ expect.any(Object),
367
+ "feature_test",
368
+ { branch_data_mode: "last_partition" }
369
+ );
370
+ });
371
+
372
+ it("keeps CLI --last-partition precedence over config", async () => {
373
+ const { loadConfigAsync } = await import("../config.js");
374
+ const { buildFromInclude } = await import("../../generator/index.js");
375
+ const { getOrCreateBranch } = await import("../../api/branches.js");
376
+ const { buildToTinybird } = await import("../../api/build.js");
377
+ const { getWorkspace } = await import("../../api/workspaces.js");
378
+ const { getBranchDashboardUrl } = await import("../../api/dashboard.js");
379
+
380
+ vi.mocked(loadConfigAsync).mockResolvedValue({
381
+ include: ["test.ts"],
382
+ token: "p.test-token",
383
+ baseUrl: "https://api.tinybird.co",
384
+ configPath: "/test/tinybird.config.json",
385
+ devMode: "branch",
386
+ cwd: "/test",
387
+ gitBranch: "feature-test",
388
+ tinybirdBranch: "feature_test",
389
+ isMainBranch: false,
390
+ branchDataMode: null,
391
+ });
392
+ vi.mocked(buildFromInclude).mockResolvedValue({
393
+ resources: { datasources: [], pipes: [], connections: [] },
394
+ entities: { datasources: {}, pipes: {}, connections: {}, rawDatasources: [], rawPipes: [], sourceFiles: [] },
395
+ stats: { datasourceCount: 0, pipeCount: 0, connectionCount: 0 },
396
+ });
397
+ vi.mocked(getOrCreateBranch).mockResolvedValue({
398
+ id: "branch-id",
399
+ name: "feature_test",
400
+ token: "branch-token",
401
+ wasCreated: false,
402
+ created_at: "2024-01-01",
403
+ });
404
+ vi.mocked(getWorkspace).mockResolvedValue({
405
+ id: "ws-id",
406
+ name: "test-workspace",
407
+ user_id: "user-id",
408
+ user_email: "user@test.com",
409
+ scope: "USER",
410
+ main: null,
411
+ });
412
+ vi.mocked(getBranchDashboardUrl).mockReturnValue("https://app.tinybird.co/dashboard");
413
+ vi.mocked(buildToTinybird).mockResolvedValue({
414
+ success: true,
415
+ result: "success",
416
+ datasourceCount: 0,
417
+ pipeCount: 0,
418
+ connectionCount: 0,
419
+ });
420
+
421
+ await runBuild({ lastPartition: true });
422
+ expect(getOrCreateBranch).toHaveBeenCalledWith(
423
+ expect.any(Object),
424
+ "feature_test",
425
+ { branch_data_mode: "last_partition" }
426
+ );
427
+ });
428
+
429
+ it("ignores config branch_data_mode in local mode", async () => {
430
+ const { loadConfigAsync } = await import("../config.js");
431
+ const { buildFromInclude } = await import("../../generator/index.js");
432
+ const { getOrCreateBranch } = await import("../../api/branches.js");
433
+ const { getLocalTokens, getOrCreateLocalWorkspace, getLocalWorkspaceName } = await import("../../api/local.js");
434
+ const { buildToTinybird } = await import("../../api/build.js");
435
+ const { getWorkspace } = await import("../../api/workspaces.js");
436
+ const { getLocalDashboardUrl } = await import("../../api/dashboard.js");
437
+
438
+ vi.mocked(loadConfigAsync).mockResolvedValue({
439
+ include: ["test.ts"],
440
+ token: "p.test-token",
441
+ baseUrl: "https://api.tinybird.co",
442
+ configPath: "/test/tinybird.config.json",
443
+ devMode: "local",
444
+ cwd: "/test",
445
+ gitBranch: "feature-test",
446
+ tinybirdBranch: "feature_test",
447
+ isMainBranch: false,
448
+ branchDataMode: "last_partition",
449
+ });
450
+ vi.mocked(buildFromInclude).mockResolvedValue({
451
+ resources: { datasources: [], pipes: [], connections: [] },
452
+ entities: { datasources: {}, pipes: {}, connections: {}, rawDatasources: [], rawPipes: [], sourceFiles: [] },
453
+ stats: { datasourceCount: 0, pipeCount: 0, connectionCount: 0 },
454
+ });
455
+ vi.mocked(getLocalTokens).mockResolvedValue({
456
+ admin_token: "admin-token",
457
+ user_token: "user-token",
458
+ workspace_admin_token: "workspace-admin-token",
459
+ });
460
+ vi.mocked(getWorkspace).mockResolvedValue({
461
+ id: "ws-id",
462
+ name: "test-workspace",
463
+ user_id: "user-id",
464
+ user_email: "user@test.com",
465
+ scope: "USER",
466
+ main: null,
467
+ });
468
+ vi.mocked(getLocalWorkspaceName).mockReturnValue("feature_test_workspace");
469
+ vi.mocked(getOrCreateLocalWorkspace).mockResolvedValue({
470
+ workspace: { id: "local-ws-id", name: "feature_test_workspace", token: "local-token" },
471
+ wasCreated: false,
472
+ });
473
+ vi.mocked(getLocalDashboardUrl).mockReturnValue("http://localhost:7181/dashboard");
474
+ vi.mocked(buildToTinybird).mockResolvedValue({
475
+ success: true,
476
+ result: "success",
477
+ datasourceCount: 0,
478
+ pipeCount: 0,
479
+ connectionCount: 0,
480
+ });
481
+
482
+ await runBuild();
483
+ expect(getOrCreateBranch).not.toHaveBeenCalled();
484
+ });
485
+ });
310
486
  });
@@ -2,7 +2,7 @@
2
2
  * Build command - generates and pushes resources to Tinybird branches
3
3
  */
4
4
 
5
- import { loadConfigAsync, LOCAL_BASE_URL, type ResolvedConfig, type DevMode } from "../config.js";
5
+ import { loadConfigAsync, LOCAL_BASE_URL, type ResolvedConfig, type DevMode, type BranchDataMode } from "../config.js";
6
6
  import { buildFromInclude, type BuildFromIncludeResult } from "../../generator/index.js";
7
7
  import { buildToTinybird, type BuildApiResult } from "../../api/build.js";
8
8
  import { getOrCreateBranch } from "../../api/branches.js";
@@ -225,13 +225,20 @@ export async function runBuild(options: BuildCommandOptions = {}): Promise<Build
225
225
  console.log(`[debug] Getting/creating Tinybird branch: ${config.tinybirdBranch}`);
226
226
  }
227
227
  try {
228
+ const branchDataMode: BranchDataMode | undefined =
229
+ options.lastPartition || config.branchDataMode === "last_partition"
230
+ ? "last_partition"
231
+ : undefined;
232
+ const branchOptions = branchDataMode
233
+ ? { branch_data_mode: branchDataMode }
234
+ : undefined;
228
235
  const tinybirdBranch = await getOrCreateBranch(
229
236
  {
230
237
  baseUrl: config.baseUrl,
231
238
  token: config.token,
232
239
  },
233
240
  config.tinybirdBranch!,
234
- { lastPartition: options.lastPartition }
241
+ branchOptions
235
242
  );
236
243
 
237
244
  if (!tinybirdBranch.token) {
@@ -13,6 +13,7 @@ import {
13
13
  import {
14
14
  clearBranch,
15
15
  BranchApiError,
16
+ type CreateBranchOptions,
16
17
  } from "../../api/branches.js";
17
18
  import {
18
19
  setBranchToken,
@@ -147,12 +148,18 @@ async function clearCloudBranch(config: ResolvedConfig): Promise<ClearResult> {
147
148
  });
148
149
 
149
150
  // Clear the branch (delete and recreate)
151
+ const branchOptions: CreateBranchOptions | undefined =
152
+ config.devMode !== "local" && config.branchDataMode === "last_partition"
153
+ ? { branch_data_mode: "last_partition" }
154
+ : undefined;
155
+
150
156
  const newBranch = await clearBranch(
151
157
  {
152
158
  baseUrl: config.baseUrl,
153
159
  token: config.token,
154
160
  },
155
- branchName
161
+ branchName,
162
+ branchOptions
156
163
  );
157
164
 
158
165
  // Update the cached token with the new branch token
@@ -37,6 +37,7 @@ describe("Deploy command", () => {
37
37
  tinybirdBranch: "feature_pro_610",
38
38
  isMainBranch: false,
39
39
  devMode: "branch",
40
+ branchDataMode: null,
40
41
  });
41
42
 
42
43
  vi.mocked(buildFromInclude).mockResolvedValue({
@@ -13,6 +13,7 @@ import {
13
13
  LOCAL_BASE_URL,
14
14
  type ResolvedConfig,
15
15
  type DevMode,
16
+ type BranchDataMode,
16
17
  } from "../config.js";
17
18
  import { runBuild, type BuildCommandResult } from "./build.js";
18
19
  import { getOrCreateBranch, type TinybirdBranch } from "../../api/branches.js";
@@ -239,6 +240,13 @@ export async function runDev(
239
240
  // Use tinybirdBranch (sanitized name) for Tinybird API, gitBranch for display
240
241
  if (config.tinybirdBranch) {
241
242
  const branchName = config.tinybirdBranch; // Sanitized name for Tinybird
243
+ const branchDataMode: BranchDataMode | undefined =
244
+ options.lastPartition || config.branchDataMode === "last_partition"
245
+ ? "last_partition"
246
+ : undefined;
247
+ const branchOptions = branchDataMode
248
+ ? { branch_data_mode: branchDataMode }
249
+ : undefined;
242
250
 
243
251
  // Always fetch fresh from API to avoid stale cache issues
244
252
  const tinybirdBranch = await getOrCreateBranch(
@@ -247,7 +255,7 @@ export async function runDev(
247
255
  token: config.token,
248
256
  },
249
257
  branchName,
250
- { lastPartition: options.lastPartition }
258
+ branchOptions
251
259
  );
252
260
 
253
261
  if (!tinybirdBranch.token) {
@@ -35,6 +35,7 @@ describe("Generate command", () => {
35
35
  tinybirdBranch: "feature_x",
36
36
  isMainBranch: false,
37
37
  devMode: "branch",
38
+ branchDataMode: null,
38
39
  });
39
40
 
40
41
  vi.mocked(buildFromInclude).mockResolvedValue({
@@ -108,6 +109,7 @@ describe("Generate command", () => {
108
109
  tinybirdBranch: "feature_x",
109
110
  isMainBranch: false,
110
111
  devMode: "branch",
112
+ branchDataMode: null,
111
113
  });
112
114
 
113
115
  vi.mocked(buildFromInclude).mockResolvedValue({
@@ -167,6 +169,7 @@ describe("Generate command", () => {
167
169
  tinybirdBranch: "feature_x",
168
170
  isMainBranch: false,
169
171
  devMode: "branch",
172
+ branchDataMode: null,
170
173
  });
171
174
  vi.mocked(buildFromInclude).mockRejectedValue(
172
175
  new Error("generator failed")
@@ -1,7 +1,45 @@
1
- import { describe, it, expect } from "vitest";
2
- import { generatePreviewBranchName } from "./preview.js";
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { generatePreviewBranchName, runPreview } from "./preview.js";
3
+
4
+ vi.mock("../config.js", () => ({
5
+ loadConfigAsync: vi.fn(),
6
+ LOCAL_BASE_URL: "http://localhost:7181",
7
+ }));
8
+
9
+ vi.mock("../../generator/index.js", () => ({
10
+ buildFromInclude: vi.fn(),
11
+ }));
12
+
13
+ vi.mock("../../api/branches.js", () => ({
14
+ createBranch: vi.fn(),
15
+ deleteBranch: vi.fn(),
16
+ getBranch: vi.fn(),
17
+ }));
18
+
19
+ vi.mock("../../api/deploy.js", () => ({
20
+ deployToMain: vi.fn(),
21
+ }));
22
+
23
+ vi.mock("../../api/build.js", () => ({
24
+ buildToTinybird: vi.fn(),
25
+ }));
26
+
27
+ vi.mock("../../api/local.js", () => ({
28
+ getLocalTokens: vi.fn(),
29
+ getOrCreateLocalWorkspace: vi.fn(),
30
+ LocalNotRunningError: class LocalNotRunningError extends Error {},
31
+ }));
32
+
33
+ vi.mock("../git.js", () => ({
34
+ sanitizeBranchName: (value: string) => value.replace(/[^a-zA-Z0-9]/g, "_"),
35
+ getCurrentGitBranch: vi.fn(() => "feature/test"),
36
+ }));
3
37
 
4
38
  describe("Preview command", () => {
39
+ beforeEach(() => {
40
+ vi.clearAllMocks();
41
+ });
42
+
5
43
  describe("generatePreviewBranchName", () => {
6
44
  it("generates name with tmp_ci prefix", () => {
7
45
  const result = generatePreviewBranchName("feature-branch");
@@ -34,4 +72,97 @@ describe("Preview command", () => {
34
72
  expect(result1).toBe(result2);
35
73
  });
36
74
  });
75
+
76
+ describe("branch_data_mode wiring", () => {
77
+ it("uses config-only last_partition when creating cloud preview branch", async () => {
78
+ const { loadConfigAsync } = await import("../config.js");
79
+ const { buildFromInclude } = await import("../../generator/index.js");
80
+ const { getBranch, createBranch } = await import("../../api/branches.js");
81
+ const { deployToMain } = await import("../../api/deploy.js");
82
+
83
+ vi.mocked(loadConfigAsync).mockResolvedValue({
84
+ include: ["test.ts"],
85
+ token: "p.test-token",
86
+ baseUrl: "https://api.tinybird.co",
87
+ configPath: "/test/tinybird.config.json",
88
+ devMode: "branch",
89
+ cwd: "/test",
90
+ gitBranch: "feature-test",
91
+ tinybirdBranch: "feature_test",
92
+ isMainBranch: false,
93
+ branchDataMode: "last_partition",
94
+ });
95
+ vi.mocked(buildFromInclude).mockResolvedValue({
96
+ resources: { datasources: [], pipes: [], connections: [] },
97
+ entities: { datasources: {}, pipes: {}, connections: {}, rawDatasources: [], rawPipes: [], sourceFiles: [] },
98
+ stats: { datasourceCount: 0, pipeCount: 0, connectionCount: 0 },
99
+ });
100
+ vi.mocked(getBranch).mockRejectedValue(new Error("not found"));
101
+ vi.mocked(createBranch).mockResolvedValue({
102
+ id: "b1",
103
+ name: "tmp_ci_feature_test",
104
+ token: "p.branch",
105
+ created_at: "2024-01-01T00:00:00Z",
106
+ });
107
+ vi.mocked(deployToMain).mockResolvedValue({
108
+ success: true,
109
+ result: "success",
110
+ datasourceCount: 0,
111
+ pipeCount: 0,
112
+ connectionCount: 0,
113
+ });
114
+
115
+ await runPreview();
116
+ expect(createBranch).toHaveBeenCalledWith(
117
+ expect.any(Object),
118
+ "tmp_ci_feature_test",
119
+ { branch_data_mode: "last_partition" }
120
+ );
121
+ });
122
+
123
+ it("ignores config branch_data_mode in local mode", async () => {
124
+ const { loadConfigAsync } = await import("../config.js");
125
+ const { buildFromInclude } = await import("../../generator/index.js");
126
+ const { createBranch } = await import("../../api/branches.js");
127
+ const { getLocalTokens, getOrCreateLocalWorkspace } = await import("../../api/local.js");
128
+ const { buildToTinybird } = await import("../../api/build.js");
129
+
130
+ vi.mocked(loadConfigAsync).mockResolvedValue({
131
+ include: ["test.ts"],
132
+ token: "p.test-token",
133
+ baseUrl: "https://api.tinybird.co",
134
+ configPath: "/test/tinybird.config.json",
135
+ devMode: "local",
136
+ cwd: "/test",
137
+ gitBranch: "feature-test",
138
+ tinybirdBranch: "feature_test",
139
+ isMainBranch: false,
140
+ branchDataMode: "last_partition",
141
+ });
142
+ vi.mocked(buildFromInclude).mockResolvedValue({
143
+ resources: { datasources: [], pipes: [], connections: [] },
144
+ entities: { datasources: {}, pipes: {}, connections: {}, rawDatasources: [], rawPipes: [], sourceFiles: [] },
145
+ stats: { datasourceCount: 0, pipeCount: 0, connectionCount: 0 },
146
+ });
147
+ vi.mocked(getLocalTokens).mockResolvedValue({
148
+ admin_token: "admin-token",
149
+ user_token: "user-token",
150
+ workspace_admin_token: "workspace-admin-token",
151
+ });
152
+ vi.mocked(getOrCreateLocalWorkspace).mockResolvedValue({
153
+ workspace: { id: "lw1", name: "tmp_ci_feature_test", token: "local-token" },
154
+ wasCreated: true,
155
+ });
156
+ vi.mocked(buildToTinybird).mockResolvedValue({
157
+ success: true,
158
+ result: "success",
159
+ datasourceCount: 0,
160
+ pipeCount: 0,
161
+ connectionCount: 0,
162
+ });
163
+
164
+ await runPreview();
165
+ expect(createBranch).not.toHaveBeenCalled();
166
+ });
167
+ });
37
168
  });
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { loadConfigAsync, LOCAL_BASE_URL, type ResolvedConfig, type DevMode } from "../config.js";
6
6
  import { buildFromInclude, type BuildFromIncludeResult } from "../../generator/index.js";
7
- import { createBranch, deleteBranch, getBranch, type TinybirdBranch } from "../../api/branches.js";
7
+ import { createBranch, deleteBranch, getBranch, type CreateBranchOptions, type TinybirdBranch } from "../../api/branches.js";
8
8
  import { deployToMain } from "../../api/deploy.js";
9
9
  import { buildToTinybird } from "../../api/build.js";
10
10
  import {
@@ -226,6 +226,10 @@ export async function runPreview(options: PreviewCommandOptions = {}): Promise<P
226
226
  let branch: TinybirdBranch;
227
227
  try {
228
228
  const apiConfig = { baseUrl: config.baseUrl, token: config.token };
229
+ const branchOptions: CreateBranchOptions | undefined =
230
+ config.branchDataMode === "last_partition"
231
+ ? { branch_data_mode: "last_partition" }
232
+ : undefined;
229
233
 
230
234
  // Check if branch already exists and delete it for a fresh start
231
235
  try {
@@ -250,7 +254,7 @@ export async function runPreview(options: PreviewCommandOptions = {}): Promise<P
250
254
  console.log(`[debug] Creating preview branch: ${previewBranchName}`);
251
255
  }
252
256
 
253
- branch = await createBranch(apiConfig, previewBranchName);
257
+ branch = await createBranch(apiConfig, previewBranchName, branchOptions);
254
258
 
255
259
  if (debug) {
256
260
  console.log(`[debug] Branch created: ${branch.name} (${branch.id})`);
@@ -11,6 +11,8 @@
11
11
  * - "local": Use local Tinybird container at localhost:7181
12
12
  */
13
13
  export type DevMode = "branch" | "local";
14
+ export type BranchDataMode = "last_partition";
15
+ export const BRANCH_DATA_MODE_VALUES = ["last_partition"] as const satisfies readonly BranchDataMode[];
14
16
 
15
17
  /**
16
18
  * Tinybird configuration file structure
@@ -26,4 +28,6 @@ export interface TinybirdConfig {
26
28
  baseUrl?: string;
27
29
  /** Development mode: "branch" (default) or "local" */
28
30
  devMode?: DevMode;
31
+ /** Branch data mode applied on cloud branch creation (shared snake_case key) */
32
+ branch_data_mode?: BranchDataMode;
29
33
  }
@@ -327,6 +327,129 @@ describe("Config", () => {
327
327
 
328
328
  expect(result.devMode).toBe("local");
329
329
  });
330
+
331
+ it("resolves branch_data_mode as last_partition", () => {
332
+ const config = {
333
+ include: ["lib/datasources.ts"],
334
+ token: "test-token",
335
+ branch_data_mode: "last_partition",
336
+ };
337
+ fs.writeFileSync(
338
+ path.join(tempDir, "tinybird.json"),
339
+ JSON.stringify(config)
340
+ );
341
+
342
+ const result = loadConfig(tempDir);
343
+ expect(result.branchDataMode).toBe("last_partition");
344
+ });
345
+
346
+ it("defaults branch_data_mode to last_partition when missing", () => {
347
+ const config = {
348
+ include: ["lib/datasources.ts"],
349
+ token: "test-token",
350
+ };
351
+ fs.writeFileSync(
352
+ path.join(tempDir, "tinybird.json"),
353
+ JSON.stringify(config)
354
+ );
355
+
356
+ const result = loadConfig(tempDir);
357
+ expect(result.branchDataMode).toBe("last_partition");
358
+ });
359
+
360
+ it("defaults empty branch_data_mode to last_partition", () => {
361
+ const config = {
362
+ include: ["lib/datasources.ts"],
363
+ token: "test-token",
364
+ branch_data_mode: " ",
365
+ };
366
+ fs.writeFileSync(
367
+ path.join(tempDir, "tinybird.json"),
368
+ JSON.stringify(config)
369
+ );
370
+
371
+ const result = loadConfig(tempDir);
372
+ expect(result.branchDataMode).toBe("last_partition");
373
+ });
374
+
375
+ it("throws when branch_data_mode is all_partitions", () => {
376
+ const config = {
377
+ include: ["lib/datasources.ts"],
378
+ token: "test-token",
379
+ branch_data_mode: "all_partitions",
380
+ };
381
+ fs.writeFileSync(
382
+ path.join(tempDir, "tinybird.json"),
383
+ JSON.stringify(config)
384
+ );
385
+
386
+ expect(() => loadConfig(tempDir)).toThrow("Invalid branch_data_mode");
387
+ });
388
+
389
+ it("throws when branch_data_mode is invalid", () => {
390
+ const config = {
391
+ include: ["lib/datasources.ts"],
392
+ token: "test-token",
393
+ branch_data_mode: "invalid",
394
+ };
395
+ fs.writeFileSync(
396
+ path.join(tempDir, "tinybird.json"),
397
+ JSON.stringify(config)
398
+ );
399
+
400
+ expect(() => loadConfig(tempDir)).toThrow("Invalid branch_data_mode");
401
+ });
402
+
403
+ it("warns when branch_data_mode is set with devMode local", () => {
404
+ const config = {
405
+ include: ["lib/datasources.ts"],
406
+ token: "test-token",
407
+ devMode: "local",
408
+ branch_data_mode: "last_partition",
409
+ };
410
+ fs.writeFileSync(
411
+ path.join(tempDir, "tinybird.json"),
412
+ JSON.stringify(config)
413
+ );
414
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
415
+
416
+ loadConfig(tempDir);
417
+
418
+ expect(warnSpy).toHaveBeenCalledWith(
419
+ expect.stringContaining("branch_data_mode is set")
420
+ );
421
+ });
422
+
423
+ it("does not warn when branch_data_mode is implicit in local mode", () => {
424
+ const config = {
425
+ include: ["lib/datasources.ts"],
426
+ token: "test-token",
427
+ devMode: "local",
428
+ };
429
+ fs.writeFileSync(
430
+ path.join(tempDir, "tinybird.json"),
431
+ JSON.stringify(config)
432
+ );
433
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
434
+
435
+ loadConfig(tempDir);
436
+
437
+ expect(warnSpy).not.toHaveBeenCalled();
438
+ });
439
+
440
+ it("throws when legacy branch_data_on_create key is used", () => {
441
+ const config = {
442
+ include: ["lib/datasources.ts"],
443
+ token: "test-token",
444
+ branch_data_on_create: "last_partition",
445
+ };
446
+ fs.writeFileSync(
447
+ path.join(tempDir, "tinybird.json"),
448
+ JSON.stringify(config)
449
+ );
450
+
451
+ expect(() => loadConfig(tempDir)).toThrow("renamed to `branch_data_mode`");
452
+ });
330
453
  });
331
454
 
332
455
  describe("loadConfigAsync", () => {