diff --git a/.changeset/weak-jobs-hide.md b/.changeset/weak-jobs-hide.md
new file mode 100644
index 0000000000..0be1f49588
--- /dev/null
+++ b/.changeset/weak-jobs-hide.md
@@ -0,0 +1,7 @@
+---
+"@trigger.dev/sdk": patch
+"trigger.dev": patch
+"@trigger.dev/core": patch
+---
+
+v4: New lifecycle hooks
diff --git a/.cursor/rules/executing-commands.mdc b/.cursor/rules/executing-commands.mdc
index eac17379e4..0d36b44949 100644
--- a/.cursor/rules/executing-commands.mdc
+++ b/.cursor/rules/executing-commands.mdc
@@ -13,12 +13,12 @@ But often, when running tests, it's better to `cd` into the directory and then r
```
cd apps/webapp
-pnpm run test
+pnpm run test --run
```
This way you can run for a single file easily:
```
cd internal-packages/run-engine
-pnpm run test ./src/engine/tests/ttl.test.ts
+pnpm run test ./src/engine/tests/ttl.test.ts --run
```
diff --git a/apps/webapp/app/components/runs/v3/RunIcon.tsx b/apps/webapp/app/components/runs/v3/RunIcon.tsx
index a557e5cd35..7c43fcfae6 100644
--- a/apps/webapp/app/components/runs/v3/RunIcon.tsx
+++ b/apps/webapp/app/components/runs/v3/RunIcon.tsx
@@ -44,6 +44,9 @@ export function RunIcon({ name, className, spanName }: TaskIconProps) {
}
if (!name) return ;
+ if (tablerIcons.has(name)) {
+ return ;
+ }
switch (name) {
case "task":
@@ -73,6 +76,28 @@ export function RunIcon({ name, className, spanName }: TaskIconProps) {
return ;
case "fatal":
return ;
+ case "task-middleware":
+ return ;
+ case "task-fn-run":
+ return ;
+ case "task-hook-init":
+ return ;
+ case "task-hook-onStart":
+ return ;
+ case "task-hook-onSuccess":
+ return ;
+ case "task-hook-onFailure":
+ return ;
+ case "task-hook-onComplete":
+ return ;
+ case "task-hook-onWait":
+ return ;
+ case "task-hook-onResume":
+ return ;
+ case "task-hook-catchError":
+ return ;
+ case "task-hook-cleanup":
+ return ;
}
return ;
diff --git a/internal-packages/run-engine/src/engine/errors.ts b/internal-packages/run-engine/src/engine/errors.ts
index 81e3b598bd..e1dd34eac4 100644
--- a/internal-packages/run-engine/src/engine/errors.ts
+++ b/internal-packages/run-engine/src/engine/errors.ts
@@ -13,6 +13,9 @@ export function runStatusFromError(error: TaskRunError): TaskRunStatus {
//e.g. a bug
switch (error.code) {
case "RECURSIVE_WAIT_DEADLOCK":
+ case "TASK_INPUT_ERROR":
+ case "TASK_OUTPUT_ERROR":
+ case "TASK_MIDDLEWARE_ERROR":
return "COMPLETED_WITH_ERRORS";
case "TASK_RUN_CANCELLED":
return "CANCELED";
@@ -41,8 +44,6 @@ export function runStatusFromError(error: TaskRunError): TaskRunStatus {
case "TASK_RUN_STALLED_EXECUTING_WITH_WAITPOINTS":
case "TASK_HAS_N0_EXECUTION_SNAPSHOT":
case "GRACEFUL_EXIT_TIMEOUT":
- case "TASK_INPUT_ERROR":
- case "TASK_OUTPUT_ERROR":
case "POD_EVICTED":
case "POD_UNKNOWN_ERROR":
case "TASK_EXECUTION_ABORTED":
diff --git a/packages/cli-v3/src/build/bundle.ts b/packages/cli-v3/src/build/bundle.ts
index 0c2dfa5631..b326a5f769 100644
--- a/packages/cli-v3/src/build/bundle.ts
+++ b/packages/cli-v3/src/build/bundle.ts
@@ -47,6 +47,7 @@ export type BundleResult = {
runControllerEntryPoint: string | undefined;
indexWorkerEntryPoint: string | undefined;
indexControllerEntryPoint: string | undefined;
+ initEntryPoint: string | undefined;
stop: (() => Promise) | undefined;
};
@@ -229,11 +230,26 @@ export async function getBundleResultFromBuild(
let runControllerEntryPoint: string | undefined;
let indexWorkerEntryPoint: string | undefined;
let indexControllerEntryPoint: string | undefined;
+ let initEntryPoint: string | undefined;
const configEntryPoint = resolvedConfig.configFile
? relative(resolvedConfig.workingDir, resolvedConfig.configFile)
: "trigger.config.ts";
+ // Check if the entry point is an init.ts file at the root of a trigger directory
+ function isInitEntryPoint(entryPoint: string): boolean {
+ const normalizedEntryPoint = entryPoint.replace(/\\/g, "/"); // Normalize path separators
+ const initFileNames = ["init.ts", "init.mts", "init.cts", "init.js", "init.mjs", "init.cjs"];
+
+ // Check if it's directly in one of the trigger directories
+ return resolvedConfig.dirs.some((dir) => {
+ const normalizedDir = dir.replace(/\\/g, "/");
+ return initFileNames.some(
+ (fileName) => normalizedEntryPoint === `${normalizedDir}/${fileName}`
+ );
+ });
+ }
+
for (const [outputPath, outputMeta] of Object.entries(result.metafile.outputs)) {
if (outputPath.endsWith(".mjs")) {
const $outputPath = resolve(workingDir, outputPath);
@@ -254,6 +270,8 @@ export async function getBundleResultFromBuild(
indexControllerEntryPoint = $outputPath;
} else if (isIndexWorkerForTarget(outputMeta.entryPoint, target)) {
indexWorkerEntryPoint = $outputPath;
+ } else if (isInitEntryPoint(outputMeta.entryPoint)) {
+ initEntryPoint = $outputPath;
} else {
if (
!outputMeta.entryPoint.startsWith("..") &&
@@ -280,6 +298,7 @@ export async function getBundleResultFromBuild(
runControllerEntryPoint,
indexWorkerEntryPoint,
indexControllerEntryPoint,
+ initEntryPoint,
contentHash: hasher.digest("hex"),
};
}
@@ -357,6 +376,7 @@ export async function createBuildManifestFromBundle({
runControllerEntryPoint: bundle.runControllerEntryPoint ?? getRunControllerForTarget(target),
runWorkerEntryPoint: bundle.runWorkerEntryPoint ?? getRunWorkerForTarget(target),
loaderEntryPoint: bundle.loaderEntryPoint,
+ initEntryPoint: bundle.initEntryPoint,
configPath: bundle.configPath,
customConditions: resolvedConfig.build.conditions ?? [],
deploy: {
diff --git a/packages/cli-v3/src/entryPoints/dev-index-worker.ts b/packages/cli-v3/src/entryPoints/dev-index-worker.ts
index a29f2f8541..86528a93fe 100644
--- a/packages/cli-v3/src/entryPoints/dev-index-worker.ts
+++ b/packages/cli-v3/src/entryPoints/dev-index-worker.ts
@@ -141,6 +141,7 @@ await sendMessageInCatalog(
controllerEntryPoint: buildManifest.runControllerEntryPoint,
loaderEntryPoint: buildManifest.loaderEntryPoint,
customConditions: buildManifest.customConditions,
+ initEntryPoint: buildManifest.initEntryPoint,
},
importErrors,
},
diff --git a/packages/cli-v3/src/entryPoints/dev-run-worker.ts b/packages/cli-v3/src/entryPoints/dev-run-worker.ts
index 007c001a79..8553e36057 100644
--- a/packages/cli-v3/src/entryPoints/dev-run-worker.ts
+++ b/packages/cli-v3/src/entryPoints/dev-run-worker.ts
@@ -1,15 +1,23 @@
import type { Tracer } from "@opentelemetry/api";
import type { Logger } from "@opentelemetry/api-logs";
import {
+ AnyOnCatchErrorHookFunction,
+ AnyOnFailureHookFunction,
+ AnyOnInitHookFunction,
+ AnyOnStartHookFunction,
+ AnyOnSuccessHookFunction,
apiClientManager,
clock,
ExecutorToWorkerMessageCatalog,
type HandleErrorFunction,
+ lifecycleHooks,
+ localsAPI,
logger,
LogLevel,
+ resourceCatalog,
runMetadata,
runtime,
- resourceCatalog,
+ runTimelineMetrics,
TaskRunErrorCodes,
TaskRunExecution,
timeout,
@@ -17,7 +25,6 @@ import {
waitUntil,
WorkerManifest,
WorkerToExecutorMessageCatalog,
- runTimelineMetrics,
} from "@trigger.dev/core/v3";
import { TriggerTracer } from "@trigger.dev/core/v3/tracer";
import {
@@ -29,15 +36,17 @@ import {
logLevels,
ManagedRuntimeManager,
OtelTaskLogger,
+ StandardLifecycleHooksManager,
+ StandardLocalsManager,
StandardMetadataManager,
StandardResourceCatalog,
+ StandardRunTimelineMetricsManager,
StandardWaitUntilManager,
TaskExecutor,
TracingDiagnosticLogLevel,
TracingSDK,
usage,
UsageTimeoutManager,
- StandardRunTimelineMetricsManager,
} from "@trigger.dev/core/v3/workers";
import { ZodIpcConnection } from "@trigger.dev/core/v3/zodIpc";
import { readFile } from "node:fs/promises";
@@ -89,10 +98,16 @@ process.on("uncaughtException", function (error, origin) {
const heartbeatIntervalMs = getEnvVar("HEARTBEAT_INTERVAL_MS");
+const standardLocalsManager = new StandardLocalsManager();
+localsAPI.setGlobalLocalsManager(standardLocalsManager);
+
const standardRunTimelineMetricsManager = new StandardRunTimelineMetricsManager();
runTimelineMetrics.setGlobalManager(standardRunTimelineMetricsManager);
standardRunTimelineMetricsManager.seedMetricsFromEnvironment();
+const standardLifecycleHooksManager = new StandardLifecycleHooksManager();
+lifecycleHooks.setGlobalLifecycleHooksManager(standardLifecycleHooksManager);
+
const devUsageManager = new DevUsageManager();
usage.setGlobalUsageManager(devUsageManager);
timeout.setGlobalManager(new UsageTimeoutManager(devUsageManager));
@@ -170,12 +185,46 @@ async function bootstrap() {
logger.setGlobalTaskLogger(otelTaskLogger);
+ if (config.init) {
+ lifecycleHooks.registerGlobalInitHook({
+ id: "config",
+ fn: config.init as AnyOnInitHookFunction,
+ });
+ }
+
+ if (config.onStart) {
+ lifecycleHooks.registerGlobalStartHook({
+ id: "config",
+ fn: config.onStart as AnyOnStartHookFunction,
+ });
+ }
+
+ if (config.onSuccess) {
+ lifecycleHooks.registerGlobalSuccessHook({
+ id: "config",
+ fn: config.onSuccess as AnyOnSuccessHookFunction,
+ });
+ }
+
+ if (config.onFailure) {
+ lifecycleHooks.registerGlobalFailureHook({
+ id: "config",
+ fn: config.onFailure as AnyOnFailureHookFunction,
+ });
+ }
+
+ if (handleError) {
+ lifecycleHooks.registerGlobalCatchErrorHook({
+ id: "config",
+ fn: handleError as AnyOnCatchErrorHookFunction,
+ });
+ }
+
return {
tracer,
tracingSDK,
consoleInterceptor,
config,
- handleErrorFn: handleError,
workerManifest,
};
}
@@ -217,7 +266,7 @@ const zodIpc = new ZodIpcConnection({
}
try {
- const { tracer, tracingSDK, consoleInterceptor, config, handleErrorFn, workerManifest } =
+ const { tracer, tracingSDK, consoleInterceptor, config, workerManifest } =
await bootstrap();
_tracingSDK = tracingSDK;
@@ -257,6 +306,18 @@ const zodIpc = new ZodIpcConnection({
async () => {
const beforeImport = performance.now();
resourceCatalog.setCurrentFileContext(taskManifest.entryPoint, taskManifest.filePath);
+
+ // Load init file if it exists
+ if (workerManifest.initEntryPoint) {
+ try {
+ await import(normalizeImportPath(workerManifest.initEntryPoint));
+ log(`Loaded init file from ${workerManifest.initEntryPoint}`);
+ } catch (err) {
+ logError(`Failed to load init file`, err);
+ throw err;
+ }
+ }
+
await import(normalizeImportPath(taskManifest.entryPoint));
resourceCatalog.clearCurrentFileContext();
const durationMs = performance.now() - beforeImport;
@@ -321,8 +382,7 @@ const zodIpc = new ZodIpcConnection({
tracer,
tracingSDK,
consoleInterceptor,
- config,
- handleErrorFn,
+ retries: config.retries,
});
try {
@@ -340,42 +400,7 @@ const zodIpc = new ZodIpcConnection({
? timeout.abortAfterTimeout(execution.run.maxDuration)
: undefined;
- signal?.addEventListener("abort", async (e) => {
- if (_isRunning) {
- _isRunning = false;
- _execution = undefined;
-
- const usageSample = usage.stop(measurement);
-
- await sender.send("TASK_RUN_COMPLETED", {
- execution,
- result: {
- ok: false,
- id: execution.run.id,
- error: {
- type: "INTERNAL_ERROR",
- code: TaskRunErrorCodes.MAX_DURATION_EXCEEDED,
- message:
- signal.reason instanceof Error
- ? signal.reason.message
- : String(signal.reason),
- },
- usage: {
- durationMs: usageSample.cpuTime,
- },
- metadata: runMetadataManager.stopAndReturnLastFlush(),
- },
- });
- }
- });
-
- const { result } = await executor.execute(
- execution,
- metadata,
- traceContext,
- measurement,
- signal
- );
+ const { result } = await executor.execute(execution, metadata, traceContext, signal);
const usageSample = usage.stop(measurement);
diff --git a/packages/cli-v3/src/entryPoints/managed-index-worker.ts b/packages/cli-v3/src/entryPoints/managed-index-worker.ts
index a29f2f8541..86528a93fe 100644
--- a/packages/cli-v3/src/entryPoints/managed-index-worker.ts
+++ b/packages/cli-v3/src/entryPoints/managed-index-worker.ts
@@ -141,6 +141,7 @@ await sendMessageInCatalog(
controllerEntryPoint: buildManifest.runControllerEntryPoint,
loaderEntryPoint: buildManifest.loaderEntryPoint,
customConditions: buildManifest.customConditions,
+ initEntryPoint: buildManifest.initEntryPoint,
},
importErrors,
},
diff --git a/packages/cli-v3/src/entryPoints/managed-run-worker.ts b/packages/cli-v3/src/entryPoints/managed-run-worker.ts
index f261a9e677..fa4f426bac 100644
--- a/packages/cli-v3/src/entryPoints/managed-run-worker.ts
+++ b/packages/cli-v3/src/entryPoints/managed-run-worker.ts
@@ -1,23 +1,30 @@
import type { Tracer } from "@opentelemetry/api";
import type { Logger } from "@opentelemetry/api-logs";
import {
+ AnyOnCatchErrorHookFunction,
+ AnyOnFailureHookFunction,
+ AnyOnInitHookFunction,
+ AnyOnStartHookFunction,
+ AnyOnSuccessHookFunction,
+ apiClientManager,
clock,
+ ExecutorToWorkerMessageCatalog,
type HandleErrorFunction,
+ lifecycleHooks,
+ localsAPI,
logger,
LogLevel,
- runtime,
resourceCatalog,
+ runMetadata,
+ runtime,
+ runTimelineMetrics,
TaskRunErrorCodes,
TaskRunExecution,
- WorkerToExecutorMessageCatalog,
- TriggerConfig,
- WorkerManifest,
- ExecutorToWorkerMessageCatalog,
timeout,
- runMetadata,
+ TriggerConfig,
waitUntil,
- apiClientManager,
- runTimelineMetrics,
+ WorkerManifest,
+ WorkerToExecutorMessageCatalog,
} from "@trigger.dev/core/v3";
import { TriggerTracer } from "@trigger.dev/core/v3/tracer";
import {
@@ -27,18 +34,20 @@ import {
getEnvVar,
getNumberEnvVar,
logLevels,
+ ManagedRuntimeManager,
OtelTaskLogger,
ProdUsageManager,
+ StandardLifecycleHooksManager,
+ StandardLocalsManager,
+ StandardMetadataManager,
StandardResourceCatalog,
+ StandardRunTimelineMetricsManager,
+ StandardWaitUntilManager,
TaskExecutor,
TracingDiagnosticLogLevel,
TracingSDK,
usage,
UsageTimeoutManager,
- StandardMetadataManager,
- StandardWaitUntilManager,
- ManagedRuntimeManager,
- StandardRunTimelineMetricsManager,
} from "@trigger.dev/core/v3/workers";
import { ZodIpcConnection } from "@trigger.dev/core/v3/zodIpc";
import { readFile } from "node:fs/promises";
@@ -93,6 +102,12 @@ const usageEventUrl = getEnvVar("USAGE_EVENT_URL");
const triggerJWT = getEnvVar("TRIGGER_JWT");
const heartbeatIntervalMs = getEnvVar("HEARTBEAT_INTERVAL_MS");
+const standardLocalsManager = new StandardLocalsManager();
+localsAPI.setGlobalLocalsManager(standardLocalsManager);
+
+const standardLifecycleHooksManager = new StandardLifecycleHooksManager();
+lifecycleHooks.setGlobalLifecycleHooksManager(standardLifecycleHooksManager);
+
const standardRunTimelineMetricsManager = new StandardRunTimelineMetricsManager();
runTimelineMetrics.setGlobalManager(standardRunTimelineMetricsManager);
standardRunTimelineMetricsManager.seedMetricsFromEnvironment();
@@ -180,12 +195,46 @@ async function bootstrap() {
logger.setGlobalTaskLogger(otelTaskLogger);
+ if (config.init) {
+ lifecycleHooks.registerGlobalInitHook({
+ id: "config",
+ fn: config.init as AnyOnInitHookFunction,
+ });
+ }
+
+ if (config.onStart) {
+ lifecycleHooks.registerGlobalStartHook({
+ id: "config",
+ fn: config.onStart as AnyOnStartHookFunction,
+ });
+ }
+
+ if (config.onSuccess) {
+ lifecycleHooks.registerGlobalSuccessHook({
+ id: "config",
+ fn: config.onSuccess as AnyOnSuccessHookFunction,
+ });
+ }
+
+ if (config.onFailure) {
+ lifecycleHooks.registerGlobalFailureHook({
+ id: "config",
+ fn: config.onFailure as AnyOnFailureHookFunction,
+ });
+ }
+
+ if (handleError) {
+ lifecycleHooks.registerGlobalCatchErrorHook({
+ id: "config",
+ fn: handleError as AnyOnCatchErrorHookFunction,
+ });
+ }
+
return {
tracer,
tracingSDK,
consoleInterceptor,
config,
- handleErrorFn: handleError,
workerManifest,
};
}
@@ -227,7 +276,7 @@ const zodIpc = new ZodIpcConnection({
}
try {
- const { tracer, tracingSDK, consoleInterceptor, config, handleErrorFn, workerManifest } =
+ const { tracer, tracingSDK, consoleInterceptor, config, workerManifest } =
await bootstrap();
_tracingSDK = tracingSDK;
@@ -331,8 +380,7 @@ const zodIpc = new ZodIpcConnection({
tracer,
tracingSDK,
consoleInterceptor,
- config,
- handleErrorFn,
+ retries: config.retries,
});
try {
@@ -350,42 +398,7 @@ const zodIpc = new ZodIpcConnection({
? timeout.abortAfterTimeout(execution.run.maxDuration)
: undefined;
- signal?.addEventListener("abort", async (e) => {
- if (_isRunning) {
- _isRunning = false;
- _execution = undefined;
-
- const usageSample = usage.stop(measurement);
-
- await sender.send("TASK_RUN_COMPLETED", {
- execution,
- result: {
- ok: false,
- id: execution.run.id,
- error: {
- type: "INTERNAL_ERROR",
- code: TaskRunErrorCodes.MAX_DURATION_EXCEEDED,
- message:
- signal.reason instanceof Error
- ? signal.reason.message
- : String(signal.reason),
- },
- usage: {
- durationMs: usageSample.cpuTime,
- },
- metadata: runMetadataManager.stopAndReturnLastFlush(),
- },
- });
- }
- });
-
- const { result } = await executor.execute(
- execution,
- metadata,
- traceContext,
- measurement,
- signal
- );
+ const { result } = await executor.execute(execution, metadata, traceContext, signal);
const usageSample = usage.stop(measurement);
diff --git a/packages/core/src/v3/config.ts b/packages/core/src/v3/config.ts
index 3862a24c90..9be80decc6 100644
--- a/packages/core/src/v3/config.ts
+++ b/packages/core/src/v3/config.ts
@@ -1,15 +1,16 @@
import type { Instrumentation } from "@opentelemetry/instrumentation";
import type { SpanExporter } from "@opentelemetry/sdk-trace-base";
import type { BuildExtension } from "./build/extensions.js";
-import type { MachinePresetName } from "./schemas/common.js";
-import type { LogLevel } from "./logger/taskLogger.js";
import type {
- FailureFnParams,
- InitFnParams,
- StartFnParams,
- SuccessFnParams,
-} from "./types/index.js";
-import type { BuildRuntime, RetryOptions } from "./index.js";
+ AnyOnFailureHookFunction,
+ AnyOnInitHookFunction,
+ AnyOnStartHookFunction,
+ AnyOnSuccessHookFunction,
+ BuildRuntime,
+ RetryOptions,
+} from "./index.js";
+import type { LogLevel } from "./logger/taskLogger.js";
+import type { MachinePresetName } from "./schemas/common.js";
export type CompatibilityFlag = "run_engine_v2";
@@ -215,23 +216,31 @@ export type TriggerConfig = {
/**
* Run before a task is executed, for all tasks. This is useful for setting up any global state that is needed for all tasks.
+ *
+ * @deprecated, please use tasks.init instead
*/
- init?: (payload: unknown, params: InitFnParams) => void | Promise;
+ init?: AnyOnInitHookFunction;
/**
* onSuccess is called after the run function has successfully completed.
+ *
+ * @deprecated, please use tasks.onSuccess instead
*/
- onSuccess?: (payload: unknown, output: unknown, params: SuccessFnParams) => Promise;
+ onSuccess?: AnyOnSuccessHookFunction;
/**
* onFailure is called after a task run has failed (meaning the run function threw an error and won't be retried anymore)
+ *
+ * @deprecated, please use tasks.onFailure instead
*/
- onFailure?: (payload: unknown, error: unknown, params: FailureFnParams) => Promise;
+ onFailure?: AnyOnFailureHookFunction;
/**
* onStart is called the first time a task is executed in a run (not before every retry)
+ *
+ * @deprecated, please use tasks.onStart instead
*/
- onStart?: (payload: unknown, params: StartFnParams) => Promise;
+ onStart?: AnyOnStartHookFunction;
/**
* @deprecated Use a custom build extension to add post install commands
diff --git a/packages/core/src/v3/errors.ts b/packages/core/src/v3/errors.ts
index a8e789f81d..bf9776d1c2 100644
--- a/packages/core/src/v3/errors.ts
+++ b/packages/core/src/v3/errors.ts
@@ -305,6 +305,7 @@ export function shouldRetryError(error: TaskRunError): boolean {
case "HANDLE_ERROR_ERROR":
case "TASK_INPUT_ERROR":
case "TASK_OUTPUT_ERROR":
+ case "TASK_MIDDLEWARE_ERROR":
case "POD_EVICTED":
case "POD_UNKNOWN_ERROR":
case "TASK_EXECUTION_ABORTED":
diff --git a/packages/core/src/v3/index.ts b/packages/core/src/v3/index.ts
index 098362a7dc..7706842c14 100644
--- a/packages/core/src/v3/index.ts
+++ b/packages/core/src/v3/index.ts
@@ -15,6 +15,8 @@ export * from "./run-metadata-api.js";
export * from "./wait-until-api.js";
export * from "./timeout-api.js";
export * from "./run-timeline-metrics-api.js";
+export * from "./lifecycle-hooks-api.js";
+export * from "./locals-api.js";
export * from "./schemas/index.js";
export { SemanticInternalAttributes } from "./semanticInternalAttributes.js";
export * from "./resource-catalog-api.js";
diff --git a/packages/core/src/v3/lifecycle-hooks-api.ts b/packages/core/src/v3/lifecycle-hooks-api.ts
new file mode 100644
index 0000000000..ec9e87c998
--- /dev/null
+++ b/packages/core/src/v3/lifecycle-hooks-api.ts
@@ -0,0 +1,35 @@
+// Split module-level variable definition into separate files to allow
+// tree-shaking on each api instance.
+import { LifecycleHooksAPI } from "./lifecycleHooks/index.js";
+/** Entrypoint for runtime API */
+export const lifecycleHooks = LifecycleHooksAPI.getInstance();
+
+export type {
+ OnInitHookFunction,
+ AnyOnInitHookFunction,
+ RegisteredHookFunction,
+ TaskInitHookParams,
+ TaskStartHookParams,
+ OnStartHookFunction,
+ AnyOnStartHookFunction,
+ TaskFailureHookParams,
+ AnyOnFailureHookFunction,
+ TaskSuccessHookParams,
+ AnyOnSuccessHookFunction,
+ TaskCompleteHookParams,
+ AnyOnCompleteHookFunction,
+ TaskWaitHookParams,
+ AnyOnWaitHookFunction,
+ TaskResumeHookParams,
+ AnyOnResumeHookFunction,
+ TaskCatchErrorHookParams,
+ AnyOnCatchErrorHookFunction,
+ TaskCompleteResult,
+ TaskMiddlewareHookParams,
+ AnyOnMiddlewareHookFunction,
+ OnMiddlewareHookFunction,
+ OnCleanupHookFunction,
+ AnyOnCleanupHookFunction,
+ TaskCleanupHookParams,
+ TaskWait,
+} from "./lifecycleHooks/types.js";
diff --git a/packages/core/src/v3/lifecycleHooks/index.ts b/packages/core/src/v3/lifecycleHooks/index.ts
new file mode 100644
index 0000000000..843ae92ce8
--- /dev/null
+++ b/packages/core/src/v3/lifecycleHooks/index.ts
@@ -0,0 +1,266 @@
+const API_NAME = "lifecycle-hooks";
+
+import { getGlobal, registerGlobal, unregisterGlobal } from "../utils/globals.js";
+import { NoopLifecycleHooksManager } from "./manager.js";
+import {
+ AnyOnCatchErrorHookFunction,
+ AnyOnCleanupHookFunction,
+ AnyOnCompleteHookFunction,
+ AnyOnFailureHookFunction,
+ AnyOnInitHookFunction,
+ AnyOnMiddlewareHookFunction,
+ AnyOnResumeHookFunction,
+ AnyOnStartHookFunction,
+ AnyOnSuccessHookFunction,
+ AnyOnWaitHookFunction,
+ RegisteredHookFunction,
+ RegisterHookFunctionParams,
+ TaskWait,
+ type LifecycleHooksManager,
+} from "./types.js";
+
+const NOOP_LIFECYCLE_HOOKS_MANAGER = new NoopLifecycleHooksManager();
+
+export class LifecycleHooksAPI {
+ private static _instance?: LifecycleHooksAPI;
+
+ private constructor() {}
+
+ public static getInstance(): LifecycleHooksAPI {
+ if (!this._instance) {
+ this._instance = new LifecycleHooksAPI();
+ }
+
+ return this._instance;
+ }
+
+ public setGlobalLifecycleHooksManager(lifecycleHooksManager: LifecycleHooksManager): boolean {
+ return registerGlobal(API_NAME, lifecycleHooksManager);
+ }
+
+ public disable() {
+ unregisterGlobal(API_NAME);
+ }
+
+ public registerGlobalInitHook(hook: RegisterHookFunctionParams): void {
+ this.#getManager().registerGlobalInitHook(hook);
+ }
+
+ public registerTaskInitHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ this.#getManager().registerTaskInitHook(taskId, hook);
+ }
+
+ public getTaskInitHook(taskId: string): AnyOnInitHookFunction | undefined {
+ return this.#getManager().getTaskInitHook(taskId);
+ }
+
+ public getGlobalInitHooks(): RegisteredHookFunction[] {
+ return this.#getManager().getGlobalInitHooks();
+ }
+
+ public registerTaskStartHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ this.#getManager().registerTaskStartHook(taskId, hook);
+ }
+
+ public registerGlobalStartHook(hook: RegisterHookFunctionParams): void {
+ this.#getManager().registerGlobalStartHook(hook);
+ }
+
+ public getTaskStartHook(taskId: string): AnyOnStartHookFunction | undefined {
+ return this.#getManager().getTaskStartHook(taskId);
+ }
+
+ public getGlobalStartHooks(): RegisteredHookFunction[] {
+ return this.#getManager().getGlobalStartHooks();
+ }
+
+ public registerGlobalFailureHook(
+ hook: RegisterHookFunctionParams
+ ): void {
+ this.#getManager().registerGlobalFailureHook(hook);
+ }
+
+ public registerTaskFailureHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ this.#getManager().registerTaskFailureHook(taskId, hook);
+ }
+
+ public getTaskFailureHook(taskId: string): AnyOnFailureHookFunction | undefined {
+ return this.#getManager().getTaskFailureHook(taskId);
+ }
+
+ public getGlobalFailureHooks(): RegisteredHookFunction[] {
+ return this.#getManager().getGlobalFailureHooks();
+ }
+
+ public registerGlobalSuccessHook(
+ hook: RegisterHookFunctionParams
+ ): void {
+ this.#getManager().registerGlobalSuccessHook(hook);
+ }
+
+ public registerTaskSuccessHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ this.#getManager().registerTaskSuccessHook(taskId, hook);
+ }
+
+ public getTaskSuccessHook(taskId: string): AnyOnSuccessHookFunction | undefined {
+ return this.#getManager().getTaskSuccessHook(taskId);
+ }
+
+ public getGlobalSuccessHooks(): RegisteredHookFunction[] {
+ return this.#getManager().getGlobalSuccessHooks();
+ }
+
+ public registerGlobalCompleteHook(
+ hook: RegisterHookFunctionParams
+ ): void {
+ this.#getManager().registerGlobalCompleteHook(hook);
+ }
+
+ public registerTaskCompleteHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ this.#getManager().registerTaskCompleteHook(taskId, hook);
+ }
+
+ public getTaskCompleteHook(taskId: string): AnyOnCompleteHookFunction | undefined {
+ return this.#getManager().getTaskCompleteHook(taskId);
+ }
+
+ public getGlobalCompleteHooks(): RegisteredHookFunction[] {
+ return this.#getManager().getGlobalCompleteHooks();
+ }
+
+ public registerGlobalWaitHook(hook: RegisterHookFunctionParams): void {
+ this.#getManager().registerGlobalWaitHook(hook);
+ }
+
+ public registerTaskWaitHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ this.#getManager().registerTaskWaitHook(taskId, hook);
+ }
+
+ public getTaskWaitHook(taskId: string): AnyOnWaitHookFunction | undefined {
+ return this.#getManager().getTaskWaitHook(taskId);
+ }
+
+ public getGlobalWaitHooks(): RegisteredHookFunction[] {
+ return this.#getManager().getGlobalWaitHooks();
+ }
+
+ public registerGlobalResumeHook(hook: RegisterHookFunctionParams): void {
+ this.#getManager().registerGlobalResumeHook(hook);
+ }
+
+ public registerTaskResumeHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ this.#getManager().registerTaskResumeHook(taskId, hook);
+ }
+
+ public getTaskResumeHook(taskId: string): AnyOnResumeHookFunction | undefined {
+ return this.#getManager().getTaskResumeHook(taskId);
+ }
+
+ public getGlobalResumeHooks(): RegisteredHookFunction[] {
+ return this.#getManager().getGlobalResumeHooks();
+ }
+
+ public registerGlobalCatchErrorHook(
+ hook: RegisterHookFunctionParams
+ ): void {
+ this.#getManager().registerGlobalCatchErrorHook(hook);
+ }
+
+ public registerTaskCatchErrorHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ this.#getManager().registerTaskCatchErrorHook(taskId, hook);
+ }
+
+ public getTaskCatchErrorHook(taskId: string): AnyOnCatchErrorHookFunction | undefined {
+ return this.#getManager().getTaskCatchErrorHook(taskId);
+ }
+
+ public getGlobalCatchErrorHooks(): RegisteredHookFunction[] {
+ return this.#getManager().getGlobalCatchErrorHooks();
+ }
+
+ public registerGlobalMiddlewareHook(
+ hook: RegisterHookFunctionParams
+ ): void {
+ this.#getManager().registerGlobalMiddlewareHook(hook);
+ }
+
+ public registerTaskMiddlewareHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ this.#getManager().registerTaskMiddlewareHook(taskId, hook);
+ }
+
+ public getTaskMiddlewareHook(taskId: string): AnyOnMiddlewareHookFunction | undefined {
+ return this.#getManager().getTaskMiddlewareHook(taskId);
+ }
+
+ public getGlobalMiddlewareHooks(): RegisteredHookFunction[] {
+ return this.#getManager().getGlobalMiddlewareHooks();
+ }
+
+ public registerGlobalCleanupHook(
+ hook: RegisterHookFunctionParams
+ ): void {
+ this.#getManager().registerGlobalCleanupHook(hook);
+ }
+
+ public registerTaskCleanupHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ this.#getManager().registerTaskCleanupHook(taskId, hook);
+ }
+
+ public getTaskCleanupHook(taskId: string): AnyOnCleanupHookFunction | undefined {
+ return this.#getManager().getTaskCleanupHook(taskId);
+ }
+
+ public getGlobalCleanupHooks(): RegisteredHookFunction[] {
+ return this.#getManager().getGlobalCleanupHooks();
+ }
+
+ public callOnWaitHookListeners(wait: TaskWait): Promise {
+ return this.#getManager().callOnWaitHookListeners(wait);
+ }
+
+ public callOnResumeHookListeners(wait: TaskWait): Promise {
+ return this.#getManager().callOnResumeHookListeners(wait);
+ }
+
+ public registerOnWaitHookListener(listener: (wait: TaskWait) => Promise): void {
+ this.#getManager().registerOnWaitHookListener(listener);
+ }
+
+ public registerOnResumeHookListener(listener: (wait: TaskWait) => Promise): void {
+ this.#getManager().registerOnResumeHookListener(listener);
+ }
+
+ #getManager(): LifecycleHooksManager {
+ return getGlobal(API_NAME) ?? NOOP_LIFECYCLE_HOOKS_MANAGER;
+ }
+}
diff --git a/packages/core/src/v3/lifecycleHooks/manager.ts b/packages/core/src/v3/lifecycleHooks/manager.ts
new file mode 100644
index 0000000000..29f4968362
--- /dev/null
+++ b/packages/core/src/v3/lifecycleHooks/manager.ts
@@ -0,0 +1,603 @@
+import {
+ AnyOnInitHookFunction,
+ AnyOnStartHookFunction,
+ LifecycleHooksManager,
+ RegisteredHookFunction,
+ RegisterHookFunctionParams,
+ AnyOnFailureHookFunction,
+ AnyOnSuccessHookFunction,
+ AnyOnCompleteHookFunction,
+ AnyOnWaitHookFunction,
+ AnyOnResumeHookFunction,
+ AnyOnCatchErrorHookFunction,
+ AnyOnMiddlewareHookFunction,
+ AnyOnCleanupHookFunction,
+ TaskWait,
+} from "./types.js";
+
+export class StandardLifecycleHooksManager implements LifecycleHooksManager {
+ private globalInitHooks: Map> = new Map();
+ private taskInitHooks: Map> = new Map();
+
+ private globalStartHooks: Map> = new Map();
+ private taskStartHooks: Map> = new Map();
+
+ private globalFailureHooks: Map> =
+ new Map();
+ private taskFailureHooks: Map> =
+ new Map();
+
+ private globalSuccessHooks: Map> =
+ new Map();
+ private taskSuccessHooks: Map> =
+ new Map();
+
+ private globalCompleteHooks: Map> =
+ new Map();
+ private taskCompleteHooks: Map> =
+ new Map();
+
+ private globalWaitHooks: Map> = new Map();
+ private taskWaitHooks: Map> = new Map();
+
+ private globalResumeHooks: Map> =
+ new Map();
+ private taskResumeHooks: Map> = new Map();
+
+ private globalCatchErrorHooks: Map> =
+ new Map();
+ private taskCatchErrorHooks: Map> =
+ new Map();
+
+ private globalMiddlewareHooks: Map> =
+ new Map();
+ private taskMiddlewareHooks: Map> =
+ new Map();
+
+ private globalCleanupHooks: Map> =
+ new Map();
+ private taskCleanupHooks: Map> =
+ new Map();
+
+ private onWaitHookListeners: ((wait: TaskWait) => Promise)[] = [];
+ private onResumeHookListeners: ((wait: TaskWait) => Promise)[] = [];
+
+ registerOnWaitHookListener(listener: (wait: TaskWait) => Promise): void {
+ this.onWaitHookListeners.push(listener);
+ }
+
+ async callOnWaitHookListeners(wait: TaskWait): Promise {
+ await Promise.allSettled(this.onWaitHookListeners.map((listener) => listener(wait)));
+ }
+
+ registerOnResumeHookListener(listener: (wait: TaskWait) => Promise): void {
+ this.onResumeHookListeners.push(listener);
+ }
+
+ async callOnResumeHookListeners(wait: TaskWait): Promise {
+ await Promise.allSettled(this.onResumeHookListeners.map((listener) => listener(wait)));
+ }
+
+ registerGlobalStartHook(hook: RegisterHookFunctionParams): void {
+ const id = generateHookId(hook);
+
+ this.globalStartHooks.set(id, {
+ id,
+ name: hook.id,
+ fn: hook.fn,
+ });
+ }
+
+ registerTaskStartHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ const id = generateHookId(hook);
+
+ this.taskStartHooks.set(taskId, {
+ id,
+ name: hook.id,
+ fn: hook.fn,
+ });
+ }
+
+ getTaskStartHook(taskId: string): AnyOnStartHookFunction | undefined {
+ return this.taskStartHooks.get(taskId)?.fn;
+ }
+
+ getGlobalStartHooks(): RegisteredHookFunction[] {
+ return Array.from(this.globalStartHooks.values());
+ }
+
+ registerGlobalInitHook(hook: RegisterHookFunctionParams): void {
+ // if there is no id, lets generate one based on the contents of the function
+ const id = generateHookId(hook);
+
+ const registeredHook = {
+ id,
+ name: hook.id,
+ fn: hook.fn,
+ };
+
+ this.globalInitHooks.set(id, registeredHook);
+ }
+
+ registerTaskInitHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ const registeredHook = {
+ id: generateHookId(hook),
+ name: taskId,
+ fn: hook.fn,
+ };
+
+ this.taskInitHooks.set(taskId, registeredHook);
+ }
+
+ getTaskInitHook(taskId: string): AnyOnInitHookFunction | undefined {
+ return this.taskInitHooks.get(taskId)?.fn;
+ }
+
+ getGlobalInitHooks(): RegisteredHookFunction[] {
+ return Array.from(this.globalInitHooks.values());
+ }
+
+ registerGlobalFailureHook(hook: RegisterHookFunctionParams): void {
+ const id = generateHookId(hook);
+
+ this.globalFailureHooks.set(id, {
+ id,
+ name: hook.id,
+ fn: hook.fn,
+ });
+ }
+
+ registerTaskFailureHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ const id = generateHookId(hook);
+
+ this.taskFailureHooks.set(taskId, {
+ id,
+ name: hook.id,
+ fn: hook.fn,
+ });
+ }
+
+ getTaskFailureHook(taskId: string): AnyOnFailureHookFunction | undefined {
+ return this.taskFailureHooks.get(taskId)?.fn;
+ }
+
+ getGlobalFailureHooks(): RegisteredHookFunction[] {
+ return Array.from(this.globalFailureHooks.values());
+ }
+
+ registerGlobalSuccessHook(hook: RegisterHookFunctionParams): void {
+ const id = generateHookId(hook);
+
+ this.globalSuccessHooks.set(id, {
+ id,
+ name: hook.id,
+ fn: hook.fn,
+ });
+ }
+
+ registerTaskSuccessHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ const id = generateHookId(hook);
+
+ this.taskSuccessHooks.set(taskId, {
+ id,
+ name: hook.id,
+ fn: hook.fn,
+ });
+ }
+
+ getTaskSuccessHook(taskId: string): AnyOnSuccessHookFunction | undefined {
+ return this.taskSuccessHooks.get(taskId)?.fn;
+ }
+
+ getGlobalSuccessHooks(): RegisteredHookFunction[] {
+ return Array.from(this.globalSuccessHooks.values());
+ }
+
+ registerGlobalCompleteHook(hook: RegisterHookFunctionParams): void {
+ const id = generateHookId(hook);
+
+ this.globalCompleteHooks.set(id, {
+ id,
+ name: hook.id,
+ fn: hook.fn,
+ });
+ }
+
+ registerTaskCompleteHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ const id = generateHookId(hook);
+
+ this.taskCompleteHooks.set(taskId, {
+ id,
+ name: hook.id,
+ fn: hook.fn,
+ });
+ }
+
+ getTaskCompleteHook(taskId: string): AnyOnCompleteHookFunction | undefined {
+ return this.taskCompleteHooks.get(taskId)?.fn;
+ }
+
+ getGlobalCompleteHooks(): RegisteredHookFunction[] {
+ return Array.from(this.globalCompleteHooks.values());
+ }
+
+ registerGlobalWaitHook(hook: RegisterHookFunctionParams): void {
+ const id = generateHookId(hook);
+
+ this.globalWaitHooks.set(id, {
+ id,
+ name: hook.id,
+ fn: hook.fn,
+ });
+ }
+
+ registerTaskWaitHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ const id = generateHookId(hook);
+
+ this.taskWaitHooks.set(taskId, {
+ id,
+ name: hook.id,
+ fn: hook.fn,
+ });
+ }
+
+ getTaskWaitHook(taskId: string): AnyOnWaitHookFunction | undefined {
+ return this.taskWaitHooks.get(taskId)?.fn;
+ }
+
+ getGlobalWaitHooks(): RegisteredHookFunction[] {
+ return Array.from(this.globalWaitHooks.values());
+ }
+
+ registerGlobalResumeHook(hook: RegisterHookFunctionParams): void {
+ const id = generateHookId(hook);
+
+ this.globalResumeHooks.set(id, {
+ id,
+ name: hook.id,
+ fn: hook.fn,
+ });
+ }
+
+ registerTaskResumeHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ const id = generateHookId(hook);
+
+ this.taskResumeHooks.set(taskId, {
+ id,
+ name: hook.id,
+ fn: hook.fn,
+ });
+ }
+
+ getTaskResumeHook(taskId: string): AnyOnResumeHookFunction | undefined {
+ return this.taskResumeHooks.get(taskId)?.fn;
+ }
+
+ getGlobalResumeHooks(): RegisteredHookFunction[] {
+ return Array.from(this.globalResumeHooks.values());
+ }
+
+ registerGlobalCatchErrorHook(
+ hook: RegisterHookFunctionParams
+ ): void {
+ const id = generateHookId(hook);
+
+ this.globalCatchErrorHooks.set(id, {
+ id,
+ name: hook.id,
+ fn: hook.fn,
+ });
+ }
+
+ registerTaskCatchErrorHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ const id = generateHookId(hook);
+
+ this.taskCatchErrorHooks.set(taskId, {
+ id,
+ name: hook.id,
+ fn: hook.fn,
+ });
+ }
+
+ getTaskCatchErrorHook(taskId: string): AnyOnCatchErrorHookFunction | undefined {
+ return this.taskCatchErrorHooks.get(taskId)?.fn;
+ }
+
+ getGlobalCatchErrorHooks(): RegisteredHookFunction[] {
+ return Array.from(this.globalCatchErrorHooks.values());
+ }
+
+ registerGlobalMiddlewareHook(
+ hook: RegisterHookFunctionParams
+ ): void {
+ const id = generateHookId(hook);
+
+ this.globalMiddlewareHooks.set(id, {
+ id,
+ name: hook.id,
+ fn: hook.fn,
+ });
+ }
+
+ registerTaskMiddlewareHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ const id = generateHookId(hook);
+
+ this.taskMiddlewareHooks.set(taskId, {
+ id,
+ name: hook.id,
+ fn: hook.fn,
+ });
+ }
+
+ getTaskMiddlewareHook(taskId: string): AnyOnMiddlewareHookFunction | undefined {
+ return this.taskMiddlewareHooks.get(taskId)?.fn;
+ }
+
+ getGlobalMiddlewareHooks(): RegisteredHookFunction[] {
+ return Array.from(this.globalMiddlewareHooks.values());
+ }
+
+ registerGlobalCleanupHook(hook: RegisterHookFunctionParams): void {
+ const id = generateHookId(hook);
+
+ this.globalCleanupHooks.set(id, {
+ id,
+ name: hook.id,
+ fn: hook.fn,
+ });
+ }
+
+ registerTaskCleanupHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ const id = generateHookId(hook);
+
+ this.taskCleanupHooks.set(taskId, {
+ id,
+ name: hook.id,
+ fn: hook.fn,
+ });
+ }
+
+ getTaskCleanupHook(taskId: string): AnyOnCleanupHookFunction | undefined {
+ return this.taskCleanupHooks.get(taskId)?.fn;
+ }
+
+ getGlobalCleanupHooks(): RegisteredHookFunction[] {
+ return Array.from(this.globalCleanupHooks.values());
+ }
+}
+
+export class NoopLifecycleHooksManager implements LifecycleHooksManager {
+ registerOnWaitHookListener(listener: (wait: TaskWait) => Promise): void {
+ // Noop
+ }
+
+ async callOnWaitHookListeners(wait: TaskWait): Promise {
+ // Noop
+ }
+
+ registerOnResumeHookListener(listener: (wait: TaskWait) => Promise): void {
+ // Noop
+ }
+
+ async callOnResumeHookListeners(wait: TaskWait): Promise {
+ // Noop
+ }
+
+ registerGlobalInitHook(hook: RegisterHookFunctionParams): void {
+ // Noop
+ }
+
+ registerTaskInitHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ // Noop
+ }
+
+ getTaskInitHook(taskId: string): AnyOnInitHookFunction | undefined {
+ return undefined;
+ }
+
+ getGlobalInitHooks(): RegisteredHookFunction[] {
+ return [];
+ }
+
+ registerGlobalStartHook(hook: RegisterHookFunctionParams): void {
+ // Noop
+ }
+
+ registerTaskStartHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ // Noop
+ }
+
+ getTaskStartHook(taskId: string): AnyOnStartHookFunction | undefined {
+ return undefined;
+ }
+
+ getGlobalStartHooks(): RegisteredHookFunction[] {
+ return [];
+ }
+
+ registerGlobalFailureHook(hook: RegisterHookFunctionParams): void {
+ // Noop
+ }
+
+ registerTaskFailureHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ // Noop
+ }
+
+ getTaskFailureHook(taskId: string): AnyOnFailureHookFunction | undefined {
+ return undefined;
+ }
+
+ getGlobalFailureHooks(): RegisteredHookFunction[] {
+ return [];
+ }
+
+ registerGlobalSuccessHook(hook: RegisterHookFunctionParams): void {
+ // Noop
+ }
+
+ registerTaskSuccessHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ // Noop
+ }
+
+ getTaskSuccessHook(taskId: string): AnyOnSuccessHookFunction | undefined {
+ return undefined;
+ }
+
+ getGlobalSuccessHooks(): RegisteredHookFunction[] {
+ return [];
+ }
+
+ registerGlobalCompleteHook(hook: RegisterHookFunctionParams): void {
+ // Noop
+ }
+
+ registerTaskCompleteHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ // Noop
+ }
+
+ getTaskCompleteHook(taskId: string): AnyOnCompleteHookFunction | undefined {
+ return undefined;
+ }
+
+ getGlobalCompleteHooks(): RegisteredHookFunction[] {
+ return [];
+ }
+
+ registerGlobalWaitHook(hook: RegisterHookFunctionParams): void {
+ // Noop
+ }
+
+ registerTaskWaitHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ // Noop
+ }
+
+ getTaskWaitHook(taskId: string): AnyOnWaitHookFunction | undefined {
+ return undefined;
+ }
+
+ getGlobalWaitHooks(): RegisteredHookFunction[] {
+ return [];
+ }
+
+ registerGlobalResumeHook(hook: RegisterHookFunctionParams): void {
+ // Noop
+ }
+
+ registerTaskResumeHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ // Noop
+ }
+
+ getTaskResumeHook(taskId: string): AnyOnResumeHookFunction | undefined {
+ return undefined;
+ }
+
+ getGlobalResumeHooks(): RegisteredHookFunction[] {
+ return [];
+ }
+
+ registerGlobalCatchErrorHook(): void {
+ // Noop
+ }
+
+ registerTaskCatchErrorHook(): void {
+ // Noop
+ }
+
+ getTaskCatchErrorHook(): undefined {
+ return undefined;
+ }
+
+ getGlobalCatchErrorHooks(): [] {
+ return [];
+ }
+
+ registerGlobalMiddlewareHook(): void {
+ // Noop
+ }
+
+ registerTaskMiddlewareHook(): void {
+ // Noop
+ }
+
+ getTaskMiddlewareHook(): undefined {
+ return undefined;
+ }
+
+ getGlobalMiddlewareHooks(): [] {
+ return [];
+ }
+
+ registerGlobalCleanupHook(hook: RegisterHookFunctionParams): void {
+ // Noop
+ }
+
+ registerTaskCleanupHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void {
+ // Noop
+ }
+
+ getTaskCleanupHook(taskId: string): AnyOnCleanupHookFunction | undefined {
+ return undefined;
+ }
+
+ getGlobalCleanupHooks(): RegisteredHookFunction[] {
+ return [];
+ }
+}
+
+function generateHookId(hook: RegisterHookFunctionParams): string {
+ return hook.id ?? hook.fn.toString();
+}
diff --git a/packages/core/src/v3/lifecycleHooks/types.ts b/packages/core/src/v3/lifecycleHooks/types.ts
new file mode 100644
index 0000000000..5d307c225b
--- /dev/null
+++ b/packages/core/src/v3/lifecycleHooks/types.ts
@@ -0,0 +1,310 @@
+import { RetryOptions, TaskRunContext } from "../schemas/index.js";
+import { HandleErrorResult } from "../types/index.js";
+
+export type TaskInitOutput = Record | void | undefined;
+
+export type TaskInitHookParams = {
+ ctx: TaskRunContext;
+ payload: TPayload;
+ task: string;
+ signal?: AbortSignal;
+};
+
+export type OnInitHookFunction = (
+ params: TaskInitHookParams
+) => TInitOutput | undefined | void | Promise;
+
+export type AnyOnInitHookFunction = OnInitHookFunction;
+
+export type TaskStartHookParams<
+ TPayload = unknown,
+ TInitOutput extends TaskInitOutput = TaskInitOutput,
+> = {
+ ctx: TaskRunContext;
+ payload: TPayload;
+ task: string;
+ signal?: AbortSignal;
+ init?: TInitOutput;
+};
+
+export type OnStartHookFunction = (
+ params: TaskStartHookParams
+) => undefined | void | Promise;
+
+export type AnyOnStartHookFunction = OnStartHookFunction;
+
+export type TaskWait =
+ | {
+ type: "duration";
+ date: Date;
+ }
+ | {
+ type: "token";
+ token: string;
+ }
+ | {
+ type: "task";
+ runId: string;
+ }
+ | {
+ type: "batch";
+ batchId: string;
+ runCount: number;
+ };
+
+export type TaskWaitHookParams<
+ TPayload = unknown,
+ TInitOutput extends TaskInitOutput = TaskInitOutput,
+> = {
+ wait: TaskWait;
+ ctx: TaskRunContext;
+ payload: TPayload;
+ task: string;
+ signal?: AbortSignal;
+ init?: TInitOutput;
+};
+
+export type OnWaitHookFunction = (
+ params: TaskWaitHookParams
+) => undefined | void | Promise;
+
+export type AnyOnWaitHookFunction = OnWaitHookFunction;
+
+export type TaskResumeHookParams<
+ TPayload = unknown,
+ TInitOutput extends TaskInitOutput = TaskInitOutput,
+> = {
+ ctx: TaskRunContext;
+ wait: TaskWait;
+ payload: TPayload;
+ task: string;
+ signal?: AbortSignal;
+ init?: TInitOutput;
+};
+
+export type OnResumeHookFunction = (
+ params: TaskResumeHookParams
+) => undefined | void | Promise;
+
+export type AnyOnResumeHookFunction = OnResumeHookFunction;
+
+export type TaskFailureHookParams<
+ TPayload = unknown,
+ TInitOutput extends TaskInitOutput = TaskInitOutput,
+> = {
+ ctx: TaskRunContext;
+ payload: TPayload;
+ task: string;
+ error: unknown;
+ signal?: AbortSignal;
+ init?: TInitOutput;
+};
+
+export type OnFailureHookFunction = (
+ params: TaskFailureHookParams
+) => undefined | void | Promise;
+
+export type AnyOnFailureHookFunction = OnFailureHookFunction;
+
+export type TaskSuccessHookParams<
+ TPayload = unknown,
+ TOutput = unknown,
+ TInitOutput extends TaskInitOutput = TaskInitOutput,
+> = {
+ ctx: TaskRunContext;
+ payload: TPayload;
+ task: string;
+ output: TOutput;
+ signal?: AbortSignal;
+ init?: TInitOutput;
+};
+
+export type OnSuccessHookFunction<
+ TPayload,
+ TOutput,
+ TInitOutput extends TaskInitOutput = TaskInitOutput,
+> = (
+ params: TaskSuccessHookParams
+) => undefined | void | Promise;
+
+export type AnyOnSuccessHookFunction = OnSuccessHookFunction;
+
+export type TaskCompleteSuccessResult = {
+ ok: true;
+ data: TOutput;
+};
+
+export type TaskCompleteErrorResult = {
+ ok: false;
+ error: unknown;
+};
+
+export type TaskCompleteResult =
+ | TaskCompleteSuccessResult
+ | TaskCompleteErrorResult;
+
+export type TaskCompleteHookParams<
+ TPayload = unknown,
+ TOutput = unknown,
+ TInitOutput extends TaskInitOutput = TaskInitOutput,
+> = {
+ ctx: TaskRunContext;
+ payload: TPayload;
+ task: string;
+ result: TaskCompleteResult;
+ signal?: AbortSignal;
+ init?: TInitOutput;
+};
+
+export type OnCompleteHookFunction<
+ TPayload,
+ TOutput,
+ TInitOutput extends TaskInitOutput = TaskInitOutput,
+> = (
+ params: TaskCompleteHookParams
+) => undefined | void | Promise;
+
+export type AnyOnCompleteHookFunction = OnCompleteHookFunction;
+
+export type RegisterHookFunctionParams any> = {
+ id?: string;
+ fn: THookFunction;
+};
+
+export type RegisteredHookFunction any> = {
+ id: string;
+ name?: string;
+ fn: THookFunction;
+};
+
+export type TaskCatchErrorHookParams<
+ TPayload = unknown,
+ TInitOutput extends TaskInitOutput = TaskInitOutput,
+> = {
+ ctx: TaskRunContext;
+ payload: TPayload;
+ task: string;
+ error: unknown;
+ retry?: RetryOptions;
+ retryAt?: Date;
+ retryDelayInMs?: number;
+ signal?: AbortSignal;
+ init?: TInitOutput;
+};
+
+export type OnCatchErrorHookFunction<
+ TPayload,
+ TInitOutput extends TaskInitOutput = TaskInitOutput,
+> = (params: TaskCatchErrorHookParams) => HandleErrorResult;
+
+export type AnyOnCatchErrorHookFunction = OnCatchErrorHookFunction;
+
+export type TaskMiddlewareHookParams = {
+ ctx: TaskRunContext;
+ payload: TPayload;
+ task: string;
+ signal?: AbortSignal;
+ next: () => Promise;
+};
+
+export type OnMiddlewareHookFunction = (
+ params: TaskMiddlewareHookParams
+) => Promise;
+
+export type AnyOnMiddlewareHookFunction = OnMiddlewareHookFunction;
+
+export type TaskCleanupHookParams<
+ TPayload = unknown,
+ TInitOutput extends TaskInitOutput = TaskInitOutput,
+> = {
+ ctx: TaskRunContext;
+ payload: TPayload;
+ task: string;
+ signal?: AbortSignal;
+ init?: TInitOutput;
+};
+
+export type OnCleanupHookFunction = (
+ params: TaskCleanupHookParams
+) => undefined | void | Promise;
+
+export type AnyOnCleanupHookFunction = OnCleanupHookFunction;
+
+export interface LifecycleHooksManager {
+ registerGlobalInitHook(hook: RegisterHookFunctionParams): void;
+ registerTaskInitHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void;
+ getTaskInitHook(taskId: string): AnyOnInitHookFunction | undefined;
+ getGlobalInitHooks(): RegisteredHookFunction[];
+ registerGlobalStartHook(hook: RegisterHookFunctionParams): void;
+ registerTaskStartHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void;
+ getTaskStartHook(taskId: string): AnyOnStartHookFunction | undefined;
+ getGlobalStartHooks(): RegisteredHookFunction[];
+ registerGlobalFailureHook(hook: RegisterHookFunctionParams): void;
+ registerTaskFailureHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void;
+ getTaskFailureHook(taskId: string): AnyOnFailureHookFunction | undefined;
+ getGlobalFailureHooks(): RegisteredHookFunction[];
+ registerGlobalSuccessHook(hook: RegisterHookFunctionParams): void;
+ registerTaskSuccessHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void;
+ getTaskSuccessHook(taskId: string): AnyOnSuccessHookFunction | undefined;
+ getGlobalSuccessHooks(): RegisteredHookFunction[];
+ registerGlobalCompleteHook(hook: RegisterHookFunctionParams): void;
+ registerTaskCompleteHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void;
+ getTaskCompleteHook(taskId: string): AnyOnCompleteHookFunction | undefined;
+ getGlobalCompleteHooks(): RegisteredHookFunction[];
+ registerGlobalWaitHook(hook: RegisterHookFunctionParams): void;
+ registerTaskWaitHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void;
+ getTaskWaitHook(taskId: string): AnyOnWaitHookFunction | undefined;
+ getGlobalWaitHooks(): RegisteredHookFunction[];
+ registerGlobalResumeHook(hook: RegisterHookFunctionParams): void;
+ registerTaskResumeHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void;
+ getTaskResumeHook(taskId: string): AnyOnResumeHookFunction | undefined;
+ getGlobalResumeHooks(): RegisteredHookFunction[];
+ registerGlobalCatchErrorHook(hook: RegisterHookFunctionParams): void;
+ registerTaskCatchErrorHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void;
+ getTaskCatchErrorHook(taskId: string): AnyOnCatchErrorHookFunction | undefined;
+ getGlobalCatchErrorHooks(): RegisteredHookFunction[];
+ registerGlobalMiddlewareHook(hook: RegisterHookFunctionParams): void;
+ registerTaskMiddlewareHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void;
+ getTaskMiddlewareHook(taskId: string): AnyOnMiddlewareHookFunction | undefined;
+ getGlobalMiddlewareHooks(): RegisteredHookFunction[];
+ registerGlobalCleanupHook(hook: RegisterHookFunctionParams): void;
+ registerTaskCleanupHook(
+ taskId: string,
+ hook: RegisterHookFunctionParams
+ ): void;
+ getTaskCleanupHook(taskId: string): AnyOnCleanupHookFunction | undefined;
+ getGlobalCleanupHooks(): RegisteredHookFunction[];
+
+ callOnWaitHookListeners(wait: TaskWait): Promise;
+ registerOnWaitHookListener(listener: (wait: TaskWait) => Promise): void;
+
+ callOnResumeHookListeners(wait: TaskWait): Promise;
+ registerOnResumeHookListener(listener: (wait: TaskWait) => Promise): void;
+}
diff --git a/packages/core/src/v3/locals-api.ts b/packages/core/src/v3/locals-api.ts
new file mode 100644
index 0000000000..a9f86494a6
--- /dev/null
+++ b/packages/core/src/v3/locals-api.ts
@@ -0,0 +1,29 @@
+// Split module-level variable definition into separate files to allow
+// tree-shaking on each api instance.
+import { LocalsAPI } from "./locals/index.js";
+import type { LocalsKey } from "./locals/types.js";
+/** Entrypoint for runtime API */
+export const localsAPI = LocalsAPI.getInstance();
+
+export const locals = {
+ create(id: string): LocalsKey {
+ return localsAPI.createLocal(id);
+ },
+ get(key: LocalsKey): T | undefined {
+ return localsAPI.getLocal(key);
+ },
+ getOrThrow(key: LocalsKey): T {
+ const value = localsAPI.getLocal(key);
+ if (!value) {
+ throw new Error(`Local with id ${key.id} not found`);
+ }
+ return value;
+ },
+ set(key: LocalsKey, value: T): T {
+ localsAPI.setLocal(key, value);
+ return value;
+ },
+};
+
+export type Locals = typeof locals;
+export type { LocalsKey };
diff --git a/packages/core/src/v3/locals/index.ts b/packages/core/src/v3/locals/index.ts
new file mode 100644
index 0000000000..def8602384
--- /dev/null
+++ b/packages/core/src/v3/locals/index.ts
@@ -0,0 +1,45 @@
+const API_NAME = "locals";
+
+import { getGlobal, registerGlobal, unregisterGlobal } from "../utils/globals.js";
+import { NoopLocalsManager } from "./manager.js";
+import { LocalsKey, type LocalsManager } from "./types.js";
+
+const NOOP_LOCALS_MANAGER = new NoopLocalsManager();
+
+export class LocalsAPI implements LocalsManager {
+ private static _instance?: LocalsAPI;
+
+ private constructor() {}
+
+ public static getInstance(): LocalsAPI {
+ if (!this._instance) {
+ this._instance = new LocalsAPI();
+ }
+
+ return this._instance;
+ }
+
+ public setGlobalLocalsManager(localsManager: LocalsManager): boolean {
+ return registerGlobal(API_NAME, localsManager);
+ }
+
+ public disable() {
+ unregisterGlobal(API_NAME);
+ }
+
+ public createLocal(id: string): LocalsKey {
+ return this.#getManager().createLocal(id);
+ }
+
+ public getLocal(key: LocalsKey): T | undefined {
+ return this.#getManager().getLocal(key);
+ }
+
+ public setLocal(key: LocalsKey, value: T): void {
+ return this.#getManager().setLocal(key, value);
+ }
+
+ #getManager(): LocalsManager {
+ return getGlobal(API_NAME) ?? NOOP_LOCALS_MANAGER;
+ }
+}
diff --git a/packages/core/src/v3/locals/manager.ts b/packages/core/src/v3/locals/manager.ts
new file mode 100644
index 0000000000..befe219260
--- /dev/null
+++ b/packages/core/src/v3/locals/manager.ts
@@ -0,0 +1,37 @@
+import { LocalsKey, LocalsManager } from "./types.js";
+
+export class NoopLocalsManager implements LocalsManager {
+ createLocal(id: string): LocalsKey {
+ return {
+ __type: Symbol(),
+ id,
+ } as unknown as LocalsKey;
+ }
+
+ getLocal(key: LocalsKey): T | undefined {
+ return undefined;
+ }
+
+ setLocal(key: LocalsKey, value: T): void {}
+}
+
+export class StandardLocalsManager implements LocalsManager {
+ private store: Map = new Map();
+
+ createLocal(id: string): LocalsKey {
+ const key = Symbol.for(id);
+ return {
+ __type: key,
+ id,
+ } as unknown as LocalsKey;
+ }
+
+ getLocal(key: LocalsKey): T | undefined {
+ return this.store.get(key.__type) as T | undefined;
+ }
+
+ setLocal(key: LocalsKey, value: T): void {
+ this.store.set(key.__type, value);
+ }
+}
+0;
diff --git a/packages/core/src/v3/locals/types.ts b/packages/core/src/v3/locals/types.ts
new file mode 100644
index 0000000000..aab683df09
--- /dev/null
+++ b/packages/core/src/v3/locals/types.ts
@@ -0,0 +1,14 @@
+declare const __local: unique symbol;
+type BrandLocal = { [__local]: T };
+
+// Create a type-safe store for your locals
+export type LocalsKey = BrandLocal & {
+ readonly id: string;
+ readonly __type: unique symbol;
+};
+
+export interface LocalsManager {
+ createLocal(id: string): LocalsKey;
+ getLocal(key: LocalsKey): T | undefined;
+ setLocal(key: LocalsKey, value: T): void;
+}
diff --git a/packages/core/src/v3/runtime/managedRuntimeManager.ts b/packages/core/src/v3/runtime/managedRuntimeManager.ts
index 67ef064498..b876a87084 100644
--- a/packages/core/src/v3/runtime/managedRuntimeManager.ts
+++ b/packages/core/src/v3/runtime/managedRuntimeManager.ts
@@ -1,3 +1,4 @@
+import { lifecycleHooks } from "../lifecycle-hooks-api.js";
import {
BatchTaskRunExecutionResult,
CompletedWaitpoint,
@@ -44,9 +45,19 @@ export class ManagedRuntimeManager implements RuntimeManager {
this.resolversByWaitId.set(params.id, resolve);
});
+ await lifecycleHooks.callOnWaitHookListeners({
+ type: "task",
+ runId: params.id,
+ });
+
const waitpoint = await promise;
const result = this.waitpointToTaskRunExecutionResult(waitpoint);
+ await lifecycleHooks.callOnResumeHookListeners({
+ type: "task",
+ runId: params.id,
+ });
+
return result;
});
}
@@ -70,8 +81,20 @@ export class ManagedRuntimeManager implements RuntimeManager {
})
);
+ await lifecycleHooks.callOnWaitHookListeners({
+ type: "batch",
+ batchId: params.id,
+ runCount: params.runCount,
+ });
+
const waitpoints = await promise;
+ await lifecycleHooks.callOnResumeHookListeners({
+ type: "batch",
+ batchId: params.id,
+ runCount: params.runCount,
+ });
+
return {
id: params.id,
items: waitpoints.map(this.waitpointToTaskRunExecutionResult),
@@ -91,8 +114,32 @@ export class ManagedRuntimeManager implements RuntimeManager {
this.resolversByWaitId.set(waitpointFriendlyId, resolve);
});
+ if (finishDate) {
+ await lifecycleHooks.callOnWaitHookListeners({
+ type: "duration",
+ date: finishDate,
+ });
+ } else {
+ await lifecycleHooks.callOnWaitHookListeners({
+ type: "token",
+ token: waitpointFriendlyId,
+ });
+ }
+
const waitpoint = await promise;
+ if (finishDate) {
+ await lifecycleHooks.callOnResumeHookListeners({
+ type: "duration",
+ date: finishDate,
+ });
+ } else {
+ await lifecycleHooks.callOnResumeHookListeners({
+ type: "token",
+ token: waitpointFriendlyId,
+ });
+ }
+
return {
ok: !waitpoint.outputIsError,
output: waitpoint.output,
diff --git a/packages/core/src/v3/schemas/build.ts b/packages/core/src/v3/schemas/build.ts
index 0b122af2ed..c3df04eaa7 100644
--- a/packages/core/src/v3/schemas/build.ts
+++ b/packages/core/src/v3/schemas/build.ts
@@ -38,6 +38,7 @@ export const BuildManifest = z.object({
indexWorkerEntryPoint: z.string(), // Dev & Deploy has a indexWorkerEntryPoint
indexControllerEntryPoint: z.string().optional(), // Only deploy has a indexControllerEntryPoint
loaderEntryPoint: z.string().optional(),
+ initEntryPoint: z.string().optional(), // Optional init.ts entry point
configPath: z.string(),
externals: BuildExternal.array().optional(),
build: z.object({
@@ -85,6 +86,7 @@ export const WorkerManifest = z.object({
workerEntryPoint: z.string(),
controllerEntryPoint: z.string().optional(),
loaderEntryPoint: z.string().optional(),
+ initEntryPoint: z.string().optional(), // Optional init.ts entry point
runtime: BuildRuntime,
customConditions: z.array(z.string()).optional(),
otelImportHook: z
diff --git a/packages/core/src/v3/schemas/common.ts b/packages/core/src/v3/schemas/common.ts
index 43030847dc..5467603e9d 100644
--- a/packages/core/src/v3/schemas/common.ts
+++ b/packages/core/src/v3/schemas/common.ts
@@ -165,6 +165,7 @@ export const TaskRunInternalError = z.object({
"TASK_RUN_CANCELLED",
"TASK_INPUT_ERROR",
"TASK_OUTPUT_ERROR",
+ "TASK_MIDDLEWARE_ERROR",
"HANDLE_ERROR_ERROR",
"GRACEFUL_EXIT_TIMEOUT",
"TASK_RUN_HEARTBEAT_TIMEOUT",
diff --git a/packages/core/src/v3/semanticInternalAttributes.ts b/packages/core/src/v3/semanticInternalAttributes.ts
index 63549bea25..4cfc03e8ec 100644
--- a/packages/core/src/v3/semanticInternalAttributes.ts
+++ b/packages/core/src/v3/semanticInternalAttributes.ts
@@ -33,6 +33,7 @@ export const SemanticInternalAttributes = {
STYLE_ICON: "$style.icon",
STYLE_VARIANT: "$style.variant",
STYLE_ACCESSORY: "$style.accessory",
+ COLLAPSED: "$collapsed",
METADATA: "$metadata",
TRIGGER: "$trigger",
PAYLOAD: "$payload",
diff --git a/packages/core/src/v3/tracer.ts b/packages/core/src/v3/tracer.ts
index 085107c017..42bc2f249e 100644
--- a/packages/core/src/v3/tracer.ts
+++ b/packages/core/src/v3/tracer.ts
@@ -168,7 +168,7 @@ export class TriggerTracer {
const attributes = options?.attributes ?? {};
- const span = this.tracer.startSpan(name, options, ctx);
+ const span = this.tracer.startSpan(name, options, parentContext);
this.tracer
.startSpan(
diff --git a/packages/core/src/v3/types/tasks.ts b/packages/core/src/v3/types/tasks.ts
index e8e54771c4..8c1e4d1014 100644
--- a/packages/core/src/v3/types/tasks.ts
+++ b/packages/core/src/v3/types/tasks.ts
@@ -1,5 +1,18 @@
import { SerializableJson } from "../../schemas/json.js";
import { TriggerApiRequestOptions } from "../apiClient/index.js";
+import {
+ AnyOnCatchErrorHookFunction,
+ OnCatchErrorHookFunction,
+ OnCleanupHookFunction,
+ OnCompleteHookFunction,
+ OnFailureHookFunction,
+ OnInitHookFunction,
+ OnMiddlewareHookFunction,
+ OnResumeHookFunction,
+ OnStartHookFunction,
+ OnSuccessHookFunction,
+ OnWaitHookFunction,
+} from "../lifecycleHooks/types.js";
import { RunTags } from "../schemas/api.js";
import {
MachineCpu,
@@ -10,10 +23,10 @@ import {
TaskRunContext,
} from "../schemas/index.js";
import { IdempotencyKey } from "./idempotencyKeys.js";
+import { QueueOptions } from "./queues.js";
import { AnySchemaParseFn, inferSchemaIn, inferSchemaOut, Schema } from "./schemas.js";
-import { Prettify } from "./utils.js";
import { inferToolParameters, ToolTaskParameters } from "./tools.js";
-import { QueueOptions } from "./queues.js";
+import { Prettify } from "./utils.js";
export type Queue = QueueOptions;
export type TaskSchema = Schema;
@@ -94,6 +107,7 @@ export type InitFnParams = Prettify<{
export type StartFnParams = Prettify<{
ctx: Context;
+ init?: InitOutput;
/** Abort signal that is aborted when a task run exceeds it's maxDuration. Can be used to automatically cancel downstream requests */
signal?: AbortSignal;
}>;
@@ -127,7 +141,7 @@ export type HandleErrorResult =
export type HandleErrorArgs = {
ctx: Context;
- init: unknown;
+ init?: Record;
retry?: RetryOptions;
retryAt?: Date;
retryDelayInMs?: number;
@@ -258,22 +272,33 @@ type CommonTaskOptions<
/**
* init is called before the run function is called. It's useful for setting up any global state.
+ *
+ * @deprecated Use locals and middleware instead
*/
- init?: (payload: TPayload, params: InitFnParams) => Promise;
+ init?: OnInitHookFunction;
/**
* cleanup is called after the run function has completed.
+ *
+ * @deprecated Use middleware instead
*/
- cleanup?: (payload: TPayload, params: RunFnParams) => Promise;
+ cleanup?: OnCleanupHookFunction;
/**
* handleError is called when the run function throws an error. It can be used to modify the error or return new retry options.
+ *
+ * @deprecated Use catchError instead
+ */
+ handleError?: OnCatchErrorHookFunction;
+
+ /**
+ * catchError 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;
+ catchError?: OnCatchErrorHookFunction;
+
+ onResume?: OnResumeHookFunction;
+ onWait?: OnWaitHookFunction