@tinybirdco/sdk 0.0.7 → 0.0.9
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/dist/api/resources.d.ts +2 -0
- package/dist/api/resources.d.ts.map +1 -1
- package/dist/api/resources.js +1 -0
- package/dist/api/resources.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +301 -205
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/init.test.js +67 -93
- package/dist/cli/commands/init.test.js.map +1 -1
- package/dist/cli/config.d.ts +3 -7
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +9 -20
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/config.test.js +11 -29
- package/dist/cli/config.test.js.map +1 -1
- package/dist/cli/git.d.ts +5 -0
- package/dist/cli/git.d.ts.map +1 -1
- package/dist/cli/git.js +15 -0
- package/dist/cli/git.js.map +1 -1
- package/dist/cli/index.js +42 -25
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/utils/package-manager.d.ts +9 -0
- package/dist/cli/utils/package-manager.d.ts.map +1 -1
- package/dist/cli/utils/package-manager.js +130 -35
- package/dist/cli/utils/package-manager.js.map +1 -1
- package/dist/cli/utils/package-manager.test.js +124 -32
- package/dist/cli/utils/package-manager.test.js.map +1 -1
- package/dist/codegen/index.d.ts +4 -0
- package/dist/codegen/index.d.ts.map +1 -1
- package/dist/codegen/index.js +96 -0
- package/dist/codegen/index.js.map +1 -1
- package/dist/codegen/index.test.js +10 -0
- package/dist/codegen/index.test.js.map +1 -1
- package/dist/generator/datasource.d.ts.map +1 -1
- package/dist/generator/datasource.js +20 -0
- package/dist/generator/datasource.js.map +1 -1
- package/dist/generator/datasource.test.js +11 -0
- package/dist/generator/datasource.test.js.map +1 -1
- package/dist/schema/datasource.d.ts +5 -0
- package/dist/schema/datasource.d.ts.map +1 -1
- package/dist/schema/datasource.js.map +1 -1
- package/package.json +1 -1
- package/src/api/resources.ts +4 -0
- package/src/cli/commands/init.test.ts +67 -107
- package/src/cli/commands/init.ts +350 -218
- package/src/cli/config.test.ts +12 -42
- package/src/cli/config.ts +9 -23
- package/src/cli/git.ts +15 -0
- package/src/cli/index.ts +46 -27
- package/src/cli/utils/package-manager.test.ts +165 -33
- package/src/cli/utils/package-manager.ts +133 -30
- package/src/codegen/index.test.ts +12 -0
- package/src/codegen/index.ts +120 -0
- package/src/generator/datasource.test.ts +13 -0
- package/src/generator/datasource.ts +24 -0
- package/src/schema/datasource.ts +5 -0
|
@@ -3,42 +3,145 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
-
import { join } from "node:path";
|
|
6
|
+
import { dirname, join, resolve } from "node:path";
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (existsSync(join(
|
|
14
|
-
|
|
8
|
+
export type PackageManager = "pnpm" | "yarn" | "bun" | "npm";
|
|
9
|
+
const TINYBIRD_SDK_PACKAGE = "@tinybirdco/sdk";
|
|
10
|
+
|
|
11
|
+
function detectPackageManagerFromLockfile(dir: string): PackageManager | undefined {
|
|
12
|
+
if (existsSync(join(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
13
|
+
if (existsSync(join(dir, "yarn.lock"))) return "yarn";
|
|
14
|
+
if (existsSync(join(dir, "bun.lockb"))) return "bun";
|
|
15
|
+
if (existsSync(join(dir, "package-lock.json"))) return "npm";
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function detectPackageManagerFromWorkspace(dir: string): PackageManager | undefined {
|
|
20
|
+
if (existsSync(join(dir, "pnpm-workspace.yaml"))) return "pnpm";
|
|
21
|
+
if (existsSync(join(dir, "pnpm-workspace.yml"))) return "pnpm";
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function detectPackageManagerFromPackageJson(
|
|
26
|
+
dir: string
|
|
27
|
+
): PackageManager | undefined {
|
|
28
|
+
const packageJsonPath = join(dir, "package.json");
|
|
29
|
+
if (!existsSync(packageJsonPath)) return undefined;
|
|
30
|
+
try {
|
|
31
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
32
|
+
const pm = packageJson.packageManager;
|
|
33
|
+
if (typeof pm !== "string") return undefined;
|
|
34
|
+
if (pm.startsWith("pnpm")) return "pnpm";
|
|
35
|
+
if (pm.startsWith("yarn")) return "yarn";
|
|
36
|
+
if (pm.startsWith("bun")) return "bun";
|
|
37
|
+
if (pm.startsWith("npm")) return "npm";
|
|
38
|
+
} catch {
|
|
39
|
+
return undefined;
|
|
15
40
|
}
|
|
16
|
-
|
|
17
|
-
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getSearchDirs(start: string): string[] {
|
|
45
|
+
const dirs: string[] = [];
|
|
46
|
+
let current = resolve(start);
|
|
47
|
+
while (true) {
|
|
48
|
+
dirs.push(current);
|
|
49
|
+
const parent = dirname(current);
|
|
50
|
+
if (parent === current) break;
|
|
51
|
+
current = parent;
|
|
52
|
+
}
|
|
53
|
+
return dirs;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function findNearestPackageJson(start: string): string | undefined {
|
|
57
|
+
for (const dir of getSearchDirs(start)) {
|
|
58
|
+
const packageJsonPath = join(dir, "package.json");
|
|
59
|
+
if (existsSync(packageJsonPath)) return packageJsonPath;
|
|
18
60
|
}
|
|
19
|
-
|
|
20
|
-
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getPackageManagerRunCmd(packageManager: PackageManager): string {
|
|
65
|
+
switch (packageManager) {
|
|
66
|
+
case "pnpm":
|
|
67
|
+
return "pnpm run";
|
|
68
|
+
case "yarn":
|
|
69
|
+
return "yarn";
|
|
70
|
+
case "bun":
|
|
71
|
+
return "bun run";
|
|
72
|
+
case "npm":
|
|
73
|
+
default:
|
|
74
|
+
return "npm run";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function getPackageManagerInstallCmd(
|
|
79
|
+
packageManager: PackageManager
|
|
80
|
+
): string {
|
|
81
|
+
switch (packageManager) {
|
|
82
|
+
case "pnpm":
|
|
83
|
+
return "pnpm install";
|
|
84
|
+
case "yarn":
|
|
85
|
+
return "yarn install";
|
|
86
|
+
case "bun":
|
|
87
|
+
return "bun install";
|
|
88
|
+
case "npm":
|
|
89
|
+
default:
|
|
90
|
+
return "npm install";
|
|
21
91
|
}
|
|
22
|
-
|
|
23
|
-
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Detect package manager (npm, pnpm, yarn, or bun)
|
|
96
|
+
*/
|
|
97
|
+
export function detectPackageManager(cwd: string = process.cwd()): PackageManager {
|
|
98
|
+
for (const dir of getSearchDirs(cwd)) {
|
|
99
|
+
const fromLockfile = detectPackageManagerFromLockfile(dir);
|
|
100
|
+
if (fromLockfile) return fromLockfile;
|
|
101
|
+
|
|
102
|
+
const fromWorkspace = detectPackageManagerFromWorkspace(dir);
|
|
103
|
+
if (fromWorkspace) return fromWorkspace;
|
|
104
|
+
|
|
105
|
+
const fromPackageJson = detectPackageManagerFromPackageJson(dir);
|
|
106
|
+
if (fromPackageJson) return fromPackageJson;
|
|
24
107
|
}
|
|
25
108
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
109
|
+
return "npm";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function detectPackageManagerInstallCmd(
|
|
113
|
+
cwd: string = process.cwd()
|
|
114
|
+
): string {
|
|
115
|
+
return getPackageManagerInstallCmd(detectPackageManager(cwd));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function hasTinybirdSdkDependency(cwd: string = process.cwd()): boolean {
|
|
119
|
+
const packageJsonPath = findNearestPackageJson(cwd);
|
|
120
|
+
if (!packageJsonPath) return false;
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
124
|
+
const dependencyFields = [
|
|
125
|
+
packageJson.dependencies,
|
|
126
|
+
packageJson.devDependencies,
|
|
127
|
+
packageJson.peerDependencies,
|
|
128
|
+
packageJson.optionalDependencies,
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
return dependencyFields.some(
|
|
132
|
+
(deps) =>
|
|
133
|
+
deps &&
|
|
134
|
+
typeof deps === "object" &&
|
|
135
|
+
Object.prototype.hasOwnProperty.call(deps, TINYBIRD_SDK_PACKAGE)
|
|
136
|
+
);
|
|
137
|
+
} catch {
|
|
138
|
+
return false;
|
|
40
139
|
}
|
|
140
|
+
}
|
|
41
141
|
|
|
42
|
-
|
|
43
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Detect package manager and return the appropriate run command
|
|
144
|
+
*/
|
|
145
|
+
export function detectPackageManagerRunCmd(cwd: string = process.cwd()): string {
|
|
146
|
+
return getPackageManagerRunCmd(detectPackageManager(cwd));
|
|
44
147
|
}
|
|
@@ -74,6 +74,18 @@ describe("generateDatasourceCode", () => {
|
|
|
74
74
|
expect(code).toContain(" * Event tracking data");
|
|
75
75
|
expect(code).toContain(" */");
|
|
76
76
|
});
|
|
77
|
+
|
|
78
|
+
it("includes forward query when present", () => {
|
|
79
|
+
const ds: DatasourceInfo = {
|
|
80
|
+
name: "events",
|
|
81
|
+
columns: [{ name: "id", type: "String" }],
|
|
82
|
+
engine: { type: "MergeTree", sorting_key: "id" },
|
|
83
|
+
forward_query: "SELECT id",
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const code = generateDatasourceCode(ds);
|
|
87
|
+
expect(code).toContain("forwardQuery: `SELECT id`");
|
|
88
|
+
});
|
|
77
89
|
});
|
|
78
90
|
|
|
79
91
|
describe("generatePipeCode", () => {
|
package/src/codegen/index.ts
CHANGED
|
@@ -20,6 +20,9 @@ export function generateDatasourceCode(ds: DatasourceInfo): string {
|
|
|
20
20
|
const typeName = toPascalCase(ds.name);
|
|
21
21
|
const lines: string[] = [];
|
|
22
22
|
|
|
23
|
+
// Check if any columns have jsonpath set
|
|
24
|
+
const hasJsonpath = ds.columns.some((col) => col.jsonpath);
|
|
25
|
+
|
|
23
26
|
// JSDoc comment
|
|
24
27
|
if (ds.description) {
|
|
25
28
|
lines.push("/**");
|
|
@@ -33,6 +36,11 @@ export function generateDatasourceCode(ds: DatasourceInfo): string {
|
|
|
33
36
|
lines.push(` description: "${escapeString(ds.description)}",`);
|
|
34
37
|
}
|
|
35
38
|
|
|
39
|
+
// Add jsonPaths: false if no columns use jsonpath
|
|
40
|
+
if (!hasJsonpath) {
|
|
41
|
+
lines.push(" jsonPaths: false,");
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
// Schema
|
|
37
45
|
lines.push(" schema: {");
|
|
38
46
|
for (const col of ds.columns) {
|
|
@@ -45,6 +53,11 @@ export function generateDatasourceCode(ds: DatasourceInfo): string {
|
|
|
45
53
|
const engineCode = generateEngineCode(ds.engine);
|
|
46
54
|
lines.push(` engine: ${engineCode},`);
|
|
47
55
|
|
|
56
|
+
if (ds.forward_query) {
|
|
57
|
+
const formattedQuery = formatSqlForTemplate(ds.forward_query);
|
|
58
|
+
lines.push(` forwardQuery: \`${formattedQuery}\`,`);
|
|
59
|
+
}
|
|
60
|
+
|
|
48
61
|
lines.push("});");
|
|
49
62
|
lines.push("");
|
|
50
63
|
lines.push(`export type ${typeName}Row = InferRow<typeof ${varName}>;`);
|
|
@@ -377,3 +390,110 @@ export function generateAllFiles(
|
|
|
377
390
|
pipeCount: pipes.length,
|
|
378
391
|
};
|
|
379
392
|
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Generate a single combined tinybird.ts file with all definitions
|
|
396
|
+
*/
|
|
397
|
+
export function generateCombinedFile(
|
|
398
|
+
datasources: DatasourceInfo[],
|
|
399
|
+
pipes: PipeInfo[]
|
|
400
|
+
): string {
|
|
401
|
+
const lines: string[] = [
|
|
402
|
+
"/**",
|
|
403
|
+
" * Tinybird Definitions",
|
|
404
|
+
" *",
|
|
405
|
+
" * This file contains all datasource and endpoint definitions.",
|
|
406
|
+
" * Generated from existing workspace resources.",
|
|
407
|
+
" */",
|
|
408
|
+
"",
|
|
409
|
+
];
|
|
410
|
+
|
|
411
|
+
// Build imports
|
|
412
|
+
const sdkImports: string[] = ["createTinybirdClient", "t"];
|
|
413
|
+
|
|
414
|
+
if (datasources.length > 0) {
|
|
415
|
+
sdkImports.push("defineDatasource", "engine", "type InferRow");
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const hasMaterialized = pipes.some((p) => p.type === "materialized");
|
|
419
|
+
const hasCopy = pipes.some((p) => p.type === "copy");
|
|
420
|
+
const hasEndpoint = pipes.some((p) => p.type === "endpoint");
|
|
421
|
+
const hasPlainPipe = pipes.some((p) => p.type === "pipe");
|
|
422
|
+
const hasParams = pipes.some(
|
|
423
|
+
(p) => p.params.length > 0 && p.type !== "materialized" && p.type !== "copy"
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
if (pipes.length > 0) {
|
|
427
|
+
sdkImports.push("node");
|
|
428
|
+
}
|
|
429
|
+
if (hasParams) {
|
|
430
|
+
sdkImports.push("p");
|
|
431
|
+
}
|
|
432
|
+
if (hasEndpoint) {
|
|
433
|
+
sdkImports.push("defineEndpoint", "type InferParams", "type InferOutputRow");
|
|
434
|
+
}
|
|
435
|
+
if (hasMaterialized) {
|
|
436
|
+
sdkImports.push("defineMaterializedView");
|
|
437
|
+
}
|
|
438
|
+
if (hasCopy) {
|
|
439
|
+
sdkImports.push("defineCopyPipe");
|
|
440
|
+
}
|
|
441
|
+
if (hasPlainPipe) {
|
|
442
|
+
sdkImports.push("definePipe");
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
lines.push(`import {`);
|
|
446
|
+
lines.push(` ${sdkImports.join(",\n ")},`);
|
|
447
|
+
lines.push(`} from "@tinybirdco/sdk";`);
|
|
448
|
+
lines.push("");
|
|
449
|
+
|
|
450
|
+
// Datasources section
|
|
451
|
+
if (datasources.length > 0) {
|
|
452
|
+
lines.push("// ============================================================================");
|
|
453
|
+
lines.push("// Datasources");
|
|
454
|
+
lines.push("// ============================================================================");
|
|
455
|
+
lines.push("");
|
|
456
|
+
|
|
457
|
+
for (const ds of datasources) {
|
|
458
|
+
lines.push(generateDatasourceCode(ds));
|
|
459
|
+
lines.push("");
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Pipes/Endpoints section
|
|
464
|
+
if (pipes.length > 0) {
|
|
465
|
+
lines.push("// ============================================================================");
|
|
466
|
+
lines.push("// Endpoints");
|
|
467
|
+
lines.push("// ============================================================================");
|
|
468
|
+
lines.push("");
|
|
469
|
+
|
|
470
|
+
for (const pipe of pipes) {
|
|
471
|
+
lines.push(generatePipeCode(pipe));
|
|
472
|
+
lines.push("");
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Client section
|
|
477
|
+
lines.push("// ============================================================================");
|
|
478
|
+
lines.push("// Client");
|
|
479
|
+
lines.push("// ============================================================================");
|
|
480
|
+
lines.push("");
|
|
481
|
+
|
|
482
|
+
const dsNames =
|
|
483
|
+
datasources.length > 0
|
|
484
|
+
? datasources.map((ds) => toCamelCase(ds.name)).join(", ")
|
|
485
|
+
: "";
|
|
486
|
+
const endpoints = pipes.filter((p) => p.type === "endpoint");
|
|
487
|
+
const pipeNames =
|
|
488
|
+
endpoints.length > 0
|
|
489
|
+
? endpoints.map((p) => toCamelCase(p.name)).join(", ")
|
|
490
|
+
: "";
|
|
491
|
+
|
|
492
|
+
lines.push("export const tinybird = createTinybirdClient({");
|
|
493
|
+
lines.push(` datasources: { ${dsNames} },`);
|
|
494
|
+
lines.push(` pipes: { ${pipeNames} },`);
|
|
495
|
+
lines.push("});");
|
|
496
|
+
lines.push("");
|
|
497
|
+
|
|
498
|
+
return lines.join("\n");
|
|
499
|
+
}
|
|
@@ -81,6 +81,19 @@ describe('Datasource Generator', () => {
|
|
|
81
81
|
const result = generateDatasource(ds);
|
|
82
82
|
expect(result.content).toContain('ENGINE_TTL "timestamp + INTERVAL 90 DAY"');
|
|
83
83
|
});
|
|
84
|
+
|
|
85
|
+
it('includes forward query when provided', () => {
|
|
86
|
+
const ds = defineDatasource('test_ds', {
|
|
87
|
+
schema: {
|
|
88
|
+
id: t.string(),
|
|
89
|
+
},
|
|
90
|
+
forwardQuery: 'SELECT id',
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const result = generateDatasource(ds);
|
|
94
|
+
expect(result.content).toContain('FORWARD_QUERY >');
|
|
95
|
+
expect(result.content).toContain(' SELECT id');
|
|
96
|
+
});
|
|
84
97
|
});
|
|
85
98
|
|
|
86
99
|
describe('Column formatting', () => {
|
|
@@ -163,6 +163,23 @@ function generateKafkaConfig(kafka: KafkaConfig): string {
|
|
|
163
163
|
return parts.join("\n");
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Generate forward query section
|
|
168
|
+
*/
|
|
169
|
+
function generateForwardQuery(forwardQuery?: string): string | null {
|
|
170
|
+
if (!forwardQuery) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const trimmed = forwardQuery.trim();
|
|
175
|
+
if (!trimmed) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const lines = trimmed.split(/\r?\n/);
|
|
180
|
+
return ["FORWARD_QUERY >", ...lines.map((line) => ` ${line}`)].join("\n");
|
|
181
|
+
}
|
|
182
|
+
|
|
166
183
|
/**
|
|
167
184
|
* Generate a .datasource file content from a DatasourceDefinition
|
|
168
185
|
*
|
|
@@ -224,6 +241,13 @@ export function generateDatasource(
|
|
|
224
241
|
parts.push(generateKafkaConfig(datasource.options.kafka));
|
|
225
242
|
}
|
|
226
243
|
|
|
244
|
+
// Add forward query if present
|
|
245
|
+
const forwardQuery = generateForwardQuery(datasource.options.forwardQuery);
|
|
246
|
+
if (forwardQuery) {
|
|
247
|
+
parts.push("");
|
|
248
|
+
parts.push(forwardQuery);
|
|
249
|
+
}
|
|
250
|
+
|
|
227
251
|
return {
|
|
228
252
|
name: datasource._name,
|
|
229
253
|
content: parts.join("\n"),
|
package/src/schema/datasource.ts
CHANGED
|
@@ -71,6 +71,11 @@ export interface DatasourceOptions<TSchema extends SchemaDefinition> {
|
|
|
71
71
|
* Defaults to true.
|
|
72
72
|
*/
|
|
73
73
|
jsonPaths?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Forward query used to evolve a datasource with incompatible schema changes.
|
|
76
|
+
* This should be the SELECT clause only (no FROM/WHERE).
|
|
77
|
+
*/
|
|
78
|
+
forwardQuery?: string;
|
|
74
79
|
/** Kafka ingestion configuration */
|
|
75
80
|
kafka?: KafkaConfig;
|
|
76
81
|
}
|