import { z } from "zod"; import * as v from "valibot"; export type ParserZodEsque = { _input: TInput; _output: TParsedInput; }; export type ParserMyZodEsque = { parse: (input: any) => TInput; }; export type ParserSuperstructEsque = { create: (input: unknown) => TInput; }; export type ParserCustomValidatorEsque = (input: unknown) => Promise | TInput; export type ParserYupEsque = { validateSync: (input: unknown) => TInput; }; export type ParserScaleEsque = { assert(value: unknown): asserts value is TInput; }; export type ParserWithoutInput = | ParserCustomValidatorEsque | ParserMyZodEsque | ParserScaleEsque | ParserSuperstructEsque | ParserYupEsque; export type ParserWithInputOutput = ParserZodEsque; export type Parser = ParserWithInputOutput | ParserWithoutInput; export type inferParser = TParser extends ParserWithInputOutput< infer $TIn, infer $TOut > ? { in: $TIn; out: $TOut; } : TParser extends ParserWithoutInput ? { in: $InOut; out: $InOut; } : never; export type Simplify = TType extends any[] | Date ? TType : { [K in keyof TType]: TType[K] }; export type TriggerResult = { id: string; }; export type TaskRunResult = | { ok: true; id: string; output: TOutput; } | { ok: false; id: string; error: unknown; }; export type RunMetadata = { run: string; }; export type RunFnParams = { /** Metadata about the task, run, attempt, queue, environment, organization, project and batch. */ meta: RunMetadata; /** Context added by task middleware */ ctx: TContext; payload: TPayload; }; export type TaskOptions< TPayloadIn, TPayloadOut, TOutput, TContext extends object, TIdentifier extends string, > = { /** An id for your task. This must be unique inside your project and not change between versions. */ id: TIdentifier; schema?: Parser; /** This gets called when a task is triggered. It's where you put the code you want to execute. * * @param payload - The payload that is passed to your task when it's triggered. This must be JSON serializable. * @param params - Metadata about the run. */ run: ( params: Simplify, TContext>> ) => Promise; }; export interface Task { /** * The id of the task. */ id: TIdentifier; /** * 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. * @param payload * @param options * @returns TriggerResult * - `id` - The id of the triggered task run. */ trigger: ( payload: Simplify>, options?: TriggerTaskOptions ) => Promise; /** * Trigger a task with the given payload, and wait for the result. Returns the result of the task run * @param payload * @param options - Options for the task run * @returns TaskRunResult * @example * ``` * const result = await task.triggerAndWait({ foo: "bar" }); * * if (result.ok) { * console.log(result.output); * } else { * console.error(result.error); * } * ``` */ triggerAndWait: ( payload: Simplify>, options?: TriggerTaskOptions ) => Promise>; } export type AnyTask = Task; type IsUnknown = unknown extends T ? (T extends unknown ? true : false) : false; type NonUnknown = IsUnknown extends true ? never : T; export type inferTaskPayloadIn = NonUnknown extends never ? TPayloadOut : TPayloadIn; export type inferTaskPayloadOut = NonUnknown extends never ? TPayloadIn : TPayloadOut; export type TaskPayloadIn = TTask extends Task< infer TPayloadIn, infer TPayloadOut, any, string > ? inferTaskPayloadIn : never; export type TaskPayloadOut = TTask extends Task< infer TPayloadIn, infer TPayloadOut, any, string > ? inferTaskPayloadOut : never; export type TaskOutput = TTask extends Task ? TOutput : never; export type TaskIdentifier = TTask extends Task< any, any, any, infer TIdentifier > ? TIdentifier : never; export type TaskTypes = TTask extends Task< infer TPayloadIn, infer TPayloadOut, infer TOutput, infer TIdentifier > ? { id: TIdentifier; payloadIn: TPayloadIn; payloadOut: TPayloadOut; output: TOutput; } : never; export type TriggerTaskOptions = { idempotencyKey?: string; maxAttempts?: number; startAt?: Date; startAfter?: number; concurrencyKey?: string; }; export type Prettify = { [K in keyof T]: T[K]; } & {}; export function task< TPayloadIn, TPayloadOut, TOutput, TContext extends object, TIdentifier extends string, >( options: TaskOptions ): Task { return createTask(options); } export function createTask< TPayloadIn, TPayloadOut, TOutput, TContext extends object, TIndentifier extends string, >( params: TaskOptions ): Task { const task: Task = { id: params.id, trigger: async (payload, options) => { return { id: "run_1234", }; }, triggerAndWait: async (payload, options) => { const output = await params.run({ meta: { run: "run_1234" }, payload: payload as unknown as inferTaskPayloadOut, ctx: {} as TContext, }); return { ok: true, id: "run_1234", output, }; }, }; return task; } export type ZodTaskOptions< TOutput, TContext extends object, TIdentifier extends string, TSchema extends z.ZodTypeAny = z.ZodTypeAny, > = { schema: TSchema; } & TaskOptions, z.output, TOutput, TContext, TIdentifier>; export function zodTask< TOutput, TContext extends object, TIdentifier extends string, TSchema extends z.ZodTypeAny = z.ZodTypeAny, >( options: ZodTaskOptions ): Task, z.output, TOutput, TIdentifier> { return createTask, z.output, TOutput, TContext, TIdentifier>(options); } export type ValibotTaskOptions< TOutput, TContext extends object, TIdentifier extends string, TSchema extends v.BaseSchema = v.AnySchema, > = { schema: TSchema; } & TaskOptions, v.Output, TOutput, TContext, TIdentifier>; export function valibotTask< TOutput, TContext extends object, TIdentifier extends string, TSchema extends v.BaseSchema = v.AnySchema, >( options: ValibotTaskOptions ): Task, v.Output, TOutput, TIdentifier> { return createTask, v.Output, TOutput, TContext, TIdentifier>(options); } export interface TaskLibraryRecord { [key: string]: AnyTask | TaskLibraryRecord; } export interface TaskLibrary { _def: { record: TRecord }; } export type AnyTaskLibrary = TaskLibrary; export type CreateTaskLibraryOptions = { [key: string]: AnyTask | AnyTaskLibrary | CreateTaskLibraryOptions; }; export type DecorateCreateTaskLibraryOptions = { [K in keyof TTaskLibraryOptions]: TTaskLibraryOptions[K] extends infer $Value ? $Value extends AnyTask ? $Value : $Value extends TaskLibrary ? TRecord : $Value extends CreateTaskLibraryOptions ? DecorateCreateTaskLibraryOptions<$Value> : never : never; }; function taskLibrary( input: TInput ): TaskLibrary>; function taskLibrary(input: TInput): TaskLibrary; function taskLibrary(input: TaskLibraryRecord | CreateTaskLibraryOptions) { // TODO: reserved words return { _def: { record: input, }, }; } // ======== client side type DecorateTask = { trigger: (id: TaskIdentifier, payload: TaskPayloadIn) => Promise<{ id: string }>; }; type DecoratedTaskLibraryRecord< TTaskLibrary extends AnyTaskLibrary, TRecord extends TaskLibraryRecord, > = { [TKey in keyof TRecord]: TRecord[TKey] extends infer $Value ? $Value extends TaskLibraryRecord ? DecoratedTaskLibraryRecord : $Value extends AnyTask ? DecorateTask<$Value> : never : never; }; export type inferTaskLibraryClient = DecoratedTaskLibraryRecord; export type CreateTriggerClient = { lib: inferTaskLibraryClient; runs: { retrieve: (id: string) => Promise<{ status: boolean }>; }; }; export type CreateTriggerClientOptions = { secretKey?: string; }; export function createTriggerClient( options?: CreateTriggerClientOptions ): CreateTriggerClient { return {} as CreateTriggerClient; } // trigger/my-tasks.ts const taskOne = task({ id: "task-1", run: async () => { const handle = await taskTwo.trigger({ url: "https://trigger.dev" }); const result = await taskTwo.triggerAndWait({ url: "https://trigger.dev" }); return "foo-bar"; }, }); const taskTwo = task({ id: "task-2", async run(params) { return { hello: "world", payload: params.payload, }; }, }); const userTaskOne = task({ id: "user/task-1", run: async (params: { payload: { userId: string } }) => { return "foo-bar"; }, }); const userTaskTwo = task({ id: "user/task-2", run: async (params: { payload: { userId: string; isAdmin: boolean } }) => { return "foo-bar"; }, }); const zodTaskOne = task({ id: "zod/task-1", schema: z.object({ foo: z.string() }), run: async (params) => {}, }); const zodTaskTwo = task({ id: "zod/task-2", schema: z.object({ foo: z.string(), isAdmin: z.boolean().default(false) }), run: async (params) => { console.log(params.payload.foo, params.meta.run); }, }); const valibotTaskOne = task({ id: "valibot/task-1", schema: v.object({ foo: v.string(), }), run: async (params) => { await zodTaskOne.trigger({ foo: "bar" }); await zodTaskTwo.trigger({ foo: "bar" }); await valibotTaskTwo.trigger({ foo: "bar" }); }, }); const valibotTaskTwo = task({ id: "valibot/task-2", schema: v.object({ foo: v.string(), isAdmin: v.optional(v.boolean(), true), }), run: async (params) => { await valibotTaskOne.trigger({ foo: "bar" }); }, }); // in trigger/lib.ts const myTaskLibrary = taskLibrary({ myTasks: { taskOne, taskTwo }, }); const userTaskLibrary = taskLibrary({ userTaskOne, userTaskTwo, }); const zodTaskLibrary = taskLibrary({ zodTaskOne, zodTaskTwo, }); const valibotTaskLibrary = taskLibrary({ valibotTaskOne, valibotTaskTwo, }); export const library = taskLibrary({ foo: myTaskLibrary, bar: userTaskLibrary, zod: zodTaskLibrary, valibot: valibotTaskLibrary, }); // Export the library type export type Library = typeof library; // Now on the client const client = createTriggerClient({ secretKey: "tr_dev_1234", }); client.runs.retrieve("run_12343"); // Call regular API client calls // Tasks are now available under lib client.lib.foo.myTasks.taskOne.trigger("task-1", { hello: "world" }); client.lib.bar.userTaskOne.trigger("user/task-1", { userId: "user_123" }); client.lib.bar.userTaskTwo.trigger("user/task-2", { userId: "user_123", isAdmin: true }); client.lib.bar.userTaskTwo.trigger("user/task-2", { userId: "user_123", isAdmin: false }); client.lib.zod.zodTaskOne.trigger("zod/task-1", { foo: "bar" }); client.lib.zod.zodTaskTwo.trigger("zod/task-2", { foo: "bar" }); client.lib.zod.zodTaskTwo.trigger("zod/task-2", { foo: "bar", isAdmin: false }); client.lib.valibot.valibotTaskTwo.trigger("valibot/task-2", { foo: "bar" }); client.lib.valibot.valibotTaskTwo.trigger("valibot/task-2", { foo: "bar", isAdmin: false });