@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,832 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipe definition for Tinybird
|
|
3
|
+
* Define SQL transformations and endpoints as TypeScript with full type safety
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { AnyTypeValidator } from "./types.js";
|
|
7
|
+
import type { AnyParamValidator } from "./params.js";
|
|
8
|
+
import type { DatasourceDefinition, SchemaDefinition, ColumnDefinition } from "./datasource.js";
|
|
9
|
+
import { getColumnType } from "./datasource.js";
|
|
10
|
+
import { getTinybirdType } from "./types.js";
|
|
11
|
+
|
|
12
|
+
/** Symbol for brand typing pipes */
|
|
13
|
+
export const PIPE_BRAND: unique symbol = Symbol("tinybird.pipe");
|
|
14
|
+
/** Symbol for brand typing nodes */
|
|
15
|
+
export const NODE_BRAND: unique symbol = Symbol("tinybird.node");
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parameter definition for a pipe
|
|
19
|
+
*/
|
|
20
|
+
export type ParamsDefinition = Record<string, AnyParamValidator>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Output schema definition for a pipe
|
|
24
|
+
*/
|
|
25
|
+
export type OutputDefinition = Record<string, AnyTypeValidator>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Node configuration options
|
|
29
|
+
*/
|
|
30
|
+
export interface NodeOptions {
|
|
31
|
+
/** Node name (must be valid identifier) */
|
|
32
|
+
name: string;
|
|
33
|
+
/** SQL query for this node */
|
|
34
|
+
sql: string;
|
|
35
|
+
/** Human-readable description */
|
|
36
|
+
description?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A node definition within a pipe
|
|
41
|
+
*/
|
|
42
|
+
export interface NodeDefinition {
|
|
43
|
+
readonly [NODE_BRAND]: true;
|
|
44
|
+
/** Node name */
|
|
45
|
+
readonly _name: string;
|
|
46
|
+
/** Type marker for inference */
|
|
47
|
+
readonly _type: "node";
|
|
48
|
+
/** SQL query */
|
|
49
|
+
readonly sql: string;
|
|
50
|
+
/** Description */
|
|
51
|
+
readonly description?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create a node within a pipe
|
|
56
|
+
*
|
|
57
|
+
* @param options - Node configuration
|
|
58
|
+
* @returns A node definition
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* import { node } from '@tinybirdco/sdk';
|
|
63
|
+
*
|
|
64
|
+
* const filteredNode = node({
|
|
65
|
+
* name: 'filtered',
|
|
66
|
+
* sql: `
|
|
67
|
+
* SELECT *
|
|
68
|
+
* FROM events
|
|
69
|
+
* WHERE timestamp >= {{DateTime(start_date)}}
|
|
70
|
+
* `,
|
|
71
|
+
* });
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export function node(options: NodeOptions): NodeDefinition {
|
|
75
|
+
// Validate name is a valid identifier
|
|
76
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(options.name)) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Invalid node name: "${options.name}". Must start with a letter or underscore and contain only alphanumeric characters and underscores.`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
[NODE_BRAND]: true,
|
|
84
|
+
_name: options.name,
|
|
85
|
+
_type: "node",
|
|
86
|
+
sql: options.sql,
|
|
87
|
+
description: options.description,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Check if a value is a node definition
|
|
93
|
+
*/
|
|
94
|
+
export function isNodeDefinition(value: unknown): value is NodeDefinition {
|
|
95
|
+
return (
|
|
96
|
+
typeof value === "object" &&
|
|
97
|
+
value !== null &&
|
|
98
|
+
NODE_BRAND in value &&
|
|
99
|
+
(value as Record<symbol, unknown>)[NODE_BRAND] === true
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Endpoint configuration for a pipe
|
|
105
|
+
*/
|
|
106
|
+
export interface EndpointConfig {
|
|
107
|
+
/** Whether this pipe is exposed as an API endpoint */
|
|
108
|
+
enabled: boolean;
|
|
109
|
+
/** Cache configuration */
|
|
110
|
+
cache?: {
|
|
111
|
+
/** Whether caching is enabled */
|
|
112
|
+
enabled: boolean;
|
|
113
|
+
/** Cache TTL in seconds */
|
|
114
|
+
ttl?: number;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Materialized view configuration for a pipe
|
|
120
|
+
*/
|
|
121
|
+
export interface MaterializedConfig<
|
|
122
|
+
TDatasource extends DatasourceDefinition<SchemaDefinition> = DatasourceDefinition<SchemaDefinition>
|
|
123
|
+
> {
|
|
124
|
+
/** Target datasource where materialized data is written */
|
|
125
|
+
datasource: TDatasource;
|
|
126
|
+
/**
|
|
127
|
+
* Deployment method for materialized views.
|
|
128
|
+
* Use 'alter' to update existing materialized views using ALTER TABLE ... MODIFY QUERY
|
|
129
|
+
* instead of recreating the table. This preserves existing data and reduces deployment time.
|
|
130
|
+
*/
|
|
131
|
+
deploymentMethod?: "alter";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Copy pipe configuration
|
|
136
|
+
*/
|
|
137
|
+
export interface CopyConfig<
|
|
138
|
+
TDatasource extends DatasourceDefinition<SchemaDefinition> = DatasourceDefinition<SchemaDefinition>
|
|
139
|
+
> {
|
|
140
|
+
/** Target datasource where copied data is written */
|
|
141
|
+
datasource: TDatasource;
|
|
142
|
+
/**
|
|
143
|
+
* Copy mode: how data is ingested
|
|
144
|
+
* - 'append': Appends the result to the target data source (default)
|
|
145
|
+
* - 'replace': Every run completely replaces the destination Data Source content
|
|
146
|
+
*/
|
|
147
|
+
copy_mode?: "append" | "replace";
|
|
148
|
+
/**
|
|
149
|
+
* Copy schedule: when the copy job runs
|
|
150
|
+
* - A cron expression (e.g., "0 * * * *" for hourly)
|
|
151
|
+
* - "@on-demand" for manual execution only
|
|
152
|
+
* Defaults to "@on-demand" if not specified
|
|
153
|
+
*/
|
|
154
|
+
copy_schedule?: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Token configuration for pipe access
|
|
159
|
+
*/
|
|
160
|
+
export interface PipeTokenConfig {
|
|
161
|
+
/** Token name */
|
|
162
|
+
name: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Options for defining a pipe (reusable SQL logic, no endpoint)
|
|
167
|
+
*/
|
|
168
|
+
export interface PipeOptions<
|
|
169
|
+
TParams extends ParamsDefinition,
|
|
170
|
+
TOutput extends OutputDefinition
|
|
171
|
+
> {
|
|
172
|
+
/** Human-readable description of the pipe */
|
|
173
|
+
description?: string;
|
|
174
|
+
/** Parameter definitions for query inputs */
|
|
175
|
+
params?: TParams;
|
|
176
|
+
/** Nodes in the transformation pipeline */
|
|
177
|
+
nodes: readonly NodeDefinition[];
|
|
178
|
+
/** Output schema (optional for reusable pipes, required for endpoints) */
|
|
179
|
+
output?: TOutput;
|
|
180
|
+
/** Whether this pipe is an API endpoint (shorthand for { enabled: true }). Mutually exclusive with materialized and copy. */
|
|
181
|
+
endpoint?: boolean | EndpointConfig;
|
|
182
|
+
/** Materialized view configuration. Mutually exclusive with endpoint and copy. */
|
|
183
|
+
materialized?: MaterializedConfig;
|
|
184
|
+
/** Copy pipe configuration. Mutually exclusive with endpoint and materialized. */
|
|
185
|
+
copy?: CopyConfig;
|
|
186
|
+
/** Access tokens for this pipe */
|
|
187
|
+
tokens?: readonly PipeTokenConfig[];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Options for defining an endpoint (API-exposed pipe)
|
|
192
|
+
*/
|
|
193
|
+
export interface EndpointOptions<
|
|
194
|
+
TParams extends ParamsDefinition,
|
|
195
|
+
TOutput extends OutputDefinition
|
|
196
|
+
> {
|
|
197
|
+
/** Human-readable description of the endpoint */
|
|
198
|
+
description?: string;
|
|
199
|
+
/** Parameter definitions for query inputs */
|
|
200
|
+
params?: TParams;
|
|
201
|
+
/** Nodes in the transformation pipeline */
|
|
202
|
+
nodes: readonly NodeDefinition[];
|
|
203
|
+
/** Output schema (required for type safety) */
|
|
204
|
+
output: TOutput;
|
|
205
|
+
/** Cache configuration */
|
|
206
|
+
cache?: {
|
|
207
|
+
/** Whether caching is enabled */
|
|
208
|
+
enabled: boolean;
|
|
209
|
+
/** Cache TTL in seconds */
|
|
210
|
+
ttl?: number;
|
|
211
|
+
};
|
|
212
|
+
/** Access tokens for this endpoint */
|
|
213
|
+
tokens?: readonly PipeTokenConfig[];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Options for defining a copy pipe
|
|
218
|
+
*/
|
|
219
|
+
export interface CopyPipeOptions<
|
|
220
|
+
TSchema extends SchemaDefinition,
|
|
221
|
+
TDatasource extends DatasourceDefinition<TSchema>
|
|
222
|
+
> {
|
|
223
|
+
/** Human-readable description of the copy pipe */
|
|
224
|
+
description?: string;
|
|
225
|
+
/** Nodes in the transformation pipeline */
|
|
226
|
+
nodes: readonly NodeDefinition[];
|
|
227
|
+
/** Target datasource where copied data is written */
|
|
228
|
+
datasource: TDatasource;
|
|
229
|
+
/**
|
|
230
|
+
* Copy mode: how data is ingested
|
|
231
|
+
* - 'append': Appends the result to the target data source (default)
|
|
232
|
+
* - 'replace': Every run completely replaces the destination Data Source content
|
|
233
|
+
*/
|
|
234
|
+
copy_mode?: "append" | "replace";
|
|
235
|
+
/**
|
|
236
|
+
* Copy schedule: when the copy job runs
|
|
237
|
+
* - A cron expression (e.g., "0 * * * *" for hourly)
|
|
238
|
+
* - "@on-demand" for manual execution only
|
|
239
|
+
* Defaults to "@on-demand" if not specified
|
|
240
|
+
*/
|
|
241
|
+
copy_schedule?: string;
|
|
242
|
+
/** Access tokens for this copy pipe */
|
|
243
|
+
tokens?: readonly PipeTokenConfig[];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* A pipe definition with full type information
|
|
248
|
+
*/
|
|
249
|
+
export interface PipeDefinition<
|
|
250
|
+
TParams extends ParamsDefinition = ParamsDefinition,
|
|
251
|
+
TOutput extends OutputDefinition = OutputDefinition
|
|
252
|
+
> {
|
|
253
|
+
readonly [PIPE_BRAND]: true;
|
|
254
|
+
/** Pipe name */
|
|
255
|
+
readonly _name: string;
|
|
256
|
+
/** Type marker for inference */
|
|
257
|
+
readonly _type: "pipe";
|
|
258
|
+
/** Parameter definitions */
|
|
259
|
+
readonly _params: TParams;
|
|
260
|
+
/** Output schema (optional for reusable pipes) */
|
|
261
|
+
readonly _output?: TOutput;
|
|
262
|
+
/** Full options */
|
|
263
|
+
readonly options: PipeOptions<TParams, TOutput>;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Define a Tinybird pipe
|
|
268
|
+
*
|
|
269
|
+
* @param name - The pipe name (must be valid identifier)
|
|
270
|
+
* @param options - Pipe configuration including params, nodes, and output schema
|
|
271
|
+
* @returns A pipe definition that can be used in a project
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* ```ts
|
|
275
|
+
* import { definePipe, node, p, t } from '@tinybirdco/sdk';
|
|
276
|
+
*
|
|
277
|
+
* export const topEvents = definePipe('top_events', {
|
|
278
|
+
* description: 'Get top events by count',
|
|
279
|
+
* params: {
|
|
280
|
+
* start_date: p.dateTime(),
|
|
281
|
+
* end_date: p.dateTime(),
|
|
282
|
+
* limit: p.int32().optional(10),
|
|
283
|
+
* },
|
|
284
|
+
* nodes: [
|
|
285
|
+
* node({
|
|
286
|
+
* name: 'filtered',
|
|
287
|
+
* sql: `
|
|
288
|
+
* SELECT *
|
|
289
|
+
* FROM events
|
|
290
|
+
* WHERE timestamp BETWEEN {{DateTime(start_date)}} AND {{DateTime(end_date)}}
|
|
291
|
+
* `,
|
|
292
|
+
* }),
|
|
293
|
+
* node({
|
|
294
|
+
* name: 'aggregated',
|
|
295
|
+
* sql: `
|
|
296
|
+
* SELECT
|
|
297
|
+
* event_type,
|
|
298
|
+
* count() as event_count,
|
|
299
|
+
* uniqExact(user_id) as unique_users
|
|
300
|
+
* FROM filtered
|
|
301
|
+
* GROUP BY event_type
|
|
302
|
+
* ORDER BY event_count DESC
|
|
303
|
+
* LIMIT {{Int32(limit, 10)}}
|
|
304
|
+
* `,
|
|
305
|
+
* }),
|
|
306
|
+
* ],
|
|
307
|
+
* output: {
|
|
308
|
+
* event_type: t.string(),
|
|
309
|
+
* event_count: t.uint64(),
|
|
310
|
+
* unique_users: t.uint64(),
|
|
311
|
+
* },
|
|
312
|
+
* endpoint: true,
|
|
313
|
+
* });
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
/**
|
|
317
|
+
* Normalize a Tinybird type for comparison by removing wrappers that don't affect compatibility
|
|
318
|
+
*/
|
|
319
|
+
function normalizeTypeForComparison(type: string): string {
|
|
320
|
+
// Remove Nullable wrapper for comparison
|
|
321
|
+
let normalized = type.replace(/^Nullable\((.+)\)$/, "$1");
|
|
322
|
+
// Remove LowCardinality wrapper
|
|
323
|
+
normalized = normalized.replace(/^LowCardinality\((.+)\)$/, "$1");
|
|
324
|
+
// Handle LowCardinality(Nullable(...))
|
|
325
|
+
normalized = normalized.replace(/^LowCardinality\(Nullable\((.+)\)\)$/, "$1");
|
|
326
|
+
// Remove timezone from DateTime types
|
|
327
|
+
normalized = normalized.replace(/^DateTime\('[^']+'\)$/, "DateTime");
|
|
328
|
+
normalized = normalized.replace(/^DateTime64\((\d+),\s*'[^']+'\)$/, "DateTime64($1)");
|
|
329
|
+
return normalized;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Check if two Tinybird types are compatible
|
|
334
|
+
*/
|
|
335
|
+
function typesAreCompatible(outputType: string, datasourceType: string): boolean {
|
|
336
|
+
const normalizedOutput = normalizeTypeForComparison(outputType);
|
|
337
|
+
const normalizedDatasource = normalizeTypeForComparison(datasourceType);
|
|
338
|
+
|
|
339
|
+
// Direct match
|
|
340
|
+
if (normalizedOutput === normalizedDatasource) {
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// SimpleAggregateFunction compatibility: output can be the base type
|
|
345
|
+
// e.g., output UInt64 -> datasource SimpleAggregateFunction(sum, UInt64)
|
|
346
|
+
const simpleAggMatch = normalizedDatasource.match(
|
|
347
|
+
/^SimpleAggregateFunction\([^,]+,\s*(.+)\)$/
|
|
348
|
+
);
|
|
349
|
+
if (simpleAggMatch && normalizedOutput === simpleAggMatch[1]) {
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// AggregateFunction compatibility
|
|
354
|
+
const aggMatch = normalizedDatasource.match(
|
|
355
|
+
/^AggregateFunction\([^,]+,\s*(.+)\)$/
|
|
356
|
+
);
|
|
357
|
+
if (aggMatch && normalizedOutput === aggMatch[1]) {
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Validate that the pipe output schema matches the target datasource schema
|
|
366
|
+
*/
|
|
367
|
+
function validateMaterializedSchema(
|
|
368
|
+
pipeName: string,
|
|
369
|
+
output: OutputDefinition,
|
|
370
|
+
datasource: DatasourceDefinition
|
|
371
|
+
): void {
|
|
372
|
+
const outputColumns = Object.keys(output);
|
|
373
|
+
const datasourceSchema = datasource._schema;
|
|
374
|
+
const datasourceColumns = Object.keys(datasourceSchema);
|
|
375
|
+
|
|
376
|
+
// Check for missing columns in output
|
|
377
|
+
const missingInOutput = datasourceColumns.filter(
|
|
378
|
+
(col) => !outputColumns.includes(col)
|
|
379
|
+
);
|
|
380
|
+
if (missingInOutput.length > 0) {
|
|
381
|
+
throw new Error(
|
|
382
|
+
`Materialized view "${pipeName}" output schema is missing columns from target datasource "${datasource._name}": ${missingInOutput.join(", ")}`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Check for extra columns in output
|
|
387
|
+
const extraInOutput = outputColumns.filter(
|
|
388
|
+
(col) => !datasourceColumns.includes(col)
|
|
389
|
+
);
|
|
390
|
+
if (extraInOutput.length > 0) {
|
|
391
|
+
throw new Error(
|
|
392
|
+
`Materialized view "${pipeName}" output schema has columns not in target datasource "${datasource._name}": ${extraInOutput.join(", ")}`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Check type compatibility for each column
|
|
397
|
+
for (const columnName of outputColumns) {
|
|
398
|
+
const outputValidator = output[columnName];
|
|
399
|
+
const datasourceColumn = datasourceSchema[columnName];
|
|
400
|
+
|
|
401
|
+
const outputType = getTinybirdType(outputValidator);
|
|
402
|
+
const datasourceValidator = getColumnType(datasourceColumn);
|
|
403
|
+
const datasourceType = getTinybirdType(datasourceValidator);
|
|
404
|
+
|
|
405
|
+
if (!typesAreCompatible(outputType, datasourceType)) {
|
|
406
|
+
throw new Error(
|
|
407
|
+
`Materialized view "${pipeName}" column "${columnName}" type mismatch: ` +
|
|
408
|
+
`output has "${outputType}" but target datasource "${datasource._name}" expects "${datasourceType}"`
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export function definePipe<
|
|
415
|
+
TParams extends ParamsDefinition,
|
|
416
|
+
TOutput extends OutputDefinition
|
|
417
|
+
>(
|
|
418
|
+
name: string,
|
|
419
|
+
options: PipeOptions<TParams, TOutput>
|
|
420
|
+
): PipeDefinition<TParams, TOutput> {
|
|
421
|
+
// Validate name is a valid identifier
|
|
422
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
423
|
+
throw new Error(
|
|
424
|
+
`Invalid pipe name: "${name}". Must start with a letter or underscore and contain only alphanumeric characters and underscores.`
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Validate at least one node
|
|
429
|
+
if (!options.nodes || options.nodes.length === 0) {
|
|
430
|
+
throw new Error(`Pipe "${name}" must have at least one node.`);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Validate output is provided for endpoints and materialized views
|
|
434
|
+
if ((options.endpoint || options.materialized) && (!options.output || Object.keys(options.output).length === 0)) {
|
|
435
|
+
throw new Error(
|
|
436
|
+
`Pipe "${name}" must have an output schema defined when used as an endpoint or materialized view.`
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Count how many types are configured
|
|
441
|
+
const typeCount = [options.endpoint, options.materialized, options.copy].filter(Boolean).length;
|
|
442
|
+
if (typeCount > 1) {
|
|
443
|
+
throw new Error(
|
|
444
|
+
`Pipe "${name}" can only have one of: endpoint, materialized, or copy configuration. ` +
|
|
445
|
+
`A pipe must be at most one type.`
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Validate materialized view schema compatibility
|
|
450
|
+
if (options.materialized) {
|
|
451
|
+
// output is guaranteed to be defined here because of the earlier validation
|
|
452
|
+
validateMaterializedSchema(name, options.output!, options.materialized.datasource);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const params = (options.params ?? {}) as TParams;
|
|
456
|
+
|
|
457
|
+
return {
|
|
458
|
+
[PIPE_BRAND]: true,
|
|
459
|
+
_name: name,
|
|
460
|
+
_type: "pipe",
|
|
461
|
+
_params: params,
|
|
462
|
+
_output: options.output,
|
|
463
|
+
options: {
|
|
464
|
+
...options,
|
|
465
|
+
params,
|
|
466
|
+
},
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Options for defining a materialized view
|
|
472
|
+
*/
|
|
473
|
+
export interface MaterializedViewOptions<
|
|
474
|
+
TDatasource extends DatasourceDefinition<SchemaDefinition>
|
|
475
|
+
> {
|
|
476
|
+
/** Human-readable description of the materialized view */
|
|
477
|
+
description?: string;
|
|
478
|
+
/** Nodes in the transformation pipeline */
|
|
479
|
+
nodes: readonly NodeDefinition[];
|
|
480
|
+
/** Target datasource where materialized data is written */
|
|
481
|
+
datasource: TDatasource;
|
|
482
|
+
/**
|
|
483
|
+
* Deployment method for materialized views.
|
|
484
|
+
* Use 'alter' to update existing materialized views using ALTER TABLE ... MODIFY QUERY
|
|
485
|
+
* instead of recreating the table. This preserves existing data and reduces deployment time.
|
|
486
|
+
*/
|
|
487
|
+
deploymentMethod?: "alter";
|
|
488
|
+
/** Access tokens for this pipe */
|
|
489
|
+
tokens?: readonly PipeTokenConfig[];
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Helper type to extract the output definition from a datasource schema
|
|
494
|
+
*/
|
|
495
|
+
type DatasourceSchemaToOutput<TSchema extends SchemaDefinition> = {
|
|
496
|
+
[K in keyof TSchema]: TSchema[K] extends ColumnDefinition<infer V>
|
|
497
|
+
? V
|
|
498
|
+
: TSchema[K] extends AnyTypeValidator
|
|
499
|
+
? TSchema[K]
|
|
500
|
+
: never;
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Define a Tinybird materialized view
|
|
505
|
+
*
|
|
506
|
+
* This is a convenience function that simplifies creating materialized views.
|
|
507
|
+
* The output schema is automatically derived from the target datasource, ensuring
|
|
508
|
+
* type safety between the pipe output and the target.
|
|
509
|
+
*
|
|
510
|
+
* @param name - The pipe name (must be valid identifier)
|
|
511
|
+
* @param options - Materialized view configuration
|
|
512
|
+
* @returns A pipe definition configured as a materialized view
|
|
513
|
+
*
|
|
514
|
+
* @example
|
|
515
|
+
* ```ts
|
|
516
|
+
* import { defineDatasource, defineMaterializedView, node, t, engine } from '@tinybirdco/sdk';
|
|
517
|
+
*
|
|
518
|
+
* // Target datasource for aggregated data
|
|
519
|
+
* const salesByHour = defineDatasource('sales_by_hour', {
|
|
520
|
+
* schema: {
|
|
521
|
+
* day: t.date(),
|
|
522
|
+
* country: t.string().lowCardinality(),
|
|
523
|
+
* total_sales: t.simpleAggregateFunction('sum', t.uint64()),
|
|
524
|
+
* },
|
|
525
|
+
* engine: engine.aggregatingMergeTree({
|
|
526
|
+
* sortingKey: ['day', 'country'],
|
|
527
|
+
* }),
|
|
528
|
+
* });
|
|
529
|
+
*
|
|
530
|
+
* // Materialized view - output schema is inferred from datasource
|
|
531
|
+
* export const salesByHourMv = defineMaterializedView('sales_by_hour_mv', {
|
|
532
|
+
* description: 'Aggregate sales per hour',
|
|
533
|
+
* datasource: salesByHour,
|
|
534
|
+
* nodes: [
|
|
535
|
+
* node({
|
|
536
|
+
* name: 'daily_sales',
|
|
537
|
+
* sql: `
|
|
538
|
+
* SELECT
|
|
539
|
+
* toStartOfDay(starting_date) as day,
|
|
540
|
+
* country,
|
|
541
|
+
* sum(sales) as total_sales
|
|
542
|
+
* FROM teams
|
|
543
|
+
* GROUP BY day, country
|
|
544
|
+
* `,
|
|
545
|
+
* }),
|
|
546
|
+
* ],
|
|
547
|
+
* deploymentMethod: 'alter', // optional
|
|
548
|
+
* });
|
|
549
|
+
* ```
|
|
550
|
+
*/
|
|
551
|
+
export function defineMaterializedView<
|
|
552
|
+
TSchema extends SchemaDefinition,
|
|
553
|
+
TDatasource extends DatasourceDefinition<TSchema>
|
|
554
|
+
>(
|
|
555
|
+
name: string,
|
|
556
|
+
options: MaterializedViewOptions<TDatasource>
|
|
557
|
+
): PipeDefinition<Record<string, never>, DatasourceSchemaToOutput<TSchema>> {
|
|
558
|
+
// Extract the schema from the datasource to build the output
|
|
559
|
+
const datasourceSchema = options.datasource._schema as TSchema;
|
|
560
|
+
const output: Record<string, AnyTypeValidator> = {};
|
|
561
|
+
|
|
562
|
+
for (const [columnName, column] of Object.entries(datasourceSchema)) {
|
|
563
|
+
output[columnName] = getColumnType(column);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return definePipe(name, {
|
|
567
|
+
description: options.description,
|
|
568
|
+
nodes: options.nodes,
|
|
569
|
+
output: output as DatasourceSchemaToOutput<TSchema>,
|
|
570
|
+
materialized: {
|
|
571
|
+
datasource: options.datasource,
|
|
572
|
+
deploymentMethod: options.deploymentMethod,
|
|
573
|
+
},
|
|
574
|
+
tokens: options.tokens,
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Define a Tinybird endpoint
|
|
580
|
+
*
|
|
581
|
+
* This is a convenience function for creating API endpoints.
|
|
582
|
+
* Endpoints are pipes that are exposed as HTTP API endpoints.
|
|
583
|
+
*
|
|
584
|
+
* @param name - The endpoint name (must be valid identifier)
|
|
585
|
+
* @param options - Endpoint configuration including params, nodes, and output schema
|
|
586
|
+
* @returns A pipe definition configured as an endpoint
|
|
587
|
+
*
|
|
588
|
+
* @example
|
|
589
|
+
* ```ts
|
|
590
|
+
* import { defineEndpoint, node, p, t } from '@tinybirdco/sdk';
|
|
591
|
+
*
|
|
592
|
+
* export const topEvents = defineEndpoint('top_events', {
|
|
593
|
+
* description: 'Get top events by count',
|
|
594
|
+
* params: {
|
|
595
|
+
* start_date: p.dateTime(),
|
|
596
|
+
* end_date: p.dateTime(),
|
|
597
|
+
* limit: p.int32().optional(10),
|
|
598
|
+
* },
|
|
599
|
+
* nodes: [
|
|
600
|
+
* node({
|
|
601
|
+
* name: 'aggregated',
|
|
602
|
+
* sql: `
|
|
603
|
+
* SELECT
|
|
604
|
+
* event_type,
|
|
605
|
+
* count() as event_count
|
|
606
|
+
* FROM events
|
|
607
|
+
* WHERE timestamp BETWEEN {{DateTime(start_date)}} AND {{DateTime(end_date)}}
|
|
608
|
+
* GROUP BY event_type
|
|
609
|
+
* ORDER BY event_count DESC
|
|
610
|
+
* LIMIT {{Int32(limit, 10)}}
|
|
611
|
+
* `,
|
|
612
|
+
* }),
|
|
613
|
+
* ],
|
|
614
|
+
* output: {
|
|
615
|
+
* event_type: t.string(),
|
|
616
|
+
* event_count: t.uint64(),
|
|
617
|
+
* },
|
|
618
|
+
* });
|
|
619
|
+
* ```
|
|
620
|
+
*/
|
|
621
|
+
export function defineEndpoint<
|
|
622
|
+
TParams extends ParamsDefinition,
|
|
623
|
+
TOutput extends OutputDefinition
|
|
624
|
+
>(
|
|
625
|
+
name: string,
|
|
626
|
+
options: EndpointOptions<TParams, TOutput>
|
|
627
|
+
): PipeDefinition<TParams, TOutput> {
|
|
628
|
+
return definePipe(name, {
|
|
629
|
+
description: options.description,
|
|
630
|
+
params: options.params,
|
|
631
|
+
nodes: options.nodes,
|
|
632
|
+
output: options.output,
|
|
633
|
+
endpoint: options.cache ? { enabled: true, cache: options.cache } : true,
|
|
634
|
+
tokens: options.tokens,
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Define a Tinybird copy pipe
|
|
640
|
+
*
|
|
641
|
+
* Copy pipes capture the result of a pipe at a moment in time and write
|
|
642
|
+
* the result into a target data source. They can be run on a schedule,
|
|
643
|
+
* or executed on demand.
|
|
644
|
+
*
|
|
645
|
+
* Unlike materialized views which continuously update as new events are inserted,
|
|
646
|
+
* copy pipes generate a single snapshot at a specific point in time.
|
|
647
|
+
*
|
|
648
|
+
* @param name - The copy pipe name (must be valid identifier)
|
|
649
|
+
* @param options - Copy pipe configuration
|
|
650
|
+
* @returns A pipe definition configured as a copy pipe
|
|
651
|
+
*
|
|
652
|
+
* @example
|
|
653
|
+
* ```ts
|
|
654
|
+
* import { defineCopyPipe, defineDatasource, node, t, engine } from '@tinybirdco/sdk';
|
|
655
|
+
*
|
|
656
|
+
* // Target datasource for daily snapshots
|
|
657
|
+
* const dailySalesSnapshot = defineDatasource('daily_sales_snapshot', {
|
|
658
|
+
* schema: {
|
|
659
|
+
* snapshot_date: t.date(),
|
|
660
|
+
* country: t.string(),
|
|
661
|
+
* total_sales: t.uint64(),
|
|
662
|
+
* },
|
|
663
|
+
* engine: engine.mergeTree({
|
|
664
|
+
* sortingKey: ['snapshot_date', 'country'],
|
|
665
|
+
* }),
|
|
666
|
+
* });
|
|
667
|
+
*
|
|
668
|
+
* // Copy pipe that runs daily at midnight
|
|
669
|
+
* export const dailySalesCopy = defineCopyPipe('daily_sales_copy', {
|
|
670
|
+
* description: 'Daily snapshot of sales by country',
|
|
671
|
+
* datasource: dailySalesSnapshot,
|
|
672
|
+
* copy_schedule: '0 0 * * *', // Daily at midnight UTC
|
|
673
|
+
* copy_mode: 'append',
|
|
674
|
+
* nodes: [
|
|
675
|
+
* node({
|
|
676
|
+
* name: 'snapshot',
|
|
677
|
+
* sql: `
|
|
678
|
+
* SELECT
|
|
679
|
+
* today() AS snapshot_date,
|
|
680
|
+
* country,
|
|
681
|
+
* sum(sales) AS total_sales
|
|
682
|
+
* FROM sales
|
|
683
|
+
* WHERE date = today() - 1
|
|
684
|
+
* GROUP BY country
|
|
685
|
+
* `,
|
|
686
|
+
* }),
|
|
687
|
+
* ],
|
|
688
|
+
* });
|
|
689
|
+
* ```
|
|
690
|
+
*/
|
|
691
|
+
export function defineCopyPipe<
|
|
692
|
+
TSchema extends SchemaDefinition,
|
|
693
|
+
TDatasource extends DatasourceDefinition<TSchema>
|
|
694
|
+
>(
|
|
695
|
+
name: string,
|
|
696
|
+
options: CopyPipeOptions<TSchema, TDatasource>
|
|
697
|
+
): PipeDefinition<Record<string, never>, DatasourceSchemaToOutput<TSchema>> {
|
|
698
|
+
// Extract the schema from the datasource to build the output
|
|
699
|
+
const datasourceSchema = options.datasource._schema as TSchema;
|
|
700
|
+
const output: Record<string, AnyTypeValidator> = {};
|
|
701
|
+
|
|
702
|
+
for (const [columnName, column] of Object.entries(datasourceSchema)) {
|
|
703
|
+
output[columnName] = getColumnType(column);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
return definePipe(name, {
|
|
707
|
+
description: options.description,
|
|
708
|
+
nodes: options.nodes,
|
|
709
|
+
output: output as DatasourceSchemaToOutput<TSchema>,
|
|
710
|
+
copy: {
|
|
711
|
+
datasource: options.datasource,
|
|
712
|
+
copy_mode: options.copy_mode,
|
|
713
|
+
copy_schedule: options.copy_schedule,
|
|
714
|
+
},
|
|
715
|
+
tokens: options.tokens,
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Check if a value is a pipe definition
|
|
721
|
+
*/
|
|
722
|
+
export function isPipeDefinition(value: unknown): value is PipeDefinition {
|
|
723
|
+
return (
|
|
724
|
+
typeof value === "object" &&
|
|
725
|
+
value !== null &&
|
|
726
|
+
PIPE_BRAND in value &&
|
|
727
|
+
(value as Record<symbol, unknown>)[PIPE_BRAND] === true
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Get the endpoint configuration from a pipe
|
|
733
|
+
*/
|
|
734
|
+
export function getEndpointConfig(pipe: PipeDefinition): EndpointConfig | null {
|
|
735
|
+
const { endpoint } = pipe.options;
|
|
736
|
+
|
|
737
|
+
if (!endpoint) {
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
if (typeof endpoint === "boolean") {
|
|
742
|
+
return endpoint ? { enabled: true } : null;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
return endpoint.enabled ? endpoint : null;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Get the materialized view configuration from a pipe
|
|
750
|
+
*/
|
|
751
|
+
export function getMaterializedConfig(pipe: PipeDefinition): MaterializedConfig | null {
|
|
752
|
+
return pipe.options.materialized ?? null;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Check if a pipe is a materialized view
|
|
757
|
+
*/
|
|
758
|
+
export function isMaterializedView(pipe: PipeDefinition): boolean {
|
|
759
|
+
return pipe.options.materialized !== undefined;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Get the copy pipe configuration from a pipe
|
|
764
|
+
*/
|
|
765
|
+
export function getCopyConfig(pipe: PipeDefinition): CopyConfig | null {
|
|
766
|
+
return pipe.options.copy ?? null;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Check if a pipe is a copy pipe
|
|
771
|
+
*/
|
|
772
|
+
export function isCopyPipe(pipe: PipeDefinition): boolean {
|
|
773
|
+
return pipe.options.copy !== undefined;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* Get all node names from a pipe
|
|
778
|
+
*/
|
|
779
|
+
export function getNodeNames(pipe: PipeDefinition): string[] {
|
|
780
|
+
return pipe.options.nodes.map((n) => n._name);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Get a specific node by name
|
|
785
|
+
*/
|
|
786
|
+
export function getNode(pipe: PipeDefinition, name: string): NodeDefinition | undefined {
|
|
787
|
+
return pipe.options.nodes.find((n) => n._name === name);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Helper type to extract params from a pipe definition
|
|
792
|
+
*/
|
|
793
|
+
export type ExtractParams<T> = T extends PipeDefinition<infer P, OutputDefinition> ? P : never;
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Helper type to extract output from a pipe definition
|
|
797
|
+
*/
|
|
798
|
+
export type ExtractOutput<T> = T extends PipeDefinition<ParamsDefinition, infer O> ? O : never;
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* SQL template helper for referencing datasources and other nodes
|
|
802
|
+
* This is a simple helper - for complex templating, use raw strings
|
|
803
|
+
*
|
|
804
|
+
* @example
|
|
805
|
+
* ```ts
|
|
806
|
+
* import { sql, events } from './datasources/events';
|
|
807
|
+
*
|
|
808
|
+
* const query = sql`SELECT * FROM ${events} WHERE id = 1`;
|
|
809
|
+
* // Results in: "SELECT * FROM events WHERE id = 1"
|
|
810
|
+
* ```
|
|
811
|
+
*/
|
|
812
|
+
export function sql(
|
|
813
|
+
strings: TemplateStringsArray,
|
|
814
|
+
...values: (DatasourceDefinition | NodeDefinition | string | number)[]
|
|
815
|
+
): string {
|
|
816
|
+
return strings.reduce((result, str, i) => {
|
|
817
|
+
const value = values[i];
|
|
818
|
+
if (value === undefined) {
|
|
819
|
+
return result + str;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
823
|
+
return result + str + String(value);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
if ("_name" in value) {
|
|
827
|
+
return result + str + value._name;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
return result + str;
|
|
831
|
+
}, "");
|
|
832
|
+
}
|