@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,588 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
definePipe,
|
|
4
|
+
defineMaterializedView,
|
|
5
|
+
node,
|
|
6
|
+
isPipeDefinition,
|
|
7
|
+
getEndpointConfig,
|
|
8
|
+
getMaterializedConfig,
|
|
9
|
+
isMaterializedView,
|
|
10
|
+
getNodeNames,
|
|
11
|
+
getNode,
|
|
12
|
+
sql,
|
|
13
|
+
} from "./pipe.js";
|
|
14
|
+
import { defineDatasource } from "./datasource.js";
|
|
15
|
+
import { t } from "./types.js";
|
|
16
|
+
import { p } from "./params.js";
|
|
17
|
+
import { engine } from "./engines.js";
|
|
18
|
+
|
|
19
|
+
describe("Pipe Schema", () => {
|
|
20
|
+
describe("node", () => {
|
|
21
|
+
it("creates a node with required fields", () => {
|
|
22
|
+
const n = node({
|
|
23
|
+
name: "endpoint",
|
|
24
|
+
sql: "SELECT * FROM events",
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
expect(n._name).toBe("endpoint");
|
|
28
|
+
expect(n.sql).toBe("SELECT * FROM events");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("creates a node with description", () => {
|
|
32
|
+
const n = node({
|
|
33
|
+
name: "endpoint",
|
|
34
|
+
description: "Main query node",
|
|
35
|
+
sql: "SELECT * FROM events",
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(n.description).toBe("Main query node");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("definePipe", () => {
|
|
43
|
+
it("creates a pipe with required fields", () => {
|
|
44
|
+
const pipe = definePipe("my_pipe", {
|
|
45
|
+
nodes: [node({ name: "endpoint", sql: "SELECT 1" })],
|
|
46
|
+
output: { value: t.int32() },
|
|
47
|
+
endpoint: true,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
expect(pipe._name).toBe("my_pipe");
|
|
51
|
+
expect(pipe._type).toBe("pipe");
|
|
52
|
+
expect(pipe.options.nodes).toHaveLength(1);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("creates a pipe with params", () => {
|
|
56
|
+
const pipe = definePipe("my_pipe", {
|
|
57
|
+
params: {
|
|
58
|
+
start_date: p.dateTime(),
|
|
59
|
+
limit: p.int32().optional(10),
|
|
60
|
+
},
|
|
61
|
+
nodes: [node({ name: "endpoint", sql: "SELECT 1" })],
|
|
62
|
+
output: { value: t.int32() },
|
|
63
|
+
endpoint: true,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(pipe._params).toBeDefined();
|
|
67
|
+
expect(pipe.options.params).toBeDefined();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("creates a pipe with description", () => {
|
|
71
|
+
const pipe = definePipe("my_pipe", {
|
|
72
|
+
description: "A test pipe",
|
|
73
|
+
nodes: [node({ name: "endpoint", sql: "SELECT 1" })],
|
|
74
|
+
output: { value: t.int32() },
|
|
75
|
+
endpoint: true,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(pipe.options.description).toBe("A test pipe");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("throws error for invalid pipe name", () => {
|
|
82
|
+
expect(() =>
|
|
83
|
+
definePipe("123invalid", {
|
|
84
|
+
nodes: [node({ name: "endpoint", sql: "SELECT 1" })],
|
|
85
|
+
output: { value: t.int32() },
|
|
86
|
+
endpoint: true,
|
|
87
|
+
})
|
|
88
|
+
).toThrow("Invalid pipe name");
|
|
89
|
+
|
|
90
|
+
expect(() =>
|
|
91
|
+
definePipe("my-pipe", {
|
|
92
|
+
nodes: [node({ name: "endpoint", sql: "SELECT 1" })],
|
|
93
|
+
output: { value: t.int32() },
|
|
94
|
+
endpoint: true,
|
|
95
|
+
})
|
|
96
|
+
).toThrow("Invalid pipe name");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("throws error for empty nodes", () => {
|
|
100
|
+
expect(() =>
|
|
101
|
+
definePipe("my_pipe", {
|
|
102
|
+
nodes: [],
|
|
103
|
+
output: { value: t.int32() },
|
|
104
|
+
endpoint: true,
|
|
105
|
+
})
|
|
106
|
+
).toThrow("must have at least one node");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("throws error for empty output", () => {
|
|
110
|
+
expect(() =>
|
|
111
|
+
definePipe("my_pipe", {
|
|
112
|
+
nodes: [node({ name: "endpoint", sql: "SELECT 1" })],
|
|
113
|
+
output: {},
|
|
114
|
+
endpoint: true,
|
|
115
|
+
})
|
|
116
|
+
).toThrow("must have an output schema");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("allows valid naming patterns", () => {
|
|
120
|
+
const pipe1 = definePipe("_private_pipe", {
|
|
121
|
+
nodes: [node({ name: "endpoint", sql: "SELECT 1" })],
|
|
122
|
+
output: { value: t.int32() },
|
|
123
|
+
endpoint: true,
|
|
124
|
+
});
|
|
125
|
+
expect(pipe1._name).toBe("_private_pipe");
|
|
126
|
+
|
|
127
|
+
const pipe2 = definePipe("pipe_v2", {
|
|
128
|
+
nodes: [node({ name: "endpoint", sql: "SELECT 1" })],
|
|
129
|
+
output: { value: t.int32() },
|
|
130
|
+
endpoint: true,
|
|
131
|
+
});
|
|
132
|
+
expect(pipe2._name).toBe("pipe_v2");
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("isPipeDefinition", () => {
|
|
137
|
+
it("returns true for valid pipe", () => {
|
|
138
|
+
const pipe = definePipe("my_pipe", {
|
|
139
|
+
nodes: [node({ name: "endpoint", sql: "SELECT 1" })],
|
|
140
|
+
output: { value: t.int32() },
|
|
141
|
+
endpoint: true,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
expect(isPipeDefinition(pipe)).toBe(true);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("returns false for non-pipe objects", () => {
|
|
148
|
+
expect(isPipeDefinition({})).toBe(false);
|
|
149
|
+
expect(isPipeDefinition(null)).toBe(false);
|
|
150
|
+
expect(isPipeDefinition(undefined)).toBe(false);
|
|
151
|
+
expect(isPipeDefinition("string")).toBe(false);
|
|
152
|
+
expect(isPipeDefinition(123)).toBe(false);
|
|
153
|
+
expect(isPipeDefinition({ _name: "test" })).toBe(false);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe("getEndpointConfig", () => {
|
|
158
|
+
it("returns null when endpoint is false", () => {
|
|
159
|
+
const pipe = definePipe("my_pipe", {
|
|
160
|
+
nodes: [node({ name: "endpoint", sql: "SELECT 1" })],
|
|
161
|
+
output: { value: t.int32() },
|
|
162
|
+
endpoint: false,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
expect(getEndpointConfig(pipe)).toBeNull();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("returns config when endpoint is true", () => {
|
|
169
|
+
const pipe = definePipe("my_pipe", {
|
|
170
|
+
nodes: [node({ name: "endpoint", sql: "SELECT 1" })],
|
|
171
|
+
output: { value: t.int32() },
|
|
172
|
+
endpoint: true,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const config = getEndpointConfig(pipe);
|
|
176
|
+
expect(config).toEqual({ enabled: true });
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("returns config with cache settings", () => {
|
|
180
|
+
const pipe = definePipe("my_pipe", {
|
|
181
|
+
nodes: [node({ name: "endpoint", sql: "SELECT 1" })],
|
|
182
|
+
output: { value: t.int32() },
|
|
183
|
+
endpoint: {
|
|
184
|
+
enabled: true,
|
|
185
|
+
cache: { enabled: true, ttl: 300 },
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const config = getEndpointConfig(pipe);
|
|
190
|
+
expect(config?.enabled).toBe(true);
|
|
191
|
+
expect(config?.cache?.ttl).toBe(300);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("returns null when endpoint config has enabled: false", () => {
|
|
195
|
+
const pipe = definePipe("my_pipe", {
|
|
196
|
+
nodes: [node({ name: "endpoint", sql: "SELECT 1" })],
|
|
197
|
+
output: { value: t.int32() },
|
|
198
|
+
endpoint: {
|
|
199
|
+
enabled: false,
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
expect(getEndpointConfig(pipe)).toBeNull();
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe("getNodeNames", () => {
|
|
208
|
+
it("returns all node names", () => {
|
|
209
|
+
const pipe = definePipe("my_pipe", {
|
|
210
|
+
nodes: [
|
|
211
|
+
node({ name: "first", sql: "SELECT 1" }),
|
|
212
|
+
node({ name: "second", sql: "SELECT 2" }),
|
|
213
|
+
node({ name: "endpoint", sql: "SELECT 3" }),
|
|
214
|
+
],
|
|
215
|
+
output: { value: t.int32() },
|
|
216
|
+
endpoint: true,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const names = getNodeNames(pipe);
|
|
220
|
+
expect(names).toEqual(["first", "second", "endpoint"]);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe("getNode", () => {
|
|
225
|
+
it("returns node by name", () => {
|
|
226
|
+
const pipe = definePipe("my_pipe", {
|
|
227
|
+
nodes: [
|
|
228
|
+
node({ name: "first", sql: "SELECT 1" }),
|
|
229
|
+
node({ name: "endpoint", sql: "SELECT 2" }),
|
|
230
|
+
],
|
|
231
|
+
output: { value: t.int32() },
|
|
232
|
+
endpoint: true,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const n = getNode(pipe, "first");
|
|
236
|
+
expect(n?._name).toBe("first");
|
|
237
|
+
expect(n?.sql).toBe("SELECT 1");
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("returns undefined for non-existent node", () => {
|
|
241
|
+
const pipe = definePipe("my_pipe", {
|
|
242
|
+
nodes: [node({ name: "endpoint", sql: "SELECT 1" })],
|
|
243
|
+
output: { value: t.int32() },
|
|
244
|
+
endpoint: true,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
expect(getNode(pipe, "nonexistent")).toBeUndefined();
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe("sql template helper", () => {
|
|
252
|
+
it("interpolates datasource references", () => {
|
|
253
|
+
const events = defineDatasource("events", {
|
|
254
|
+
schema: { id: t.string() },
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const query = sql`SELECT * FROM ${events}`;
|
|
258
|
+
expect(query).toBe("SELECT * FROM events");
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("interpolates node references", () => {
|
|
262
|
+
const n = node({ name: "aggregated", sql: "SELECT 1" });
|
|
263
|
+
|
|
264
|
+
const query = sql`SELECT * FROM ${n}`;
|
|
265
|
+
expect(query).toBe("SELECT * FROM aggregated");
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("interpolates string values", () => {
|
|
269
|
+
const tableName = "events";
|
|
270
|
+
const query = sql`SELECT * FROM ${tableName}`;
|
|
271
|
+
expect(query).toBe("SELECT * FROM events");
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("interpolates number values", () => {
|
|
275
|
+
const limit = 10;
|
|
276
|
+
const query = sql`SELECT * FROM events LIMIT ${limit}`;
|
|
277
|
+
expect(query).toBe("SELECT * FROM events LIMIT 10");
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("handles multiple interpolations", () => {
|
|
281
|
+
const events = defineDatasource("events", {
|
|
282
|
+
schema: { id: t.string() },
|
|
283
|
+
});
|
|
284
|
+
const limit = 100;
|
|
285
|
+
|
|
286
|
+
const query = sql`SELECT * FROM ${events} WHERE id = ${"test"} LIMIT ${limit}`;
|
|
287
|
+
expect(query).toBe("SELECT * FROM events WHERE id = test LIMIT 100");
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("handles no interpolations", () => {
|
|
291
|
+
const query = sql`SELECT 1`;
|
|
292
|
+
expect(query).toBe("SELECT 1");
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
describe("Materialized Views", () => {
|
|
297
|
+
const salesByHour = defineDatasource("sales_by_hour", {
|
|
298
|
+
schema: {
|
|
299
|
+
day: t.date(),
|
|
300
|
+
country: t.string().lowCardinality(),
|
|
301
|
+
total_sales: t.simpleAggregateFunction("sum", t.uint64()),
|
|
302
|
+
},
|
|
303
|
+
engine: engine.aggregatingMergeTree({
|
|
304
|
+
sortingKey: ["day", "country"],
|
|
305
|
+
}),
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe("definePipe with materialized", () => {
|
|
309
|
+
it("creates a materialized view pipe with datasource (preferred)", () => {
|
|
310
|
+
const pipe = definePipe("sales_by_hour_mv", {
|
|
311
|
+
description: "Aggregate sales per hour",
|
|
312
|
+
nodes: [
|
|
313
|
+
node({
|
|
314
|
+
name: "daily_sales",
|
|
315
|
+
sql: `
|
|
316
|
+
SELECT
|
|
317
|
+
toStartOfDay(starting_date) as day,
|
|
318
|
+
country,
|
|
319
|
+
sum(sales) as total_sales
|
|
320
|
+
FROM teams
|
|
321
|
+
GROUP BY day, country
|
|
322
|
+
`,
|
|
323
|
+
}),
|
|
324
|
+
],
|
|
325
|
+
output: {
|
|
326
|
+
day: t.date(),
|
|
327
|
+
country: t.string().lowCardinality(),
|
|
328
|
+
total_sales: t.simpleAggregateFunction("sum", t.uint64()),
|
|
329
|
+
},
|
|
330
|
+
materialized: {
|
|
331
|
+
datasource: salesByHour,
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
expect(pipe._name).toBe("sales_by_hour_mv");
|
|
336
|
+
expect(pipe.options.materialized).toBeDefined();
|
|
337
|
+
// Internally normalized to datasource
|
|
338
|
+
expect(pipe.options.materialized?.datasource?._name).toBe("sales_by_hour");
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it("creates a materialized view pipe with datasource", () => {
|
|
342
|
+
const pipe = definePipe("sales_by_hour_mv_2", {
|
|
343
|
+
nodes: [node({ name: "mv", sql: "SELECT 1 as day, 'US' as country, 100 as total_sales" })],
|
|
344
|
+
output: {
|
|
345
|
+
day: t.date(),
|
|
346
|
+
country: t.string().lowCardinality(),
|
|
347
|
+
total_sales: t.simpleAggregateFunction("sum", t.uint64()),
|
|
348
|
+
},
|
|
349
|
+
materialized: {
|
|
350
|
+
datasource: salesByHour,
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
expect(pipe._name).toBe("sales_by_hour_mv_2");
|
|
355
|
+
expect(pipe.options.materialized).toBeDefined();
|
|
356
|
+
expect(pipe.options.materialized?.datasource?._name).toBe("sales_by_hour");
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it("creates a materialized view with deployment method", () => {
|
|
360
|
+
const pipe = definePipe("sales_by_hour_mv", {
|
|
361
|
+
nodes: [node({ name: "mv", sql: "SELECT 1 as day, 'US' as country, 100 as total_sales" })],
|
|
362
|
+
output: {
|
|
363
|
+
day: t.date(),
|
|
364
|
+
country: t.string().lowCardinality(),
|
|
365
|
+
total_sales: t.simpleAggregateFunction("sum", t.uint64()),
|
|
366
|
+
},
|
|
367
|
+
materialized: {
|
|
368
|
+
datasource: salesByHour,
|
|
369
|
+
deploymentMethod: "alter",
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
expect(pipe.options.materialized?.deploymentMethod).toBe("alter");
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it("throws error when both endpoint and materialized are set", () => {
|
|
377
|
+
expect(() =>
|
|
378
|
+
definePipe("invalid_pipe", {
|
|
379
|
+
nodes: [node({ name: "endpoint", sql: "SELECT 1 as day, 'US' as country, 100 as total_sales" })],
|
|
380
|
+
output: {
|
|
381
|
+
day: t.date(),
|
|
382
|
+
country: t.string().lowCardinality(),
|
|
383
|
+
total_sales: t.simpleAggregateFunction("sum", t.uint64()),
|
|
384
|
+
},
|
|
385
|
+
endpoint: true,
|
|
386
|
+
materialized: {
|
|
387
|
+
datasource: salesByHour,
|
|
388
|
+
},
|
|
389
|
+
})
|
|
390
|
+
).toThrow("can only have one of: endpoint, materialized, or copy");
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
describe("Schema validation", () => {
|
|
396
|
+
it("throws error when output is missing columns", () => {
|
|
397
|
+
expect(() =>
|
|
398
|
+
definePipe("invalid_mv", {
|
|
399
|
+
nodes: [node({ name: "mv", sql: "SELECT 1" })],
|
|
400
|
+
output: {
|
|
401
|
+
day: t.date(),
|
|
402
|
+
// missing country and total_sales
|
|
403
|
+
},
|
|
404
|
+
materialized: {
|
|
405
|
+
datasource: salesByHour,
|
|
406
|
+
},
|
|
407
|
+
})
|
|
408
|
+
).toThrow("missing columns from target datasource");
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it("throws error when output has extra columns", () => {
|
|
412
|
+
expect(() =>
|
|
413
|
+
definePipe("invalid_mv", {
|
|
414
|
+
nodes: [node({ name: "mv", sql: "SELECT 1" })],
|
|
415
|
+
output: {
|
|
416
|
+
day: t.date(),
|
|
417
|
+
country: t.string().lowCardinality(),
|
|
418
|
+
total_sales: t.simpleAggregateFunction("sum", t.uint64()),
|
|
419
|
+
extra_column: t.string(), // extra column
|
|
420
|
+
},
|
|
421
|
+
materialized: {
|
|
422
|
+
datasource: salesByHour,
|
|
423
|
+
},
|
|
424
|
+
})
|
|
425
|
+
).toThrow("columns not in target datasource");
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it("throws error when column types do not match", () => {
|
|
429
|
+
expect(() =>
|
|
430
|
+
definePipe("invalid_mv", {
|
|
431
|
+
nodes: [node({ name: "mv", sql: "SELECT 1" })],
|
|
432
|
+
output: {
|
|
433
|
+
day: t.string(), // should be date
|
|
434
|
+
country: t.string().lowCardinality(),
|
|
435
|
+
total_sales: t.simpleAggregateFunction("sum", t.uint64()),
|
|
436
|
+
},
|
|
437
|
+
materialized: {
|
|
438
|
+
datasource: salesByHour,
|
|
439
|
+
},
|
|
440
|
+
})
|
|
441
|
+
).toThrow("type mismatch");
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it("allows compatible types (base type to aggregate function)", () => {
|
|
445
|
+
// When the output has UInt64 and datasource has SimpleAggregateFunction(sum, UInt64)
|
|
446
|
+
// they should be compatible
|
|
447
|
+
const simpleDatasource = defineDatasource("simple_agg", {
|
|
448
|
+
schema: {
|
|
449
|
+
value: t.simpleAggregateFunction("sum", t.uint64()),
|
|
450
|
+
},
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
const pipe = definePipe("valid_mv", {
|
|
454
|
+
nodes: [node({ name: "mv", sql: "SELECT sum(x) as value FROM table" })],
|
|
455
|
+
output: {
|
|
456
|
+
value: t.uint64(), // base type compatible with SimpleAggregateFunction(sum, UInt64)
|
|
457
|
+
},
|
|
458
|
+
materialized: {
|
|
459
|
+
datasource: simpleDatasource,
|
|
460
|
+
},
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
expect(pipe.options.materialized).toBeDefined();
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it("allows compatible types with modifiers (nullable, low cardinality)", () => {
|
|
467
|
+
const datasource = defineDatasource("test_ds", {
|
|
468
|
+
schema: {
|
|
469
|
+
name: t.string().lowCardinality().nullable(),
|
|
470
|
+
},
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
const pipe = definePipe("valid_mv", {
|
|
474
|
+
nodes: [node({ name: "mv", sql: "SELECT name FROM table" })],
|
|
475
|
+
output: {
|
|
476
|
+
name: t.string().lowCardinality().nullable(),
|
|
477
|
+
},
|
|
478
|
+
materialized: {
|
|
479
|
+
datasource: datasource,
|
|
480
|
+
},
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
expect(pipe.options.materialized).toBeDefined();
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
describe("getMaterializedConfig", () => {
|
|
488
|
+
it("returns null for endpoint pipe", () => {
|
|
489
|
+
const pipe = definePipe("endpoint_pipe", {
|
|
490
|
+
nodes: [node({ name: "endpoint", sql: "SELECT 1" })],
|
|
491
|
+
output: { value: t.int32() },
|
|
492
|
+
endpoint: true,
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
expect(getMaterializedConfig(pipe)).toBeNull();
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it("returns config for materialized view with datasource", () => {
|
|
499
|
+
const pipe = definePipe("mv_pipe", {
|
|
500
|
+
nodes: [node({ name: "mv", sql: "SELECT 1 as day, 'US' as country, 100 as total_sales" })],
|
|
501
|
+
output: {
|
|
502
|
+
day: t.date(),
|
|
503
|
+
country: t.string().lowCardinality(),
|
|
504
|
+
total_sales: t.simpleAggregateFunction("sum", t.uint64()),
|
|
505
|
+
},
|
|
506
|
+
materialized: {
|
|
507
|
+
datasource: salesByHour,
|
|
508
|
+
deploymentMethod: "alter",
|
|
509
|
+
},
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
const config = getMaterializedConfig(pipe);
|
|
513
|
+
expect(config).toBeDefined();
|
|
514
|
+
// Normalized config always has datasource set
|
|
515
|
+
expect(config?.datasource?._name).toBe("sales_by_hour");
|
|
516
|
+
expect(config?.deploymentMethod).toBe("alter");
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
describe("isMaterializedView", () => {
|
|
521
|
+
it("returns false for endpoint pipe", () => {
|
|
522
|
+
const pipe = definePipe("endpoint_pipe", {
|
|
523
|
+
nodes: [node({ name: "endpoint", sql: "SELECT 1" })],
|
|
524
|
+
output: { value: t.int32() },
|
|
525
|
+
endpoint: true,
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
expect(isMaterializedView(pipe)).toBe(false);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it("returns true for materialized view", () => {
|
|
532
|
+
const pipe = definePipe("mv_pipe", {
|
|
533
|
+
nodes: [node({ name: "mv", sql: "SELECT 1 as day, 'US' as country, 100 as total_sales" })],
|
|
534
|
+
output: {
|
|
535
|
+
day: t.date(),
|
|
536
|
+
country: t.string().lowCardinality(),
|
|
537
|
+
total_sales: t.simpleAggregateFunction("sum", t.uint64()),
|
|
538
|
+
},
|
|
539
|
+
materialized: {
|
|
540
|
+
datasource: salesByHour,
|
|
541
|
+
},
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
expect(isMaterializedView(pipe)).toBe(true);
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
describe("defineMaterializedView", () => {
|
|
549
|
+
it("creates a materialized view with inferred output schema using datasource", () => {
|
|
550
|
+
const pipe = defineMaterializedView("sales_mv", {
|
|
551
|
+
description: "Sales materialized view",
|
|
552
|
+
datasource: salesByHour,
|
|
553
|
+
nodes: [
|
|
554
|
+
node({
|
|
555
|
+
name: "daily_sales",
|
|
556
|
+
sql: "SELECT toStartOfDay(date) as day, country, sum(sales) as total_sales FROM events GROUP BY day, country",
|
|
557
|
+
}),
|
|
558
|
+
],
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
expect(pipe._name).toBe("sales_mv");
|
|
562
|
+
expect(pipe.options.description).toBe("Sales materialized view");
|
|
563
|
+
expect(pipe.options.materialized?.datasource?._name).toBe("sales_by_hour");
|
|
564
|
+
expect(Object.keys(pipe._output!)).toEqual(["day", "country", "total_sales"]);
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
it("creates a materialized view with deployment method", () => {
|
|
568
|
+
const pipe = defineMaterializedView("sales_mv", {
|
|
569
|
+
datasource: salesByHour,
|
|
570
|
+
nodes: [node({ name: "mv", sql: "SELECT 1" })],
|
|
571
|
+
deploymentMethod: "alter",
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
expect(pipe.options.materialized?.deploymentMethod).toBe("alter");
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
it("creates a materialized view with datasource", () => {
|
|
578
|
+
const pipe = defineMaterializedView("sales_mv_2", {
|
|
579
|
+
datasource: salesByHour,
|
|
580
|
+
nodes: [node({ name: "mv", sql: "SELECT 1" })],
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
expect(pipe._name).toBe("sales_mv_2");
|
|
584
|
+
expect(pipe.options.materialized?.datasource?._name).toBe("sales_by_hour");
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
});
|