@tinybirdco/sdk 0.0.63 → 0.0.65
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.
- package/README.md +7 -0
- package/dist/api/branches.d.ts +2 -0
- package/dist/api/branches.d.ts.map +1 -1
- package/dist/api/branches.js +14 -6
- package/dist/api/branches.js.map +1 -1
- package/dist/api/branches.test.js +31 -0
- package/dist/api/branches.test.js.map +1 -1
- package/dist/client/base.d.ts.map +1 -1
- package/dist/client/base.js +5 -1
- package/dist/client/base.js.map +1 -1
- package/dist/client/base.test.js +72 -1
- package/dist/client/base.test.js.map +1 -1
- package/dist/client/preview.d.ts +1 -0
- package/dist/client/preview.d.ts.map +1 -1
- package/dist/client/preview.js +4 -3
- package/dist/client/preview.js.map +1 -1
- package/dist/client/preview.test.js +35 -0
- package/dist/client/preview.test.js.map +1 -1
- package/dist/generator/datasource.test.js +12 -1
- package/dist/generator/datasource.test.js.map +1 -1
- package/dist/schema/project.d.ts +2 -0
- package/dist/schema/project.d.ts.map +1 -1
- package/dist/schema/project.js +7 -1
- package/dist/schema/project.js.map +1 -1
- package/dist/schema/project.test.js +67 -0
- package/dist/schema/project.test.js.map +1 -1
- package/dist/schema/types.js +2 -2
- package/dist/schema/types.js.map +1 -1
- package/dist/schema/types.test.js +9 -2
- package/dist/schema/types.test.js.map +1 -1
- package/package.json +1 -1
- package/src/api/branches.test.ts +39 -0
- package/src/api/branches.ts +18 -6
- package/src/client/base.test.ts +85 -1
- package/src/client/base.ts +5 -1
- package/src/client/preview.test.ts +44 -0
- package/src/client/preview.ts +11 -3
- package/src/generator/datasource.test.ts +14 -1
- package/src/schema/project.test.ts +79 -0
- package/src/schema/project.ts +10 -1
- package/src/schema/types.test.ts +10 -2
- package/src/schema/types.ts +2 -2
package/src/api/branches.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Uses the /v1/environments endpoints (Forward API)
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { createTinybirdFetcher } from "./fetcher.js";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Branch information from Tinybird API
|
|
@@ -35,6 +35,12 @@ export interface BranchApiConfig {
|
|
|
35
35
|
baseUrl: string;
|
|
36
36
|
/** Parent workspace token (used to create/manage branches) */
|
|
37
37
|
token: string;
|
|
38
|
+
/** Custom fetch implementation (optional) */
|
|
39
|
+
fetch?: typeof fetch;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getFetch(config: BranchApiConfig) {
|
|
43
|
+
return createTinybirdFetcher(config.fetch ?? globalThis.fetch);
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
/**
|
|
@@ -89,10 +95,12 @@ async function pollJob(
|
|
|
89
95
|
maxAttempts = 120,
|
|
90
96
|
intervalMs = 1000
|
|
91
97
|
): Promise<JobStatusResponse> {
|
|
98
|
+
const fetchFn = getFetch(config);
|
|
99
|
+
|
|
92
100
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
93
101
|
const url = new URL(`/v0/jobs/${jobId}`, config.baseUrl);
|
|
94
102
|
|
|
95
|
-
const response = await
|
|
103
|
+
const response = await fetchFn(url.toString(), {
|
|
96
104
|
method: "GET",
|
|
97
105
|
headers: {
|
|
98
106
|
Authorization: `Bearer ${config.token}`,
|
|
@@ -153,6 +161,7 @@ export async function createBranch(
|
|
|
153
161
|
name: string,
|
|
154
162
|
options?: CreateBranchOptions
|
|
155
163
|
): Promise<TinybirdBranch> {
|
|
164
|
+
const fetchFn = getFetch(config);
|
|
156
165
|
const url = new URL("/v1/environments", config.baseUrl);
|
|
157
166
|
url.searchParams.set("name", name);
|
|
158
167
|
if (options?.lastPartition) {
|
|
@@ -164,7 +173,7 @@ export async function createBranch(
|
|
|
164
173
|
console.log(`[debug] POST ${url.toString()}`);
|
|
165
174
|
}
|
|
166
175
|
|
|
167
|
-
const response = await
|
|
176
|
+
const response = await fetchFn(url.toString(), {
|
|
168
177
|
method: "POST",
|
|
169
178
|
headers: {
|
|
170
179
|
Authorization: `Bearer ${config.token}`,
|
|
@@ -219,9 +228,10 @@ export async function createBranch(
|
|
|
219
228
|
export async function listBranches(
|
|
220
229
|
config: BranchApiConfig
|
|
221
230
|
): Promise<TinybirdBranch[]> {
|
|
231
|
+
const fetchFn = getFetch(config);
|
|
222
232
|
const url = new URL("/v1/environments", config.baseUrl);
|
|
223
233
|
|
|
224
|
-
const response = await
|
|
234
|
+
const response = await fetchFn(url.toString(), {
|
|
225
235
|
method: "GET",
|
|
226
236
|
headers: {
|
|
227
237
|
Authorization: `Bearer ${config.token}`,
|
|
@@ -253,10 +263,11 @@ export async function getBranch(
|
|
|
253
263
|
config: BranchApiConfig,
|
|
254
264
|
name: string
|
|
255
265
|
): Promise<TinybirdBranch> {
|
|
266
|
+
const fetchFn = getFetch(config);
|
|
256
267
|
const url = new URL(`/v0/environments/${encodeURIComponent(name)}`, config.baseUrl);
|
|
257
268
|
url.searchParams.set("with_token", "true");
|
|
258
269
|
|
|
259
|
-
const response = await
|
|
270
|
+
const response = await fetchFn(url.toString(), {
|
|
260
271
|
method: "GET",
|
|
261
272
|
headers: {
|
|
262
273
|
Authorization: `Bearer ${config.token}`,
|
|
@@ -292,10 +303,11 @@ export async function deleteBranch(
|
|
|
292
303
|
): Promise<void> {
|
|
293
304
|
// First get the branch to find its ID
|
|
294
305
|
const branch = await getBranch(config, name);
|
|
306
|
+
const fetchFn = getFetch(config);
|
|
295
307
|
|
|
296
308
|
const url = new URL(`/v0/environments/${branch.id}`, config.baseUrl);
|
|
297
309
|
|
|
298
|
-
const response = await
|
|
310
|
+
const response = await fetchFn(url.toString(), {
|
|
299
311
|
method: "DELETE",
|
|
300
312
|
headers: {
|
|
301
313
|
Authorization: `Bearer ${config.token}`,
|
package/src/client/base.test.ts
CHANGED
|
@@ -1,8 +1,46 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
2
|
import { TinybirdClient, createClient } from "./base.js";
|
|
3
3
|
import type { DatasourcesNamespace } from "./types.js";
|
|
4
|
+
import { loadConfigAsync } from "../cli/config.js";
|
|
5
|
+
import { getOrCreateBranch } from "../api/branches.js";
|
|
6
|
+
|
|
7
|
+
vi.mock("../cli/config.js", () => ({
|
|
8
|
+
loadConfigAsync: vi.fn(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
vi.mock("../api/branches.js", () => ({
|
|
12
|
+
getOrCreateBranch: vi.fn(),
|
|
13
|
+
}));
|
|
4
14
|
|
|
5
15
|
describe("TinybirdClient", () => {
|
|
16
|
+
const originalEnv = { ...process.env };
|
|
17
|
+
const mockedLoadConfigAsync = vi.mocked(loadConfigAsync);
|
|
18
|
+
const mockedGetOrCreateBranch = vi.mocked(getOrCreateBranch);
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
process.env = { ...originalEnv };
|
|
22
|
+
delete process.env.VERCEL_ENV;
|
|
23
|
+
delete process.env.GITHUB_HEAD_REF;
|
|
24
|
+
delete process.env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME;
|
|
25
|
+
delete process.env.CI;
|
|
26
|
+
delete process.env.TINYBIRD_PREVIEW_MODE;
|
|
27
|
+
delete process.env.VERCEL_GIT_COMMIT_REF;
|
|
28
|
+
delete process.env.GITHUB_REF_NAME;
|
|
29
|
+
delete process.env.CI_COMMIT_BRANCH;
|
|
30
|
+
delete process.env.CIRCLE_BRANCH;
|
|
31
|
+
delete process.env.BUILD_SOURCEBRANCHNAME;
|
|
32
|
+
delete process.env.BITBUCKET_BRANCH;
|
|
33
|
+
delete process.env.TINYBIRD_BRANCH_NAME;
|
|
34
|
+
delete process.env.TINYBIRD_BRANCH_TOKEN;
|
|
35
|
+
mockedLoadConfigAsync.mockReset();
|
|
36
|
+
mockedGetOrCreateBranch.mockReset();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
process.env = { ...originalEnv };
|
|
41
|
+
vi.restoreAllMocks();
|
|
42
|
+
});
|
|
43
|
+
|
|
6
44
|
describe("constructor", () => {
|
|
7
45
|
it("throws error when baseUrl is missing", () => {
|
|
8
46
|
expect(() => new TinybirdClient({ baseUrl: "", token: "test-token" })).toThrow(
|
|
@@ -115,6 +153,52 @@ describe("TinybirdClient", () => {
|
|
|
115
153
|
expect(context.baseUrl).toBe("https://api.us-east.tinybird.co");
|
|
116
154
|
expect(context.token).toBe("us-token");
|
|
117
155
|
});
|
|
156
|
+
|
|
157
|
+
it("passes custom fetch to devMode branch resolution", async () => {
|
|
158
|
+
const customFetch = vi.fn();
|
|
159
|
+
|
|
160
|
+
mockedLoadConfigAsync.mockResolvedValue({
|
|
161
|
+
include: [],
|
|
162
|
+
token: "workspace-token",
|
|
163
|
+
baseUrl: "https://api.tinybird.co",
|
|
164
|
+
configPath: "/tmp/tinybird.config.json",
|
|
165
|
+
cwd: "/tmp",
|
|
166
|
+
gitBranch: "feature/add-fetch",
|
|
167
|
+
tinybirdBranch: "feature_add_fetch",
|
|
168
|
+
isMainBranch: false,
|
|
169
|
+
devMode: "branch",
|
|
170
|
+
});
|
|
171
|
+
mockedGetOrCreateBranch.mockResolvedValue({
|
|
172
|
+
id: "branch-123",
|
|
173
|
+
name: "feature_add_fetch",
|
|
174
|
+
token: "branch-token",
|
|
175
|
+
created_at: "2024-01-01T00:00:00Z",
|
|
176
|
+
wasCreated: false,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const client = new TinybirdClient({
|
|
180
|
+
baseUrl: "https://api.tinybird.co",
|
|
181
|
+
token: "workspace-token",
|
|
182
|
+
fetch: customFetch as typeof fetch,
|
|
183
|
+
devMode: true,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const context = await client.getContext();
|
|
187
|
+
|
|
188
|
+
expect(mockedLoadConfigAsync).toHaveBeenCalled();
|
|
189
|
+
expect(mockedGetOrCreateBranch).toHaveBeenCalledWith(
|
|
190
|
+
{
|
|
191
|
+
baseUrl: "https://api.tinybird.co",
|
|
192
|
+
token: "workspace-token",
|
|
193
|
+
fetch: customFetch,
|
|
194
|
+
},
|
|
195
|
+
"feature_add_fetch"
|
|
196
|
+
);
|
|
197
|
+
expect(context.token).toBe("branch-token");
|
|
198
|
+
expect(context.isBranchToken).toBe(true);
|
|
199
|
+
expect(context.branchName).toBe("feature_add_fetch");
|
|
200
|
+
expect(context.gitBranch).toBe("feature/add-fetch");
|
|
201
|
+
});
|
|
118
202
|
});
|
|
119
203
|
|
|
120
204
|
describe("createClient", () => {
|
package/src/client/base.ts
CHANGED
|
@@ -343,7 +343,11 @@ export class TinybirdClient {
|
|
|
343
343
|
|
|
344
344
|
// Get or create branch (always fetch fresh to avoid stale cache issues)
|
|
345
345
|
const branch = await getOrCreateBranch(
|
|
346
|
-
{
|
|
346
|
+
{
|
|
347
|
+
baseUrl: this.config.baseUrl,
|
|
348
|
+
token: this.config.token,
|
|
349
|
+
fetch: this.config.fetch,
|
|
350
|
+
},
|
|
347
351
|
branchName
|
|
348
352
|
);
|
|
349
353
|
|
|
@@ -157,6 +157,50 @@ describe("Preview environment detection", () => {
|
|
|
157
157
|
const token = await resolveToken({ token: "my-token" });
|
|
158
158
|
expect(token).toBe("my-token");
|
|
159
159
|
});
|
|
160
|
+
|
|
161
|
+
it("uses custom fetch for preview branch token resolution", async () => {
|
|
162
|
+
process.env.VERCEL_ENV = "preview";
|
|
163
|
+
process.env.VERCEL_GIT_COMMIT_REF = "feature/add-fetch";
|
|
164
|
+
|
|
165
|
+
const customFetch = vi.fn().mockResolvedValueOnce({
|
|
166
|
+
ok: true,
|
|
167
|
+
json: () =>
|
|
168
|
+
Promise.resolve({
|
|
169
|
+
id: "branch-123",
|
|
170
|
+
name: "tmp_ci_feature_add_fetch",
|
|
171
|
+
token: "branch-token",
|
|
172
|
+
created_at: "2024-01-01T00:00:00Z",
|
|
173
|
+
}),
|
|
174
|
+
});
|
|
175
|
+
const originalFetch = global.fetch;
|
|
176
|
+
const globalFetch = vi.fn().mockRejectedValue(
|
|
177
|
+
new Error("global fetch should not be called")
|
|
178
|
+
);
|
|
179
|
+
global.fetch = globalFetch as typeof fetch;
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
const token = await resolveToken({
|
|
183
|
+
baseUrl: "https://api.tinybird.co",
|
|
184
|
+
token: "workspace-token",
|
|
185
|
+
fetch: customFetch as typeof fetch,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
expect(token).toBe("branch-token");
|
|
189
|
+
expect(customFetch).toHaveBeenCalledTimes(1);
|
|
190
|
+
expect(globalFetch).not.toHaveBeenCalled();
|
|
191
|
+
|
|
192
|
+
const [url, init] = customFetch.mock.calls[0] as [string, RequestInit];
|
|
193
|
+
const parsed = new URL(url);
|
|
194
|
+
expect(parsed.pathname).toBe("/v0/environments/tmp_ci_feature_add_fetch");
|
|
195
|
+
expect(parsed.searchParams.get("with_token")).toBe("true");
|
|
196
|
+
expect(parsed.searchParams.get("from")).toBe("ts-sdk");
|
|
197
|
+
expect(new Headers(init.headers).get("Authorization")).toBe(
|
|
198
|
+
"Bearer workspace-token"
|
|
199
|
+
);
|
|
200
|
+
} finally {
|
|
201
|
+
global.fetch = originalFetch;
|
|
202
|
+
}
|
|
203
|
+
});
|
|
160
204
|
});
|
|
161
205
|
|
|
162
206
|
describe("clearTokenCache", () => {
|
package/src/client/preview.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Tinybird branch token for the current git branch.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { createTinybirdFetcher } from "../api/fetcher.js";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Branch information with token
|
|
@@ -110,13 +110,15 @@ function sanitizeBranchName(branchName: string): string {
|
|
|
110
110
|
async function fetchBranchToken(
|
|
111
111
|
baseUrl: string,
|
|
112
112
|
workspaceToken: string,
|
|
113
|
-
branchName: string
|
|
113
|
+
branchName: string,
|
|
114
|
+
fetchFn?: typeof fetch
|
|
114
115
|
): Promise<string | null> {
|
|
115
116
|
const sanitizedName = sanitizeBranchName(branchName);
|
|
116
117
|
// Look for the preview branch with tmp_ci_ prefix (matches what tinybird preview creates)
|
|
117
118
|
const previewBranchName = `tmp_ci_${sanitizedName}`;
|
|
118
119
|
const url = new URL(`/v0/environments/${encodeURIComponent(previewBranchName)}`, baseUrl);
|
|
119
120
|
url.searchParams.set("with_token", "true");
|
|
121
|
+
const tinybirdFetch = createTinybirdFetcher(fetchFn ?? globalThis.fetch);
|
|
120
122
|
|
|
121
123
|
try {
|
|
122
124
|
const response = await tinybirdFetch(url.toString(), {
|
|
@@ -153,6 +155,7 @@ async function fetchBranchToken(
|
|
|
153
155
|
export async function resolveToken(options?: {
|
|
154
156
|
baseUrl?: string;
|
|
155
157
|
token?: string;
|
|
158
|
+
fetch?: typeof fetch;
|
|
156
159
|
}): Promise<string> {
|
|
157
160
|
// 1. Check for explicit branch token override
|
|
158
161
|
if (process.env.TINYBIRD_BRANCH_TOKEN) {
|
|
@@ -181,7 +184,12 @@ export async function resolveToken(options?: {
|
|
|
181
184
|
const baseUrl = options?.baseUrl ?? process.env.TINYBIRD_URL ?? "https://api.tinybird.co";
|
|
182
185
|
|
|
183
186
|
// Fetch branch token
|
|
184
|
-
const branchToken = await fetchBranchToken(
|
|
187
|
+
const branchToken = await fetchBranchToken(
|
|
188
|
+
baseUrl,
|
|
189
|
+
configuredToken,
|
|
190
|
+
branchName,
|
|
191
|
+
options?.fetch
|
|
192
|
+
);
|
|
185
193
|
|
|
186
194
|
if (branchToken) {
|
|
187
195
|
// Cache for subsequent calls
|
|
@@ -142,7 +142,7 @@ describe('Datasource Generator', () => {
|
|
|
142
142
|
expect(result.content).toContain('country LowCardinality(String)');
|
|
143
143
|
});
|
|
144
144
|
|
|
145
|
-
it('formats LowCardinality(Nullable) correctly', () => {
|
|
145
|
+
it('formats LowCardinality(Nullable) correctly with .lowCardinality().nullable()', () => {
|
|
146
146
|
const ds = defineDatasource('test_ds', {
|
|
147
147
|
schema: {
|
|
148
148
|
country: t.string().lowCardinality().nullable(),
|
|
@@ -151,6 +151,19 @@ describe('Datasource Generator', () => {
|
|
|
151
151
|
|
|
152
152
|
const result = generateDatasource(ds);
|
|
153
153
|
expect(result.content).toContain('country LowCardinality(Nullable(String))');
|
|
154
|
+
expect(result.content).not.toContain('Nullable(LowCardinality');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('formats LowCardinality(Nullable) correctly with .nullable().lowCardinality()', () => {
|
|
158
|
+
const ds = defineDatasource('test_ds', {
|
|
159
|
+
schema: {
|
|
160
|
+
country: t.string().nullable().lowCardinality(),
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const result = generateDatasource(ds);
|
|
165
|
+
expect(result.content).toContain('country LowCardinality(Nullable(String))');
|
|
166
|
+
expect(result.content).not.toContain('Nullable(LowCardinality');
|
|
154
167
|
});
|
|
155
168
|
|
|
156
169
|
it('includes default values', () => {
|
|
@@ -13,6 +13,29 @@ import { definePipe, node } from "./pipe.js";
|
|
|
13
13
|
import { t } from "./types.js";
|
|
14
14
|
|
|
15
15
|
describe("Project Schema", () => {
|
|
16
|
+
const originalEnv = { ...process.env };
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
process.env = { ...originalEnv };
|
|
20
|
+
delete process.env.VERCEL_ENV;
|
|
21
|
+
delete process.env.GITHUB_HEAD_REF;
|
|
22
|
+
delete process.env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME;
|
|
23
|
+
delete process.env.CI;
|
|
24
|
+
delete process.env.TINYBIRD_PREVIEW_MODE;
|
|
25
|
+
delete process.env.VERCEL_GIT_COMMIT_REF;
|
|
26
|
+
delete process.env.GITHUB_REF_NAME;
|
|
27
|
+
delete process.env.CI_COMMIT_BRANCH;
|
|
28
|
+
delete process.env.CIRCLE_BRANCH;
|
|
29
|
+
delete process.env.BUILD_SOURCEBRANCHNAME;
|
|
30
|
+
delete process.env.BITBUCKET_BRANCH;
|
|
31
|
+
delete process.env.TINYBIRD_BRANCH_NAME;
|
|
32
|
+
delete process.env.TINYBIRD_BRANCH_TOKEN;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
process.env = { ...originalEnv };
|
|
37
|
+
});
|
|
38
|
+
|
|
16
39
|
describe("defineProject", () => {
|
|
17
40
|
it("creates a project with empty config", () => {
|
|
18
41
|
const project = defineProject({});
|
|
@@ -390,6 +413,7 @@ describe("Project Schema", () => {
|
|
|
390
413
|
const events = defineDatasource("events", {
|
|
391
414
|
schema: { id: t.string() },
|
|
392
415
|
});
|
|
416
|
+
const customFetch = vi.fn();
|
|
393
417
|
|
|
394
418
|
// Should accept all options without throwing
|
|
395
419
|
const client = createTinybirdClient({
|
|
@@ -397,6 +421,7 @@ describe("Project Schema", () => {
|
|
|
397
421
|
pipes: {},
|
|
398
422
|
baseUrl: "https://custom.tinybird.co",
|
|
399
423
|
token: "test-token",
|
|
424
|
+
fetch: customFetch as typeof fetch,
|
|
400
425
|
configDir: "/custom/config/dir",
|
|
401
426
|
devMode: true,
|
|
402
427
|
});
|
|
@@ -440,5 +465,59 @@ describe("Project Schema", () => {
|
|
|
440
465
|
expect(client._options).toBeDefined();
|
|
441
466
|
expect(() => client.client).toThrow("Client not initialized");
|
|
442
467
|
});
|
|
468
|
+
|
|
469
|
+
it("uses custom fetch for typed endpoint queries", async () => {
|
|
470
|
+
const topEvents = definePipe("top_events", {
|
|
471
|
+
nodes: [node({ name: "endpoint", sql: "SELECT 1" })],
|
|
472
|
+
output: { count: t.int64() },
|
|
473
|
+
endpoint: true,
|
|
474
|
+
});
|
|
475
|
+
const customFetch = vi.fn().mockResolvedValueOnce({
|
|
476
|
+
ok: true,
|
|
477
|
+
json: () =>
|
|
478
|
+
Promise.resolve({
|
|
479
|
+
data: [],
|
|
480
|
+
meta: [],
|
|
481
|
+
rows: 0,
|
|
482
|
+
statistics: {
|
|
483
|
+
elapsed: 0,
|
|
484
|
+
rows_read: 0,
|
|
485
|
+
bytes_read: 0,
|
|
486
|
+
},
|
|
487
|
+
}),
|
|
488
|
+
});
|
|
489
|
+
const originalFetch = global.fetch;
|
|
490
|
+
const globalFetch = vi.fn().mockRejectedValue(
|
|
491
|
+
new Error("global fetch should not be called")
|
|
492
|
+
);
|
|
493
|
+
global.fetch = globalFetch as typeof fetch;
|
|
494
|
+
|
|
495
|
+
try {
|
|
496
|
+
const client = createTinybirdClient({
|
|
497
|
+
datasources: {},
|
|
498
|
+
pipes: { topEvents },
|
|
499
|
+
baseUrl: "https://api.tinybird.co",
|
|
500
|
+
token: "test-token",
|
|
501
|
+
fetch: customFetch as typeof fetch,
|
|
502
|
+
devMode: false,
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
const result = await client.topEvents.query({});
|
|
506
|
+
|
|
507
|
+
expect(result.rows).toBe(0);
|
|
508
|
+
expect(customFetch).toHaveBeenCalledTimes(1);
|
|
509
|
+
expect(globalFetch).not.toHaveBeenCalled();
|
|
510
|
+
|
|
511
|
+
const [url, init] = customFetch.mock.calls[0] as [string, RequestInit];
|
|
512
|
+
const parsed = new URL(url);
|
|
513
|
+
expect(parsed.pathname).toBe("/v0/pipes/top_events.json");
|
|
514
|
+
expect(parsed.searchParams.get("from")).toBe("ts-sdk");
|
|
515
|
+
expect(new Headers(init.headers).get("Authorization")).toBe(
|
|
516
|
+
"Bearer test-token"
|
|
517
|
+
);
|
|
518
|
+
} finally {
|
|
519
|
+
global.fetch = originalFetch;
|
|
520
|
+
}
|
|
521
|
+
});
|
|
443
522
|
});
|
|
444
523
|
});
|
package/src/schema/project.ts
CHANGED
|
@@ -130,6 +130,8 @@ export interface TinybirdClientConfig<
|
|
|
130
130
|
baseUrl?: string;
|
|
131
131
|
/** Tinybird API token (defaults to TINYBIRD_TOKEN env var) */
|
|
132
132
|
token?: string;
|
|
133
|
+
/** Custom fetch implementation (optional, defaults to global fetch) */
|
|
134
|
+
fetch?: typeof fetch;
|
|
133
135
|
/**
|
|
134
136
|
* Directory to use as the starting point when searching for tinybird.json config.
|
|
135
137
|
* In monorepo setups, this should be set to the directory containing tinybird.json
|
|
@@ -295,6 +297,7 @@ export const Tinybird: TinybirdConstructor = class Tinybird<
|
|
|
295
297
|
readonly #options: {
|
|
296
298
|
baseUrl?: string;
|
|
297
299
|
token?: string;
|
|
300
|
+
fetch?: typeof fetch;
|
|
298
301
|
configDir?: string;
|
|
299
302
|
devMode?: boolean;
|
|
300
303
|
};
|
|
@@ -303,6 +306,7 @@ export const Tinybird: TinybirdConstructor = class Tinybird<
|
|
|
303
306
|
this.#options = {
|
|
304
307
|
baseUrl: config.baseUrl,
|
|
305
308
|
token: config.token,
|
|
309
|
+
fetch: config.fetch,
|
|
306
310
|
configDir: config.configDir,
|
|
307
311
|
devMode: config.devMode,
|
|
308
312
|
};
|
|
@@ -392,11 +396,16 @@ export const Tinybird: TinybirdConstructor = class Tinybird<
|
|
|
392
396
|
|
|
393
397
|
const baseUrl =
|
|
394
398
|
this.#options.baseUrl ?? process.env.TINYBIRD_URL ?? "https://api.tinybird.co";
|
|
395
|
-
const token = await resolveToken({
|
|
399
|
+
const token = await resolveToken({
|
|
400
|
+
baseUrl,
|
|
401
|
+
token: this.#options.token,
|
|
402
|
+
fetch: this.#options.fetch,
|
|
403
|
+
});
|
|
396
404
|
|
|
397
405
|
this.#client = createClient({
|
|
398
406
|
baseUrl,
|
|
399
407
|
token,
|
|
408
|
+
fetch: this.#options.fetch,
|
|
400
409
|
devMode: this.#options.devMode ?? process.env.NODE_ENV === "development",
|
|
401
410
|
configDir: this.#options.configDir,
|
|
402
411
|
});
|
package/src/schema/types.test.ts
CHANGED
|
@@ -87,10 +87,18 @@ describe("Type Validators (t.*)", () => {
|
|
|
87
87
|
expect(type._tinybirdType).toBe("LowCardinality(Nullable(String))");
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
-
it("preserves
|
|
90
|
+
it("preserves lowCardinality modifier and omits nullable when combined (nullable is in the type string)", () => {
|
|
91
91
|
const type = t.string().lowCardinality().nullable();
|
|
92
92
|
expect(type._modifiers.lowCardinality).toBe(true);
|
|
93
|
-
expect(type._modifiers.nullable).
|
|
93
|
+
expect(type._modifiers.nullable).toBeUndefined();
|
|
94
|
+
expect(type._tinybirdType).toBe("LowCardinality(Nullable(String))");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("omits nullable modifier when nullable().lowCardinality() is chained", () => {
|
|
98
|
+
const type = t.string().nullable().lowCardinality();
|
|
99
|
+
expect(type._modifiers.lowCardinality).toBe(true);
|
|
100
|
+
expect(type._modifiers.nullable).toBeUndefined();
|
|
101
|
+
expect(type._tinybirdType).toBe("LowCardinality(Nullable(String))");
|
|
94
102
|
});
|
|
95
103
|
});
|
|
96
104
|
|
package/src/schema/types.ts
CHANGED
|
@@ -106,7 +106,6 @@ function createValidator<TType, TTinybirdType extends string>(
|
|
|
106
106
|
`LowCardinality(Nullable(${string}))`
|
|
107
107
|
>(newType as `LowCardinality(Nullable(${string}))`, {
|
|
108
108
|
...modifiers,
|
|
109
|
-
nullable: true,
|
|
110
109
|
}) as unknown as TypeValidator<
|
|
111
110
|
TType | null,
|
|
112
111
|
`Nullable(${TTinybirdType})`,
|
|
@@ -129,9 +128,10 @@ function createValidator<TType, TTinybirdType extends string>(
|
|
|
129
128
|
// Extract base type from Nullable(X) and wrap as LowCardinality(Nullable(X))
|
|
130
129
|
const baseType = tinybirdType.replace(/^Nullable\((.+)\)$/, "$1");
|
|
131
130
|
const newType = `LowCardinality(Nullable(${baseType}))`;
|
|
131
|
+
const { nullable: _, ...rest } = modifiers;
|
|
132
132
|
return createValidator<TType, `LowCardinality(Nullable(${string}))`>(
|
|
133
133
|
newType as `LowCardinality(Nullable(${string}))`,
|
|
134
|
-
{ ...
|
|
134
|
+
{ ...rest, lowCardinality: true },
|
|
135
135
|
) as unknown as TypeValidator<
|
|
136
136
|
TType,
|
|
137
137
|
`LowCardinality(${TTinybirdType})`,
|