Skip to content

Commit 8c10ef8

Browse files
committed
Nice type support
1 parent 2528714 commit 8c10ef8

File tree

4 files changed

+208
-73
lines changed

4 files changed

+208
-73
lines changed

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

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
XCircleIcon,
1313
} from "@heroicons/react/20/solid";
1414
import type { TaskRunStatus } from "@trigger.dev/database";
15+
import { runFriendlyStatus, type RunFriendlyStatus } from "@trigger.dev/core/v3";
1516
import assertNever from "assert-never";
1617
import { HourglassIcon } from "lucide-react";
1718
import { TimedOutIcon } from "~/assets/icons/TimedOutIcon";
@@ -248,26 +249,9 @@ export function runStatusFromFriendlyTitle(friendly: RunFriendlyStatus): TaskRun
248249
return result[0] as TaskRunStatus;
249250
}
250251

251-
export const runFriendlyStatus = [
252-
"Delayed",
253-
"Queued",
254-
"Pending version",
255-
"Dequeued",
256-
"Executing",
257-
"Waiting",
258-
"Reattempting",
259-
"Paused",
260-
"Canceled",
261-
"Interrupted",
262-
"Completed",
263-
"Failed",
264-
"System failure",
265-
"Crashed",
266-
"Expired",
267-
"Timed out",
268-
] as const;
269-
270-
export type RunFriendlyStatus = (typeof runFriendlyStatus)[number];
252+
// runFriendlyStatus and RunFriendlyStatus are imported from @trigger.dev/core/v3
253+
// and re-exported here for backward compatibility.
254+
export { runFriendlyStatus, type RunFriendlyStatus } from "@trigger.dev/core/v3";
271255

272256
/**
273257
* Check if a value is a valid TaskRunStatus

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

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import { TypeOf, z } from "zod";
2+
import type { MachinePresetName } from "./common.js";
3+
import type { RuntimeEnvironmentType } from "./common.js";
4+
import type { IdempotencyKeyScope } from "../idempotency-key-catalog/catalog.js";
25

36
/**
47
* Request body schema for executing a query
@@ -39,3 +42,168 @@ export const QueryExecuteResponseBody = z.discriminatedUnion("format", [
3942
QueryExecuteCSVResponseBody,
4043
]);
4144
export type QueryExecuteResponseBody = z.infer<typeof QueryExecuteResponseBody>;
45+
46+
// ---------------------------------------------------------------------------
47+
// Query table row types
48+
// ---------------------------------------------------------------------------
49+
50+
/**
51+
* User-facing friendly run status values returned by the query system.
52+
*/
53+
export const runFriendlyStatus = [
54+
"Delayed",
55+
"Queued",
56+
"Pending version",
57+
"Dequeued",
58+
"Executing",
59+
"Waiting",
60+
"Reattempting",
61+
"Paused",
62+
"Canceled",
63+
"Interrupted",
64+
"Completed",
65+
"Failed",
66+
"System failure",
67+
"Crashed",
68+
"Expired",
69+
"Timed out",
70+
] as const;
71+
72+
export type RunFriendlyStatus = (typeof runFriendlyStatus)[number];
73+
74+
/**
75+
* Full row type for the `runs` query table.
76+
*
77+
* Each property corresponds to a column available in TSQL queries against the
78+
* `runs` table. Types are mapped from the underlying ClickHouse column types:
79+
*
80+
* - `String` → `string`
81+
* - `UInt8` / `UInt32` / `Int64` / `Float64` → `number`
82+
* - `DateTime64` → `string`
83+
* - `Nullable(X)` → `X | null`
84+
* - `Array(String)` → `string[]`
85+
* - `JSON` → `Record<string, unknown>`
86+
* - `LowCardinality(String)` with constrained values → narrow union type
87+
*/
88+
export interface RunsTableRow {
89+
/** Unique run ID (e.g. `run_cm1a2b3c4d5e6f7g8h9i`) */
90+
run_id: string;
91+
/** Environment slug */
92+
environment: string;
93+
/** Project reference (e.g. `proj_howcnaxbfxdmwmxazktx`) */
94+
project: string;
95+
/** Environment type */
96+
environment_type: RuntimeEnvironmentType;
97+
/** Number of attempts (starts at 1) */
98+
attempt_count: number;
99+
/** Run status (friendly name) */
100+
status: RunFriendlyStatus;
101+
/** Whether the run is finished (0 or 1) */
102+
is_finished: number;
103+
/** Task identifier/slug */
104+
task_identifier: string;
105+
/** Queue name */
106+
queue: string;
107+
/** Batch ID (if part of a batch), or `null` */
108+
batch_id: string | null;
109+
/** Root run ID (for child runs), or `null` */
110+
root_run_id: string | null;
111+
/** Parent run ID (for child runs), or `null` */
112+
parent_run_id: string | null;
113+
/** Nesting depth (0 for root runs) */
114+
depth: number;
115+
/** Whether this is a root run (0 or 1) */
116+
is_root_run: number;
117+
/** Whether this is a child run (0 or 1) */
118+
is_child_run: number;
119+
/** Idempotency key */
120+
idempotency_key: string;
121+
/** Idempotency key scope (empty string means no idempotency key is set) */
122+
idempotency_key_scope: IdempotencyKeyScope | "";
123+
/** Region, or `null` */
124+
region: string | null;
125+
/** When the run was triggered (ISO 8601) */
126+
triggered_at: string;
127+
/** When the run was queued, or `null` */
128+
queued_at: string | null;
129+
/** When the run was dequeued, or `null` */
130+
dequeued_at: string | null;
131+
/** When execution began, or `null` */
132+
executed_at: string | null;
133+
/** When the run completed, or `null` */
134+
completed_at: string | null;
135+
/** Delayed execution until this time, or `null` */
136+
delay_until: string | null;
137+
/** Whether the run had a delay (0 or 1) */
138+
has_delay: number;
139+
/** When the run expired, or `null` */
140+
expired_at: string | null;
141+
/** TTL string for expiration (e.g. `"10m"`) */
142+
ttl: string;
143+
/** Time from execution start to completion in ms, or `null` */
144+
execution_duration: number | null;
145+
/** Time from trigger to completion in ms, or `null` */
146+
total_duration: number | null;
147+
/** Time from queued to dequeued in ms, or `null` */
148+
queued_duration: number | null;
149+
/** Compute usage duration in ms */
150+
usage_duration: number;
151+
/** Compute cost in dollars */
152+
compute_cost: number;
153+
/** Invocation cost in dollars */
154+
invocation_cost: number;
155+
/** Total cost in dollars (compute + invocation) */
156+
total_cost: number;
157+
/** The data returned from the task */
158+
output: Record<string, unknown>;
159+
/** Error data if the run failed */
160+
error: Record<string, unknown>;
161+
/** Tags added to the run */
162+
tags: string[];
163+
/** Code version in reverse date format (e.g. `"20240115.1"`) */
164+
task_version: string;
165+
/** SDK package version */
166+
sdk_version: string;
167+
/** CLI package version */
168+
cli_version: string;
169+
/** Machine preset the run executed on */
170+
machine: MachinePresetName;
171+
/** Whether this is a test run (0 or 1) */
172+
is_test: number;
173+
/** Concurrency key passed when triggering */
174+
concurrency_key: string;
175+
/** Max allowed compute duration in seconds, or `null` */
176+
max_duration: number | null;
177+
/** Bulk action group IDs that operated on this run */
178+
bulk_action_group_ids: string[];
179+
}
180+
181+
/** @internal Map of query table names to their full row types */
182+
type QueryTableMap = {
183+
runs: RunsTableRow;
184+
};
185+
186+
/**
187+
* Type helper for Query results.
188+
*
189+
* @example
190+
* ```typescript
191+
* // All columns from the runs table
192+
* type AllRuns = QueryTable<"runs">;
193+
*
194+
* // Only specific columns
195+
* type MyResult = QueryTable<"runs", "status" | "run_id">;
196+
*
197+
* // Access a single field type
198+
* type Status = QueryTable<"runs">["status"]; // RunFriendlyStatus
199+
*
200+
* // Use with query.execute
201+
* const result = await query.execute<QueryTable<"runs", "status" | "run_id">>(
202+
* "SELECT status, run_id FROM runs"
203+
* );
204+
* ```
205+
*/
206+
export type QueryTable<
207+
TTable extends keyof QueryTableMap,
208+
TColumns extends keyof QueryTableMap[TTable] = keyof QueryTableMap[TTable],
209+
> = Pick<QueryTableMap[TTable], TColumns>;

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import type {
22
ApiRequestOptions,
3+
Prettify,
34
QueryExecuteResponseBody,
45
QueryExecuteCSVResponseBody,
56
} from "@trigger.dev/core/v3";
67
import { apiClientManager, mergeRequestOptions } from "@trigger.dev/core/v3";
78
import { tracer } from "./tracer.js";
89

10+
export type { QueryTable, RunsTableRow, RunFriendlyStatus } from "@trigger.dev/core/v3";
11+
912
export type QueryScope = "environment" | "project" | "organization";
1013
export type QueryFormat = "json" | "csv";
1114

@@ -21,7 +24,7 @@ export type QueryOptions = {
2124
*
2225
* @default "environment"
2326
*/
24-
scope?: "environment" | "project" | "organization";
27+
scope?: QueryScope;
2528

2629
/**
2730
* Time period to query (e.g., "7d", "30d", "1h")
@@ -67,7 +70,7 @@ function execute<TRow extends Record<string, any> = Record<string, any>>(
6770
tsql: string,
6871
options?: Omit<QueryOptions, "format"> | (QueryOptions & { format?: "json" }),
6972
requestOptions?: ApiRequestOptions
70-
): Promise<{ format: "json"; results: Array<TRow> }>;
73+
): Promise<{ format: "json"; results: Array<Prettify<TRow>> }>;
7174

7275
/**
7376
* Execute a TSQL query against your Trigger.dev data

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

Lines changed: 31 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
import { logger, query, task } from "@trigger.dev/sdk";
2-
3-
// Type definition for a run row
4-
type RunRow = {
5-
run_id: string;
6-
status: string;
7-
triggered_at: string;
8-
total_duration: number;
9-
};
2+
import type { QueryTable } from "@trigger.dev/sdk";
103

114
// Simple query example - just the query string, all defaults
125
export const simpleQueryTask = task({
@@ -23,23 +16,23 @@ export const simpleQueryTask = task({
2316
firstRow: result.results[0],
2417
});
2518

26-
// Type-safe query with explicit row type
27-
const typedResult = await query.execute<RunRow>(
28-
"SELECT run_id, status, triggered_at, total_duration FROM runs LIMIT 10"
29-
);
19+
// Type-safe query using QueryTable with specific columns
20+
const typedResult = await query.execute<
21+
QueryTable<"runs", "run_id" | "status" | "triggered_at" | "total_duration">
22+
>("SELECT run_id, status, triggered_at, total_duration FROM runs LIMIT 10");
3023

3124
logger.info("Query results (typed)", {
3225
format: typedResult.format,
3326
rowCount: typedResult.results.length,
3427
firstRow: typedResult.results[0],
3528
});
3629

37-
// Now we have full type safety on the rows!
30+
// Full type safety on the rows - status is narrowly typed!
3831
typedResult.results.forEach((row, index) => {
3932
logger.info(`Run ${index + 1}`, {
40-
run_id: row.run_id, // TypeScript knows this is a string
41-
status: row.status, // TypeScript knows this is a string
42-
total_duration: row.total_duration, // TypeScript knows this is a number
33+
run_id: row.run_id, // string
34+
status: row.status, // RunFriendlyStatus ("Completed" | "Failed" | ...)
35+
total_duration: row.total_duration, // number | null
4336
});
4437
});
4538

@@ -50,13 +43,14 @@ export const simpleQueryTask = task({
5043
},
5144
});
5245

53-
// JSON query with all options and inline type
46+
// JSON query with all options - aggregation queries use inline types
5447
export const fullJsonQueryTask = task({
5548
id: "full-json-query",
5649
run: async () => {
5750
logger.info("Running full JSON query example with all options");
5851

59-
// All options specified with inline type for aggregation
52+
// For aggregation queries, use inline types since the result shape
53+
// doesn't match a table row. For non-aggregated queries, use QueryTable.
6054
const result = await query.execute<{
6155
status: string;
6256
count: number;
@@ -67,7 +61,7 @@ export const fullJsonQueryTask = task({
6761
COUNT(*) as count,
6862
AVG(total_duration) as avg_duration
6963
FROM runs
70-
WHERE status IN ('COMPLETED', 'FAILED')
64+
WHERE status IN ('Completed', 'Failed')
7165
GROUP BY status`,
7266
{
7367
scope: "environment", // Query current environment only
@@ -126,60 +120,46 @@ export const csvQueryTask = task({
126120
},
127121
});
128122

129-
// Organization-wide query with date range and type safety
123+
// Organization-wide query with QueryTable for full row access
130124
export const orgQueryTask = task({
131125
id: "org-query",
132126
run: async () => {
133127
logger.info("Running organization-wide query");
134128

135-
// Define the shape of our aggregated results
136-
type ProjectStats = {
137-
project: string;
138-
environment: string;
139-
total_runs: number;
140-
successful_runs: number;
141-
failed_runs: number;
142-
};
143-
144-
const result = await query.execute<ProjectStats>(
145-
`SELECT
146-
project,
147-
environment,
148-
COUNT(*) as total_runs,
149-
SUM(CASE WHEN status = 'COMPLETED' THEN 1 ELSE 0 END) as successful_runs,
150-
SUM(CASE WHEN status = 'FAILED' THEN 1 ELSE 0 END) as failed_runs
129+
// Use QueryTable to get typed rows for specific columns
130+
const result = await query.execute<
131+
QueryTable<"runs", "run_id" | "project" | "environment" | "status" | "task_identifier" | "machine">
132+
>(
133+
`SELECT run_id, project, environment, status, task_identifier, machine
151134
FROM runs
152-
GROUP BY project, environment
153-
ORDER BY total_runs DESC`,
135+
ORDER BY triggered_at DESC
136+
LIMIT 50`,
154137
{
155138
scope: "organization", // Query across all projects
156139
from: "2025-02-01T00:00:00Z", // Custom date range
157140
to: "2025-02-11T23:59:59Z",
158-
// format defaults to "json"
159141
}
160142
);
161143

162144
logger.info("Organization query completed", {
163145
format: result.format,
164-
projectCount: result.results.length,
146+
runCount: result.results.length,
165147
});
166148

167-
// Full type safety on aggregated results
149+
// Fully typed - status is RunFriendlyStatus, machine is MachinePresetName
168150
result.results.forEach((row) => {
169-
const successRate = (row.successful_runs / row.total_runs) * 100;
170-
171-
logger.info("Project stats", {
172-
project: row.project,
173-
environment: row.environment,
174-
totalRuns: row.total_runs,
175-
successfulRuns: row.successful_runs,
176-
failedRuns: row.failed_runs,
177-
successRate: `${successRate.toFixed(2)}%`,
151+
logger.info("Run info", {
152+
runId: row.run_id, // string
153+
project: row.project, // string
154+
environment: row.environment, // string
155+
status: row.status, // "Completed" | "Failed" | "Executing" | ...
156+
task: row.task_identifier, // string
157+
machine: row.machine, // "micro" | "small-1x" | "small-2x" | ...
178158
});
179159
});
180160

181161
return {
182-
projects: result.results,
162+
runs: result.results,
183163
};
184164
},
185165
});

0 commit comments

Comments
 (0)