/** * Chart rule vocabulary — shared types. * * A rule is a small, serializable value discriminated by `kind`. The * agent builds it with the helpers in `builders.ts`, which emit it wrapped in a * `{$rule}` placeholder — the same idea as the `{$fn}` placeholders a chart * already emits. The renderer resolves it with `evaluate`; the settings panel * reads it with `describe`. All three agree because a node can only ever be one * of the kinds defined here — the closed set the executable `chartRuleSchema` * validates at runtime. * * v1 is deliberately non-recursive: compare this datum's `value` or a literal * against another literal, then choose between two colour literals. Rules may * not appear inside operands or branches. Richer expressions require a future * contract version. */ import { COMPARISON_KINDS, RULE_CHANNELS } from './vocabulary'; export const CHART_RULE_CONTRACT_VERSION = 'chart_rule.v1' as const; export type ChartRuleContractVersion = typeof CHART_RULE_CONTRACT_VERSION; /** A leaf value a rule can carry or resolve to. */ export type Scalar = number | string | boolean | null; /* -------------------------------------------------------------------------- */ /* Nodes — the closed set, discriminated by `kind`. Each appears in every face. */ /* -------------------------------------------------------------------------- */ /** Reads a value out of the evaluation context — this datum's plotted value. */ export type RefNode = { kind: 'value' }; export interface LiteralNode { kind: 'literal'; value: TValue; } export type ComparisonOperandNode = RefNode | LiteralNode; export type NumericOperandNode = RefNode | LiteralNode; export type ComparisonKind = (typeof COMPARISON_KINDS)[number]; export interface EqualityComparisonNode { kind: 'equals' | 'isNot'; left: ComparisonOperandNode; right: ComparisonOperandNode; } export interface NumericComparisonNode { kind: Exclude; left: NumericOperandNode; right: NumericOperandNode; } export type ComparisonNode = EqualityComparisonNode | NumericComparisonNode; /** A node whose evaluated result is valid as a `when(...)` condition in v1. */ export type ConditionNode = ComparisonNode; /** The conditional-value form produced by `when(cond, then, else)`. */ export interface IfElseNode { kind: 'ifElse'; cond: ConditionNode; then: LiteralNode; else: LiteralNode; } /** Any top-level node accepted by the v1 runtime boundary. */ export type RuleNode = RefNode | LiteralNode | ComparisonNode | IfElseNode; /* -------------------------------------------------------------------------- */ /* The placeholder — how a rule is embedded in a chart option (like {$fn}). */ /* -------------------------------------------------------------------------- */ /** * The chart channel a rule drives. v1 supports colour only — a rule decides the * colour of a series/datum. Other channels (opacity, label visibility, …) are * the documented roadmap and would be added here. */ export type RuleChannel = (typeof RULE_CHANNELS)[number]; /** * What a builder returns: a flat rule value, marked so the renderer can find it, and * tagged with the channel it targets. `channel: 'color'` says this resolves to a * colour — so a reader sees the target without inferring it from where it sits. */ export interface RulePlaceholder { $rule: IfElseNode; channel: RuleChannel; } /** A placeholder, or a bare node — what `evaluate` and `describe` accept. */ export type Rule = RulePlaceholder | RuleNode; type DeepReadonly = T extends object ? { readonly [Key in keyof T]: DeepReadonly } : T; /** The deeply frozen value returned by `parseChartRule`. */ export type ParsedRule = DeepReadonly; /* -------------------------------------------------------------------------- */ /* Evaluation */ /* -------------------------------------------------------------------------- */ /** What a rule is evaluated against: the current datum's value. */ export interface RuleContext { value: number; } /** The result of evaluating a node. */ export type RuleValue = Scalar; /** @deprecated v1 is non-recursive. Retained for source compatibility. */ export const MAX_RULE_DEPTH = 256; /* -------------------------------------------------------------------------- */ /* Description — what the settings panel renders */ /* -------------------------------------------------------------------------- */ export type ControlRole = 'rule' | 'comparison' | 'ref' | 'literal'; /** A node as the panel should show it: a label, its editable value, its parts. */ export interface Control { role: ControlRole; label: string; value?: Scalar; parts?: Control[]; }