UNPKG

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
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