From 9491a1649c4ac62e5bd1211f9ae3db978e1ad532 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Thu, 25 Apr 2024 11:17:13 +0100 Subject: [PATCH] v3: Support decorators (#1060) * Adds support for `emitDecoratorMetadata: true` and `experimentalDecorators: true` in your tsconfig * Implement task.onSuccess/onFailure and config.onSuccess/onFailure * Added onStart and more docs for lifecycle functions * Use onStart instead of init for TypeORM --- .changeset/clever-carrots-travel.md | 7 + .changeset/mighty-flowers-train.md | 83 +++++++ docs/v3/tasks-overview.mdx | 136 +++++++++++- docs/v3/trigger-config.mdx | 122 ++++++++++- packages/cli-v3/package.json | 1 + packages/cli-v3/src/commands/deploy.ts | 15 +- packages/cli-v3/src/commands/dev.tsx | 6 + packages/cli-v3/src/utilities/configFiles.ts | 8 + packages/core/src/v3/types/config.ts | 23 +- packages/core/src/v3/types/index.ts | 14 +- packages/core/src/v3/workers/taskExecutor.ts | 203 +++++++++++++++++- packages/trigger-sdk/src/v3/shared.ts | 69 +++++- pnpm-lock.yaml | 203 +++++++++++++++++- references/v3-catalog/package.json | 3 + references/v3-catalog/src/handleError.ts | 2 - .../v3-catalog/src/trigger/decorators.ts | 32 +++ .../v3-catalog/src/trigger/orm/index.ts | 36 ++++ references/v3-catalog/trigger.config.ts | 13 ++ references/v3-catalog/tsconfig.json | 4 +- 19 files changed, 946 insertions(+), 34 deletions(-) create mode 100644 .changeset/clever-carrots-travel.md create mode 100644 .changeset/mighty-flowers-train.md create mode 100644 references/v3-catalog/src/trigger/decorators.ts create mode 100644 references/v3-catalog/src/trigger/orm/index.ts diff --git a/.changeset/clever-carrots-travel.md b/.changeset/clever-carrots-travel.md new file mode 100644 index 000000000..1e4afac37 --- /dev/null +++ b/.changeset/clever-carrots-travel.md @@ -0,0 +1,7 @@ +--- +"@trigger.dev/sdk": patch +"trigger.dev": patch +"@trigger.dev/core": patch +--- + +Implement task.onSuccess/onFailure and config.onSuccess/onFailure diff --git a/.changeset/mighty-flowers-train.md b/.changeset/mighty-flowers-train.md new file mode 100644 index 000000000..32d4ff8e4 --- /dev/null +++ b/.changeset/mighty-flowers-train.md @@ -0,0 +1,83 @@ +--- +"trigger.dev": patch +"@trigger.dev/core": patch +--- + +Adds support for `emitDecoratorMetadata: true` and `experimentalDecorators: true` in your tsconfig using the [`@anatine/esbuild-decorators`](https://github.com/anatine/esbuildnx/tree/main/packages/esbuild-decorators) package. This allows you to use libraries like TypeORM: + +```ts orm/index.ts +import "reflect-metadata"; +import { DataSource } from "typeorm"; +import { Entity, Column, PrimaryColumn } from "typeorm"; + +@Entity() +export class Photo { + @PrimaryColumn() + id!: number; + + @Column() + name!: string; + + @Column() + description!: string; + + @Column() + filename!: string; + + @Column() + views!: number; + + @Column() + isPublished!: boolean; +} + +export const AppDataSource = new DataSource({ + type: "postgres", + host: "localhost", + port: 5432, + username: "postgres", + password: "postgres", + database: "v3-catalog", + entities: [Photo], + synchronize: true, + logging: false, +}); +``` + +And then in your trigger.config.ts file you can initialize the datasource using the new `init` option: + +```ts trigger.config.ts +import type { TriggerConfig } from "@trigger.dev/sdk/v3"; +import { AppDataSource } from "@/trigger/orm"; + +export const config: TriggerConfig = { + // ... other options here + init: async (payload, { ctx }) => { + await AppDataSource.initialize(); + }, +}; +``` + +Now you are ready to use this in your tasks: + +```ts +import { task } from "@trigger.dev/sdk/v3"; +import { AppDataSource, Photo } from "./orm"; + +export const taskThatUsesDecorators = task({ + id: "taskThatUsesDecorators", + run: async (payload: { message: string }) => { + console.log("Creating a photo..."); + + const photo = new Photo(); + photo.id = 2; + photo.name = "Me and Bears"; + photo.description = "I am near polar bears"; + photo.filename = "photo-with-bears.jpg"; + photo.views = 1; + photo.isPublished = true; + + await AppDataSource.manager.save(photo); + }, +}); +``` diff --git a/docs/v3/tasks-overview.mdx b/docs/v3/tasks-overview.mdx index ab81fb82e..09ec23ab7 100644 --- a/docs/v3/tasks-overview.mdx +++ b/docs/v3/tasks-overview.mdx @@ -79,7 +79,7 @@ export const taskWithRetries = task({ maxTimeoutInMs: 30_000, randomize: false, }, - run: async ({ payload, ctx }) => { + run: async (payload: any, { ctx }) => { //... }, }); @@ -99,7 +99,7 @@ export const oneAtATime = task({ queue: { concurrencyLimit: 1, }, - run: async ({ payload, ctx }) => { + run: async (payload: any, { ctx }) => { //... }, }); @@ -116,7 +116,7 @@ export const heavyTask = task({ cpu: 2, memory: 4, }, - run: async ({ payload, ctx }) => { + run: async (payload: any, { ctx }) => { //... }, }); @@ -124,27 +124,143 @@ export const heavyTask = task({ ### `init` function -This function is called before a run attempt. +This function is called before a run attempt: + +```ts /trigger/init.ts +export const taskWithInit = task({ + id: "task-with-init", + init: async (payload, { ctx }) => { + //... + }, + run: async (payload: any, { ctx }) => { + //... + }, +}); +``` + +You can also return data from the `init` function that will be available in the params of the `run`, `cleanup`, `onSuccess`, and `onFailure` functions. + +```ts /trigger/init-return.ts +export const taskWithInitReturn = task({ + id: "task-with-init-return", + init: async (payload, { ctx }) => { + return { someData: "someValue" }; + }, + run: async (payload: any, { ctx, init }) => { + console.log(init.someData); // "someValue" + }, +}); +``` ### `cleanup` function -This function is called after a run attempt has succeeded or failed. +This function is called after the `run` function is executed, regardless of whether the run was successful or not. It's useful for cleaning up resources, logging, or other side effects. + +```ts /trigger/cleanup.ts +export const taskWithCleanup = task({ + id: "task-with-cleanup", + cleanup: async (payload, { ctx }) => { + //... + }, + run: async (payload: any, { ctx }) => { + //... + }, +}); +``` ### `middleware` function This function is called before the `run` function, it allows you to wrap the run function with custom code. For more information [read the guide](/v3/middleware). +### `onStart` function + +When a task run starts, the `onStart` function is called. It's useful for sending notifications, logging, and other side effects. This function will only be called one per run (not per retry). If you want to run code before each retry, use the `init` function. + +```ts /trigger/on-start.ts +export const taskWithOnStart = task({ + id: "task-with-on-start", + onStart: async (payload, { ctx }) => { + //... + }, + run: async (payload: any, { ctx }) => { + //... + }, +}); +``` + +You can also define an `onStart` function in your `trigger.config.ts` file to get notified when any task starts. + +```ts trigger.config.ts +import type { TriggerConfig } from "@trigger.dev/sdk/v3"; + +export const config: TriggerConfig = { + onStart: async (payload, { ctx }) => { + console.log("Task started", ctx.task.id); + }, +}; +``` + ### `onSuccess` function -When a task attempt succeeds, the `onSuccess` function is called. It's useful for sending notifications, logging, or other side effects. +When a task run succeeds, the `onSuccess` function is called. It's useful for sending notifications, logging, syncing state to your database, or other side effects. - +```ts /trigger/on-success.ts +export const taskWithOnSuccess = task({ + id: "task-with-on-success", + onSuccess: async (payload, output, { ctx }) => { + //... + }, + run: async (payload: any, { ctx }) => { + //... + }, +}); +``` -### `onError` function +You can also define an `onSuccess` function in your `trigger.config.ts` file to get notified when any task succeeds. -When a task attempt fails, the `onError` function is called. It's useful for sending notifications, logging, or other side effects. +```ts trigger.config.ts +import type { TriggerConfig } from "@trigger.dev/sdk/v3"; - +export const config: TriggerConfig = { + onSuccess: async (payload, output, { ctx }) => { + console.log("Task succeeded", ctx.task.id); + }, +}; +``` + +### `onFailure` function + +When a task run fails, the `onFailure` function is called. It's useful for sending notifications, logging, or other side effects. It will only be executed once the task run has exhausted all its retries. + +```ts /trigger/on-failure.ts +export const taskWithOnFailure = task({ + id: "task-with-on-failure", + onFailure: async (payload, error, { ctx }) => { + //... + }, + run: async (payload: any, { ctx }) => { + //... + }, +}); +``` + +You can also define an `onFailure` function in your `trigger.config.ts` file to get notified when any task fails. + +```ts trigger.config.ts +import type { TriggerConfig } from "@trigger.dev/sdk/v3"; + +export const config: TriggerConfig = { + onFailure: async (payload, error, { ctx }) => { + console.log("Task failed", ctx.task.id); + }, +}; +``` + +### `handleError` functions + +You can define a function that will be called when an error is thrown in the `run` function, that allows you to control how the error is handled and whether the task should be retried. + +Read more about `handleError` in our [Errors and Retrying guide](/v3/errors-retrying). ## Next steps diff --git a/docs/v3/trigger-config.mdx b/docs/v3/trigger-config.mdx index 7d74070ab..2cff40c29 100644 --- a/docs/v3/trigger-config.mdx +++ b/docs/v3/trigger-config.mdx @@ -31,6 +31,46 @@ export const config: TriggerConfig = { Most of the time you don't need to change anything in this file, or if you do then we will tell you when you the run the CLI command. +## Global initialization + +You can run code before any task is run by adding a `init` function to your `trigger.config.ts` file. + +```ts trigger.config.ts +import type { TriggerConfig } from "@trigger.dev/sdk/v3"; + +export const config: TriggerConfig = { + //..other stuff + init: async (payload, { ctx }) => { + console.log("I run before any task is run"); + }, +}; +``` + +You'll have access to the run payload and the context object. Currently you cannot return anything from this function. + +## Lifecycle functions + +You can add lifecycle functions to get notified when any task starts, succeeds, or fails using `onStart`, `onSuccess` and `onFailure`: + +```ts trigger.config.ts +import type { TriggerConfig } from "@trigger.dev/sdk/v3"; + +export const config: TriggerConfig = { + //..other stuff + onSuccess: async (payload, output, { ctx }) => { + console.log("Task succeeded", ctx.task.id); + }, + onFailure: async (payload, error, { ctx }) => { + console.log("Task failed", ctx.task.id); + }, + onStart: async (payload, { ctx }) => { + console.log("Task started", ctx.task.id); + }, +}; +``` + +Read more about task lifecycle functions in the [tasks overview](/v3/tasks-overview). + ## Instrumentations We use OpenTelemetry (OTEL) for our run logs. This means you get a lot of information about your tasks with no effort. But you probably want to add more information to your logs. For example, here's all the Prisma calls automatically logged: @@ -92,7 +132,6 @@ Prisma works by generating a client from your `prisma.schema` file. This means y - ```json default path @@ -140,6 +179,87 @@ Prisma works by generating a client from your `prisma.schema` file. This means y +## TypeORM support + +We support using TypeORM with Trigger. You can use decorators in your entities and then use them in your tasks. Here's an example: + +```ts orm/index.ts +import "reflect-metadata"; +import { DataSource } from "typeorm"; +import { Entity, Column, PrimaryColumn } from "typeorm"; + +@Entity() +export class Photo { + @PrimaryColumn() + id!: number; + + @Column() + name!: string; + + @Column() + description!: string; + + @Column() + filename!: string; + + @Column() + views!: number; + + @Column() + isPublished!: boolean; +} + +export const AppDataSource = new DataSource({ + type: "postgres", + host: "localhost", + port: 5432, + username: "postgres", + password: "postgres", + database: "my-database", + entities: [Photo], + synchronize: true, + logging: false, +}); +``` + +And then in your trigger.config.ts file you can initialize the datasource using the `onStart` lifecycle function option: + +```ts trigger.config.ts +import type { TriggerConfig } from "@trigger.dev/sdk/v3"; +import { AppDataSource } from "@/trigger/orm"; + +export const config: TriggerConfig = { + // ... other options here + onStart: async (payload, { ctx }) => { + await AppDataSource.initialize(); + }, +}; +``` + +Now you are ready to use this in your tasks: + +```ts +import { task } from "@trigger.dev/sdk/v3"; +import { AppDataSource, Photo } from "./orm"; + +export const taskThatUsesDecorators = task({ + id: "task-that-uses-decorators", + run: async (payload: { message: string }) => { + console.log("Creating a photo..."); + + const photo = new Photo(); + photo.id = 2; + photo.name = "Me and Bears"; + photo.description = "I am near polar bears"; + photo.filename = "photo-with-bears.jpg"; + photo.views = 1; + photo.isPublished = true; + + await AppDataSource.manager.save(photo); + }, +}); +``` + ## Troubleshooting If you have an issue with bundling let us know on [Discord](https://trigger.dev/discord) or [via email](https://trigger.dev/contact). diff --git a/packages/cli-v3/package.json b/packages/cli-v3/package.json index 5533d068d..f1287ad6b 100644 --- a/packages/cli-v3/package.json +++ b/packages/cli-v3/package.json @@ -71,6 +71,7 @@ "test": "vitest" }, "dependencies": { + "@anatine/esbuild-decorators": "^0.2.19", "@clack/prompts": "^0.7.0", "@depot/cli": "0.0.1-cli.2.55.0", "@opentelemetry/api": "^1.8.0", diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index 55193c349..6ca164878 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -8,14 +8,13 @@ import { flattenAttributes, } from "@trigger.dev/core/v3"; import { recordSpanException } from "@trigger.dev/core/v3/workers"; -import chalk from "chalk"; import { Command, Option as CommandOption } from "commander"; import { Metafile, build } from "esbuild"; import { execa } from "execa"; import { createHash } from "node:crypto"; import { readFileSync } from "node:fs"; import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises"; -import { dirname, join, relative, posix } from "node:path"; +import { dirname, join, posix, relative } from "node:path"; import { setTimeout } from "node:timers/promises"; import terminalLink from "terminal-link"; import invariant from "tiny-invariant"; @@ -32,7 +31,7 @@ import { wrapCommandAction, } from "../cli/common.js"; import { readConfig } from "../utilities/configFiles.js"; -import { createTempDir, readJSONFile, writeJSONFile } from "../utilities/fileSystem"; +import { createTempDir, writeJSONFile } from "../utilities/fileSystem"; import { printStandloneInitialBanner } from "../utilities/initialBanner.js"; import { detectPackageNameFromImportPath, @@ -43,6 +42,7 @@ import { logger } from "../utilities/logger.js"; import { createTaskFileImports, gatherTaskFiles } from "../utilities/taskFiles"; import { login } from "./login"; +import { esbuildDecorators } from "@anatine/esbuild-decorators"; import { Glob, GlobOptions } from "glob"; import type { SetOptional } from "type-fest"; import { bundleDependenciesPlugin, workerSetupImportConfigPlugin } from "../utilities/build"; @@ -53,12 +53,12 @@ import { parseBuildErrorStack, parseNpmInstallError, } from "../utilities/deployErrors"; -import { safeJsonParse } from "../utilities/safeJsonParse"; import { JavascriptProject } from "../utilities/javascriptProject"; +import { docs, getInTouch } from "../utilities/links"; import { cliRootPath } from "../utilities/resolveInternalFilePath"; +import { safeJsonParse } from "../utilities/safeJsonParse"; import { escapeImportPath, spinner } from "../utilities/windows"; import { updateTriggerPackages } from "./update"; -import { docs, getInTouch } from "../utilities/links"; const DeployCommandOptions = CommonCommandOptions.extend({ skipTypecheck: z.boolean().default(false), @@ -1137,6 +1137,11 @@ async function compileProject( config.tsconfigPath ), workerSetupImportConfigPlugin(configPath), + esbuildDecorators({ + tsconfig: config.tsconfigPath, + tsx: true, + force: false, + }), ], }); diff --git a/packages/cli-v3/src/commands/dev.tsx b/packages/cli-v3/src/commands/dev.tsx index 33516e797..b182b535b 100644 --- a/packages/cli-v3/src/commands/dev.tsx +++ b/packages/cli-v3/src/commands/dev.tsx @@ -53,6 +53,7 @@ import { findUp, pathExists } from "find-up"; import { cliRootPath } from "../utilities/resolveInternalFilePath"; import { escapeImportPath } from "../utilities/windows"; import { updateTriggerPackages } from "./update"; +import { esbuildDecorators } from "@anatine/esbuild-decorators"; let apiClient: CliApiClient | undefined; @@ -409,6 +410,11 @@ function useDev({ config.tsconfigPath ), workerSetupImportConfigPlugin(configPath), + esbuildDecorators({ + tsconfig: config.tsconfigPath, + tsx: true, + force: false, + }), { name: "trigger.dev v3", setup(build) { diff --git a/packages/cli-v3/src/utilities/configFiles.ts b/packages/cli-v3/src/utilities/configFiles.ts index 87ed14779..5725e0e67 100644 --- a/packages/cli-v3/src/utilities/configFiles.ts +++ b/packages/cli-v3/src/utilities/configFiles.ts @@ -10,6 +10,7 @@ import { createTempDir, readJSONFileSync } from "./fileSystem.js"; import { logger } from "./logger.js"; import { findTriggerDirectories, resolveTriggerDirectories } from "./taskFiles.js"; import { build } from "esbuild"; +import { esbuildDecorators } from "@anatine/esbuild-decorators"; function getGlobalConfigFolderPath() { const configDir = xdgAppPaths("trigger").config(); @@ -172,6 +173,13 @@ export async function readConfig( target: ["es2018", "node18"], outfile: builtConfigFilePath, logLevel: "silent", + plugins: [ + esbuildDecorators({ + cwd: absoluteDir, + tsx: false, + force: false, + }), + ], }); // import the config file diff --git a/packages/core/src/v3/types/config.ts b/packages/core/src/v3/types/config.ts index e2fe65242..81786576d 100644 --- a/packages/core/src/v3/types/config.ts +++ b/packages/core/src/v3/types/config.ts @@ -1,3 +1,4 @@ +import { FailureFnParams, InitFnParams, StartFnParams, SuccessFnParams } from "."; import { LogLevel } from "../logger/taskLogger"; import { RetryOptions } from "../schemas"; import type { InstrumentationOption } from "@opentelemetry/instrumentation"; @@ -16,7 +17,7 @@ export interface ProjectConfig { * List of additional files to include in your trigger.dev bundle. e.g. ["./prisma/schema.prisma"] * * Supports glob patterns. - * + * * Note: The path separator for glob patterns is `/`, even on Windows! */ additionalFiles?: string[]; @@ -48,4 +49,24 @@ export interface ProjectConfig { * Enable console logging while running the dev CLI. This will print out logs from console.log, console.warn, and console.error. By default all logs are sent to the trigger.dev backend, and not logged to the console. */ enableConsoleLogging?: boolean; + + /** + * Run before a task is executed, for all tasks. This is useful for setting up any global state that is needed for all tasks. + */ + init?: (payload: unknown, params: InitFnParams) => void | Promise; + + /** + * onSuccess is called after the run function has successfully completed. + */ + onSuccess?: (payload: unknown, output: unknown, params: SuccessFnParams) => Promise; + + /** + * onFailure is called after a task run has failed (meaning the run function threw an error and won't be retried anymore) + */ + onFailure?: (payload: unknown, error: unknown, params: FailureFnParams) => Promise; + + /** + * onStart is called the first time a task is executed in a run (not before every retry) + */ + onStart?: (payload: unknown, params: StartFnParams) => Promise; } diff --git a/packages/core/src/v3/types/index.ts b/packages/core/src/v3/types/index.ts index f6072f8f4..e1a79157f 100644 --- a/packages/core/src/v3/types/index.ts +++ b/packages/core/src/v3/types/index.ts @@ -22,12 +22,15 @@ export type InitFnParams = Prettify<{ ctx: Context; }>; +export type StartFnParams = Prettify<{ + ctx: Context; +}>; + export type Context = TaskRunContext; -export type SuccessFnParams = RunFnParams & - Prettify<{ - output: TOutput; - }>; +export type SuccessFnParams = RunFnParams; + +export type FailureFnParams = RunFnParams; export type HandleErrorFnParams = RunFnParams & Prettify<{ @@ -74,5 +77,8 @@ export type TaskMetadataWithFunctions = TaskMetadata & { error: unknown, params: HandleErrorFnParams ) => HandleErrorResult; + onSuccess?: (payload: any, output: any, params: SuccessFnParams) => Promise; + onFailure?: (payload: any, error: unknown, params: FailureFnParams) => Promise; + onStart?: (payload: any, params: StartFnParams) => Promise; }; }; diff --git a/packages/core/src/v3/workers/taskExecutor.ts b/packages/core/src/v3/workers/taskExecutor.ts index 48fd1abd1..16a396ede 100644 --- a/packages/core/src/v3/workers/taskExecutor.ts +++ b/packages/core/src/v3/workers/taskExecutor.ts @@ -90,10 +90,16 @@ export class TaskExecutor { parsedPayload = await parsePacket(payloadPacket); - initOutput = await this.#callTaskInit(parsedPayload, ctx); + if (execution.attempt.number === 1) { + await this.#callOnStartFunctions(parsedPayload, ctx); + } + + initOutput = await this.#callInitFunctions(parsedPayload, ctx); const output = await this.#callRun(parsedPayload, ctx, initOutput); + await this.#callOnSuccessFunctions(parsedPayload, output, ctx, initOutput); + try { const stringifiedOutput = await stringifyIO(output); @@ -148,6 +154,15 @@ export class TaskExecutor { recordSpanException(span, handleErrorResult.error ?? runError); + if (handleErrorResult.status !== "retry") { + await this.#callOnFailureFunctions( + parsedPayload, + handleErrorResult.error ?? runError, + ctx, + initOutput + ); + } + return { id: execution.run.id, ok: false, @@ -218,16 +233,194 @@ export class TaskExecutor { return middlewareFn(payload, { ctx, next: async () => runFn(payload, { ctx, init }) }); } - async #callTaskInit(payload: unknown, ctx: TaskRunContext) { + async #callInitFunctions(payload: unknown, ctx: TaskRunContext) { + await this.#callConfigInit(payload, ctx); + const initFn = this.task.fns.init; if (!initFn) { return {}; } - return this._tracer.startActiveSpan("init", async (span) => { - return await initFn(payload, { ctx }); - }); + return this._tracer.startActiveSpan( + "init", + async (span) => { + return await initFn(payload, { ctx }); + }, + { + attributes: { + [SemanticInternalAttributes.STYLE_ICON]: "function", + }, + } + ); + } + + async #callConfigInit(payload: unknown, ctx: TaskRunContext) { + const initFn = this._importedConfig?.init; + + if (!initFn) { + return {}; + } + + return this._tracer.startActiveSpan( + "config.init", + async (span) => { + return await initFn(payload, { ctx }); + }, + { + attributes: { + [SemanticInternalAttributes.STYLE_ICON]: "function", + }, + } + ); + } + + async #callOnSuccessFunctions( + payload: unknown, + output: any, + ctx: TaskRunContext, + initOutput: any + ) { + await this.#callOnSuccessFunction( + this.task.fns.onSuccess, + "task.onSuccess", + payload, + output, + ctx, + initOutput + ); + + await this.#callOnSuccessFunction( + this._importedConfig?.onSuccess, + "config.onSuccess", + payload, + output, + ctx, + initOutput + ); + } + + async #callOnSuccessFunction( + onSuccessFn: TaskMetadataWithFunctions["fns"]["onSuccess"], + name: string, + payload: unknown, + output: any, + ctx: TaskRunContext, + initOutput: any + ) { + if (!onSuccessFn) { + return; + } + + try { + await this._tracer.startActiveSpan( + name, + async (span) => { + return await onSuccessFn(payload, output, { ctx, init: initOutput }); + }, + { + attributes: { + [SemanticInternalAttributes.STYLE_ICON]: "function", + }, + } + ); + } catch { + // Ignore errors from onSuccess functions + } + } + + async #callOnFailureFunctions( + payload: unknown, + error: unknown, + ctx: TaskRunContext, + initOutput: any + ) { + await this.#callOnFailureFunction( + this.task.fns.onFailure, + "task.onFailure", + payload, + error, + ctx, + initOutput + ); + + await this.#callOnFailureFunction( + this._importedConfig?.onFailure, + "config.onFailure", + payload, + error, + ctx, + initOutput + ); + } + + async #callOnFailureFunction( + onFailureFn: TaskMetadataWithFunctions["fns"]["onFailure"], + name: string, + payload: unknown, + error: unknown, + ctx: TaskRunContext, + initOutput: any + ) { + if (!onFailureFn) { + return; + } + + try { + return await this._tracer.startActiveSpan( + name, + async (span) => { + return await onFailureFn(payload, error, { ctx, init: initOutput }); + }, + { + attributes: { + [SemanticInternalAttributes.STYLE_ICON]: "function", + }, + } + ); + } catch (e) { + // Ignore errors from onFailure functions + } + } + + async #callOnStartFunctions(payload: unknown, ctx: TaskRunContext) { + await this.#callOnStartFunction( + this._importedConfig?.onStart, + "config.onStart", + payload, + ctx, + {} + ); + + await this.#callOnStartFunction(this.task.fns.onStart, "task.onStart", payload, ctx, {}); + } + + async #callOnStartFunction( + onStartFn: TaskMetadataWithFunctions["fns"]["onStart"], + name: string, + payload: unknown, + ctx: TaskRunContext, + initOutput: any + ) { + if (!onStartFn) { + return; + } + + try { + await this._tracer.startActiveSpan( + name, + async (span) => { + return await onStartFn(payload, { ctx }); + }, + { + attributes: { + [SemanticInternalAttributes.STYLE_ICON]: "function", + }, + } + ); + } catch { + // Ignore errors from onStart functions + } } async #callTaskCleanup(payload: unknown, ctx: TaskRunContext, init: unknown) { diff --git a/packages/trigger-sdk/src/v3/shared.ts b/packages/trigger-sdk/src/v3/shared.ts index aa950897a..ff1121f6a 100644 --- a/packages/trigger-sdk/src/v3/shared.ts +++ b/packages/trigger-sdk/src/v3/shared.ts @@ -6,6 +6,7 @@ import { } from "@opentelemetry/semantic-conventions"; import { BatchTaskRunExecutionResult, + FailureFnParams, HandleErrorFnParams, HandleErrorResult, InitFnParams, @@ -17,6 +18,7 @@ import { RetryOptions, RunFnParams, SemanticInternalAttributes, + StartFnParams, SuccessFnParams, TaskRunContext, TaskRunExecutionResult, @@ -143,15 +145,67 @@ export type TaskOptions< * @param params - Metadata about the run. */ run: (payload: TPayload, params: RunFnParams) => Promise; + + /** + * init is called before the run function is called. It's useful for setting up any global state. + */ init?: (payload: TPayload, params: InitFnParams) => Promise; + + /** + * cleanup is called after the run function has completed. + */ + cleanup?: (payload: TPayload, params: RunFnParams) => Promise; + + /** + * handleError is called when the run function throws an error. It can be used to modify the error or return new retry options. + */ handleError?: ( payload: TPayload, error: unknown, params: HandleErrorFnParams ) => HandleErrorResult; - cleanup?: (payload: TPayload, params: RunFnParams) => Promise; + + /** + * middleware allows you to run code "around" the run function. This can be useful for logging, metrics, or other cross-cutting concerns. + * + * When writing middleware, you should always call `next()` to continue the execution of the task: + * + * ```ts + * export const middlewareTask = task({ + * id: "middleware-task", + * middleware: async (payload, { ctx, next }) => { + * console.log("Before run"); + * await next(); + * console.log("After run"); + * }, + * run: async (payload, { ctx }) => {} + * }); + * ``` + */ middleware?: (payload: TPayload, params: MiddlewareFnParams) => Promise; - onSuccess?: (payload: TPayload, params: SuccessFnParams) => Promise; + + /** + * onStart is called the first time a task is executed in a run (not before every retry) + */ + onStart?: (payload: TPayload, params: StartFnParams) => Promise; + + /** + * onSuccess is called after the run function has successfully completed. + */ + onSuccess?: ( + payload: TPayload, + output: TOutput, + params: SuccessFnParams + ) => Promise; + + /** + * onFailure is called after a task run has failed (meaning the run function threw an error and won't be retried anymore) + */ + onFailure?: ( + payload: TPayload, + error: unknown, + params: FailureFnParams + ) => Promise; }; type InvokeHandle = { @@ -248,6 +302,14 @@ export interface Task { batchTriggerAndWait: (items: Array>) => Promise>; } +export type TaskPayload = TTask extends Task + ? TInput + : never; + +export type TaskOutput = TTask extends Task + ? TOutput + : never; + type TaskRunOptions = { idempotencyKey?: string; maxAttempts?: number; @@ -624,6 +686,9 @@ export function createTask=18.10.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} @@ -13130,6 +13150,10 @@ packages: - supports-color dev: false + /@sqltools/formatter@1.2.5: + resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} + dev: false + /@supabase/functions-js@2.1.2: resolution: {integrity: sha512-QCR6pwJs9exCl37bmpMisUd6mf+0SUBJ6mUpiAjEkSJ/+xW8TCuO14bvkWHADd5hElJK9MxNlMQXxSA4DRz9nQ==} dependencies: @@ -15437,7 +15461,6 @@ packages: /acorn-walk@8.3.2: resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} engines: {node: '>=0.4.0'} - dev: true /acorn@7.4.1: resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} @@ -15649,6 +15672,11 @@ packages: normalize-path: 3.0.0 picomatch: 2.3.1 + /app-root-path@3.1.0: + resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==} + engines: {node: '>= 6.0.0'} + dev: false + /append-field@1.0.0: resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} @@ -17073,6 +17101,19 @@ packages: dependencies: restore-cursor: 4.0.0 + /cli-highlight@2.1.11: + resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true + dependencies: + chalk: 4.1.2 + highlight.js: 10.7.3 + mz: 2.7.0 + parse5: 5.1.1 + parse5-htmlparser2-tree-adapter: 6.0.1 + yargs: 16.2.0 + dev: false + /cli-spinners@2.9.1: resolution: {integrity: sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==} engines: {node: '>=6'} @@ -17869,6 +17910,10 @@ packages: '@babel/runtime': 7.22.5 dev: true + /dayjs@1.11.10: + resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==} + dev: false + /deasync@0.1.28: resolution: {integrity: sha512-QqLF6inIDwiATrfROIyQtwOQxjZuek13WRYZ7donU5wJPLoP67MnYxA6QtqdvdBy2mMqv5m3UefBVdJjvevOYg==} engines: {node: '>=0.11.0'} @@ -21385,6 +21430,7 @@ packages: minimatch: 9.0.3 minipass: 7.0.3 path-scurry: 1.10.1 + dev: true /glob@7.1.6: resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} @@ -21881,6 +21927,10 @@ packages: engines: {node: '>=8'} dev: true + /highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + dev: false + /highlight.run@7.3.4: resolution: {integrity: sha512-Rgx+gy0tb2tH4hNzxYi/VK5pL/msaAtaQBIy8XsPHLujdSgo5OPWO6vOdjjB7ufM1l/CI2RLmlQ+L2QZOuHBjw==} dev: false @@ -22979,6 +23029,7 @@ packages: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 + dev: true /jackspeak@2.3.6: resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} @@ -23679,7 +23730,7 @@ packages: dependencies: config-chain: 1.1.13 editorconfig: 1.0.4 - glob: 10.3.3 + glob: 10.3.10 js-cookie: 3.0.5 nopt: 7.2.0 dev: false @@ -25340,6 +25391,12 @@ packages: engines: {node: '>=10'} hasBin: true + /mkdirp@2.1.6: + resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==} + engines: {node: '>=10'} + hasBin: true + dev: false + /mlly@1.4.2: resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==} dependencies: @@ -26886,6 +26943,16 @@ packages: engines: {node: '>=6'} dev: true + /parse5-htmlparser2-tree-adapter@6.0.1: + resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} + dependencies: + parse5: 6.0.1 + dev: false + + /parse5@5.1.1: + resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} + dev: false + /parse5@6.0.1: resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} @@ -27021,10 +27088,20 @@ packages: is-reference: 3.0.1 dev: true + /pg-cloudflare@1.1.1: + resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} + requiresBuild: true + dev: false + optional: true + /pg-connection-string@2.5.0: resolution: {integrity: sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==} dev: false + /pg-connection-string@2.6.4: + resolution: {integrity: sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==} + dev: false + /pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} @@ -27038,10 +27115,22 @@ packages: pg: 8.10.0 dev: false + /pg-pool@3.6.2(pg@8.11.5): + resolution: {integrity: sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==} + peerDependencies: + pg: '>=8.0' + dependencies: + pg: 8.11.5 + dev: false + /pg-protocol@1.6.0: resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==} dev: false + /pg-protocol@1.6.1: + resolution: {integrity: sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==} + dev: false + /pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} @@ -27071,6 +27160,24 @@ packages: pgpass: 1.0.5 dev: false + /pg@8.11.5: + resolution: {integrity: sha512-jqgNHSKL5cbDjFlHyYsCXmQDrfIX/3RsNwYqpd4N0Kt8niLuNoRNH+aazv6cOd43gPh9Y4DjQCtb+X0MH0Hvnw==} + engines: {node: '>= 8.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + dependencies: + pg-connection-string: 2.6.4 + pg-pool: 3.6.2(pg@8.11.5) + pg-protocol: 1.6.1 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.1.1 + dev: false + /pgpass@1.0.5: resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} dependencies: @@ -28682,6 +28789,10 @@ packages: /reflect-metadata@0.1.13: resolution: {integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==} + /reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + dev: false + /regenerate-unicode-properties@10.1.0: resolution: {integrity: sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==} engines: {node: '>=4'} @@ -29655,6 +29766,14 @@ packages: /setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + /sha.js@2.4.11: + resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} + hasBin: true + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + dev: false + /sharp@0.32.6: resolution: {integrity: sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==} engines: {node: '>=14.15.0'} @@ -31500,7 +31619,6 @@ packages: typescript: 5.3.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - dev: true /ts-poet@6.6.0: resolution: {integrity: sha512-4vEH/wkhcjRPFOdBwIh9ItO6jOoumVLRF4aABDX5JSNEubSqwOulihxQPqai+OkuygJm3WYMInxXQX4QwVNMuw==} @@ -32061,6 +32179,85 @@ packages: /typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + /typeorm@0.3.20(pg@8.11.5)(ts-node@10.9.2): + resolution: {integrity: sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q==} + engines: {node: '>=16.13.0'} + hasBin: true + peerDependencies: + '@google-cloud/spanner': ^5.18.0 + '@sap/hana-client': ^2.12.25 + better-sqlite3: ^7.1.2 || ^8.0.0 || ^9.0.0 + hdb-pool: ^0.1.6 + ioredis: ^5.0.4 + mongodb: ^5.8.0 + mssql: ^9.1.1 || ^10.0.1 + mysql2: ^2.2.5 || ^3.0.1 + oracledb: ^6.3.0 + pg: ^8.5.1 + pg-native: ^3.0.0 + pg-query-stream: ^4.0.0 + redis: ^3.1.1 || ^4.0.0 + sql.js: ^1.4.0 + sqlite3: ^5.0.3 + ts-node: ^10.7.0 + typeorm-aurora-data-api-driver: ^2.0.0 + peerDependenciesMeta: + '@google-cloud/spanner': + optional: true + '@sap/hana-client': + optional: true + better-sqlite3: + optional: true + hdb-pool: + optional: true + ioredis: + optional: true + mongodb: + optional: true + mssql: + optional: true + mysql2: + optional: true + oracledb: + optional: true + pg: + optional: true + pg-native: + optional: true + pg-query-stream: + optional: true + redis: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + ts-node: + optional: true + typeorm-aurora-data-api-driver: + optional: true + dependencies: + '@sqltools/formatter': 1.2.5 + app-root-path: 3.1.0 + buffer: 6.0.3 + chalk: 4.1.2 + cli-highlight: 2.1.11 + dayjs: 1.11.10 + debug: 4.3.4(supports-color@8.1.1) + dotenv: 16.4.5 + glob: 10.3.10 + mkdirp: 2.1.6 + pg: 8.11.5 + reflect-metadata: 0.2.2 + sha.js: 2.4.11 + ts-node: 10.9.2(@types/node@20.4.2)(typescript@5.3.3) + tslib: 2.6.2 + uuid: 9.0.0 + yargs: 17.7.2 + transitivePeerDependencies: + - supports-color + dev: false + /typescript@4.9.4: resolution: {integrity: sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==} engines: {node: '>=4.2.0'} diff --git a/references/v3-catalog/package.json b/references/v3-catalog/package.json index 06199b4aa..a6e915e7b 100644 --- a/references/v3-catalog/package.json +++ b/references/v3-catalog/package.json @@ -18,7 +18,10 @@ "execa": "^8.0.1", "msw": "^2.2.1", "openai": "^4.28.0", + "pg": "^8.11.5", + "reflect-metadata": "^0.1.13", "stripe": "^12.14.0", + "typeorm": "^0.3.20", "yt-dlp-wrap": "^2.3.12" }, "devDependencies": { diff --git a/references/v3-catalog/src/handleError.ts b/references/v3-catalog/src/handleError.ts index 1dbec25bb..261f7e589 100644 --- a/references/v3-catalog/src/handleError.ts +++ b/references/v3-catalog/src/handleError.ts @@ -2,6 +2,4 @@ import { logger, type HandleErrorFunction } from "@trigger.dev/sdk/v3"; export const handleError: HandleErrorFunction = async (payload, error, { ctx, retry }) => { logger.log("handling error", { error }); - - return { skipRetrying: true }; }; diff --git a/references/v3-catalog/src/trigger/decorators.ts b/references/v3-catalog/src/trigger/decorators.ts new file mode 100644 index 000000000..fd7e9cf02 --- /dev/null +++ b/references/v3-catalog/src/trigger/decorators.ts @@ -0,0 +1,32 @@ +import { logger, task } from "@trigger.dev/sdk/v3"; +import { AppDataSource, Photo } from "./orm"; + +export const taskThatUsesDecorators = task({ + id: "taskThatUsesDecorators", + run: async (payload: { message: string }) => { + console.log("Creating a photo..."); + + const photo = new Photo(); + photo.id = 2; + photo.name = "Me and Bears"; + photo.description = "I am near polar bears"; + photo.filename = "photo-with-bears.jpg"; + photo.views = 1; + photo.isPublished = true; + + await AppDataSource.manager.save(photo); + + if (Math.random() > 0.5) { + throw new Error("Failed to create photo"); + } + }, + onSuccess: async (payload, output, { ctx }) => { + logger.log("Photo created successfully", { output, ctx }); + }, + onFailure: async (payload, error, { ctx }) => { + logger.error("Failed to create photo", { error, ctx }); + }, + onStart: async (payload, { ctx }) => { + logger.log("Starting to create photo", { ctx }); + }, +}); diff --git a/references/v3-catalog/src/trigger/orm/index.ts b/references/v3-catalog/src/trigger/orm/index.ts new file mode 100644 index 000000000..766822049 --- /dev/null +++ b/references/v3-catalog/src/trigger/orm/index.ts @@ -0,0 +1,36 @@ +import "reflect-metadata"; +import { DataSource } from "typeorm"; +import { Entity, Column, PrimaryColumn } from "typeorm"; + +@Entity() +export class Photo { + @PrimaryColumn() + id!: number; + + @Column() + name!: string; + + @Column() + description!: string; + + @Column() + filename!: string; + + @Column() + views!: number; + + @Column() + isPublished!: boolean; +} + +export const AppDataSource = new DataSource({ + type: "postgres", + host: "localhost", + port: 5432, + username: "postgres", + password: "postgres", + database: "v3-catalog", + entities: [Photo], + synchronize: true, + logging: false, +}); diff --git a/references/v3-catalog/trigger.config.ts b/references/v3-catalog/trigger.config.ts index 2c1bd722d..84cfc710f 100644 --- a/references/v3-catalog/trigger.config.ts +++ b/references/v3-catalog/trigger.config.ts @@ -1,5 +1,6 @@ import type { TriggerConfig } from "@trigger.dev/sdk/v3"; import { OpenAIInstrumentation } from "@traceloop/instrumentation-openai"; +import { AppDataSource } from "@/trigger/orm"; export { handleError } from "./src/handleError"; @@ -21,4 +22,16 @@ export const config: TriggerConfig = { instrumentations: [new OpenAIInstrumentation()], logLevel: "log", enableConsoleLogging: true, + onStart: async (payload, { ctx }) => { + if (ctx.organization.id === "clsylhs0v0002dyx75xx4pod1") { + console.log("Initializing the app data source"); + + await AppDataSource.initialize(); + } + }, + onFailure: async (payload, error, { ctx }) => { + console.log(`Task ${ctx.task.id} failed ${ctx.run.id}`); + + throw error; + }, }; diff --git a/references/v3-catalog/tsconfig.json b/references/v3-catalog/tsconfig.json index 822dd4f3e..11f740215 100644 --- a/references/v3-catalog/tsconfig.json +++ b/references/v3-catalog/tsconfig.json @@ -10,6 +10,8 @@ "@trigger.dev/core/v3/*": ["../../packages/core/src/v3/*"], "@trigger.dev/sdk/v3": ["../../packages/trigger-sdk/src/v3/index"], "@trigger.dev/sdk/v3/*": ["../../packages/trigger-sdk/src/v3/*"] - } + }, + "emitDecoratorMetadata": true, + "experimentalDecorators": true } }