Skip to content

Commit 4a68166

Browse files
committed
Improve types for hooks
1 parent cd82ef3 commit 4a68166

File tree

11 files changed

+388
-326
lines changed

11 files changed

+388
-326
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {
4444
runShapeStream,
4545
RunStreamCallback,
4646
RunSubscription,
47+
TaskRunShape,
4748
} from "./runStream.js";
4849
import {
4950
CreateEnvironmentVariableParams,
@@ -75,7 +76,7 @@ const DEFAULT_ZOD_FETCH_OPTIONS: ZodFetchOptions = {
7576

7677
export { isRequestOptions };
7778
export type { ApiRequestOptions };
78-
export type { RunShape, AnyRunShape, RunStreamCallback, RunSubscription };
79+
export type { RunShape, AnyRunShape, TaskRunShape, RunStreamCallback, RunSubscription };
7980

8081
/**
8182
* Trigger.dev v3 API client

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { DeserializedJson } from "../../schemas/json.js";
22
import { RunStatus, SubscribeRunRawShape } from "../schemas/api.js";
33
import { SerializedError } from "../schemas/common.js";
4+
import { AnyTask, TaskOutput, TaskPayload } from "../types/tasks.js";
45
import {
56
conditionallyImportAndParsePacket,
67
IOPacket,
@@ -35,6 +36,8 @@ export type RunShape<TPayload = any, TOutput = any> = {
3536

3637
export type AnyRunShape = RunShape<any, any>;
3738

39+
export type TaskRunShape<TTask extends AnyTask> = RunShape<TaskPayload<TTask>, TaskOutput<TTask>>;
40+
3841
export type RunStreamCallback<TPayload = any, TOutput = any> = (
3942
run: RunShape<TPayload, TOutput>
4043
) => void | Promise<void>;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
declare const __brand: unique symbol;
2+
type Brand<B> = { [__brand]: B };
3+
type Branded<T, B> = T & Brand<B>;
4+
5+
export type IdempotencyKey = Branded<string, "IdempotencyKey">;

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import { RetryOptions, TaskMetadata, TaskManifest, TaskRunContext } from "../schemas/index.js";
1+
import { RetryOptions, TaskMetadata, TaskRunContext } from "../schemas/index.js";
22
import { Prettify } from "./utils.js";
33

44
export * from "./utils.js";
5+
export * from "./tasks.js";
6+
export * from "./idempotencyKeys.js";
57

68
export type InitOutput = Record<string, any> | void | undefined;
79

packages/core/src/v3/types/tasks.ts

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
import { SerializableJson } from "../../schemas/json.js";
2+
import { RunTags } from "../schemas/api.js";
3+
import { QueueOptions } from "../schemas/schemas.js";
4+
import { IdempotencyKey } from "./idempotencyKeys.js";
5+
6+
type RequireOne<T, K extends keyof T> = {
7+
[X in Exclude<keyof T, K>]?: T[X];
8+
} & {
9+
[P in K]-?: T[P];
10+
};
11+
12+
export type Queue = RequireOne<QueueOptions, "name">;
13+
14+
type TaskRunConcurrencyOptions = Queue;
15+
16+
export class SubtaskUnwrapError extends Error {
17+
public readonly taskId: string;
18+
public readonly runId: string;
19+
public readonly cause?: unknown;
20+
21+
constructor(taskId: string, runId: string, subtaskError: unknown) {
22+
if (subtaskError instanceof Error) {
23+
super(`Error in ${taskId}: ${subtaskError.message}`, { cause: subtaskError });
24+
this.name = "SubtaskUnwrapError";
25+
} else {
26+
super(`Error in ${taskId}`, { cause: subtaskError });
27+
this.name = "SubtaskUnwrapError";
28+
}
29+
30+
this.taskId = taskId;
31+
this.runId = runId;
32+
}
33+
}
34+
35+
export class TaskRunPromise<T> extends Promise<TaskRunResult<T>> {
36+
constructor(
37+
executor: (
38+
resolve: (value: TaskRunResult<T> | PromiseLike<TaskRunResult<T>>) => void,
39+
reject: (reason?: any) => void
40+
) => void,
41+
private readonly taskId: string
42+
) {
43+
super(executor);
44+
}
45+
46+
unwrap(): Promise<T> {
47+
return this.then((result) => {
48+
if (result.ok) {
49+
return result.output;
50+
} else {
51+
throw new SubtaskUnwrapError(this.taskId, result.id, result.error);
52+
}
53+
});
54+
}
55+
}
56+
57+
declare const __output: unique symbol;
58+
declare const __payload: unique symbol;
59+
type BrandRun<P, O> = { [__output]: O; [__payload]: P };
60+
export type BrandedRun<T, P, O> = T & BrandRun<O, P>;
61+
62+
export type RunHandle<TPayload, TOutput> = BrandedRun<
63+
{
64+
id: string;
65+
},
66+
TPayload,
67+
TOutput
68+
>;
69+
70+
export type AnyRunHandle = RunHandle<any, any>;
71+
72+
/**
73+
* A BatchRunHandle can be used to retrieve the runs of a batch trigger in a typesafe manner.
74+
*/
75+
export type BatchRunHandle<TPayload, TOutput> = BrandedRun<
76+
{
77+
batchId: string;
78+
runs: Array<RunHandle<TPayload, TOutput>>;
79+
},
80+
TOutput,
81+
TPayload
82+
>;
83+
84+
export type RunHandleOutput<TRunHandle> = TRunHandle extends RunHandle<any, infer TOutput>
85+
? TOutput
86+
: never;
87+
88+
export type RunHandlePayload<TRunHandle> = TRunHandle extends RunHandle<infer TPayload, any>
89+
? TPayload
90+
: never;
91+
92+
export type TaskRunResult<TOutput = any> =
93+
| {
94+
ok: true;
95+
id: string;
96+
output: TOutput;
97+
}
98+
| {
99+
ok: false;
100+
id: string;
101+
error: unknown;
102+
};
103+
104+
export type BatchResult<TOutput = any> = {
105+
id: string;
106+
runs: TaskRunResult<TOutput>[];
107+
};
108+
109+
export type BatchItem<TInput> = TInput extends void
110+
? { payload?: TInput; options?: TaskRunOptions }
111+
: { payload: TInput; options?: TaskRunOptions };
112+
113+
export interface Task<TIdentifier extends string, TInput = void, TOutput = any> {
114+
/**
115+
* The id of the task.
116+
*/
117+
id: TIdentifier;
118+
/**
119+
* Trigger a task with the given payload, and continue without waiting for the result. If you want to wait for the result, use `triggerAndWait`. Returns the id of the triggered task run.
120+
* @param payload
121+
* @param options
122+
* @returns RunHandle
123+
* - `id` - The id of the triggered task run.
124+
*/
125+
trigger: (payload: TInput, options?: TaskRunOptions) => Promise<RunHandle<TInput, TOutput>>;
126+
127+
/**
128+
* Batch trigger multiple task runs with the given payloads, and continue without waiting for the results. If you want to wait for the results, use `batchTriggerAndWait`. Returns the id of the triggered batch.
129+
* @param items
130+
* @returns InvokeBatchHandle
131+
* - `batchId` - The id of the triggered batch.
132+
* - `runs` - The ids of the triggered task runs.
133+
*/
134+
batchTrigger: (items: Array<BatchItem<TInput>>) => Promise<BatchRunHandle<TInput, TOutput>>;
135+
136+
/**
137+
* Trigger a task with the given payload, and wait for the result. Returns the result of the task run
138+
* @param payload
139+
* @param options - Options for the task run
140+
* @returns TaskRunResult
141+
* @example
142+
* ```
143+
* const result = await task.triggerAndWait({ foo: "bar" });
144+
*
145+
* if (result.ok) {
146+
* console.log(result.output);
147+
* } else {
148+
* console.error(result.error);
149+
* }
150+
* ```
151+
*/
152+
triggerAndWait: (payload: TInput, options?: TaskRunOptions) => TaskRunPromise<TOutput>;
153+
154+
/**
155+
* Batch trigger multiple task runs with the given payloads, and wait for the results. Returns the results of the task runs.
156+
* @param items
157+
* @returns BatchResult
158+
* @example
159+
* ```
160+
* const result = await task.batchTriggerAndWait([
161+
* { payload: { foo: "bar" } },
162+
* { payload: { foo: "baz" } },
163+
* ]);
164+
*
165+
* for (const run of result.runs) {
166+
* if (run.ok) {
167+
* console.log(run.output);
168+
* } else {
169+
* console.error(run.error);
170+
* }
171+
* }
172+
* ```
173+
*/
174+
batchTriggerAndWait: (items: Array<BatchItem<TInput>>) => Promise<BatchResult<TOutput>>;
175+
}
176+
177+
export type AnyTask = Task<string, any, any>;
178+
179+
export type TaskPayload<TTask extends AnyTask> = TTask extends Task<string, infer TInput, any>
180+
? TInput
181+
: never;
182+
183+
export type TaskOutput<TTask extends AnyTask> = TTask extends Task<string, any, infer TOutput>
184+
? TOutput
185+
: never;
186+
187+
export type TaskOutputHandle<TTask extends AnyTask> = TTask extends Task<
188+
string,
189+
infer TInput,
190+
infer TOutput
191+
>
192+
? RunHandle<TOutput, TInput>
193+
: never;
194+
195+
export type TaskBatchOutputHandle<TTask extends AnyTask> = TTask extends Task<
196+
string,
197+
infer TInput,
198+
infer TOutput
199+
>
200+
? BatchRunHandle<TOutput, TInput>
201+
: never;
202+
203+
export type TaskIdentifier<TTask extends AnyTask> = TTask extends Task<infer TIdentifier, any, any>
204+
? TIdentifier
205+
: never;
206+
207+
export type TaskRunOptions = {
208+
/**
209+
* A unique key that can be used to ensure that a task is only triggered once per key.
210+
*
211+
* You can use `idempotencyKeys.create` to create an idempotency key first, and then pass it to the task options.
212+
*
213+
* @example
214+
*
215+
* ```typescript
216+
* import { idempotencyKeys, task } from "@trigger.dev/sdk/v3";
217+
*
218+
* export const myTask = task({
219+
* id: "my-task",
220+
* run: async (payload: any) => {
221+
* // scoped to the task run by default
222+
* const idempotencyKey = await idempotencyKeys.create("my-task-key");
223+
*
224+
* // Use the idempotency key when triggering child tasks
225+
* await childTask.triggerAndWait(payload, { idempotencyKey });
226+
*
227+
* // scoped globally, does not include the task run ID
228+
* const globalIdempotencyKey = await idempotencyKeys.create("my-task-key", { scope: "global" });
229+
*
230+
* await childTask.triggerAndWait(payload, { idempotencyKey: globalIdempotencyKey });
231+
*
232+
* // You can also pass a string directly, which is the same as a global idempotency key
233+
* await childTask.triggerAndWait(payload, { idempotencyKey: "my-very-unique-key" });
234+
* }
235+
* });
236+
* ```
237+
*
238+
* When triggering a task inside another task, we automatically inject the run ID into the key material.
239+
*
240+
* If you are triggering a task from your backend, ensure you include some sufficiently unique key material to prevent collisions.
241+
*
242+
* @example
243+
*
244+
* ```typescript
245+
* import { idempotencyKeys, tasks } from "@trigger.dev/sdk/v3";
246+
*
247+
* // Somewhere in your backend
248+
* const idempotencyKey = await idempotenceKeys.create(["my-task-trigger", "user-123"]);
249+
* await tasks.trigger("my-task", { foo: "bar" }, { idempotencyKey });
250+
* ```
251+
*
252+
*/
253+
idempotencyKey?: IdempotencyKey | string | string[];
254+
maxAttempts?: number;
255+
queue?: TaskRunConcurrencyOptions;
256+
concurrencyKey?: string;
257+
/**
258+
* The delay before the task is executed. This can be a string like "1h" or a Date object.
259+
*
260+
* @example
261+
* "1h" - 1 hour
262+
* "30d" - 30 days
263+
* "15m" - 15 minutes
264+
* "2w" - 2 weeks
265+
* "60s" - 60 seconds
266+
* new Date("2025-01-01T00:00:00Z")
267+
*/
268+
delay?: string | Date;
269+
270+
/**
271+
* Set a time-to-live for this run. If the run is not executed within this time, it will be removed from the queue and never execute.
272+
*
273+
* @example
274+
*
275+
* ```ts
276+
* await myTask.trigger({ foo: "bar" }, { ttl: "1h" });
277+
* await myTask.trigger({ foo: "bar" }, { ttl: 60 * 60 }); // 1 hour
278+
* ```
279+
*
280+
* The minimum value is 1 second. Setting the `ttl` to `0` will disable the TTL and the run will never expire.
281+
*
282+
* **Note:** Runs in development have a default `ttl` of 10 minutes. You can override this by setting the `ttl` option.
283+
*/
284+
ttl?: string | number;
285+
286+
/**
287+
* Tags to attach to the run. Tags can be used to filter runs in the dashboard and using the SDK.
288+
*
289+
* You can set up to 5 tags per run, they must be less than 64 characters each.
290+
*
291+
* We recommend prefixing tags with a namespace using an underscore or colon, like `user_1234567` or `org:9876543`.
292+
*
293+
* @example
294+
*
295+
* ```ts
296+
* await myTask.trigger({ foo: "bar" }, { tags: ["user:1234567", "org:9876543"] });
297+
* ```
298+
*/
299+
tags?: RunTags;
300+
301+
/**
302+
* Metadata to attach to the run. Metadata can be used to store additional information about the run. Limited to 4KB.
303+
*/
304+
metadata?: Record<string, SerializableJson>;
305+
306+
/**
307+
* The maximum duration in compute-time seconds that a task run is allowed to run. If the task run exceeds this duration, it will be stopped.
308+
*
309+
* This will override the task's maxDuration.
310+
*
311+
* Minimum value is 5 seconds
312+
*/
313+
maxDuration?: number;
314+
};

packages/react-hooks/src/hooks/useRun.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
"use client";
22

3-
import { type AnyRunShape } from "@trigger.dev/core/v3";
3+
import { AnyTask, TaskRunShape } from "@trigger.dev/core/v3";
44
import { useEffect, useState } from "react";
55
import { useApiClient } from "./useApiClient.js";
66

7-
export function useRun(runId: string) {
8-
const [runShape, setRunShape] = useState<AnyRunShape | undefined>(undefined);
7+
export function useRun<TTask extends AnyTask>(runId: string) {
8+
const [runShape, setRunShape] = useState<TaskRunShape<TTask> | undefined>(undefined);
99
const [error, setError] = useState<Error | null>(null);
1010
const apiClient = useApiClient();
1111

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
1-
import { taskContext } from "@trigger.dev/core/v3";
1+
import { type IdempotencyKey, taskContext } from "@trigger.dev/core/v3";
22

33
export const idempotencyKeys = {
44
create: createIdempotencyKey,
55
};
66

7-
declare const __brand: unique symbol;
8-
type Brand<B> = { [__brand]: B };
9-
type Branded<T, B> = T & Brand<B>;
10-
11-
export type IdempotencyKey = Branded<string, "IdempotencyKey">;
7+
export type { IdempotencyKey };
128

139
export function isIdempotencyKey(
1410
value: string | string[] | IdempotencyKey

0 commit comments

Comments
 (0)