Skip to content

Commit 9e7b152

Browse files
committed
Rework the API/SDK so we can get nice spans
1 parent fccfb2a commit 9e7b152

File tree

6 files changed

+48
-72
lines changed

6 files changed

+48
-72
lines changed

apps/webapp/app/components/runs/v3/RunIcon.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
InformationCircleIcon,
55
RectangleStackIcon,
66
Squares2X2Icon,
7+
TableCellsIcon,
78
TagIcon,
89
} from "@heroicons/react/20/solid";
910
import { AttemptIcon } from "~/assets/icons/AttemptIcon";
@@ -81,6 +82,8 @@ export function RunIcon({ name, className, spanName }: TaskIconProps) {
8182
return <WaitpointTokenIcon className={cn(className, "text-sky-500")} />;
8283
case "function":
8384
return <FunctionIcon className={cn(className, "text-text-dimmed")} />;
85+
case "query":
86+
return <TableCellsIcon className={cn(className, "text-query")} />;
8487
//log levels
8588
case "debug":
8689
case "log":
@@ -110,7 +113,7 @@ export function RunIcon({ name, className, spanName }: TaskIconProps) {
110113
case "task-hook-catchError":
111114
return <FunctionIcon className={cn(className, "text-error")} />;
112115
case "streams":
113-
return <StreamsIcon className={cn(className, "text-text-dimmed")} />;
116+
return <StreamsIcon className={cn(className, "text-text-dimmed")} />;
114117
}
115118

116119
return <InformationCircleIcon className={cn(className, "text-text-dimmed")} />;

apps/webapp/app/routes/api.v1.query.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,15 @@ const { action, loader } = createActionApiRoute(
5858
if (format === "csv") {
5959
const csv = rowsToCSV(result.rows, result.columns);
6060

61-
return new Response(csv, {
62-
status: 200,
63-
headers: {
64-
"Content-Type": "text/csv; charset=utf-8",
65-
"Content-Disposition": "attachment; filename=query-results.csv",
66-
},
61+
return json({
62+
format: "csv",
63+
results: csv,
6764
});
6865
}
6966

7067
return json({
71-
rows: result.rows,
68+
format: "json",
69+
results: result.rows,
7270
});
7371
}
7472
);

packages/core/src/v3/apiClient/index.ts

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,7 +1419,7 @@ export class ApiClient {
14191419
format?: "json" | "csv";
14201420
},
14211421
requestOptions?: ZodFetchOptions
1422-
): Promise<QueryExecuteResponseBody | QueryExecuteCSVResponseBody> {
1422+
): Promise<QueryExecuteResponseBody> {
14231423
const body = {
14241424
query,
14251425
scope: options?.scope ?? "environment",
@@ -1430,37 +1430,9 @@ export class ApiClient {
14301430
};
14311431

14321432
const format = options?.format ?? "json";
1433-
1434-
if (format === "csv") {
1435-
// For CSV, we get a text response
1436-
const response = await fetch(`${this.baseUrl}/api/v1/query`, {
1437-
method: "POST",
1438-
headers: this.#getHeaders(false),
1439-
body: JSON.stringify(body),
1440-
});
1441-
1442-
if (!response.ok) {
1443-
const errText = await response.text().catch((e) => (e as Error).message);
1444-
let errJSON: Object | undefined;
1445-
try {
1446-
errJSON = JSON.parse(errText) as Object;
1447-
} catch {
1448-
// ignore
1449-
}
1450-
const errMessage = errJSON ? undefined : errText;
1451-
const responseHeaders = Object.fromEntries(response.headers.entries());
1452-
1453-
throw ApiError.generate(response.status, errJSON, errMessage, responseHeaders);
1454-
}
1455-
1456-
return await response.text();
1457-
}
1458-
14591433
// For JSON, use zodfetch
14601434
return zodfetch(
1461-
z.object({
1462-
rows: z.array(z.record(z.any())),
1463-
}),
1435+
QueryExecuteResponseBody,
14641436
`${this.baseUrl}/api/v1/query`,
14651437
{
14661438
method: "POST",

packages/core/src/v3/schemas/query.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { z } from "zod";
1+
import { TypeOf, z } from "zod";
22

33
/**
44
* Request body schema for executing a query
@@ -17,13 +17,25 @@ export type QueryExecuteRequestBody = z.infer<typeof QueryExecuteRequestBody>;
1717
/**
1818
* Response body schema for JSON format queries
1919
*/
20-
export const QueryExecuteResponseBody = z.object({
21-
rows: z.array(z.record(z.any())),
20+
export const QueryExecuteJSONResponseBody = z.object({
21+
format: z.literal("json"),
22+
results: z.array(z.record(z.any())),
2223
});
2324

24-
export type QueryExecuteResponseBody = z.infer<typeof QueryExecuteResponseBody>;
25+
export type QueryExecuteJSONResponseBody = z.infer<typeof QueryExecuteResponseBody>;
2526

2627
/**
27-
* Response body type for CSV format queries (returns a string)
28+
* Response body type for CSV format queries
2829
*/
29-
export type QueryExecuteCSVResponseBody = string;
30+
export const QueryExecuteCSVResponseBody = z.object({
31+
format: z.literal("json"),
32+
results: z.string(),
33+
});
34+
35+
export type QueryExecuteCSVResponseBody = z.infer<typeof QueryExecuteCSVResponseBody>;
36+
37+
export const QueryExecuteResponseBody = z.discriminatedUnion("format", [
38+
QueryExecuteJSONResponseBody,
39+
QueryExecuteCSVResponseBody,
40+
]);
41+
export type QueryExecuteResponseBody = z.infer<typeof QueryExecuteResponseBody>;

packages/trigger-sdk/src/v3/query.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,14 @@ function execute<TRow extends Record<string, any> = Record<string, any>>(
8181
* @example
8282
* ```typescript
8383
* // Basic query with defaults (environment scope, json format)
84-
* const result = await query.execute("SELECT * FROM runs LIMIT 10");
84+
* const result = await query.execute("SELECT run_id, status FROM runs LIMIT 10");
8585
* console.log(result.format); // "json"
8686
* console.log(result.results); // Array<Record<string, any>>
8787
*
8888
* // Type-safe query with row type
8989
* type RunRow = { id: string; status: string; duration: number };
9090
* const typedResult = await query.execute<RunRow>(
91-
* "SELECT id, status, duration FROM runs LIMIT 10"
91+
* "SELECT run_id, status, triggered_at FROM runs LIMIT 10"
9292
* );
9393
* typedResult.results.forEach(row => {
9494
* console.log(row.id, row.status); // Fully typed!
@@ -105,14 +105,14 @@ function execute<TRow extends Record<string, any> = Record<string, any>>(
105105
* // Query with custom period
106106
* const lastMonth = await query.execute(
107107
* "SELECT COUNT(*) as count FROM runs",
108-
* { period: "30d" }
108+
* { period: "3d" }
109109
* );
110110
* console.log(lastMonth.results[0].count); // Type-safe access
111111
*
112112
* // Export as CSV - automatically narrowed!
113113
* const csvResult = await query.execute(
114114
* "SELECT * FROM runs",
115-
* { format: "csv", period: "7d" }
115+
* { format: "csv", period: "1d" }
116116
* );
117117
* console.log(csvResult.format); // "csv"
118118
* const lines = csvResult.results.split('\n'); // ✓ results is string
@@ -133,26 +133,27 @@ function execute<TRow extends Record<string, any> = Record<string, any>>(
133133
): Promise<{ format: "json"; results: Array<TRow> } | { format: "csv"; results: string }> {
134134
const apiClient = apiClientManager.clientOrThrow();
135135

136+
const format = options?.format ?? "json";
137+
136138
const $requestOptions = mergeRequestOptions(
137139
{
138140
tracer,
139141
name: "query.execute()",
140-
icon: "sparkles",
142+
icon: "query",
141143
attributes: {
142144
scope: options?.scope ?? "environment",
143145
format: options?.format ?? "json",
146+
query: tsql,
147+
period: options?.period,
148+
from: options?.from,
149+
to: options?.to,
144150
},
145151
},
146152
requestOptions
147153
);
148154

149-
const format = options?.format ?? "json";
150-
151155
return apiClient.executeQuery(tsql, options, $requestOptions).then((response) => {
152-
if (typeof response === "string") {
153-
return { format: "csv" as const, results: response };
154-
}
155-
return { format: "json" as const, results: response.rows };
156+
return response;
156157
}) as Promise<{ format: "json"; results: Array<TRow> } | { format: "csv"; results: string }>;
157158
}
158159

references/hello-world/src/trigger/query.ts

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { logger, query, task } from "@trigger.dev/sdk";
44
type RunRow = {
55
id: string;
66
status: string;
7-
created_at: string;
8-
duration: number;
7+
triggered_at: string;
8+
total_duration: number;
99
};
1010

1111
// Simple query example - just the query string, all defaults
@@ -25,7 +25,7 @@ export const simpleQueryTask = task({
2525

2626
// Type-safe query with explicit row type
2727
const typedResult = await query.execute<RunRow>(
28-
"SELECT id, status, created_at, duration FROM runs LIMIT 10"
28+
"SELECT run_id, status, triggered_at, total_duration FROM runs LIMIT 10"
2929
);
3030

3131
logger.info("Query results (typed)", {
@@ -39,7 +39,7 @@ export const simpleQueryTask = task({
3939
logger.info(`Run ${index + 1}`, {
4040
id: row.id, // TypeScript knows this is a string
4141
status: row.status, // TypeScript knows this is a string
42-
duration: row.duration, // TypeScript knows this is a number
42+
total_duration: row.total_duration, // TypeScript knows this is a number
4343
});
4444
});
4545

@@ -65,7 +65,7 @@ export const fullJsonQueryTask = task({
6565
`SELECT
6666
status,
6767
COUNT(*) as count,
68-
AVG(duration) as avg_duration
68+
AVG(total_duration) as avg_duration
6969
FROM runs
7070
WHERE status IN ('COMPLETED', 'FAILED')
7171
GROUP BY status`,
@@ -104,7 +104,7 @@ export const csvQueryTask = task({
104104

105105
// Query with CSV format - automatically typed as discriminated union!
106106
const result = await query.execute(
107-
"SELECT id, status, created_at, duration FROM runs LIMIT 100",
107+
"SELECT run_id, status, triggered_at, total_duration FROM runs LIMIT 10",
108108
{
109109
scope: "project", // Query all environments in the project
110110
period: "7d", // Last 7 days
@@ -116,22 +116,12 @@ export const csvQueryTask = task({
116116
logger.info("CSV query completed", {
117117
format: result.format,
118118
dataLength: result.results.length,
119-
preview: result.results.substring(0, 200), // Show first 200 chars
120-
});
121-
122-
// Count the number of rows (lines - 1 for header)
123-
const lines = result.results.split("\n");
124-
const rowCount = lines.length - 1;
125-
126-
logger.info("CSV stats", {
127-
totalRows: rowCount,
128-
headerLine: lines[0],
119+
results: result.results,
129120
});
130121

131122
return {
132123
format: result.format,
133124
csv: result.results,
134-
rowCount,
135125
};
136126
},
137127
});

0 commit comments

Comments
 (0)