import { Action } from '../action/action'; import { LimeObject } from '../limeobject'; import { ConfigMetadata } from '../core'; /** * Represents a conditional evaluation rule that can be registered and used * throughout the application. * * Conditions are predicate functions that determine whether certain UI elements should be * visible, enabled, or available based on runtime state. For example, they can be used to: * - Control action visibility (show/hide menu items based on object state) * - Enable/disable features based on user permissions or context * - Filter lists of items based on dynamic criteria * - Implement business rules for conditional UI rendering * * Each condition has a unique ID and a type that indicates what kind of subject it evaluates. * The most common types are 'limeobject' and 'action', but custom types can be defined. * * @example Registering a simple LimeObject condition * ```ts * import { * LimeObjectCondition, * PlatformServiceName, * } from '@limetech/lime-web-components'; * * const registry = platform.get(PlatformServiceName.ConditionRegistry); * * const isDealWon: LimeObjectCondition = { * id: 'deal.is-won', * type: 'limeobject', * evaluate: (deal) => deal.getValue('dealstatus') === 'won', * }; * * registry.addCondition(isDealWon); * ``` * * @example Condition with additional parameters * ```ts * import { * LimeObjectCondition, * PlatformServiceName, * } from '@limetech/lime-web-components'; * * type MinimumValueParams = { * field: string; * minValue: number; * }; * * const hasMinimumValue: LimeObjectCondition = { * id: 'object.min-value', * type: 'limeobject', * evaluate: (object, params) => { * const { field, minValue } = params as MinimumValueParams; * const value = object.getValue(field); * * return typeof value === 'number' && value >= minValue; * }, * }; * * const registry = platform.get(PlatformServiceName.ConditionRegistry); * registry.addCondition(hasMinimumValue); * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const deal = repository.getObject('deal', 1234); * * const isHighValue = * !!deal && * hasMinimumValue.evaluate(deal, { field: 'value', minValue: 100_000 }); * ``` * * @see {@link ConditionRegistry} for managing conditions * @see {@link LimeObjectCondition} for LimeObject-specific conditions * @see {@link ActionCondition} for action-specific conditions * * @deprecated Use {@link RulePrimitive} together with {@link Rule}. A * condition's predicate becomes a `RulePrimitive`, and the boolean tree * that combined conditions becomes a `Rule`. * * @beta * @group Conditions */ export type Condition = { /** * The type of subject this condition evaluates. * * Common types include: * - `limeobject` - Evaluates {@link LimeObject} instances * - `action` - Evaluates {@link Action} instances * * Custom types can be defined for specialized evaluation contexts. */ type: ConditionType; /** * The evaluation function that determines if the condition is met. * * This predicate function receives the subject to evaluate and optional parameters * for configuration. It should return `true` if the condition is satisfied, * `false` otherwise. * * The function should be pure and side-effect free when possible, as it may be * called frequently during UI rendering and updates. * * @param subject - The value to evaluate. Type depends on the condition type. * For 'limeobject' conditions, this is a {@link LimeObject}. * For 'action' conditions, this is an {@link Action}. * @param params - Optional configuration data for the evaluation. * Can be used to pass context-specific values like field names, thresholds, * or other criteria. The evaluation function should validate params if used. * @returns `true` if the condition is met, `false` otherwise. * @throws May throw an error if params has an unexpected type or required data is missing. * * @example Simple boolean check * ```ts * import { LimeObject } from '@limetech/lime-web-components'; * * const evaluate = (deal: LimeObject) => deal.getValue('dealstatus') === 'won'; * ``` * * @example Using params for configuration * ```ts * import { LimeObject } from '@limetech/lime-web-components'; * * const evaluate = (object: LimeObject, params?: unknown) => { * const { propertyName } = params as { propertyName: string }; * * return object.getValue(propertyName) !== null; * }; * ``` */ evaluate(subject: T, params?: unknown): boolean; /** * Unique identifier for this condition. * * The ID is used to reference the condition when registering, removing, or * looking it up from the {@link ConditionRegistry}. It must be unique across * all registered conditions. */ id: string; }; /** * A specialized condition type for evaluating {@link LimeObject} instances. * * LimeObject conditions are the most common type of condition, used to control * UI elements based on the state of business objects (deals, contacts, companies, etc.). * The evaluate function receives a LimeObject and can check any of its properties * to determine if the condition is met. * * @example Condition based on object status * ```ts * import { * LimeObjectCondition, * PlatformServiceName, * } from '@limetech/lime-web-components'; * * const registry = platform.get(PlatformServiceName.ConditionRegistry); * * const isDealActive: LimeObjectCondition = { * id: 'deal.is-active', * type: 'limeobject', * evaluate: (deal) => { * const status = deal.getValue('dealstatus'); * * return status === 'qualification' || status === 'proposal'; * }, * }; * * registry.addCondition(isDealActive); * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const deal = repository.getObject('deal', 1234); * * const condition = registry.getCondition('deal.is-active'); * const isActive = !!deal && condition.evaluate(deal); * ``` * * @example Condition for conditional action visibility * ```ts * import { * Action, * Command, * LimeObjectCondition, * } from '@limetech/lime-web-components'; * * @Command({ id: 'send-email' }) * class SendEmailCommand { * to: string; * } * * const canSendEmail: LimeObjectCondition = { * id: 'contact.can-send-email', * type: 'limeobject', * evaluate: (contact) => { * const email = contact.getValue('email'); * * return typeof email === 'string' && email.length > 0; * }, * }; * * const sendEmailAction: Action = { * label: 'Send Email', * icon: 'envelope', * command: new SendEmailCommand(), * condition: canSendEmail, * }; * ``` * * @see {@link Condition} for the base condition type * @see {@link LimeObject} for the object structure * * @deprecated Use a {@link RulePrimitive} that declares `reads: ['limeobject']` * directly, rather than a typed condition. * * @beta * @group Conditions */ export type LimeObjectCondition = Condition & { type: 'limeobject'; }; /** * A specialized condition type for evaluating {@link Action} instances. * * Action conditions are used to control the availability or visibility of actions * themselves, based on properties of the action. This is less common than {@link LimeObject} * conditions but useful for creating dynamic action menus that filter or organize * actions based on their metadata. * * @example Condition to filter bulk actions * ```ts * import { * ActionCondition, * getCommandId, * PlatformServiceName, * } from '@limetech/lime-web-components'; * * const registry = platform.get(PlatformServiceName.ConditionRegistry); * * const isBulkAction: ActionCondition = { * id: 'action.is-bulk', * type: 'action', * evaluate: (action) => { * const commandId = getCommandId(action.command); * * return commandId?.startsWith('bulk-') ?? false; * }, * }; * * registry.addCondition(isBulkAction); * ``` * * @see {@link Condition} for the base condition type * @see {@link Action} for the action structure * * @deprecated Use a {@link RulePrimitive} that declares `reads: ['action']` * directly, rather than a typed condition. * * @beta * @group Conditions */ export type ActionCondition = Condition & { type: 'action'; }; /** * Check if condition expects a {@link LimeObject} to be passed when it is evaluated * * @param condition - the condition to check * @returns true if the condition is a condition for a {@link LimeObject}, * false otherwise * * @deprecated No longer needed. A {@link RulePrimitive} declares the context * keys it reads via `reads`, so there is no type discriminant to narrow. * * @beta * @group Conditions */ export declare function isLimeObjectCondition(condition: Condition): condition is LimeObjectCondition; /** * Check if condition expects an action to be passed when it is evaluated * * @param condition - the condition to check * @returns true if the condition is a condition for an {@link Action}, * false otherwise * * @deprecated No longer needed. A {@link RulePrimitive} declares the context * keys it reads via `reads`, so there is no type discriminant to narrow. * * @beta * @group Conditions */ export declare function isActionCondition(condition: Condition): condition is ActionCondition; /** * Central registry for managing conditional evaluation rules across the application. * * The ConditionRegistry acts as a global repository for {@link Condition} instances, * allowing different parts of the application to register, retrieve, and evaluate * conditions. This is particularly useful for: * - Controlling action visibility based on object state * - Implementing permission-based UI rendering * - Creating dynamic, context-aware interfaces * - Sharing reusable business logic across components * * Components typically access the registry via the platform service and register * their conditions when the application initializes. Conditions can be * added and removed dynamically, allowing for flexible runtime configuration. * * @example Registering and using conditions in a component * ```ts * import { * Action, * Command, * LimeObjectCondition, * PlatformServiceName, * } from '@limetech/lime-web-components'; * * @Command({ id: 'win-deal' }) * class WinDealCommand { * dealId: number; * } * * @Command({ id: 'lose-deal' }) * class LoseDealCommand { * dealId: number; * } * * const registry = platform.get(PlatformServiceName.ConditionRegistry); * * const canWinDeal: LimeObjectCondition = { * id: 'deal.can-win', * type: 'limeobject', * evaluate: (deal) => * deal.getValue('dealstatus') === 'proposal' && * deal.getValue('value') > 0, * }; * * const canLoseDeal: LimeObjectCondition = { * id: 'deal.can-lose', * type: 'limeobject', * evaluate: (deal) => { * const status = deal.getValue('dealstatus'); * * return status !== 'lost' && status !== 'won'; * }, * }; * * registry.addCondition(canWinDeal); * registry.addCondition(canLoseDeal); * * const actions: Action[] = [ * { * label: 'Mark as Won', * icon: 'trophy', * command: new WinDealCommand(), * condition: registry.getCondition('deal.can-win'), * }, * { * label: 'Mark as Lost', * icon: 'times', * command: new LoseDealCommand(), * condition: registry.getCondition('deal.can-lose'), * }, * ]; * * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const deal = repository.getObject('deal', 1234); * * const availableActions = actions.filter( * (action) => !action.condition || (!!deal && action.condition.evaluate(deal)) * ); * ``` * * @see {@link Condition} for the condition structure * @see {@link LimeObjectCondition} for object-specific conditions * @see {@link ActionCondition} for action-specific conditions * * @deprecated Use {@link RuleRegistry} instead. * * @beta * @group Conditions */ export interface ConditionRegistry { /** * Registers a new condition in the registry. * * The condition's ID must be unique across all registered conditions. * If a condition with the same ID already exists, this method throws an error. * It's recommended to register conditions during application startup. * * @param condition - The condition to register. Must have a unique ID. * @param metadata - Optional metadata associated with the condition. The * {@link ConfigMetadata} fields passed here move onto {@link RulePrimitive} * fields when migrating to {@link RuleRegistry}. * @throws Error if a condition with the same ID already exists in the registry. * * @example Register a condition with metadata * ```ts * import { * LimeObjectCondition, * PlatformServiceName, * } from '@limetech/lime-web-components'; * * const registry = platform.get(PlatformServiceName.ConditionRegistry); * * const isActive: LimeObjectCondition = { * id: 'deal.is-active', * type: 'limeobject', * evaluate: (deal) => deal.getValue('dealstatus') === 'active', * }; * * registry.addCondition(isActive, { * title: 'Deal is active', * description: 'True while the deal is still being worked on', * }); * ``` */ addCondition(condition: T, metadata?: ConfigMetadata): void; /** * Removes a condition from the registry. * * The condition is identified by its ID. If no condition with the given ID * exists in the registry, this method throws an error. * * @param condition - The condition to remove. Must exist in the registry. * @throws Error if the condition's ID does not exist in the registry. * * @example Remove a registered condition * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * const registry = platform.get(PlatformServiceName.ConditionRegistry); * * const condition = registry.getCondition('deal.is-active'); * registry.removeCondition(condition); * ``` * * @example Safe removal with existence check * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * const registry = platform.get(PlatformServiceName.ConditionRegistry); * * if (registry.hasCondition('deal.is-active')) { * registry.removeCondition(registry.getCondition('deal.is-active')); * } * ``` */ removeCondition(condition: Condition): void; /** * Checks whether a condition with the given ID exists in the registry. * * This method is useful for defensive programming, allowing you to verify * that a condition exists before attempting to retrieve or remove it. * * @param id - The unique identifier of the condition to check. * @returns `true` if a condition with the ID exists, `false` otherwise. * * @example Check before using a condition * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * const registry = platform.get(PlatformServiceName.ConditionRegistry); * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const deal = repository.getObject('deal', 1234); * * if (deal && registry.hasCondition('deal.can-win')) { * const condition = registry.getCondition('deal.can-win'); * const canWin = condition.evaluate(deal); * } * ``` */ hasCondition(id: string): boolean; /** * Retrieves all registered conditions. * * This method returns a list of all conditions currently in the registry, * regardless of their type. This can be useful for debugging, inspection, * or building dynamic UIs that need to enumerate available conditions. * * @returns An array of all registered conditions. * * @example List the registered conditions * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * const registry = platform.get(PlatformServiceName.ConditionRegistry); * * const allConditions = registry.getConditions(); * console.log(`Found ${allConditions.length} conditions`); * * const limeObjectConditions = allConditions.filter( * (condition) => condition.type === 'limeobject' * ); * ``` */ getConditions(): Condition[]; /** * Retrieves a specific condition by its unique identifier. * * If no condition with the given ID exists, this method throws an error. * Use {@link ConditionRegistry.hasCondition} first if you need to check for existence. * * @param id - The unique identifier of the condition to retrieve. * @returns The condition with the specified ID. * @throws Error if no condition with the given ID exists in the registry. * * @example Get a condition and evaluate it * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * const registry = platform.get(PlatformServiceName.ConditionRegistry); * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const deal = repository.getObject('deal', 1234); * * const canWin = registry.getCondition('deal.can-win'); * const isEligible = !!deal && canWin.evaluate(deal); * ``` * * @example Safe retrieval with type checking * ```ts * import { * isLimeObjectCondition, * PlatformServiceName, * } from '@limetech/lime-web-components'; * * const registry = platform.get(PlatformServiceName.ConditionRegistry); * const repository = platform.get(PlatformServiceName.LimeObjectRepository); * const deal = repository.getObject('deal', 1234); * * const condition = registry.getCondition('deal.is-active'); * if (deal && isLimeObjectCondition(condition)) { * const result = condition.evaluate(deal); * } * ``` */ getCondition(id: string): Condition; /** * Retrieves metadata for all conditions in the registry. * * This method returns an array of metadata objects containing information * about all registered conditions, including their IDs, types, and configuration details. * * @returns An array of condition metadata objects, or undefined if not supported by the registry implementation. * * @example Read metadata for every condition * ```ts * import { PlatformServiceName } from '@limetech/lime-web-components'; * * const registry = platform.get(PlatformServiceName.ConditionRegistry); * * const allMetadata = registry.getAllMetadata?.(); * if (allMetadata) { * for (const meta of allMetadata) { * console.log( * `Condition: ${meta.id}, Type: ${meta.type}, Title: ${meta.title}` * ); * } * } * ``` */ getAllMetadata?(): ConditionMetadata[]; } /** * Metadata for a condition * * @deprecated Display metadata moves onto {@link RulePrimitive} fields, * registered alongside the primitive. There is no separate condition * metadata shape. * * @group Conditions * @beta */ export type ConditionMetadata = ConfigMetadata & { /** * Unique identifier of the condition */ id: string; /** * Type identifier indicating the kind of subject this condition evaluates */ type: ConditionType; }; /** * Type identifier for a condition * * Specifies the kind of subject the condition evaluates. * - `'limeobject'`: Condition evaluates LimeObject instances * - `'action'`: Condition evaluates Action instances * Custom string types can also be used for domain-specific conditions * * @deprecated No longer needed. A {@link RulePrimitive} declares the context * keys it reads via `reads` instead of a single subject type. * * @group Conditions * @beta */ export type ConditionType = 'limeobject' | 'action' | (string & {});