import { generateId } from './_helpers'; import type { CustomObject } from './custom-object'; import { getExistingManifestContext, type ExistingManifestContextOptions } from './existing-manifest-context'; import type { ManifestComponentDeletion } from './manifest-builder'; import type { ManifestFunction } from './manifest-function'; import { requireArrayComponentField, requireStringComponentField } from './_existing-component-fields'; export type CustomObjectActionVisibilityPreference = 'LIST_VIEW' | 'DETAIL_VIEW'; export interface CustomObjectActionDeletionIdentifier { actionId: string; objectRqlName: string; } export interface CustomObjectActionProps { /** Stable identifier for this action. Auto-generated when omitted. */ actionId?: string; /** Display name shown in the list or detail view. */ name: string; /** Function invoked by the action. */ function: ManifestFunction; /** Views where the action is shown. */ visibilityPreferences: CustomObjectActionVisibilityPreference[]; } export interface CustomObjectActionLoadFromExistingOptions extends ExistingManifestContextOptions { customObjectApiName?: string; } /** Defines an action shown on a custom object's list or detail view. */ export class CustomObjectAction { static readonly componentType = 'CUSTOM_OBJECT_ACTION' as const; static toDeletionIdentifier(identifier: CustomObjectActionDeletionIdentifier): ManifestComponentDeletion { return { type: CustomObjectAction.componentType, action_id: identifier.actionId, object_rql_name: identifier.objectRqlName, }; } private readonly _actionId: string; private readonly _objectRqlName: string; private _name: string; private _functionApiName: string; private _visibilityPreferences: CustomObjectActionVisibilityPreference[]; constructor(customObject: CustomObject, props: CustomObjectActionProps) { if (!props.name.trim()) throw new Error('CustomObjectAction: name cannot be empty'); this._actionId = props.actionId ?? generateId('action'); this._objectRqlName = customObject.getApiName(); this._name = props.name; this._functionApiName = props.function.getApiName(); this._visibilityPreferences = [...props.visibilityPreferences]; customObject._register(this); } static loadFromExisting( actionId: string, options: CustomObjectActionLoadFromExistingOptions = {}, ): CustomObjectAction { return getExistingManifestContext(options).loadCustomObjectAction(actionId, options); } /** @internal Hydrates an action from existing manifest wire JSON. */ static _fromExistingComponent( customObject: CustomObject, component: Record, resolveFunction: (apiName: string) => ManifestFunction, ): CustomObjectAction { return new CustomObjectAction(customObject, { actionId: requireStringComponentField(component, 'action_id', CustomObjectAction.componentType), name: requireStringComponentField(component, 'name', CustomObjectAction.componentType), function: resolveFunction( requireStringComponentField(component, 'function_api_name', CustomObjectAction.componentType), ), visibilityPreferences: requireArrayComponentField( component, 'visibility_preferences', CustomObjectAction.componentType, ), }); } getActionId(): string { return this._actionId; } /** Replaces the action's display name. */ setName(name: string): this { if (!name.trim()) throw new Error('CustomObjectAction: name cannot be empty'); this._name = name; return this; } /** Replaces the function invoked by this action. */ setFunction(fn: ManifestFunction): this { this._functionApiName = fn.getApiName(); return this; } /** Replaces the views where this action is shown. */ setVisibilityPreferences(preferences: CustomObjectActionVisibilityPreference[]): this { this._visibilityPreferences = [...preferences]; return this; } toDict(): Record { return { type: CustomObjectAction.componentType, action_id: this._actionId, object_rql_name: this._objectRqlName, name: this._name, function_api_name: this._functionApiName, object_type: 'CUSTOM', visibility_preferences: this._visibilityPreferences, }; } }