chrono-forge
Version:
A comprehensive framework for building resilient Temporal workflows, advanced state management, and real-time streaming activities in TypeScript. Designed for a seamless developer experience with powerful abstractions, dynamic orchestration, and full cont
1,046 lines • 85.7 kB
TypeScript
import * as workflow from '@temporalio/workflow';
import { EntitiesState, EntityAction, EntityStrategy } from '../store';
import { DetailedDiff } from 'deep-object-diff';
import { Schema } from 'normalizr';
import { Workflow, TemporalOptions } from './Workflow';
import { ActionOptions } from '../decorators';
import { EnhancedEntity, SchemaManager } from '../store/SchemaManager';
import { StateManager } from '../store/StateManager';
import { Duration } from '@temporalio/common';
import { LRUCacheWithDelete } from 'mnemonist';
/**
* Configuration structure defining how entity paths and associated workflows are managed.
*
* This type outlines the available options for configuring entity management within workflows. It includes
* essential details such as entity identifiers, workflow behavior, and conditions for workflow execution.
*
* ## Properties
* - **`entityName`** (optional): Represents the entity's name used for identification purposes.
* - **`path`** (optional): Describes the path in the state where the entity data is stored.
* - **`workflowType`** (optional): Specifies the workflow type that corresponds to this entity.
* - **`idAttribute`** (optional): Identifies the attribute(s) that uniquely determine the entity; can be a string or an array for composite keys.
* - **`isMany`** (optional): Boolean indicating whether the path refers to multiple entities.
* - **`includeParentId`** (optional): If true, includes parent identifiers in the workflow IDs to ensure they are unique.
* - **`autoStart`** (optional): Determines if the workflow should initiate automatically once configured conditions are met.
* - **`cancellationType`** (optional): Determines the child workflow's cancellation policy using values from `workflow.ChildWorkflowCancellationType`.
* - **`parentClosePolicy`** (optional): Defines the effect of a parent workflow's closure on the child workflow, using `workflow.ParentClosePolicy`.
* - **`workflowIdConflictPolicy`** (optional): Dictates how to handle workflow ID conflicts, utilizing values from `workflow.WorkflowIdConflictPolicy`.
* - **`workflowIdReusePolicy`** (optional): Indicates policy for workflow ID reuse, using values from `workflow.WorkflowIdReusePolicy`.
* - **`workflowTaskTimeout`** (optional): Duration after which the workflow should time out if it hasn't completed.
* - **`startDelay`** (optional): Duration to wait before starting the workflow.
* - **`retry`** (optional): Configuration object defining retry parameters for the workflow:
* - `initialInterval`: Initial retry interval in milliseconds.
* - `maximumInterval`: Maximum interval for retries.
* - `backoffCoefficient`: Coefficient for retry backoff calculations.
* - `maximumAttempts`: Maximum retry attempts allowed.
* - **`startToCloseTimeout`** (optional): Duration after which the workflow should time out if it hasn't completed.
* - **`condition`** (optional): Function determining whether the workflow should start, given the entity and related data.
* - **`getSubscriptions`** (optional): Function returning an array of subscription configurations based on an update subscription object.
* - **`processData`** (optional): Function for processing entity data before starting the workflow, providing ready-to-use data for workflow context.
*
* ## Usage Example
* ```typescript
* const configPath: ManagedPath = {
* entityName: "User",
* path: "users",
* idAttribute: "userId",
* autoStart: true,
* processData: (entity, data) => ({ ...entity, modified: true }),
* };
* ```
*
* ## Notes
* - The `ManagedPath` type captures a comprehensive set of configuration options for entity management, supporting both single and composite entities.
* - Proper configuration is crucial for robust, automated workflow operations, ensuring effective entity lifecycle handling.
*/
export type ManagedPath = {
entityName?: string;
path?: string;
workflowType?: string;
idAttribute?: string | string[];
isMany?: boolean;
includeParentId?: boolean;
autoStart?: boolean;
cancellationType?: workflow.ChildWorkflowCancellationType;
parentClosePolicy?: workflow.ParentClosePolicy;
workflowIdConflictPolicy?: workflow.WorkflowIdConflictPolicy;
workflowIdReusePolicy?: workflow.WorkflowIdReusePolicy;
workflowTaskTimeout?: Duration;
startDelay?: Duration;
retry?: {
initialInterval?: number;
maximumInterval?: number;
backoffCoefficient?: number;
maximumAttempts?: number;
};
startToCloseTimeout?: string;
condition?: (entity: Record<string, any>, data: StatefulWorkflow['data']) => boolean;
strategy?: '$set' | '$merge';
subscriptions?: {
update?: {
enabled?: boolean;
sync?: boolean;
strategy?: '$set' | '$merge';
selector?: string;
};
delete?: {
enabled?: boolean;
sync?: boolean;
strategy?: '$set' | '$merge';
selector?: string;
};
};
processData?: (entity: Record<string, any>, data: StatefulWorkflow['data']) => Record<string, any>;
};
/**
* Defines the configuration structure for managing entity paths and associated workflows.
*
* This type specifies the layout and options available for configuring how entities are managed
* within workflows, capturing details such as entity identifiers, workflow types, and start conditions.
* @see ManagedPath
*/
export type ManagedPaths = {
[path: string]: ManagedPath;
};
/**
* Subscription configuration for workflow state changes.
*
* Defines how a workflow subscribes to state changes from another workflow,
* specifying what signals to receive and under what conditions.
*
* ## Usage Example
* ```typescript
* const workflowSubscription: Subscription = {
* workflowId: "order-processing-workflow",
* signalName: "orderUpdated",
* selector: "*",
* entityName: "Order",
* condition: (state) => state.status === "Pending",
* };
* ```
*
* ## Notes
* - Subscriptions facilitate complex, event-driven interactions within workflow systems, leveraging properties
* such as `signalName` and `selector` to fine-tune reactions.
* - Optional fields provide flexibility for hierarchical and condition-based designs, enhancing adaptability in various workflows.
* - Proper subscription management ensures responsive and efficient workflows, enabling precise event handling and state transitions.
*
* @property {string} workflowId - The ID of the workflow being subscribed to.
* @property {string} subscriptionId - Unique identifier for this subscription.
* @property {'update' | 'delete'} signalName - The type of signal to subscribe to.
* @property {string} selector - Path selector pattern to filter state changes.
* @property {string} [parent] - Optional parent entity identifier.
* @property {string} [child] - Optional child entity identifier.
* @property {string[]} [ancestorWorkflowIds] - IDs of ancestor workflows to prevent circular updates.
* @property {(state: any) => boolean} [condition] - Optional function to further filter changes.
* @property {boolean} [sync] - Whether updates should be processed synchronously.
* @property {'$set' | '$merge'} [strategy] - Strategy for applying updates to state.
*/
type Subscription = {
workflowId: string;
subscriptionId: string;
signalName: 'update' | 'delete';
selector: string;
parent: string;
child: string;
ancestorWorkflowIds: string[];
condition?: (state: any) => boolean;
sync?: boolean;
strategy?: '$set' | '$merge';
};
/**
* Parameters for initializing a stateful workflow, providing configuration and data context.
*
* This type captures the essential details and optional settings needed to instantiate and
* manage a stateful workflow, including entity, API, and subscription configurations.
*
* ## Properties
* - `id`: A string serving as the unique identifier for the workflow instance.
* - `entityName`: The name of the entity associated with this workflow, used for context and identification.
* - `data` (optional): A generic parameter `D` representing the data specific to the entity within the workflow.
* - `state` (optional): The state of entities at the moment of workflow initialization, used for context and processing.
* - `status`: The current status of the workflow, indicating its operational state.
* - `apiUrl` (optional): The URL for API interactions related to the workflow, facilitating external communication if needed.
* - `apiToken` (optional): A token for authenticating API requests, providing secure access to external systems.
* - `subscriptions` (optional): An array of `Subscription` objects, defining event-driven connections for the workflow.
* - `autoStart` (optional): A boolean indicating if the workflow should start automatically upon initialization.
* - `ancestorWorkflowIds` (optional): An array of strings denoting the IDs of ancestor workflows, useful for lineage tracking.
*
* ## Usage Example
* ```typescript
* const workflowParams: StatefulWorkflowParams = {
* id: "workflow-123",
* entityName: "Order",
* status: "active",
* autoStart: true,
* subscriptions: [orderSubscription],
* };
* ```
*/
export type StatefulWorkflowParams<D = {}> = {
id: string;
entityName: string;
data?: D;
state?: EntitiesState;
status: string;
apiUrl?: string;
apiToken?: string;
subscriptions?: Subscription[];
autoStart?: boolean;
ancestorWorkflowIds?: string[];
startDelay?: Duration;
};
/**
* Options for configuring a stateful workflow, focusing on schema and execution settings.
*
* This type provides configuration options for setting up workflows, emphasizing schema integration
* and auto-start capabilities for seamless workflow operations.
*
* ## Properties
* - `schema` (optional): A `schema.Entity` object representing the data schema for the workflow,
* crucial for data validation and normalization.
* - `schemaName` (optional): The name of the schema, assisting in referencing and managing various schemas.
* - `autoStart` (optional): A boolean indicating whether the workflow should initiate automatically.
* - `apiUrl` (optional): The API endpoint used for workflow-related communications.
* - `workflowType` (optional): The type of the workflow, identifying the workflow's structure and purpose.
*
* ## Usage Example
* ```typescript
* const workflowOptions: StatefulWorkflowOptions = {
* schemaName: "OrderSchema",
* autoStart: true,
* workflowType: "order-processing",
* apiUrl: "https://api.example.com",
* };
* ```
*/
export type StatefulWorkflowOptions = {
schema?: EnhancedEntity;
schemaName?: string;
saveStateToMemo?: boolean;
apiUrl?: string;
workflowType?: string;
children?: {
autoStart?: boolean;
update?: {
enabled?: boolean;
sync?: boolean;
strategy?: '$set' | '$merge';
};
delete?: {
enabled?: boolean;
sync?: boolean;
strategy?: '$set' | '$merge';
};
};
};
/**
* Represents a pending change awaiting application within a workflow, detailing the modifications and approach.
*
* This type specifies pending updates or deletions along with the strategy to apply changes,
* assisting workflows in maintaining accurate state adjustments.
*
* ## Properties
* - `updates` (optional): A record of fields and values marked for updating, keyed by entity attributes.
* - `deletions` (optional): A record of fields designated for deletion, identifying which data should be removed.
* - `entityName`: The name of the entity to which changes are applied, offering context for the modifications.
* - `strategy` (optional): Specifies the change application strategy, either '$set' or '$merge',
* determining overwrite or merge behavior.
* - `changeOrigin` (optional): A string identifying the source of change, useful for auditing and reverse-tracing.
* - `sync` (optional): A boolean indicating if the change should be synchronized immediately, emphasizing real-time application.
* - `action` (optional): An `EntityAction` instance representing any associated actions that arise from the change.
*
* ## Usage Example
* ```typescript
* const pendingChange: PendingChange = {
* updates: { status: "shipped" },
* entityName: "Order",
* strategy: '$merge',
* changeOrigin: "user-update",
* };
* ```
*/
export type PendingChange = {
updates?: Record<string, any>;
deletions?: Record<string, any>;
entityName: string;
strategy?: EntityStrategy;
changeOrigin?: string;
sync?: boolean;
action?: EntityAction;
};
/**
* Metadata related to an action method within a stateful workflow, including options for execution.
*
* This interface defines metadata for action methods, encapsulating execution options
* accessible via the workflow framework.
*
* ## Properties
* - `method`: A key representing the action method within the `StatefulWorkflow`, indicating what function is being described.
* - `options` (optional): An `ActionOptions<any, any>` object specifying configuration details or execution modifiers for the method.
*
* ## Usage Example
* ```typescript
* const actionMetadata: ActionMetadata = {
* method: "updateOrderStatus",
* options: { retries: 3 },
* };
* ```
*/
export interface ActionMetadata {
method: keyof StatefulWorkflow<any, any>;
options?: ActionOptions<any, any>;
}
/**
* Entry detailing a connection to an ancestor workflow, maintaining identification and relationship data.
*
* This type manages the association with ancestor workflows, storing identification details
* and the external workflow handle for effective cross-workflow communication.
*
* ## Properties
* - `entityId`: The ID of the related entity, serving as a primary identifier for association.
* - `entityName`: The name of the entity, facilitating context and clarity within multiple workflows.
* - `isParent`: A boolean indicating if the ancestor workflow is directly a parent, clarifying lineage roles.
* - `handle`: An `ExternalWorkflowHandle` object, affording access to the ancestor workflow's external interface.
*
* ## Usage Example
* ```typescript
* const ancestorEntry: AncestorHandleEntry = {
* entityId: "order-789",
* entityName: "Order",
* isParent: true,
* handle: orderWorkflowHandle,
* };
* ```
*/
export type AncestorHandleEntry = {
entityId: string;
entityName: string;
isParent: boolean;
handle: workflow.ExternalWorkflowHandle;
};
export type StateEventListener = {
newState: EntitiesState;
previousState: EntitiesState;
changeType: 'added' | 'updated' | 'deleted';
changes: DetailedDiff;
origins: string[];
};
export type LoadDataResult<T> = {
data?: Partial<T>;
updates?: EntitiesState;
strategy?: EntityStrategy;
};
export declare abstract class StatefulWorkflow<P extends StatefulWorkflowParams = StatefulWorkflowParams, O extends StatefulWorkflowOptions = StatefulWorkflowOptions> extends Workflow<P, O> {
/**
* Internal state binding flags
*/
private _actionsBound;
protected _eventsBound: boolean;
private readonly selectorPatternCache;
/**
* Converts a selector string into a regular expression pattern for matching paths.
*
* @private
* @param {string} selector - The selector string to convert.
* @returns {RegExp} A regular expression that matches paths according to the selector pattern.
*/
private getSelectorPattern;
/**
* Internal reference object to hold the values for memo properties.
*/
private _memoProperties;
/**
* Internal flag used to determine if there is currently an @Action() running (executeUpdate).
*/
protected actionRunning: boolean;
/**
* Determines whether the workflow is a long running continueAsNew type workflow or a short lived one.
* In the case of workflows extending from StatefulWorkflow, they are considered long running entity workflows.
*/
protected continueAsNew: boolean;
/**
* Flag indicating whether the workflow should continue as new in the next iteration.
*
* When set to `true`, the workflow will be recreated with a new history after the current
* iteration completes. This is useful for:
*
* - Preventing workflow histories from growing too large
* - Managing long-running workflows efficiently
* - Implementing workflow versioning and updates
* - Clearing accumulated state when needed
*
* The workflow will automatically continue as new when:
* - The history size approaches Temporal's limit (default ~40MB)
* - The maximum number of iterations is reached
* - This flag is manually set to true
*
* @example
* ```typescript
* class MyWorkflow extends Workflow {
* async execute() {
* // After processing a large batch of data
* if (this.processedItemCount > 1000) {
* this.shouldContinueAsNew = true;
* return this.result;
* }
* }
* }
* ```
*
* @default false
* @see {@link https://docs.temporal.io/workflows#continue-as-new Temporal Continue-as-New Documentation}
*/
protected shouldContinueAsNew: boolean;
/**
* Internal reference to the StateManager instance for this workflow.
*/
protected stateManager: StateManager;
/**
* Internal reference to the StateManager instance for this workflow.
*/
protected schemaManager: SchemaManager;
/**
* Current workflow iteration count.
*/
protected iteration: number;
/**
* Internal reference to the Schema instance for this workflow.
*/
protected schema: Schema;
/**
* The `conditionTimeout` property is utilized in the `StatefulWorkflow` class to manage timeout settings
* for workflow conditions. This optional parameter allows the developer to define a specific duration
* for which the workflow should wait for certain conditions to be satisfied before proceeding.
*
* Example:
* ```typescript
* protected conditionTimeout: Duration = Duration.fromMinutes(1);
* ```
*
* ### Usage:
* - **Context**: Used within the `executeWorkflow` method to prevent indefinite waits in case certain
* conditions are not met in a timely manner.
*
* - **Definition**:
* ```typescript
* protected conditionTimeout?: Duration | undefined = undefined;
* ```
*
* - **Integration**: Applied in scenarios where `await workflow.condition()` is called, controlling how long
* the workflow can pause before proceeding.
*
* ### Configuration:
* - **Default Value**: `undefined`, implying no timeout unless explicitly set.
* - **Custom Values**: Use the `Duration` type to set a desired wait duration:
* ```typescript
* this.conditionTimeout = Duration.fromSeconds(30);
* ```
*
* **Considerations**:
* - **Performance**: Setting appropriate timeouts ensures the workflow remains responsive and avoids long pauses.
* - **Scalability**: Timeouts should align with the overall system architecture, especially for systems with
* nested or multiple workflows.
* - **Failure Handling**: Implement retries or alternative paths to handle situations where conditions
* remain unmet beyond the timeout.
*/
protected conditionTimeout: Duration | undefined;
/**
* Optional method that can be implemented by subclasses to define custom continue-as-new behavior.
* If implemented, this method will be called when the workflow reaches its maximum iterations.
* @returns A promise that resolves when the continue-as-new process is complete.
*/
protected onContinue?(): Promise<Record<string, unknown>>;
/**
* Optional method that can be implemented by extending classes to load data from external sources.
* This method is called automatically when pendingUpdate is true.
*
* @returns A promise that resolves to an object containing data and/or updates to be applied to the state
*/
protected loadData?(): Promise<LoadDataResult<P['data']>>;
/**
* Determines whether data should be loaded in the workflow based on internal state.
*
* This method checks if the `loadData` function exists and if there is a pending update.
* It is essential to ensure data is only loaded when necessary, avoiding unnecessary calls
* or updates.
*
* @returns {boolean}
* - `true` if the workflow has a `loadData` method and a pending update, meaning data
* needs to be refreshed.
* - `false` otherwise, indicating that no data needs to be loaded.
*/
protected shouldLoadData(): boolean;
/**
* Abstract method that represents the core logic to be executed by the workflow.
*
* This method must be implemented by any subclass extending `StatefulWorkflow`. It is the
* central piece where the workflow's specific operations and tasks are defined.
*
* Implementations of this method will define how the workflow should behave in its execution
* cycle. This is typically where the main business logic resides.
*
* @param args - Optional arguments that may be passed to the workflow.
* @param options - Additional execution options that may affect the workflow behavior.
* @returns {Promise<unknown>}
* - A promise that resolves with the result of the workflow's execution. The return value
* may vary depending on the specific workflow implementation.
*/
protected abstract execute(args?: unknown, options?: TemporalOptions): Promise<unknown>;
/**
* Sets the API token for the workflow.
* This token is used for authenticating API requests made by the workflow.
*
* @param apiToken - The API token to set.
*/
protected apiToken?: string;
/**
* Signal to set or update the API token for the workflow.
*
* This signal allows external systems or child workflows to update the API token used by
* this workflow. Once the token is set, it marks the workflow's state as needing an update
* (`pendingUpdate`), and forwards the token update signal to child workflows.
*
* @param {string} apiToken - The new API token to be used by the workflow. If the new token
* differs from the current token, the workflow will trigger a pending update and propagate
* the token to its child workflows.
* @returns {void}
*/
setApiToken(apiToken: string): void;
protected id: string;
protected entityName: string;
/**
* Getter and setter for the workflow's internal state.
*
* The `state` property represents the current state of the workflow's entities,
* managed by the `schemaManager`. This property can be used to retrieve or update
* the entire state of the workflow.
*
* When the state is updated, it is directly set in the schema manager, ensuring
* that all entity-related operations reflect the latest state.
*
* @returns {EntitiesState}
* - The current state of all entities in the workflow.
*/
getState(): EntitiesState;
get state(): EntitiesState;
setState(state: EntitiesState): Promise<void>;
/**
* Retrieves the workflow's entity-specific data from the schema manager.
*
* This getter dynamically fetches the data associated with the specific entity (`entityName`)
* and `id` of the workflow from the `SchemaManager`. The retrieved data is wrapped in a
* proxy that allows for real-time updates. Any mutations to the data trigger state changes
* and are automatically dispatched via the `SchemaManager`.
*
* The getter handles the complexity of normalized data, ensuring that the denormalization
* process respects the relationships defined in the schema.
*
* @returns {P['data']}
* - The current data for the entity, denormalized and proxied for real-time updates.
*/
protected get data(): P['data'];
/**
* Getter and setter for the workflow's entity-specific data, managed by the SchemaManager.
*
* **Getter**: This retrieves the workflow's data associated with the entity (`entityName`)
* and `id`. The data is automatically **denormalized** using the entity schema, so it
* includes any relational data. The getter returns a **proxied** version of the data,
* allowing the workflow to react to any changes made to the data.
*
* The Proxy intercepts interactions with the data. If you modify the data, the proxy
* triggers an update to the workflow's state, and the SchemaManager normalizes the new data.
*
* **Setter**: This updates the workflow's data by dispatching a state update via the
* SchemaManager. When new data is set, it is **normalized** according to the entity schema,
* then saved to the workflow's internal state.
*
* The setter works asynchronously, as the update is dispatched through the workflow's
* event-driven state management, ensuring that changes are synchronized with child
* workflows and other parts of the system.
*
* ### Example Usage:
* Suppose you have a `BookWorkflow` managing a `Book` entity with multiple `Chapters`:
*
* ```typescript
* @Workflow({
* schema: schemas.Book,
* schemaName: 'Book',
* })
* export class BookWorkflow extends StatefulWorkflow<
* StatefulWorkflowParams<BookModel>,
* StatefulWorkflowOptions
* > {
* @Property({ path: 'chapters' })
* protected chapters!: ChapterModel[];
*
* @Action<AddChapterAction, BookModel>()
* protected async addChapter(action: AddChapterAction): Promise<BookModel> {
* // Access current data through the getter
* if (!this.data) {
* throw new Error('No book data available yet');
* }
* // Add a new chapter to the book
* const newChapter = action.payload.chapter;
* this.chapters.push(newChapter);
* return this.data; // Return the updated book data
* }
* }
* ```
*
* In the above example:
* - The `data` getter is used to fetch the current state of the `Book` entity, which includes its `Chapters`.
* - The Proxy automatically tracks changes to the `chapters` array. When a new chapter is added, the workflow updates its state and triggers the necessary state changes.
*
* **Real-Time Updates with Proxy**:
* The data is wrapped in a Proxy, so any modification to the `Book` or its `Chapters` will automatically trigger an update. For example, if you modify a specific chapter:
*
* ```typescript
* // Update the title of a specific chapter
* this.chapters[0].title = 'New Chapter Title';
* ```
*
* This change will be automatically detected, and the Proxy will trigger an update, saving the new title and ensuring that the state remains synchronized.
*
* **SchemaManager's Role**:
* The `SchemaManager` normalizes the data when setting it and denormalizes it when retrieving it. The Proxy ensures real-time synchronization between the denormalized data and the internal state.
*
* @returns {P['data']}
* - The denormalized data for the entity, proxied for real-time updates.
*/
protected set data(data: any);
/**
* Boolean flag indicating a pending update, setting this to true will result in loadData() being called if it is defined.
*/
protected pendingUpdate: boolean;
/**
* An array of pending changes that are in the StateManager queue waiting to be processed.
*/
get pendingChanges(): import("typescript-algos").Queue<import("../store").QueueItem>;
/**
* An array of workflows that have open subscriptions to this workflows state.
*/
protected subscriptions: Subscription[];
/**
* A map to keep track of workflows that have open subscriptions to this workflows state.
*/
protected subscriptionHandles: LRUCacheWithDelete<string, workflow.ExternalWorkflowHandle>;
/**
* Defines paths within the workflow's state that are automatically managed, often
* involving child workflows or nested entities. This allows workflows to handle
* complex, hierarchical data structures with ease.
*
* **managedPaths** is a mapping of entity properties to configurations that control
* how these properties (and their related entities) are managed by the parent workflow.
* Each path in `managedPaths` defines how the workflow should interact with a nested
* entity or set of entities, including:
*
* - Automatically starting child workflows for these entities.
* - Handling the relationships between parent and child workflows.
* - Synchronizing state between the parent workflow and child workflows.
*
* ### Key Configuration Options:
*
* - **`entityName`**: The name of the entity managed at this path. This corresponds to the entity name
* as defined in the schema.
*
* - **`path`**: The key in the parent entity where the nested entity (or entities) are located.
* For example, in a `Book` entity, the path could be `'chapters'`, representing a list of `Chapter` entities.
*
* - **`workflowType`**: The type of the child workflow to start for the nested entities. This could be
* a `ChapterWorkflow` for chapters in a book or a `TaskWorkflow` for tasks in a project.
*
* - **`idAttribute`**: The unique identifier or combination of attributes that uniquely identify the
* nested entities. This can be a single field (e.g., `'id'`) or an array of fields for composite keys
* (e.g., `['projectId', 'taskId']`).
*
* - **`isMany`**: Indicates whether the path contains multiple entities (e.g., an array of chapters
* in a book). If `true`, the workflow will manage multiple entities at this path.
*
* - **`includeParentId`**: If `true`, the parent entity's ID is included in the child workflow's ID.
* This is useful when child workflows need to track their parent entity (e.g., a chapter must know
* which book it belongs to).
*
* - **`autoStart`**: Automatically starts child workflows for the entities at this path.
* When `true`, each entity in the path will have its corresponding child workflow started automatically.
*
* - **`cancellationType`**: Defines the cancellation policy for the child workflows. For example, this
* can ensure that child workflows are canceled if the parent workflow is canceled or errors out.
*
* - **`parentClosePolicy`**: Determines how the child workflows should behave when the parent workflow
* closes. For instance, child workflows can be canceled when the parent is done, or they can continue
* running independently.
*
* - **`condition`**: A function that specifies whether a child workflow should be started for a particular
* entity based on its data. This provides fine-grained control over when child workflows are started.
*
* - **`processData`**: A function that processes the entity's data before starting the child workflow.
* This is useful when additional data needs to be passed to the child workflow, such as including
* the parent entity's information.
*
* ### Example Usage:
* Suppose you have a `ProjectWorkflow` that manages a `Project` entity with nested `Task` entities:
*
* ```typescript
* @Workflow({
* schema: schemas.Project,
* schemaName: 'Project',
* autoStart: true
* })
* export class ProjectWorkflow extends StatefulWorkflow<
* StatefulWorkflowParams<ProjectModel>,
* StatefulWorkflowOptions
* > {
* protected managedPaths: ManagedPaths = {
* tasks: {
* idAttribute: 'id',
* entityName: 'Task',
* workflowType: 'TaskWorkflow',
* autoStart: true,
* includeParentId: true,
* processData: (task, project) => {
* task.projectId = project.id; // Attach the project ID to the task
* return task;
* }
* }
* };
* }
* ```
*
* In the example above:
*
* - **`tasks`**: The `tasks` path defines how the `Task` entities within a `Project` are managed.
* Each task is treated as a separate entity, and a `TaskWorkflow` is automatically started for each.
*
* - **`idAttribute: 'id'`**: The unique identifier for each task is the `id` field.
*
* - **`autoStart: true`**: Child workflows for tasks are automatically started when the project
* workflow is executed.
*
* - **`includeParentId: true`**: The `TaskWorkflow` will receive the `projectId` to keep track of which
* project each task belongs to.
*
* - **`processData`**: Before starting the `TaskWorkflow`, the `processData` function attaches the
* project ID to each task.
*
* ### Custom Child Workflow Conditions:
* In some cases, you may only want to start child workflows based on certain conditions. This can be
* controlled using the `condition` option.
*
* For example, you might only want to start a `TaskWorkflow` if the task is marked as "important":
*
* ```typescript
* protected managedPaths: ManagedPaths = {
* tasks: {
* idAttribute: 'id',
* entityName: 'Task',
* workflowType: 'TaskWorkflow',
* autoStart: true,
* condition: (task, project) => task.isImportant
* }
* };
* ```
*
* In this case, only tasks with `isImportant: true` will have their corresponding `TaskWorkflow` started.
*
* ### Child Workflow Cancellation and Close Policies:
* The `cancellationType` and `parentClosePolicy` settings define how child workflows are managed when the
* parent workflow is canceled or closed. For example:
*
* ```typescript
* protected managedPaths: ManagedPaths = {
* tasks: {
* idAttribute: 'id',
* entityName: 'Task',
* workflowType: 'TaskWorkflow',
* autoStart: true,
* cancellationType: workflow.ChildWorkflowCancellationType.WAIT_CANCELLATION_COMPLETED,
* parentClosePolicy: workflow.ParentClosePolicy.PARENT_CLOSE_POLICY_REQUEST_CANCEL
* }
* };
* ```
*
* In this configuration:
*
* - **`cancellationType: WAIT_CANCELLATION_COMPLETED`**: The parent workflow waits for the child workflows
* to complete before canceling them.
*
* - **`parentClosePolicy: REQUEST_CANCEL`**: When the parent workflow finishes, it requests the cancellation
* of its child workflows.
*
* ### ManagedPaths Summary:
* - `managedPaths` is essential for handling nested entities in workflows.
* - It automates the creation and management of child workflows.
* - It provides flexibility in defining how and when child workflows are started, as well as how their
* lifecycle is controlled.
*
* @type {ManagedPaths}
*/
protected managedPaths: ManagedPaths;
/**
* The apiUrl for this workflows entity.
*/
protected apiUrl?: string;
/**
* An array to keep track of ancestor workflow's ids.
*/
protected ancestorWorkflowIds: string[];
/**
* The initial parameters as provided to this workflow when started.
*/
protected params: P;
/**
* The initial options as provided to this workflow when started.
*/
protected options: O;
/**
* A map to keep track of handles to ancestor workflows.
*/
protected ancestorHandles: LRUCacheWithDelete<string, AncestorHandleEntry>;
/**
* Constructs an instance of the class with the specified parameters and options.
*
* @constructor
* @param {P} params - The parameters for initializing the instance. It may include:
* - `ancestorWorkflowIds`: Optional list of ancestor workflow identifiers.
* - `id`: Optional identifier for the instance.
* - `entityName`: Optional name of the entity associated with this instance.
* - `subscriptions`: Optional array of subscriptions to be registered.
* - `state`: Optional initial state for the entity.
* - `data`: Optional data to update the entity.
* - `status`: Optional status of the workflow; defaults to 'running'.
* - `apiUrl`: Optional URL for API interactions.
* - `apiToken`: Optional token for API authentication.
* @param {O} options - Configuration options which might include:
* - `schemaName`: Optional schema name for the entity.
* - `schema`: Schema object for the entity.
* - `apiUrl`: Default API URL if not provided in params.
*
* Initializes:
* - The `stateManager` by retrieving it from `StateManager` using the workflow's info.
* - The `ancestorHandles` map if `ancestorWorkflowIds` are present.
* - The schema using either the parameters or options.
* - Subscriptions using the provided parameter subscriptions.
*
* Sets up state management by listening for state changes and initializes any pending operations.
*/
constructor(params: P, options: O);
protected onSetup(): Promise<void>;
/**
* Executes the workflow in a controlled and synchronized manner, ensuring thread safety
* by using a mutex on the 'executeWorkflow' function.
*
* @protected
* @async
* @returns {Promise<any>} Resolves with the result of the workflow execution, or with any
* data processed if terminal state is reached without error. Rejects if an error state is
* encountered.
*
* Actions:
* - Configures managed paths based on the schema, if available.
* - Continuously executes the workflow logic while the current iteration is within the
* allowed maximum iterations (`maxIterations`).
* - Waits for certain conditions to be met using the workflow's condition, pausing and
* resuming execution based on the workflow status.
* - Loads data, executes the primary task, and checks for terminal states, resolving
* or rejecting the promise based on the success or failure of the workflow.
* - Manages iterations, respecting maximum iterations and handling scenarios where
* the new state of execution may need to continue based on workflow constraints.
* - Handles any execution errors appropriately by delegating to a specified error handler.
*
* This function logs relevant debug information, including entity details and current
* execution states, making it suitable for tracing workflow progress through complex stages.
*/
protected executeWorkflow(): Promise<any>;
/**
* Updates the state of an entity within the system with the given data and updates. This function
* ensures that changes are dispatched appropriately, either as part of a specific `action` or
* through a direct update of normalized entities.
*
* @param {Object} parameters An object containing all parameters required to perform the update.
* @param {Record<string, any>} [parameters.data]
* Optional. A record containing the raw data to be normalized and used for the update. The data structure
* should match the expected schema for the entity being updated.
* @param {any} [parameters.updates]
* Optional. Pre-normalized update data for directly updating the entity's state. If not provided,
* and if `data` is specified, `data` will be normalized to create updates.
* @param {string} parameters.entityName
* The name of the entity for which the state update is being applied. This is used to fetch and use the
* correct schema for normalization if `data` is provided.
* @param {any} [parameters.action]
* Optional. An action object that encapsulates specific state changes or commands to be dispatched directly.
* If provided, the function prioritizes dispatching this action.
* @param {string} [parameters.strategy='$merge']
* Optional. The strategy used when merging updates into the current state. Defaults to '$merge' which
* merges new data into existing state.
* @param {any} [parameters.changeOrigin]
* Optional. Indicates the origin of the changes, such as user input or automated system process. This
* can be used for logging or conditional logic during dispatch.
* @param {boolean} [parameters.sync=true]
* Optional. A boolean flag indicating whether the state change should be synchronous. Defaults to `true`,
* meaning operations are synchronous by default.
*
* @returns {void}
* This method does not return a value but will log errors or trace information to facilitate debugging.
*
* @throws Will log an error if both `data` and `updates` are invalid or cannot be processed.
*
* @example
* // Example usage:
* update({
* data: { id: 1, name: 'John Doe' },
* entityName: 'User',
* strategy: '$replace',
* changeOrigin: 'client',
* sync: false
* });
*
* @remark
* Ensure that either `data` or `updates` are provided. If neither can be normalised or used,
* the method logs an error indicating the failure if no action is supplied.
*/
update({ data, updates, entityName, action, strategy, changeOrigin, sync }: PendingChange & {
data?: Record<string, any>;
}): void;
/**
* Deletes entries of a specified entity type from the state, based on given data or predefined deletions.
* This function facilitates the removal of entities by dispatching the appropriate delete action after
* optionally normalizing the input data.
*
* @param {Object} parameters An object containing all parameters required to perform the deletion.
* @param {Record<string, any>} [parameters.data]
* Optional. A record containing the raw data that specifies the entities to be deleted. This data will
* be normalized according to the entity's schema if provided.
* @param {any} [parameters.deletions]
* Optional. Pre-normalized deletion data that directly specifies which entities should be removed.
* If not provided, and if `data` is specified, `data` will be normalized to infer deletions.
* @param {any} [parameters.action]
* Optional. An action object that encapsulates specific state changes or commands to be dispatched directly.
* If provided, the function prioritizes dispatching this action.
* @param {string} parameters.entityName
* The name of the entity type from which the specified entries should be deleted. This is crucial for
* ensuring that the correct schema is used for normalization if `data` is supplied.
* @param {any} [parameters.changeOrigin]
* Optional. An identifier for the origin of the delete request, such as user action or internal mechanic,
* used for logging or conditional logic during dispatch.
* @param {boolean} [parameters.sync=true]
* Optional. A boolean indicating whether the deletion operation should be synchronous. Defaults to `true`,
* which implies synchronous operations.
*
* @returns {void}
* This method does not return a value but logs errors or trace information to help diagnose issues.
*
* @throws Will log an error if both `data` and `deletions` are invalid or cannot be processed.
*
* @example
* // Example usage:
* delete({
* data: { id: 2 },
* entityName: 'User',
* changeOrigin: 'admin',
* sync: false
* });
*
* @remark
* Ensure that either `data` or `deletions` are provided. If neither can be normalised or used,
* the method logs an error indicating the failure if no action is supplied.
*/
delete({ data, action, deletions, entityName, changeOrigin, sync }: PendingChange & {
data?: Record<string, any>;
}): void;
/**
* Adds a subscription to the current instance, ensuring that it is unique.
*
* @signal
* @public
* @async
* @param {Subscription} subscription - The subscription object that contains details
* about the workflow and subscription IDs. It has the following properties:
* - `workflowId`: The unique identifier of the workflow.
* - `subscriptionId`: The unique identifier of the subscription within the workflow.
* @returns {Promise<void>} A promise that resolves when the subscription handling is complete.
*
* Operations:
* - Logs a trace of the subscription process for debugging, indicating the class, entity, and ID.
* - Checks if the subscription already exists by comparing both `workflowId` and `subscriptionId`.
* - If the subscription is unique, it adds the subscription to the `subscriptions` list.
* - Sets up a handle for interacting with the specified workflow via `subscriptionHandles`.
*/
subscribe(subscription: Subscription): Promise<void>;
/**
* Removes a subscription from the current instance based on the given subscription details.
*
* @signal
* @public
* @async
* @param {Subscription} subscription - The subscription object that contains details
* about the workflow and subscription IDs. It has the following properties:
* - `workflowId`: The unique identifier of the workflow.
* - `subscriptionId`: The unique identifier of the subscription within the workflow.
* @returns {Promise<void>} A promise that resolves when the unsubscription process is complete.
*
* Operations:
* - Logs a trace of the unsubscription process for debugging purposes, indicating the class, entity, and ID.
* - Searches for the subscription in the `subscriptions` list using the `workflowId` and `subscriptionId`.
* - If a matching subscription is found, it deletes the corresponding workflow handle from `subscriptionHandles`.
* - Removes the identified subscription from the `subscriptions` list.
*/
unsubscribe(subscription: Subscription): Promise<void>;
/**
* Processes the current state of the entity, ensuring all pending changes are applied
* when no actions are currently running.
*
* @before 'execute'
* @protected
* @async
* @returns {Promise<void>} A promise that resolves once the state processing is complete and any pending changes are applied.
*
* Operations:
* - Checks if an action is currently running (`actionRunning`). If so, logs an informational message
* and waits for the action to complete using the workflow condition.
* - Logs a debug message indicating the start of the state processing.
* - If there are any pending changes, it triggers the `stateManager` to process these changes.
*/
protected processState(): Promise<void>;
/**
* Handles changes in the entity's state by processing state differences and
* triggering appropriate events based on the nature of these changes.
*
* @mutex 'processState'
* @protected
* @async
* @param {Object} param0 - The object containing state change details, including:
* - `newState`: The state of the entities after changes have been applied.
* - `previousState`: The state of the entities before changes were applied.
* - `differences`: An object detailing the differences between the new and previous states.
* Includes `added`, `updated`, and `deleted` properties.
* - `changeOrigins`: An array of strings indicating the origins of the changes.
* @returns {Promise<void>} A promise that resolves when all state change handling is complete.
*
* Operations:
* - Logs a debug message indicating the transition and processing of state changes.
* - Checks if there are any differences in the state (`added`, `updated`, `deleted`).
* - Based on the differences:
* - Emits a 'created' event if the entity was newly added.
* - Emits an 'updated' event if the entity was modified.
* - Emits a 'deleted' event if the entity was removed, potentially setting the status to 'cancelled' if the deletion event fails.
* - Processes the states of child entities and related subscriptions if applicable, triggering further handling mechanisms.
* - Sets the `pendingIteration` flag to true, signaling readiness for further processing.
*/
protected stateChanged({ newState, previousState, differences, changeOrigins }: {
newState: EntitiesState;
previousState: EntitiesState;
differences: DetailedDiff;
changeOrigins: string[];
}): Promise<void>;
/**
* Updates the workflow's memo with the current state, ensuring that any changes are persisted.
*
* @protected
* @async
* @returns {Promise<void>} A promise that resolves once the current state has been conditionally saved to memo.
*
* Operations:
* - Logs information about the memo update process, including the current workflow ID.
* - Retrieves the existing memo and flattens the current state and memo properties for comparison.
* - Compa