@tinybirdco/sdk 0.0.38 → 0.0.40
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 +47 -2
- package/dist/api/api.d.ts +18 -1
- package/dist/api/api.d.ts.map +1 -1
- package/dist/api/api.js +56 -0
- package/dist/api/api.js.map +1 -1
- package/dist/api/api.test.js +111 -0
- package/dist/api/api.test.js.map +1 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +4 -9
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/init.d.ts +12 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +217 -2
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/init.test.js +15 -0
- package/dist/cli/commands/init.test.js.map +1 -1
- package/dist/cli/config-types.d.ts +1 -1
- package/dist/cli/config-types.d.ts.map +1 -1
- package/dist/cli/config.d.ts +1 -1
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/client/base.d.ts +16 -0
- package/dist/client/base.d.ts.map +1 -1
- package/dist/client/base.js +38 -0
- package/dist/client/base.js.map +1 -1
- package/dist/client/base.test.js +4 -0
- package/dist/client/base.test.js.map +1 -1
- package/dist/client/types.d.ts +50 -0
- package/dist/client/types.d.ts.map +1 -1
- package/dist/generator/include-paths.d.ts +7 -0
- package/dist/generator/include-paths.d.ts.map +1 -0
- package/dist/generator/include-paths.js +164 -0
- package/dist/generator/include-paths.js.map +1 -0
- package/dist/generator/index.d.ts +1 -1
- package/dist/generator/index.d.ts.map +1 -1
- package/dist/generator/index.test.js +36 -0
- package/dist/generator/index.test.js.map +1 -1
- package/dist/generator/loader.d.ts +1 -1
- package/dist/generator/loader.d.ts.map +1 -1
- package/dist/generator/loader.js +5 -8
- package/dist/generator/loader.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/schema/project.d.ts +8 -4
- package/dist/schema/project.d.ts.map +1 -1
- package/dist/schema/project.js +8 -0
- package/dist/schema/project.js.map +1 -1
- package/dist/schema/project.test.js +14 -2
- package/dist/schema/project.test.js.map +1 -1
- package/package.json +1 -1
- package/src/api/api.test.ts +165 -0
- package/src/api/api.ts +104 -0
- package/src/cli/commands/dev.ts +4 -9
- package/src/cli/commands/init.test.ts +17 -0
- package/src/cli/commands/init.ts +300 -2
- package/src/cli/config-types.ts +1 -1
- package/src/cli/config.ts +1 -1
- package/src/client/base.test.ts +4 -0
- package/src/client/base.ts +53 -0
- package/src/client/types.ts +54 -0
- package/src/generator/include-paths.ts +234 -0
- package/src/generator/index.test.ts +44 -0
- package/src/generator/index.ts +1 -1
- package/src/generator/loader.ts +6 -10
- package/src/index.ts +6 -0
- package/src/schema/project.test.ts +14 -2
- package/src/schema/project.ts +19 -3
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface ResolvedIncludeFile {
|
|
5
|
+
sourcePath: string;
|
|
6
|
+
absolutePath: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const GLOB_SEGMENT_REGEX = /[*?[]/;
|
|
10
|
+
const IGNORED_DIRECTORIES = new Set([".git", "node_modules"]);
|
|
11
|
+
const SEGMENT_REGEX_CACHE = new Map<string, RegExp>();
|
|
12
|
+
|
|
13
|
+
function hasGlobPattern(value: string): boolean {
|
|
14
|
+
return GLOB_SEGMENT_REGEX.test(value);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizePath(value: string): string {
|
|
18
|
+
return value.replace(/\\/g, "/");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function splitAbsolutePath(filePath: string): { root: string; segments: string[] } {
|
|
22
|
+
const absolutePath = path.resolve(filePath);
|
|
23
|
+
const root = path.parse(absolutePath).root;
|
|
24
|
+
const relative = path.relative(root, absolutePath);
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
root: normalizePath(root),
|
|
28
|
+
segments: normalizePath(relative).split("/").filter(Boolean),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function segmentMatcher(segment: string): RegExp {
|
|
33
|
+
const cached = SEGMENT_REGEX_CACHE.get(segment);
|
|
34
|
+
if (cached) {
|
|
35
|
+
return cached;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const escaped = segment
|
|
39
|
+
.replace(/[.+^${}()|\\]/g, "\\$&")
|
|
40
|
+
.replace(/\*/g, "[^/]*")
|
|
41
|
+
.replace(/\?/g, "[^/]");
|
|
42
|
+
|
|
43
|
+
const matcher = new RegExp(`^${escaped}$`);
|
|
44
|
+
SEGMENT_REGEX_CACHE.set(segment, matcher);
|
|
45
|
+
return matcher;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function matchSegment(patternSegment: string, valueSegment: string): boolean {
|
|
49
|
+
if (!hasGlobPattern(patternSegment)) {
|
|
50
|
+
return patternSegment === valueSegment;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return segmentMatcher(patternSegment).test(valueSegment);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function matchGlobSegments(
|
|
57
|
+
patternSegments: string[],
|
|
58
|
+
pathSegments: string[],
|
|
59
|
+
patternIndex: number,
|
|
60
|
+
pathIndex: number,
|
|
61
|
+
memo: Map<string, boolean>
|
|
62
|
+
): boolean {
|
|
63
|
+
const key = `${patternIndex}:${pathIndex}`;
|
|
64
|
+
const cached = memo.get(key);
|
|
65
|
+
if (cached !== undefined) {
|
|
66
|
+
return cached;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (patternIndex === patternSegments.length) {
|
|
70
|
+
const matches = pathIndex === pathSegments.length;
|
|
71
|
+
memo.set(key, matches);
|
|
72
|
+
return matches;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const patternSegment = patternSegments[patternIndex];
|
|
76
|
+
let matches = false;
|
|
77
|
+
|
|
78
|
+
if (patternSegment === "**") {
|
|
79
|
+
matches = matchGlobSegments(patternSegments, pathSegments, patternIndex + 1, pathIndex, memo);
|
|
80
|
+
|
|
81
|
+
if (!matches && pathIndex < pathSegments.length) {
|
|
82
|
+
matches = matchGlobSegments(patternSegments, pathSegments, patternIndex, pathIndex + 1, memo);
|
|
83
|
+
}
|
|
84
|
+
} else if (
|
|
85
|
+
pathIndex < pathSegments.length &&
|
|
86
|
+
matchSegment(patternSegment, pathSegments[pathIndex])
|
|
87
|
+
) {
|
|
88
|
+
matches = matchGlobSegments(patternSegments, pathSegments, patternIndex + 1, pathIndex + 1, memo);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
memo.set(key, matches);
|
|
92
|
+
return matches;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function matchGlobPath(absolutePattern: string, absolutePath: string): boolean {
|
|
96
|
+
const patternParts = splitAbsolutePath(absolutePattern);
|
|
97
|
+
const pathParts = splitAbsolutePath(absolutePath);
|
|
98
|
+
|
|
99
|
+
if (patternParts.root.toLowerCase() !== pathParts.root.toLowerCase()) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return matchGlobSegments(
|
|
104
|
+
patternParts.segments,
|
|
105
|
+
pathParts.segments,
|
|
106
|
+
0,
|
|
107
|
+
0,
|
|
108
|
+
new Map<string, boolean>()
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function getGlobRootDirectory(absolutePattern: string): string {
|
|
113
|
+
const { root, segments } = splitAbsolutePath(absolutePattern);
|
|
114
|
+
const firstGlobIndex = segments.findIndex((segment) => hasGlobPattern(segment));
|
|
115
|
+
const baseSegments =
|
|
116
|
+
firstGlobIndex === -1 ? segments : segments.slice(0, firstGlobIndex);
|
|
117
|
+
|
|
118
|
+
return path.join(root, ...baseSegments);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function collectFilesRecursive(directory: string, result: string[]): void {
|
|
122
|
+
const entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
123
|
+
|
|
124
|
+
for (const entry of entries) {
|
|
125
|
+
const fullPath = path.join(directory, entry.name);
|
|
126
|
+
|
|
127
|
+
if (entry.isDirectory()) {
|
|
128
|
+
if (IGNORED_DIRECTORIES.has(entry.name)) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
collectFilesRecursive(fullPath, result);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (entry.isFile()) {
|
|
137
|
+
result.push(fullPath);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function expandGlobPattern(absolutePattern: string): string[] {
|
|
143
|
+
const rootDirectory = getGlobRootDirectory(absolutePattern);
|
|
144
|
+
|
|
145
|
+
if (!fs.existsSync(rootDirectory)) {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!fs.statSync(rootDirectory).isDirectory()) {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const files: string[] = [];
|
|
154
|
+
collectFilesRecursive(rootDirectory, files);
|
|
155
|
+
|
|
156
|
+
return files
|
|
157
|
+
.filter((filePath) => matchGlobPath(absolutePattern, filePath))
|
|
158
|
+
.sort((a, b) => a.localeCompare(b));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function resolveIncludeFiles(
|
|
162
|
+
includePaths: string[],
|
|
163
|
+
cwd: string
|
|
164
|
+
): ResolvedIncludeFile[] {
|
|
165
|
+
const resolved: ResolvedIncludeFile[] = [];
|
|
166
|
+
const seen = new Set<string>();
|
|
167
|
+
|
|
168
|
+
for (const includePath of includePaths) {
|
|
169
|
+
const absoluteIncludePath = path.isAbsolute(includePath)
|
|
170
|
+
? includePath
|
|
171
|
+
: path.resolve(cwd, includePath);
|
|
172
|
+
|
|
173
|
+
if (hasGlobPattern(includePath)) {
|
|
174
|
+
const matchedFiles = expandGlobPattern(absoluteIncludePath);
|
|
175
|
+
|
|
176
|
+
if (matchedFiles.length === 0) {
|
|
177
|
+
throw new Error(`Include pattern matched no files: ${includePath}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
for (const matchedFile of matchedFiles) {
|
|
181
|
+
if (seen.has(matchedFile)) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
seen.add(matchedFile);
|
|
186
|
+
resolved.push({
|
|
187
|
+
sourcePath: path.isAbsolute(includePath)
|
|
188
|
+
? matchedFile
|
|
189
|
+
: path.relative(cwd, matchedFile),
|
|
190
|
+
absolutePath: matchedFile,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!fs.existsSync(absoluteIncludePath)) {
|
|
197
|
+
throw new Error(`Include file not found: ${absoluteIncludePath}`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (seen.has(absoluteIncludePath)) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
seen.add(absoluteIncludePath);
|
|
205
|
+
resolved.push({
|
|
206
|
+
sourcePath: includePath,
|
|
207
|
+
absolutePath: absoluteIncludePath,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return resolved;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function getIncludeWatchDirectories(
|
|
215
|
+
includePaths: string[],
|
|
216
|
+
cwd: string
|
|
217
|
+
): string[] {
|
|
218
|
+
const watchDirs = new Set<string>();
|
|
219
|
+
|
|
220
|
+
for (const includePath of includePaths) {
|
|
221
|
+
const absoluteIncludePath = path.isAbsolute(includePath)
|
|
222
|
+
? includePath
|
|
223
|
+
: path.resolve(cwd, includePath);
|
|
224
|
+
|
|
225
|
+
if (hasGlobPattern(includePath)) {
|
|
226
|
+
watchDirs.add(getGlobRootDirectory(absoluteIncludePath));
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
watchDirs.add(path.dirname(absoluteIncludePath));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return Array.from(watchDirs);
|
|
234
|
+
}
|
|
@@ -325,5 +325,49 @@ TYPE endpoint
|
|
|
325
325
|
expect(rawPipe).toBeDefined();
|
|
326
326
|
expect(rawPipe!.content).toBe(rawPipeContent);
|
|
327
327
|
});
|
|
328
|
+
|
|
329
|
+
it("supports glob include patterns", async () => {
|
|
330
|
+
const nestedDir = path.join(tempDir, "tinybird", "legacy");
|
|
331
|
+
fs.mkdirSync(nestedDir, { recursive: true });
|
|
332
|
+
|
|
333
|
+
const datasourceContent = `SCHEMA >
|
|
334
|
+
id String
|
|
335
|
+
|
|
336
|
+
ENGINE "MergeTree"
|
|
337
|
+
ENGINE_SORTING_KEY "id"
|
|
338
|
+
`;
|
|
339
|
+
const datasourcePath = path.join(nestedDir, "events.datasource");
|
|
340
|
+
fs.writeFileSync(datasourcePath, datasourceContent);
|
|
341
|
+
|
|
342
|
+
const pipeContent = `NODE endpoint
|
|
343
|
+
SQL >
|
|
344
|
+
SELECT * FROM events
|
|
345
|
+
|
|
346
|
+
TYPE endpoint
|
|
347
|
+
`;
|
|
348
|
+
const pipePath = path.join(nestedDir, "events.pipe");
|
|
349
|
+
fs.writeFileSync(pipePath, pipeContent);
|
|
350
|
+
|
|
351
|
+
const result = await buildFromInclude({
|
|
352
|
+
includePaths: ["tinybird/**/*.datasource", "tinybird/**/*.pipe"],
|
|
353
|
+
cwd: tempDir,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
expect(result.resources.datasources).toHaveLength(1);
|
|
357
|
+
expect(result.resources.datasources[0].name).toBe("events");
|
|
358
|
+
expect(result.resources.datasources[0].content).toBe(datasourceContent);
|
|
359
|
+
expect(result.resources.pipes).toHaveLength(1);
|
|
360
|
+
expect(result.resources.pipes[0].name).toBe("events");
|
|
361
|
+
expect(result.resources.pipes[0].content).toBe(pipeContent);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it("throws when include glob matches no files", async () => {
|
|
365
|
+
await expect(
|
|
366
|
+
buildFromInclude({
|
|
367
|
+
includePaths: ["tinybird/**/*.datasource"],
|
|
368
|
+
cwd: tempDir,
|
|
369
|
+
})
|
|
370
|
+
).rejects.toThrow("Include pattern matched no files");
|
|
371
|
+
});
|
|
328
372
|
});
|
|
329
373
|
});
|
package/src/generator/index.ts
CHANGED
|
@@ -122,7 +122,7 @@ export async function build(options: BuildOptions): Promise<BuildResult> {
|
|
|
122
122
|
* Build options using include paths
|
|
123
123
|
*/
|
|
124
124
|
export interface BuildFromIncludeOptions {
|
|
125
|
-
/** Array of file paths to scan for datasources and pipes */
|
|
125
|
+
/** Array of file paths or glob patterns to scan for datasources and pipes */
|
|
126
126
|
includePaths: string[];
|
|
127
127
|
/** Working directory (defaults to cwd) */
|
|
128
128
|
cwd?: string;
|
package/src/generator/loader.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { isProjectDefinition, type ProjectDefinition, type DatasourcesDefinition
|
|
|
11
11
|
import { isDatasourceDefinition, type DatasourceDefinition } from "../schema/datasource.js";
|
|
12
12
|
import { isPipeDefinition, type PipeDefinition } from "../schema/pipe.js";
|
|
13
13
|
import { isConnectionDefinition, type ConnectionDefinition } from "../schema/connection.js";
|
|
14
|
+
import { resolveIncludeFiles } from "./include-paths.js";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Result of loading a schema file
|
|
@@ -184,7 +185,7 @@ export interface LoadedEntities {
|
|
|
184
185
|
* Options for loading entities
|
|
185
186
|
*/
|
|
186
187
|
export interface LoadEntitiesOptions {
|
|
187
|
-
/** Array of file paths to scan (can be relative or absolute) */
|
|
188
|
+
/** Array of file paths or glob patterns to scan (can be relative or absolute) */
|
|
188
189
|
includePaths: string[];
|
|
189
190
|
/** The working directory for resolution (defaults to cwd) */
|
|
190
191
|
cwd?: string;
|
|
@@ -211,6 +212,7 @@ export interface LoadEntitiesOptions {
|
|
|
211
212
|
*/
|
|
212
213
|
export async function loadEntities(options: LoadEntitiesOptions): Promise<LoadedEntities> {
|
|
213
214
|
const cwd = options.cwd ?? process.cwd();
|
|
215
|
+
const includeFiles = resolveIncludeFiles(options.includePaths, cwd);
|
|
214
216
|
const result: LoadedEntities = {
|
|
215
217
|
datasources: {},
|
|
216
218
|
pipes: {},
|
|
@@ -220,15 +222,9 @@ export async function loadEntities(options: LoadEntitiesOptions): Promise<Loaded
|
|
|
220
222
|
sourceFiles: [],
|
|
221
223
|
};
|
|
222
224
|
|
|
223
|
-
for (const
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
: path.resolve(cwd, includePath);
|
|
227
|
-
|
|
228
|
-
// Verify the file exists
|
|
229
|
-
if (!fs.existsSync(absolutePath)) {
|
|
230
|
-
throw new Error(`Include file not found: ${absolutePath}`);
|
|
231
|
-
}
|
|
225
|
+
for (const includeFile of includeFiles) {
|
|
226
|
+
const includePath = includeFile.sourcePath;
|
|
227
|
+
const absolutePath = includeFile.absolutePath;
|
|
232
228
|
|
|
233
229
|
result.sourceFiles.push(includePath);
|
|
234
230
|
|
package/src/index.ts
CHANGED
|
@@ -208,10 +208,14 @@ export type {
|
|
|
208
208
|
ClientContext,
|
|
209
209
|
CsvDialectOptions,
|
|
210
210
|
DatasourcesNamespace,
|
|
211
|
+
DeleteOptions,
|
|
212
|
+
DeleteResult,
|
|
211
213
|
QueryResult,
|
|
212
214
|
IngestResult,
|
|
213
215
|
QueryOptions,
|
|
214
216
|
IngestOptions,
|
|
217
|
+
TruncateOptions,
|
|
218
|
+
TruncateResult,
|
|
215
219
|
ColumnMeta,
|
|
216
220
|
QueryStatistics,
|
|
217
221
|
TinybirdErrorResponse,
|
|
@@ -231,6 +235,8 @@ export type {
|
|
|
231
235
|
TinybirdApiQueryOptions,
|
|
232
236
|
TinybirdApiIngestOptions,
|
|
233
237
|
TinybirdApiAppendOptions,
|
|
238
|
+
TinybirdApiDeleteOptions,
|
|
239
|
+
TinybirdApiTruncateOptions,
|
|
234
240
|
TinybirdApiRequestInit,
|
|
235
241
|
TinybirdApiTokenScope,
|
|
236
242
|
TinybirdApiCreateTokenRequest,
|
|
@@ -94,7 +94,7 @@ describe("Project Schema", () => {
|
|
|
94
94
|
expect((project.tinybird as unknown as Record<string, unknown>).pipes).toBeUndefined();
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
-
it("creates datasource accessors with append
|
|
97
|
+
it("creates datasource accessors with append/delete/truncate methods", () => {
|
|
98
98
|
const events = defineDatasource("events", {
|
|
99
99
|
schema: { timestamp: t.dateTime() },
|
|
100
100
|
});
|
|
@@ -105,6 +105,8 @@ describe("Project Schema", () => {
|
|
|
105
105
|
|
|
106
106
|
expect(project.tinybird.events).toBeDefined();
|
|
107
107
|
expect(typeof project.tinybird.events.append).toBe("function");
|
|
108
|
+
expect(typeof project.tinybird.events.delete).toBe("function");
|
|
109
|
+
expect(typeof project.tinybird.events.truncate).toBe("function");
|
|
108
110
|
});
|
|
109
111
|
|
|
110
112
|
it("creates multiple datasource accessors", () => {
|
|
@@ -123,6 +125,10 @@ describe("Project Schema", () => {
|
|
|
123
125
|
expect(project.tinybird.pageViews).toBeDefined();
|
|
124
126
|
expect(typeof project.tinybird.events.append).toBe("function");
|
|
125
127
|
expect(typeof project.tinybird.pageViews.append).toBe("function");
|
|
128
|
+
expect(typeof project.tinybird.events.delete).toBe("function");
|
|
129
|
+
expect(typeof project.tinybird.pageViews.delete).toBe("function");
|
|
130
|
+
expect(typeof project.tinybird.events.truncate).toBe("function");
|
|
131
|
+
expect(typeof project.tinybird.pageViews.truncate).toBe("function");
|
|
126
132
|
});
|
|
127
133
|
|
|
128
134
|
it("throws error when accessing client before initialization", () => {
|
|
@@ -304,7 +310,7 @@ describe("Project Schema", () => {
|
|
|
304
310
|
expect((client as unknown as Record<string, unknown>).pipes).toBeUndefined();
|
|
305
311
|
});
|
|
306
312
|
|
|
307
|
-
it("creates datasource accessors with append
|
|
313
|
+
it("creates datasource accessors with append/delete/truncate methods", () => {
|
|
308
314
|
const events = defineDatasource("events", {
|
|
309
315
|
schema: { id: t.string() },
|
|
310
316
|
});
|
|
@@ -316,6 +322,8 @@ describe("Project Schema", () => {
|
|
|
316
322
|
|
|
317
323
|
expect(client.events).toBeDefined();
|
|
318
324
|
expect(typeof client.events.append).toBe("function");
|
|
325
|
+
expect(typeof client.events.delete).toBe("function");
|
|
326
|
+
expect(typeof client.events.truncate).toBe("function");
|
|
319
327
|
});
|
|
320
328
|
|
|
321
329
|
it("creates multiple datasource accessors", () => {
|
|
@@ -335,6 +343,10 @@ describe("Project Schema", () => {
|
|
|
335
343
|
expect(client.pageViews).toBeDefined();
|
|
336
344
|
expect(typeof client.events.append).toBe("function");
|
|
337
345
|
expect(typeof client.pageViews.append).toBe("function");
|
|
346
|
+
expect(typeof client.events.delete).toBe("function");
|
|
347
|
+
expect(typeof client.pageViews.delete).toBe("function");
|
|
348
|
+
expect(typeof client.events.truncate).toBe("function");
|
|
349
|
+
expect(typeof client.pageViews.truncate).toBe("function");
|
|
338
350
|
});
|
|
339
351
|
|
|
340
352
|
it("accepts devMode option", () => {
|
package/src/schema/project.ts
CHANGED
|
@@ -12,8 +12,12 @@ import type {
|
|
|
12
12
|
AppendOptions,
|
|
13
13
|
AppendResult,
|
|
14
14
|
DatasourcesNamespace,
|
|
15
|
+
DeleteOptions,
|
|
16
|
+
DeleteResult,
|
|
15
17
|
QueryOptions,
|
|
16
18
|
QueryResult,
|
|
19
|
+
TruncateOptions,
|
|
20
|
+
TruncateResult,
|
|
17
21
|
} from "../client/types.js";
|
|
18
22
|
import type { InferRow, InferParams, InferOutputRow } from "../infer/index.js";
|
|
19
23
|
import type { TokensNamespace } from "../client/tokens.js";
|
|
@@ -82,16 +86,20 @@ type IngestMethods<T extends DatasourcesDefinition> = {
|
|
|
82
86
|
};
|
|
83
87
|
|
|
84
88
|
/**
|
|
85
|
-
* Type for a datasource accessor with
|
|
89
|
+
* Type for a datasource accessor with import/mutation methods
|
|
86
90
|
*/
|
|
87
91
|
type DatasourceAccessor = {
|
|
88
92
|
/** Append data from a URL or file */
|
|
89
93
|
append(options: AppendOptions): Promise<AppendResult>;
|
|
94
|
+
/** Delete rows using a SQL condition */
|
|
95
|
+
delete(options: DeleteOptions): Promise<DeleteResult>;
|
|
96
|
+
/** Truncate all rows */
|
|
97
|
+
truncate(options?: TruncateOptions): Promise<TruncateResult>;
|
|
90
98
|
};
|
|
91
99
|
|
|
92
100
|
/**
|
|
93
101
|
* Type for datasource accessors object
|
|
94
|
-
* Maps each datasource to an accessor with
|
|
102
|
+
* Maps each datasource to an accessor with import/mutation methods
|
|
95
103
|
*/
|
|
96
104
|
type DatasourceAccessors<T extends DatasourcesDefinition> = {
|
|
97
105
|
[K in keyof T]: DatasourceAccessor;
|
|
@@ -105,7 +113,7 @@ interface ProjectClientBase<TDatasources extends DatasourcesDefinition> {
|
|
|
105
113
|
ingest: IngestMethods<TDatasources>;
|
|
106
114
|
/** Token operations (JWT creation, etc.) */
|
|
107
115
|
readonly tokens: TokensNamespace;
|
|
108
|
-
/** Datasource operations (append
|
|
116
|
+
/** Datasource operations (append/delete/truncate) */
|
|
109
117
|
readonly datasources: DatasourcesNamespace;
|
|
110
118
|
/** Execute raw SQL queries */
|
|
111
119
|
sql<T = unknown>(sql: string, options?: QueryOptions): Promise<QueryResult<T>>;
|
|
@@ -374,6 +382,14 @@ function buildProjectClient<
|
|
|
374
382
|
const client = await getClient();
|
|
375
383
|
return client.datasources.append(tinybirdName, options);
|
|
376
384
|
},
|
|
385
|
+
delete: async (options: DeleteOptions) => {
|
|
386
|
+
const client = await getClient();
|
|
387
|
+
return client.datasources.delete(tinybirdName, options);
|
|
388
|
+
},
|
|
389
|
+
truncate: async (options: TruncateOptions = {}) => {
|
|
390
|
+
const client = await getClient();
|
|
391
|
+
return client.datasources.truncate(tinybirdName, options);
|
|
392
|
+
},
|
|
377
393
|
};
|
|
378
394
|
}
|
|
379
395
|
|