@tinybirdco/sdk 0.0.1
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 +518 -0
- package/bin/tinybird.js +7 -0
- package/dist/api/branches.d.ts +98 -0
- package/dist/api/branches.d.ts.map +1 -0
- package/dist/api/branches.js +203 -0
- package/dist/api/branches.js.map +1 -0
- package/dist/api/branches.test.d.ts +2 -0
- package/dist/api/branches.test.d.ts.map +1 -0
- package/dist/api/branches.test.js +286 -0
- package/dist/api/branches.test.js.map +1 -0
- package/dist/api/build.d.ts +130 -0
- package/dist/api/build.d.ts.map +1 -0
- package/dist/api/build.js +143 -0
- package/dist/api/build.js.map +1 -0
- package/dist/api/build.test.d.ts +2 -0
- package/dist/api/build.test.d.ts.map +1 -0
- package/dist/api/build.test.js +138 -0
- package/dist/api/build.test.js.map +1 -0
- package/dist/api/deploy.d.ts +39 -0
- package/dist/api/deploy.d.ts.map +1 -0
- package/dist/api/deploy.js +135 -0
- package/dist/api/deploy.js.map +1 -0
- package/dist/api/deploy.test.d.ts +2 -0
- package/dist/api/deploy.test.d.ts.map +1 -0
- package/dist/api/deploy.test.js +118 -0
- package/dist/api/deploy.test.js.map +1 -0
- package/dist/api/workspaces.d.ts +46 -0
- package/dist/api/workspaces.d.ts.map +1 -0
- package/dist/api/workspaces.js +39 -0
- package/dist/api/workspaces.js.map +1 -0
- package/dist/api/workspaces.test.d.ts +2 -0
- package/dist/api/workspaces.test.d.ts.map +1 -0
- package/dist/api/workspaces.test.js +65 -0
- package/dist/api/workspaces.test.js.map +1 -0
- package/dist/cli/auth.d.ts +86 -0
- package/dist/cli/auth.d.ts.map +1 -0
- package/dist/cli/auth.js +284 -0
- package/dist/cli/auth.js.map +1 -0
- package/dist/cli/branch-store.d.ts +53 -0
- package/dist/cli/branch-store.d.ts.map +1 -0
- package/dist/cli/branch-store.js +91 -0
- package/dist/cli/branch-store.js.map +1 -0
- package/dist/cli/branch-store.test.d.ts +2 -0
- package/dist/cli/branch-store.test.d.ts.map +1 -0
- package/dist/cli/branch-store.test.js +115 -0
- package/dist/cli/branch-store.test.js.map +1 -0
- package/dist/cli/commands/branch.d.ts +82 -0
- package/dist/cli/commands/branch.d.ts.map +1 -0
- package/dist/cli/commands/branch.js +215 -0
- package/dist/cli/commands/branch.js.map +1 -0
- package/dist/cli/commands/build.d.ts +43 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/build.js +138 -0
- package/dist/cli/commands/build.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +78 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/dev.js +226 -0
- package/dist/cli/commands/dev.js.map +1 -0
- package/dist/cli/commands/init.d.ts +45 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +277 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/init.test.d.ts +2 -0
- package/dist/cli/commands/init.test.d.ts.map +1 -0
- package/dist/cli/commands/init.test.js +158 -0
- package/dist/cli/commands/init.test.js.map +1 -0
- package/dist/cli/commands/login.d.ts +37 -0
- package/dist/cli/commands/login.d.ts.map +1 -0
- package/dist/cli/commands/login.js +64 -0
- package/dist/cli/commands/login.js.map +1 -0
- package/dist/cli/config.d.ts +114 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +258 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/config.test.d.ts +2 -0
- package/dist/cli/config.test.d.ts.map +1 -0
- package/dist/cli/config.test.js +243 -0
- package/dist/cli/config.test.js.map +1 -0
- package/dist/cli/env.d.ts +29 -0
- package/dist/cli/env.d.ts.map +1 -0
- package/dist/cli/env.js +66 -0
- package/dist/cli/env.js.map +1 -0
- package/dist/cli/git.d.ts +29 -0
- package/dist/cli/git.d.ts.map +1 -0
- package/dist/cli/git.js +114 -0
- package/dist/cli/git.js.map +1 -0
- package/dist/cli/git.test.d.ts +2 -0
- package/dist/cli/git.test.d.ts.map +1 -0
- package/dist/cli/git.test.js +125 -0
- package/dist/cli/git.test.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +337 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/utils/schema-validation.d.ts +95 -0
- package/dist/cli/utils/schema-validation.d.ts.map +1 -0
- package/dist/cli/utils/schema-validation.js +175 -0
- package/dist/cli/utils/schema-validation.js.map +1 -0
- package/dist/cli/utils/schema-validation.test.d.ts +5 -0
- package/dist/cli/utils/schema-validation.test.d.ts.map +1 -0
- package/dist/cli/utils/schema-validation.test.js +173 -0
- package/dist/cli/utils/schema-validation.test.js.map +1 -0
- package/dist/client/base.d.ts +116 -0
- package/dist/client/base.d.ts.map +1 -0
- package/dist/client/base.js +328 -0
- package/dist/client/base.js.map +1 -0
- package/dist/client/types.d.ts +137 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +43 -0
- package/dist/client/types.js.map +1 -0
- package/dist/generator/client.d.ts +44 -0
- package/dist/generator/client.d.ts.map +1 -0
- package/dist/generator/client.js +144 -0
- package/dist/generator/client.js.map +1 -0
- package/dist/generator/datasource.d.ts +57 -0
- package/dist/generator/datasource.d.ts.map +1 -0
- package/dist/generator/datasource.js +169 -0
- package/dist/generator/datasource.js.map +1 -0
- package/dist/generator/datasource.test.d.ts +2 -0
- package/dist/generator/datasource.test.d.ts.map +1 -0
- package/dist/generator/datasource.test.js +254 -0
- package/dist/generator/datasource.test.js.map +1 -0
- package/dist/generator/index.d.ts +131 -0
- package/dist/generator/index.d.ts.map +1 -0
- package/dist/generator/index.js +121 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/generator/index.test.d.ts +2 -0
- package/dist/generator/index.test.d.ts.map +1 -0
- package/dist/generator/index.test.js +175 -0
- package/dist/generator/index.test.js.map +1 -0
- package/dist/generator/loader.d.ts +156 -0
- package/dist/generator/loader.d.ts.map +1 -0
- package/dist/generator/loader.js +295 -0
- package/dist/generator/loader.js.map +1 -0
- package/dist/generator/pipe.d.ts +72 -0
- package/dist/generator/pipe.d.ts.map +1 -0
- package/dist/generator/pipe.js +174 -0
- package/dist/generator/pipe.js.map +1 -0
- package/dist/generator/pipe.test.d.ts +2 -0
- package/dist/generator/pipe.test.d.ts.map +1 -0
- package/dist/generator/pipe.test.js +393 -0
- package/dist/generator/pipe.test.js.map +1 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -0
- package/dist/infer/index.d.ts +202 -0
- package/dist/infer/index.d.ts.map +1 -0
- package/dist/infer/index.js +5 -0
- package/dist/infer/index.js.map +1 -0
- package/dist/schema/datasource.d.ts +135 -0
- package/dist/schema/datasource.d.ts.map +1 -0
- package/dist/schema/datasource.js +105 -0
- package/dist/schema/datasource.js.map +1 -0
- package/dist/schema/datasource.test.d.ts +2 -0
- package/dist/schema/datasource.test.d.ts.map +1 -0
- package/dist/schema/datasource.test.js +142 -0
- package/dist/schema/datasource.test.js.map +1 -0
- package/dist/schema/engines.d.ts +157 -0
- package/dist/schema/engines.d.ts.map +1 -0
- package/dist/schema/engines.js +155 -0
- package/dist/schema/engines.js.map +1 -0
- package/dist/schema/engines.test.d.ts +2 -0
- package/dist/schema/engines.test.d.ts.map +1 -0
- package/dist/schema/engines.test.js +221 -0
- package/dist/schema/engines.test.js.map +1 -0
- package/dist/schema/params.d.ts +106 -0
- package/dist/schema/params.d.ts.map +1 -0
- package/dist/schema/params.js +138 -0
- package/dist/schema/params.js.map +1 -0
- package/dist/schema/params.test.d.ts +2 -0
- package/dist/schema/params.test.d.ts.map +1 -0
- package/dist/schema/params.test.js +175 -0
- package/dist/schema/params.test.js.map +1 -0
- package/dist/schema/pipe.d.ts +436 -0
- package/dist/schema/pipe.d.ts.map +1 -0
- package/dist/schema/pipe.js +484 -0
- package/dist/schema/pipe.js.map +1 -0
- package/dist/schema/pipe.test.d.ts +2 -0
- package/dist/schema/pipe.test.d.ts.map +1 -0
- package/dist/schema/pipe.test.js +488 -0
- package/dist/schema/pipe.test.js.map +1 -0
- package/dist/schema/project.d.ts +202 -0
- package/dist/schema/project.d.ts.map +1 -0
- package/dist/schema/project.js +188 -0
- package/dist/schema/project.js.map +1 -0
- package/dist/schema/project.test.d.ts +2 -0
- package/dist/schema/project.test.d.ts.map +1 -0
- package/dist/schema/project.test.js +180 -0
- package/dist/schema/project.test.js.map +1 -0
- package/dist/schema/types.d.ts +140 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/types.js +174 -0
- package/dist/schema/types.js.map +1 -0
- package/dist/schema/types.test.d.ts +2 -0
- package/dist/schema/types.test.d.ts.map +1 -0
- package/dist/schema/types.test.js +176 -0
- package/dist/schema/types.test.js.map +1 -0
- package/dist/test/handlers.d.ts +58 -0
- package/dist/test/handlers.d.ts.map +1 -0
- package/dist/test/handlers.js +62 -0
- package/dist/test/handlers.js.map +1 -0
- package/dist/test/setup.d.ts +5 -0
- package/dist/test/setup.d.ts.map +1 -0
- package/dist/test/setup.js +11 -0
- package/dist/test/setup.js.map +1 -0
- package/package.json +57 -0
- package/src/api/branches.test.ts +377 -0
- package/src/api/branches.ts +334 -0
- package/src/api/build.test.ts +216 -0
- package/src/api/build.ts +266 -0
- package/src/api/deploy.test.ts +193 -0
- package/src/api/deploy.ts +163 -0
- package/src/api/workspaces.test.ts +81 -0
- package/src/api/workspaces.ts +77 -0
- package/src/cli/auth.ts +358 -0
- package/src/cli/branch-store.test.ts +139 -0
- package/src/cli/branch-store.ts +137 -0
- package/src/cli/commands/branch.ts +306 -0
- package/src/cli/commands/build.ts +183 -0
- package/src/cli/commands/dev.ts +334 -0
- package/src/cli/commands/init.test.ts +249 -0
- package/src/cli/commands/init.ts +323 -0
- package/src/cli/commands/login.ts +98 -0
- package/src/cli/config.test.ts +359 -0
- package/src/cli/config.ts +335 -0
- package/src/cli/env.ts +86 -0
- package/src/cli/git.test.ts +147 -0
- package/src/cli/git.ts +125 -0
- package/src/cli/index.ts +382 -0
- package/src/cli/utils/schema-validation.test.ts +222 -0
- package/src/cli/utils/schema-validation.ts +272 -0
- package/src/client/base.ts +414 -0
- package/src/client/types.ts +165 -0
- package/src/generator/client.ts +194 -0
- package/src/generator/datasource.test.ts +297 -0
- package/src/generator/datasource.ts +217 -0
- package/src/generator/index.test.ts +209 -0
- package/src/generator/index.ts +203 -0
- package/src/generator/loader.ts +406 -0
- package/src/generator/pipe.test.ts +441 -0
- package/src/generator/pipe.ts +220 -0
- package/src/index.ts +191 -0
- package/src/infer/index.ts +247 -0
- package/src/schema/datasource.test.ts +187 -0
- package/src/schema/datasource.ts +195 -0
- package/src/schema/engines.test.ts +247 -0
- package/src/schema/engines.ts +271 -0
- package/src/schema/params.test.ts +208 -0
- package/src/schema/params.ts +249 -0
- package/src/schema/pipe.test.ts +588 -0
- package/src/schema/pipe.ts +832 -0
- package/src/schema/project.test.ts +236 -0
- package/src/schema/project.ts +394 -0
- package/src/schema/types.test.ts +212 -0
- package/src/schema/types.ts +366 -0
- package/src/test/handlers.ts +79 -0
- package/src/test/setup.ts +13 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client types for Tinybird API interactions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for the Tinybird client
|
|
7
|
+
*/
|
|
8
|
+
export interface ClientConfig {
|
|
9
|
+
/** Tinybird API base URL (e.g., 'https://api.tinybird.co' or 'https://api.us-east.tinybird.co') */
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
/** API token for authentication */
|
|
12
|
+
token: string;
|
|
13
|
+
/** Custom fetch implementation (optional, defaults to global fetch) */
|
|
14
|
+
fetch?: typeof fetch;
|
|
15
|
+
/** Default timeout in milliseconds (optional) */
|
|
16
|
+
timeout?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Enable dev mode to automatically use branch tokens when on a feature branch.
|
|
19
|
+
* When enabled, the client will detect the git branch and use the corresponding
|
|
20
|
+
* Tinybird branch token instead of the workspace token.
|
|
21
|
+
*/
|
|
22
|
+
devMode?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Column metadata from query response
|
|
27
|
+
*/
|
|
28
|
+
export interface ColumnMeta {
|
|
29
|
+
/** Column name */
|
|
30
|
+
name: string;
|
|
31
|
+
/** Column type (Tinybird/ClickHouse type) */
|
|
32
|
+
type: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Query statistics from response
|
|
37
|
+
*/
|
|
38
|
+
export interface QueryStatistics {
|
|
39
|
+
/** Time elapsed in seconds */
|
|
40
|
+
elapsed: number;
|
|
41
|
+
/** Number of rows read */
|
|
42
|
+
rows_read: number;
|
|
43
|
+
/** Number of bytes read */
|
|
44
|
+
bytes_read: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Result of a query operation
|
|
49
|
+
*/
|
|
50
|
+
export interface QueryResult<T> {
|
|
51
|
+
/** Query result data */
|
|
52
|
+
data: T[];
|
|
53
|
+
/** Column metadata */
|
|
54
|
+
meta: ColumnMeta[];
|
|
55
|
+
/** Number of rows returned */
|
|
56
|
+
rows: number;
|
|
57
|
+
/** Query statistics */
|
|
58
|
+
statistics: QueryStatistics;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Result of an ingest operation
|
|
63
|
+
*/
|
|
64
|
+
export interface IngestResult {
|
|
65
|
+
/** Number of rows successfully ingested */
|
|
66
|
+
successful_rows: number;
|
|
67
|
+
/** Number of rows that failed to ingest */
|
|
68
|
+
quarantined_rows: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Error response from Tinybird API
|
|
73
|
+
*/
|
|
74
|
+
export interface TinybirdErrorResponse {
|
|
75
|
+
/** Error message */
|
|
76
|
+
error: string;
|
|
77
|
+
/** HTTP status code */
|
|
78
|
+
status?: number;
|
|
79
|
+
/** Additional error details */
|
|
80
|
+
documentation?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Custom error class for Tinybird API errors
|
|
85
|
+
*/
|
|
86
|
+
export class TinybirdError extends Error {
|
|
87
|
+
/** HTTP status code */
|
|
88
|
+
readonly statusCode: number;
|
|
89
|
+
/** Raw error response */
|
|
90
|
+
readonly response?: TinybirdErrorResponse;
|
|
91
|
+
|
|
92
|
+
constructor(message: string, statusCode: number, response?: TinybirdErrorResponse) {
|
|
93
|
+
super(message);
|
|
94
|
+
this.name = "TinybirdError";
|
|
95
|
+
this.statusCode = statusCode;
|
|
96
|
+
this.response = response;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Check if this is a rate limit error
|
|
101
|
+
*/
|
|
102
|
+
isRateLimitError(): boolean {
|
|
103
|
+
return this.statusCode === 429;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check if this is an authentication error
|
|
108
|
+
*/
|
|
109
|
+
isAuthError(): boolean {
|
|
110
|
+
return this.statusCode === 401 || this.statusCode === 403;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check if this is a not found error
|
|
115
|
+
*/
|
|
116
|
+
isNotFoundError(): boolean {
|
|
117
|
+
return this.statusCode === 404;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Check if this is a server error
|
|
122
|
+
*/
|
|
123
|
+
isServerError(): boolean {
|
|
124
|
+
return this.statusCode >= 500;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Options for query requests
|
|
130
|
+
*/
|
|
131
|
+
export interface QueryOptions {
|
|
132
|
+
/** Request timeout in milliseconds */
|
|
133
|
+
timeout?: number;
|
|
134
|
+
/** AbortController signal for cancellation */
|
|
135
|
+
signal?: AbortSignal;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Options for ingest requests
|
|
140
|
+
*/
|
|
141
|
+
export interface IngestOptions {
|
|
142
|
+
/** Request timeout in milliseconds */
|
|
143
|
+
timeout?: number;
|
|
144
|
+
/** AbortController signal for cancellation */
|
|
145
|
+
signal?: AbortSignal;
|
|
146
|
+
/** Wait for the ingestion to complete before returning */
|
|
147
|
+
wait?: boolean;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Base interface for typed pipe endpoints
|
|
152
|
+
*/
|
|
153
|
+
export interface TypedPipeEndpoint<TParams, TOutput> {
|
|
154
|
+
(params: TParams, options?: QueryOptions): Promise<QueryResult<TOutput>>;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Base interface for typed datasource ingestion
|
|
159
|
+
*/
|
|
160
|
+
export interface TypedDatasourceIngest<TRow> {
|
|
161
|
+
/** Send a single event */
|
|
162
|
+
send(event: TRow, options?: IngestOptions): Promise<IngestResult>;
|
|
163
|
+
/** Send multiple events in a batch */
|
|
164
|
+
sendBatch(events: TRow[], options?: IngestOptions): Promise<IngestResult>;
|
|
165
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client file generator
|
|
3
|
+
* Generates a typed tinybird.ts client file from discovered entities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
import type { LoadedEntities } from "./loader.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Options for generating the client file
|
|
11
|
+
*/
|
|
12
|
+
export interface GenerateClientOptions {
|
|
13
|
+
/** Loaded entities from source files */
|
|
14
|
+
entities: LoadedEntities;
|
|
15
|
+
/** Output file path (relative to cwd) */
|
|
16
|
+
outputPath: string;
|
|
17
|
+
/** Working directory */
|
|
18
|
+
cwd: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Result of generating the client file
|
|
23
|
+
*/
|
|
24
|
+
export interface GeneratedClient {
|
|
25
|
+
/** The generated file content */
|
|
26
|
+
content: string;
|
|
27
|
+
/** Absolute path to output file */
|
|
28
|
+
absolutePath: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Convert a file path to a relative import path
|
|
33
|
+
* e.g., "src/datasources.ts" -> "./datasources" when output is "src/tinybird.ts"
|
|
34
|
+
*/
|
|
35
|
+
function toRelativeImport(fromPath: string, toPath: string): string {
|
|
36
|
+
// Get directory of the from file
|
|
37
|
+
const fromDir = path.dirname(fromPath);
|
|
38
|
+
|
|
39
|
+
// Get relative path from output dir to source file
|
|
40
|
+
let relativePath = path.relative(fromDir, toPath);
|
|
41
|
+
|
|
42
|
+
// Normalize Windows separators to forward slashes for TS imports
|
|
43
|
+
relativePath = relativePath.replace(/\\/g, "/");
|
|
44
|
+
|
|
45
|
+
// Remove .ts extension
|
|
46
|
+
relativePath = relativePath.replace(/\.tsx?$/, "");
|
|
47
|
+
|
|
48
|
+
// Ensure it starts with ./ or ../
|
|
49
|
+
if (!relativePath.startsWith(".") && !relativePath.startsWith("/")) {
|
|
50
|
+
relativePath = "./" + relativePath;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return relativePath;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Convert camelCase to PascalCase
|
|
58
|
+
*/
|
|
59
|
+
function toPascalCase(str: string): string {
|
|
60
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Generate the client file content
|
|
65
|
+
*
|
|
66
|
+
* @param options - Generation options
|
|
67
|
+
* @returns Generated client file info
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* const result = generateClientFile({
|
|
72
|
+
* entities: loadedEntities,
|
|
73
|
+
* outputPath: 'src/tinybird.ts',
|
|
74
|
+
* cwd: '/path/to/project',
|
|
75
|
+
* });
|
|
76
|
+
*
|
|
77
|
+
* fs.writeFileSync(result.absolutePath, result.content);
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export function generateClientFile(options: GenerateClientOptions): GeneratedClient {
|
|
81
|
+
const { entities, outputPath, cwd } = options;
|
|
82
|
+
const absolutePath = path.isAbsolute(outputPath) ? outputPath : path.join(cwd, outputPath);
|
|
83
|
+
|
|
84
|
+
// Group entities by source file for imports
|
|
85
|
+
const importsByFile = new Map<string, { datasources: string[]; pipes: string[] }>();
|
|
86
|
+
|
|
87
|
+
for (const [name, { info }] of Object.entries(entities.datasources)) {
|
|
88
|
+
if (!importsByFile.has(info.sourceFile)) {
|
|
89
|
+
importsByFile.set(info.sourceFile, { datasources: [], pipes: [] });
|
|
90
|
+
}
|
|
91
|
+
importsByFile.get(info.sourceFile)!.datasources.push(name);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const [name, { info }] of Object.entries(entities.pipes)) {
|
|
95
|
+
if (!importsByFile.has(info.sourceFile)) {
|
|
96
|
+
importsByFile.set(info.sourceFile, { datasources: [], pipes: [] });
|
|
97
|
+
}
|
|
98
|
+
importsByFile.get(info.sourceFile)!.pipes.push(name);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Build import statements
|
|
102
|
+
const importLines: string[] = [];
|
|
103
|
+
const reexportLines: string[] = [];
|
|
104
|
+
|
|
105
|
+
// SDK imports
|
|
106
|
+
const sdkTypes = ["InferRow"];
|
|
107
|
+
if (Object.keys(entities.pipes).length > 0) {
|
|
108
|
+
sdkTypes.push("InferParams", "InferOutputRow");
|
|
109
|
+
}
|
|
110
|
+
importLines.push(
|
|
111
|
+
`import { createTinybirdClient, type ${sdkTypes.join(", type ")} } from "@tinybirdco/sdk";`
|
|
112
|
+
);
|
|
113
|
+
importLines.push("");
|
|
114
|
+
|
|
115
|
+
// Entity imports and re-exports
|
|
116
|
+
for (const [sourceFile, { datasources, pipes }] of importsByFile) {
|
|
117
|
+
const allExports = [...datasources, ...pipes];
|
|
118
|
+
if (allExports.length === 0) continue;
|
|
119
|
+
|
|
120
|
+
// Resolve to absolute paths for correct relative path calculation
|
|
121
|
+
const sourceAbsolute = path.isAbsolute(sourceFile) ? sourceFile : path.join(cwd, sourceFile);
|
|
122
|
+
const relativePath = toRelativeImport(absolutePath, sourceAbsolute);
|
|
123
|
+
importLines.push(`import { ${allExports.join(", ")} } from "${relativePath}";`);
|
|
124
|
+
reexportLines.push(`export { ${allExports.join(", ")} } from "${relativePath}";`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Build createTinybirdClient call
|
|
128
|
+
const datasourceNames = Object.keys(entities.datasources);
|
|
129
|
+
const pipeNames = Object.keys(entities.pipes);
|
|
130
|
+
|
|
131
|
+
const clientLines: string[] = [];
|
|
132
|
+
clientLines.push("export const tinybird = createTinybirdClient({");
|
|
133
|
+
|
|
134
|
+
if (datasourceNames.length > 0) {
|
|
135
|
+
clientLines.push(` datasources: { ${datasourceNames.join(", ")} },`);
|
|
136
|
+
} else {
|
|
137
|
+
clientLines.push(" datasources: {},");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (pipeNames.length > 0) {
|
|
141
|
+
clientLines.push(` pipes: { ${pipeNames.join(", ")} },`);
|
|
142
|
+
} else {
|
|
143
|
+
clientLines.push(" pipes: {},");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
clientLines.push("});");
|
|
147
|
+
|
|
148
|
+
// Build type exports
|
|
149
|
+
const typeLines: string[] = [];
|
|
150
|
+
|
|
151
|
+
for (const name of datasourceNames) {
|
|
152
|
+
const pascalName = toPascalCase(name);
|
|
153
|
+
typeLines.push(`export type ${pascalName}Row = InferRow<typeof ${name}>;`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
for (const name of pipeNames) {
|
|
157
|
+
const { definition } = entities.pipes[name];
|
|
158
|
+
const pascalName = toPascalCase(name);
|
|
159
|
+
typeLines.push(`export type ${pascalName}Params = InferParams<typeof ${name}>;`);
|
|
160
|
+
// Only generate Output type for pipes with output schema
|
|
161
|
+
if (definition._output) {
|
|
162
|
+
typeLines.push(`export type ${pascalName}Output = InferOutputRow<typeof ${name}>;`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Combine all sections
|
|
167
|
+
const sections: string[] = [
|
|
168
|
+
"// Auto-generated by @tinybirdco/sdk - DO NOT EDIT",
|
|
169
|
+
"// This file is regenerated on every build. Manual changes will be overwritten.",
|
|
170
|
+
"",
|
|
171
|
+
importLines.join("\n"),
|
|
172
|
+
"",
|
|
173
|
+
"// Typed Tinybird client",
|
|
174
|
+
clientLines.join("\n"),
|
|
175
|
+
"",
|
|
176
|
+
"// Re-export entities for convenience",
|
|
177
|
+
reexportLines.join("\n"),
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
if (typeLines.length > 0) {
|
|
181
|
+
sections.push("");
|
|
182
|
+
sections.push("// Inferred types from entity definitions");
|
|
183
|
+
sections.push(typeLines.join("\n"));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
sections.push(""); // Trailing newline
|
|
187
|
+
|
|
188
|
+
const content = sections.join("\n");
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
content,
|
|
192
|
+
absolutePath,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { generateDatasource, generateAllDatasources } from './datasource.js';
|
|
3
|
+
import { defineDatasource } from '../schema/datasource.js';
|
|
4
|
+
import { t } from '../schema/types.js';
|
|
5
|
+
import { engine } from '../schema/engines.js';
|
|
6
|
+
|
|
7
|
+
describe('Datasource Generator', () => {
|
|
8
|
+
describe('generateDatasource', () => {
|
|
9
|
+
it('generates basic datasource with schema', () => {
|
|
10
|
+
const ds = defineDatasource('test_ds', {
|
|
11
|
+
schema: {
|
|
12
|
+
id: t.string(),
|
|
13
|
+
count: t.int32(),
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const result = generateDatasource(ds);
|
|
18
|
+
expect(result.name).toBe('test_ds');
|
|
19
|
+
expect(result.content).toContain('SCHEMA >');
|
|
20
|
+
expect(result.content).toContain('id String');
|
|
21
|
+
expect(result.content).toContain('count Int32');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('includes description when provided', () => {
|
|
25
|
+
const ds = defineDatasource('test_ds', {
|
|
26
|
+
description: 'Test datasource description',
|
|
27
|
+
schema: {
|
|
28
|
+
id: t.string(),
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const result = generateDatasource(ds);
|
|
33
|
+
expect(result.content).toContain('DESCRIPTION >');
|
|
34
|
+
expect(result.content).toContain('Test datasource description');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('includes engine configuration', () => {
|
|
38
|
+
const ds = defineDatasource('test_ds', {
|
|
39
|
+
schema: {
|
|
40
|
+
id: t.string(),
|
|
41
|
+
},
|
|
42
|
+
engine: engine.mergeTree({
|
|
43
|
+
sortingKey: ['id'],
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const result = generateDatasource(ds);
|
|
48
|
+
expect(result.content).toContain('ENGINE "MergeTree"');
|
|
49
|
+
expect(result.content).toContain('ENGINE_SORTING_KEY "id"');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('includes partition key in engine config', () => {
|
|
53
|
+
const ds = defineDatasource('test_ds', {
|
|
54
|
+
schema: {
|
|
55
|
+
id: t.string(),
|
|
56
|
+
timestamp: t.dateTime(),
|
|
57
|
+
},
|
|
58
|
+
engine: engine.mergeTree({
|
|
59
|
+
sortingKey: ['id'],
|
|
60
|
+
partitionKey: 'toYYYYMM(timestamp)',
|
|
61
|
+
}),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const result = generateDatasource(ds);
|
|
65
|
+
expect(result.content).toContain('ENGINE_PARTITION_KEY "toYYYYMM(timestamp)"');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('includes TTL in engine config', () => {
|
|
69
|
+
const ds = defineDatasource('test_ds', {
|
|
70
|
+
schema: {
|
|
71
|
+
id: t.string(),
|
|
72
|
+
timestamp: t.dateTime(),
|
|
73
|
+
},
|
|
74
|
+
engine: engine.mergeTree({
|
|
75
|
+
sortingKey: ['id'],
|
|
76
|
+
ttl: 'timestamp + INTERVAL 90 DAY',
|
|
77
|
+
}),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const result = generateDatasource(ds);
|
|
81
|
+
expect(result.content).toContain('ENGINE_TTL "timestamp + INTERVAL 90 DAY"');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('Column formatting', () => {
|
|
86
|
+
it('formats Nullable columns correctly', () => {
|
|
87
|
+
const ds = defineDatasource('test_ds', {
|
|
88
|
+
schema: {
|
|
89
|
+
name: t.string().nullable(),
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const result = generateDatasource(ds);
|
|
94
|
+
expect(result.content).toContain('name Nullable(String)');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('formats LowCardinality columns correctly', () => {
|
|
98
|
+
const ds = defineDatasource('test_ds', {
|
|
99
|
+
schema: {
|
|
100
|
+
country: t.string().lowCardinality(),
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const result = generateDatasource(ds);
|
|
105
|
+
expect(result.content).toContain('country LowCardinality(String)');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('formats LowCardinality(Nullable) correctly', () => {
|
|
109
|
+
const ds = defineDatasource('test_ds', {
|
|
110
|
+
schema: {
|
|
111
|
+
country: t.string().lowCardinality().nullable(),
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const result = generateDatasource(ds);
|
|
116
|
+
expect(result.content).toContain('country LowCardinality(Nullable(String))');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('includes default values', () => {
|
|
120
|
+
const ds = defineDatasource('test_ds', {
|
|
121
|
+
schema: {
|
|
122
|
+
status: t.string().default('pending'),
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const result = generateDatasource(ds);
|
|
127
|
+
expect(result.content).toContain("status String `json:$.status` DEFAULT 'pending'");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('formats null default values', () => {
|
|
131
|
+
const ds = defineDatasource('test_ds', {
|
|
132
|
+
schema: {
|
|
133
|
+
// Using nullable with explicit null default
|
|
134
|
+
status: t.string().nullable().default(null),
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const result = generateDatasource(ds);
|
|
139
|
+
expect(result.content).toContain('DEFAULT NULL');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('formats number default values', () => {
|
|
143
|
+
const ds = defineDatasource('test_ds', {
|
|
144
|
+
schema: {
|
|
145
|
+
count: t.int32().default(42),
|
|
146
|
+
score: t.float64().default(3.14),
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const result = generateDatasource(ds);
|
|
151
|
+
expect(result.content).toContain('count Int32 `json:$.count` DEFAULT 42');
|
|
152
|
+
expect(result.content).toContain('score Float64 `json:$.score` DEFAULT 3.14');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('formats boolean default values', () => {
|
|
156
|
+
const ds = defineDatasource('test_ds', {
|
|
157
|
+
schema: {
|
|
158
|
+
is_active: t.bool().default(true),
|
|
159
|
+
is_deleted: t.bool().default(false),
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const result = generateDatasource(ds);
|
|
164
|
+
expect(result.content).toContain('is_active Bool `json:$.is_active` DEFAULT 1');
|
|
165
|
+
expect(result.content).toContain('is_deleted Bool `json:$.is_deleted` DEFAULT 0');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('formats Date default values for DateTime type', () => {
|
|
169
|
+
const ds = defineDatasource('test_ds', {
|
|
170
|
+
schema: {
|
|
171
|
+
created_at: t.dateTime().default(new Date('2024-01-15T10:30:00Z')),
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const result = generateDatasource(ds);
|
|
176
|
+
expect(result.content).toContain("created_at DateTime `json:$.created_at` DEFAULT '2024-01-15 10:30:00'");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('formats Date default values for Date type', () => {
|
|
180
|
+
const ds = defineDatasource('test_ds', {
|
|
181
|
+
schema: {
|
|
182
|
+
birth_date: t.date().default(new Date('2024-01-15T10:30:00Z')),
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const result = generateDatasource(ds);
|
|
187
|
+
expect(result.content).toContain("birth_date Date `json:$.birth_date` DEFAULT '2024-01-15'");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('formats array default values', () => {
|
|
191
|
+
const ds = defineDatasource('test_ds', {
|
|
192
|
+
schema: {
|
|
193
|
+
tags: t.array(t.string()).default(['a', 'b']),
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const result = generateDatasource(ds);
|
|
198
|
+
expect(result.content).toContain('tags Array(String) `json:$.tags` DEFAULT ["a","b"]');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('formats object default values for JSON type', () => {
|
|
202
|
+
const ds = defineDatasource('test_ds', {
|
|
203
|
+
schema: {
|
|
204
|
+
metadata: t.json<{ key: string }>().default({ key: 'value' }),
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const result = generateDatasource(ds);
|
|
209
|
+
expect(result.content).toContain('metadata JSON `json:$.metadata` DEFAULT {"key":"value"}');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('escapes single quotes in string default values', () => {
|
|
213
|
+
const ds = defineDatasource('test_ds', {
|
|
214
|
+
schema: {
|
|
215
|
+
message: t.string().default("it's working"),
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const result = generateDatasource(ds);
|
|
220
|
+
expect(result.content).toContain("message String `json:$.message` DEFAULT 'it\\'s working'");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('includes codec', () => {
|
|
224
|
+
const ds = defineDatasource('test_ds', {
|
|
225
|
+
schema: {
|
|
226
|
+
data: t.string().codec('LZ4'),
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const result = generateDatasource(ds);
|
|
231
|
+
expect(result.content).toContain('data String `json:$.data` CODEC(LZ4)');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('adds commas between columns except last', () => {
|
|
235
|
+
const ds = defineDatasource('test_ds', {
|
|
236
|
+
schema: {
|
|
237
|
+
id: t.string(),
|
|
238
|
+
name: t.string(),
|
|
239
|
+
count: t.int32(),
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const result = generateDatasource(ds);
|
|
244
|
+
const lines = result.content.split('\n');
|
|
245
|
+
const schemaLines = lines.filter(l => l.trim().startsWith('id') || l.trim().startsWith('name') || l.trim().startsWith('count'));
|
|
246
|
+
|
|
247
|
+
expect(schemaLines[0]).toContain(',');
|
|
248
|
+
expect(schemaLines[1]).toContain(',');
|
|
249
|
+
expect(schemaLines[2]).not.toContain(',');
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe('generateAllDatasources', () => {
|
|
254
|
+
it('generates all datasources', () => {
|
|
255
|
+
const ds1 = defineDatasource('ds1', { schema: { id: t.string() } });
|
|
256
|
+
const ds2 = defineDatasource('ds2', { schema: { id: t.int32() } });
|
|
257
|
+
|
|
258
|
+
const results = generateAllDatasources({ ds1, ds2 });
|
|
259
|
+
expect(results).toHaveLength(2);
|
|
260
|
+
expect(results.map(r => r.name).sort()).toEqual(['ds1', 'ds2']);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe('Full integration', () => {
|
|
265
|
+
it('generates complete datasource file', () => {
|
|
266
|
+
const ds = defineDatasource('page_views', {
|
|
267
|
+
description: 'Page view tracking data',
|
|
268
|
+
schema: {
|
|
269
|
+
timestamp: t.dateTime(),
|
|
270
|
+
pathname: t.string(),
|
|
271
|
+
session_id: t.string(),
|
|
272
|
+
country: t.string().lowCardinality().nullable(),
|
|
273
|
+
},
|
|
274
|
+
engine: engine.mergeTree({
|
|
275
|
+
sortingKey: ['pathname', 'timestamp'],
|
|
276
|
+
partitionKey: 'toYYYYMM(timestamp)',
|
|
277
|
+
ttl: 'timestamp + INTERVAL 90 DAY',
|
|
278
|
+
}),
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const result = generateDatasource(ds);
|
|
282
|
+
|
|
283
|
+
expect(result.name).toBe('page_views');
|
|
284
|
+
expect(result.content).toContain('DESCRIPTION >');
|
|
285
|
+
expect(result.content).toContain('Page view tracking data');
|
|
286
|
+
expect(result.content).toContain('SCHEMA >');
|
|
287
|
+
expect(result.content).toContain('timestamp DateTime');
|
|
288
|
+
expect(result.content).toContain('pathname String');
|
|
289
|
+
expect(result.content).toContain('session_id String');
|
|
290
|
+
expect(result.content).toContain('country LowCardinality(Nullable(String))');
|
|
291
|
+
expect(result.content).toContain('ENGINE "MergeTree"');
|
|
292
|
+
expect(result.content).toContain('ENGINE_PARTITION_KEY "toYYYYMM(timestamp)"');
|
|
293
|
+
expect(result.content).toContain('ENGINE_SORTING_KEY "pathname, timestamp"');
|
|
294
|
+
expect(result.content).toContain('ENGINE_TTL "timestamp + INTERVAL 90 DAY"');
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
});
|