import type { CronExpression, TBlackHole, TContext } from "@digital-alchemy/core"; import type { ByIdProxy, ENTITY_STATE, PICK_ENTITY, RemoveCallback, TEntityUpdateCallback, TRawDomains } from "@digital-alchemy/hass"; import type { CamelCase } from "type-fest"; import type { CreateRemovableCallback, TEventMap } from "./base-domain.mts"; import type { TSynapseEntityStorage } from "./storage.mts"; import type { TSynapseDeviceId } from "./utility.mts"; export type EntityConfigCommon = { /** * Use a different device to register this entity */ device_id?: TSynapseDeviceId; /** * Attempt to create the entity id using this string * * `binary_sensor.{suggested id}` * * Home assistant _may_ append numbers to the end in case of object_id conflicts where `unique_id` isn't the same. * * > **NOTE:** Default value based on `name` */ suggested_object_id?: string; /** * Provide your own unique id for this entity * * This ID uniquely identifies the entity, through `entity_id` renames */ unique_id?: string; disabled?: SettableConfiguration; icon?: SettableConfiguration; /** * An entity with a category will: * - Not be exposed to cloud, Alexa, or Google Assistant components * - Not be included in indirect service calls to devices or areas * * **Config**: An entity which allows changing the configuration of a device. * * **Diagnostic**: An entity exposing some configuration parameter, or diagnostics of a device. */ entity_category?: "config" | "diagnostic"; /** * Default name to provide for the entity */ name: string; translation_key?: string; /** * passed through as extra entity attributes to home assistant * * > consider creating sensor entities instead */ attributes?: ATTRIBUTES; /** * local state data, not sent to home assistant * * can be used as a sqlite backed cache for entity specific data */ locals?: LOCALS; /** * Automatically trigger reactive config updates in response to updates from these entities * * List gets merged with `onUpdate` array in the configs, is convenient shorthand */ bind?: Updatable[]; }; export declare const isCommonConfigKey: (key: string) => key is keyof EntityConfigCommon; export type SettableConfiguration = /** * Straight provide the value. * If this changes in the definition (hard coded value usually), then the entity config will be reset * * This option can be used with assignments * * ```typescript * entity.field = new_value; * ``` */ TYPE | ReactiveConfig | ((data: DATA) => TYPE); export type Updatable = { onUpdate: (callback: (data: DATA) => TBlackHole) => void; }; /** * > **NOTE**: `onUpdate` list is merged with the `bind` array that is provided to the entity * ```typescript * { * icon: { * current() { * return someLogic ? "mdi:cookie-clock" : "mdi:cookie-alert-outline"; * }, * onUpdate: [hassEntityReference, synapseEntityReference], * // every 30 seconds by default * schedule: CronExpression.EVERY_SECOND, * }, * } * ``` */ export type ReactiveConfig = { /** * Update immediately in response to entity updates */ onUpdate?: Updatable[]; /** * Every 30s by default */ schedule?: CronExpression | string; /** * Calculate current value */ current(data: DATA): TYPE; }; export declare const isShortReactiveConfig: (key: string, value: unknown) => value is ReactiveConfig; export declare const isReactiveConfig: (key: string, value: unknown) => value is ReactiveConfig; export declare const NO_LIVE_UPDATE: Set; export declare const COMMON_CONFIG_KEYS: Set; export type NON_SETTABLE = "managed" | "suggested_object_id" | "unique_id" | "device_id" | "device_class" | "translation_key" | "entity_category"; export type NonReactive = { [KEY in Extract]: CONFIGURATION[KEY] extends SettableConfiguration ? TYPE : CONFIGURATION[KEY]; }; /** * Extract the domain from a PICK_ENTITY type */ type ExtractDomain = ENTITY extends PICK_ENTITY ? DOMAIN : TRawDomains; export type CommonMethods = { /** * The domain of this entity (e.g., "sensor", "light", "switch") */ domain: ExtractDomain; /** * Look up the actual entity_id that is mapped to this entity by unique_id */ entity_id: ENTITY; /** * retrieve the related hass entity reference * * note: requires that entity actually exist in home assistant to be valid (does a lookup) */ getEntity: () => ByIdProxy; /** * Run callback once, for next update */ once: (callback: TEntityUpdateCallback) => RemoveCallback; /** * Will resolve with the next state of the next value. No time limit */ nextState: (timeoutMs?: number) => Promise>; /** * Will resolve when state */ waitForState: (state: string | number, timeoutMs?: number) => Promise>; /** * triggered by the hass entity emitting a state change, requires full round trip: * * 0) trigger * 1) you change something * 2) synapse sends change to extension * 3) extension notifies hass * 4) hass emits update event * 5) this callback gets triggered */ onUpdate(callback: TEntityUpdateCallback): RemoveCallback; /** * @internal */ storage: TSynapseEntityStorage>; /** * add a listener that can be removed with the removeAllListeners call * * for use by other libraries */ addListener: (remove: RemoveCallback) => void; /** * Remove all runtime resources related to this particular entity * * - does not remove entity from database * - does not emit anything to home assistant */ removeAllListeners: () => void; /** * - removes entity from local database * - attempts to remove entity from integration */ purge: () => void; }; /** * Synapse proxy */ type ProxyBase = CommonMethods & NonReactive & BuildCallbacks & EntityConfigCommon & { destroy: () => Promise; /** * @internal * * duplicate the entity proxy, used for management of listeners */ child: (context: TContext) => ProxyBase; }; /** * The combination of all properties that went in, minus those that don't play well with runtime updates * * That is also enforced */ export type SynapseEntityProxy> = Omit>; export type BuildCallbacks = { [EVENT_NAME in Extract as CamelCase<`on-${EVENT_NAME}`>]: CreateRemovableCallback; }; export type GenericSynapseEntity = SynapseEntityProxy; export {};