Skip to content
Merged
4 changes: 2 additions & 2 deletions ai-context/core-features-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ This document provides the correct import paths and type definitions for commonl

### TaskDefinition

- **Import:** `import { TaskDefinition } from "@webiny/api-core/features/task/TaskDefinition/index.js"`
- **Import:** `import { TaskDefinition, TaskHandler } from "@webiny/api-core/features/task/TaskDefinition/index.js"`
- **Interface Type:** See `packages/api-core/src/features/task/TaskDefinition/abstractions.ts`
- **Usage:** Define background tasks. Use `TaskDefinition.createImplementation({ implementation, dependencies })`. Register with `context.container.register(MyTask)`. The `run` method receives `{ input, controller }` where controller provides `response.done/error/aborted/continue` and `runtime.isAborted/isCloseToTimeout`.
- **Usage:** Define background tasks as a PAIR, in one file. A `TaskHandler` implementation holds `run`, the lifecycle hooks and all the dependencies; a `TaskDefinition` implementation holds the metadata (`id`, `title`, `maxIterations`, `selfCleanup`, …), declares NO dependencies, and names the handler via `handler`. Only the definition is registered: `container.register(MyTaskDefinition)`. Looking a task up by id builds every registered definition, so keeping them dependency-free is the point; the runner resolves the one handler it needs. `run` receives `{ input, controller, definition }`, where `controller` provides `response.done/error/aborted/continue` and `runtime.isAborted/isCloseToTimeout`, and `definition` is the task's own metadata (useful in a `TaskHandler` decorator, which wraps every task).

### TaskService (high-level — trigger/abort)

Expand Down
31 changes: 20 additions & 11 deletions extensions/tasks/SelfCleaningTask.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
import { TaskDefinition } from "webiny/api/tasks";
import { TaskDefinition, TaskHandler } from "webiny/api/tasks";
import { ListModelsUseCase } from "webiny/api/cms/model.js";
import { ListLatestEntriesUseCase } from "webiny/api/cms/entry";

class SelfCleaningTaskImpl implements TaskDefinition.Interface {
public readonly id = "selfCleaningTask";
public readonly title = "Self-Cleaning Task";
public readonly description =
"A task which will remove db records related to itself after execution.";
public readonly isPrivate = false;
public readonly selfCleanup = "always";

class SelfCleaningTaskHandlerImpl implements TaskHandler.Interface {
public constructor(
private readonly listCmsModels: ListModelsUseCase.Interface,
private readonly listLatestCmsEntries: ListLatestEntriesUseCase.Interface
) {}

public async run(params: TaskDefinition.RunParams): Promise<TaskDefinition.Result> {
public async run(params: TaskHandler.RunParams): Promise<TaskHandler.Result> {
// NOTE (temporary): `controller.response` is added to api-core's TaskController by a
// `declare module` augmentation living in @webiny/background-tasks. It only resolves when
// that augmentation is in the compile program (i.e. something imports background-tasks).
Expand Down Expand Up @@ -54,7 +47,23 @@ class SelfCleaningTaskImpl implements TaskDefinition.Interface {
}
}

const SelfCleaningTaskHandler = TaskHandler.createImplementation({
implementation: SelfCleaningTaskHandlerImpl,
dependencies: [ListModelsUseCase, ListLatestEntriesUseCase]
});

class SelfCleaningTaskImpl implements TaskDefinition.Interface {
public readonly id = "selfCleaningTask";
public readonly title = "Self-Cleaning Task";
public readonly description =
"A task which will remove db records related to itself after execution.";
public readonly isPrivate = false;
public readonly selfCleanup = "always";

public readonly handler = SelfCleaningTaskHandler;
}

export default TaskDefinition.createImplementation({
implementation: SelfCleaningTaskImpl,
dependencies: [ListModelsUseCase, ListLatestEntriesUseCase]
dependencies: []
});
31 changes: 20 additions & 11 deletions extensions/tasks/SendEmailTask.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import { TaskDefinition } from "webiny/api/tasks";
import { TaskDefinition, TaskHandler } from "webiny/api/tasks";
import { MailerService } from "webiny/api/mailer";

class SelfCleaningTaskImpl implements TaskDefinition.Interface {
public readonly id = "sendEmailTask";
public readonly title = "Send Email";
public readonly description = "A task which will send an email with stringified input params.";
public readonly isPrivate = false;
public readonly selfCleanup = ["onSuccess" as const, "onAbort" as const];

class SendEmailTaskHandlerImpl implements TaskHandler.Interface {
public constructor(private readonly mailerService: MailerService.Interface) {}

public async run(params: TaskDefinition.RunParams): Promise<TaskDefinition.Result> {
public async run(params: TaskHandler.RunParams): Promise<TaskHandler.Result> {
const { controller, input } = params;

const result = await this.mailerService.sendMail({
Expand All @@ -32,7 +26,22 @@ class SelfCleaningTaskImpl implements TaskDefinition.Interface {
}
}

export default TaskDefinition.createImplementation({
implementation: SelfCleaningTaskImpl,
const SendEmailTaskHandler = TaskHandler.createImplementation({
implementation: SendEmailTaskHandlerImpl,
dependencies: [MailerService]
});

class SendEmailTaskImpl implements TaskDefinition.Interface {
public readonly id = "sendEmailTask";
public readonly title = "Send Email";
public readonly description = "A task which will send an email with stringified input params.";
public readonly isPrivate = false;
public readonly selfCleanup = ["onSuccess" as const, "onAbort" as const];

public readonly handler = SendEmailTaskHandler;
}

export default TaskDefinition.createImplementation({
implementation: SendEmailTaskImpl,
dependencies: []
});
34 changes: 13 additions & 21 deletions packages/api-core/src/features/task/TaskDefinition/abstractions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ export interface ITaskOutput {
* wraps every task; without this it has nothing to branch on but a hardcoded list of ids, which
* means editing the decorator every time a task is added.
*
* This is {@link ITaskMetadata}: identity and policy, no behaviour. The object passed at runtime is
* the resolved task, so `run` is physically present; naming the metadata type here stops the obvious
* mistake of a handler calling `params.definition.run(...)` and recursing forever.
* This is {@link ITaskMetadata}: identity and policy, no behaviour and no handler pointer. The
* object passed at runtime is the resolved task, so `run` is physically present; naming the metadata
* type here stops the obvious mistake of a handler calling `params.definition.run(...)` and
* recursing forever.
*
* Only the fields declared here arrive. A field a project adds to its own definition class is
* currently dropped, because `RunnableTaskDecorator` and `SelfCleaningTaskDecorator` are fixed
* pass-throughs that expose a known set of getters and nothing else. Making them forward unknown
* currently dropped, because `TaskDefinitionDefaultsDecorator` is a fixed pass-through that
* exposes a known set of getters and nothing else. Making them forward unknown
* properties would turn a definition into a place to declare policy (a rate limit, a set of tags)
* that a decorator acts on; `taskDefinitionInParams.test.ts` pins the current behaviour so that
* change announces itself.
Expand Down Expand Up @@ -127,7 +128,7 @@ export type ITaskLifecycleHook<
* What a task IS: identity and runtime policy, with no behaviour and no dependencies.
*
* Listing tasks, resolving one by id, and applying defaults all need only this. Keeping it free of
* dependencies is the whole point — `GetTaskDefinitionUseCase` builds every registered definition
* dependencies is the whole point — `GetRunnableTaskDefinitionUseCase` builds every registered definition
* to find one by id, so anything expensive here is paid 24 times per lookup.
*/
export interface ITaskMetadata {
Expand Down Expand Up @@ -176,26 +177,17 @@ export interface ITaskHandler<
}

/**
* Core TaskDefinition - minimal interface
* What you register: a task's {@link ITaskMetadata} plus the class that does the work.
*
* TRANSITIONAL SHAPE. A definition supplies its behaviour in one of two ways:
*
* - the new way: `handler` names a {@link ITaskHandler} class, which the runner builds only for the
* task it is about to run;
* - the old way: the definition implements `run()` and the hooks itself, which forces every
* definition (and every dependency it injects) to be built just to look one up by id.
*
* `run` is optional ONLY to let both shapes coexist while packages migrate. Once every definition
* carries a `handler`, this interface becomes `ITaskMetadata & { handler }` and the optionality
* goes away. Exactly one of `run` or `handler` must be present; `GetTaskDefinitionUseCase` fails
* with {@link TaskDefinitionNotRunnableError} if neither is.
* The definition takes no dependencies of its own, so `GetRunnableTaskDefinitionUseCase` can build every
* registered one to find a task by id without constructing anything expensive. Only the winner's
* `handler` gets built.
*/
export interface ITaskDefinition<
I extends ITaskInput = ITaskInput,
O extends ITaskOutput = ITaskOutput
>
extends ITaskMetadata, Partial<ITaskHandler<I, O>> {
handler?: Constructor<ITaskHandler<I, O>>;
> extends ITaskMetadata {
handler: Constructor<ITaskHandler<I, O>>;
}

export interface ITaskCreateInputValidationParams {
Expand Down

This file was deleted.

Loading
Loading