Skip to main content

presolve_compiler/
runtime_component.rs

1use crate::{
2    ApplicationSemanticModel, ComponentInstanceId, ComponentInstanceStatus, ComponentInvocationId,
3    ContextSourceInstanceId, ExecutionBoundary, InstanceContextValueSlotId,
4    OptimizedComponentIrReport, SemanticId, SlotBindingId, SlotContentFragmentId, SlotId,
5    SlotOutletId, SourceProvenance,
6};
7
8/// Frozen H13 contract for compiler-owned component runtime metadata.
9pub const RUNTIME_COMPONENT_REGISTRY_SCHEMA_CONTRACT_VERSION: u32 = 1;
10
11/// Metadata consumed by the component runtime. It contains no authored-name
12/// lookup table and never grants runtime authority to resolve composition.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct RuntimeComponentRegistry {
15    pub schema_contract_version: u32,
16    pub component_definitions: Vec<RuntimeComponentDefinitionRecord>,
17    pub instances: Vec<RuntimeComponentInstanceRecord>,
18    pub slot_bindings: Vec<RuntimeComponentSlotBindingRecord>,
19    pub instance_context_bindings: Vec<RuntimeComponentContextBindingRecord>,
20    pub initialization_batches: Vec<RuntimeComponentInitializationBatch>,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct RuntimeComponentDefinitionRecord {
25    pub component: SemanticId,
26    pub template: SemanticId,
27    pub declared_slots: Vec<SlotId>,
28    pub boundary: ExecutionBoundary,
29    pub provenance: SourceProvenance,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct RuntimeComponentInstanceRecord {
34    pub instance: ComponentInstanceId,
35    pub component: SemanticId,
36    pub invocation: Option<ComponentInvocationId>,
37    pub parent: Option<ComponentInstanceId>,
38    pub depth: usize,
39    pub initialization_batch: usize,
40    pub instance_cache_prefix: String,
41    pub instance_context_prefix: String,
42    pub optimized_instruction_indices: Vec<usize>,
43    pub structural_region: Option<crate::ComponentStructuralRegionId>,
44    pub provenance: SourceProvenance,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct RuntimeComponentSlotBindingRecord {
49    pub binding: SlotBindingId,
50    pub caller_instance: ComponentInstanceId,
51    pub callee_instance: ComponentInstanceId,
52    pub slot: SlotId,
53    pub outlet: SlotOutletId,
54    pub fragment: SlotContentFragmentId,
55    pub content_owner_instance: ComponentInstanceId,
56    pub provenance: SourceProvenance,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct RuntimeComponentContextBindingRecord {
61    pub consumer_instance: crate::ConsumerInstanceId,
62    pub selected_source: ContextSourceInstanceId,
63    pub provider_source: Option<crate::ProviderInstanceId>,
64    pub runtime_slot: InstanceContextValueSlotId,
65    pub provenance: SourceProvenance,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct RuntimeComponentInitializationBatch {
70    pub index: usize,
71    pub instances: Vec<ComponentInstanceId>,
72    pub context_sources: Vec<ContextSourceInstanceId>,
73    pub slot_bindings: Vec<SlotBindingId>,
74}
75
76/// Projects H10/H12 into deterministic runtime metadata without resolving any
77/// component, slot, Provider, Context, ancestry, or dependency at runtime.
78#[must_use]
79#[allow(clippy::too_many_lines)]
80pub fn build_runtime_component_registry(
81    model: &ApplicationSemanticModel,
82    optimized: &OptimizedComponentIrReport,
83) -> RuntimeComponentRegistry {
84    let executable = model
85        .component_initialization
86        .instance_batches
87        .iter()
88        .enumerate()
89        .flat_map(|(batch, item)| item.instances.iter().cloned().map(move |id| (id, batch)))
90        .collect::<std::collections::BTreeMap<_, _>>();
91    let component_ids = executable
92        .keys()
93        .filter_map(|id| model.component_instance_plan.instances.get(id))
94        .map(|instance| instance.component.clone())
95        .collect::<std::collections::BTreeSet<_>>();
96    let component_definitions = component_ids
97        .iter()
98        .filter_map(|component| {
99            let provenance = model.provenance(component)?.clone();
100            Some(RuntimeComponentDefinitionRecord {
101                component: component.clone(),
102                template: component.template(),
103                declared_slots: model
104                    .slots
105                    .values()
106                    .filter(|slot| slot.owner == *component)
107                    .map(|slot| slot.id.clone())
108                    .collect(),
109                boundary: ExecutionBoundary::Client,
110                provenance,
111            })
112        })
113        .collect();
114    let instances = executable
115        .iter()
116        .filter_map(|(id, batch)| instance_record(model, optimized, id, *batch))
117        .collect();
118    let slot_bindings = model
119        .slot_bindings
120        .bindings
121        .values()
122        .filter(|binding| executable.contains_key(&binding.callee_instance))
123        .filter_map(|binding| {
124            Some(RuntimeComponentSlotBindingRecord {
125                binding: binding.id.clone(),
126                caller_instance: binding.caller_instance.clone(),
127                callee_instance: binding.callee_instance.clone(),
128                slot: binding.slot.clone()?,
129                outlet: binding.outlet.clone()?,
130                fragment: binding.content_fragment.clone()?,
131                content_owner_instance: binding.content_owner_instance.clone(),
132                provenance: binding.provenance.clone(),
133            })
134        })
135        .collect();
136    let instance_context_bindings = model
137        .instance_context
138        .resolutions
139        .values()
140        .filter(|resolution| {
141            executable.contains_key(&resolution.consumer_instance.component_instance)
142                && model
143                    .composition_types
144                    .instance_context_bindings
145                    .get(&resolution.consumer_instance)
146                    .is_some_and(|record| {
147                        record.overall == crate::CompositionCompatibility::Compatible
148                    })
149        })
150        .filter_map(|resolution| {
151            Some(RuntimeComponentContextBindingRecord {
152                consumer_instance: resolution.consumer_instance.clone(),
153                selected_source: resolution.selected_source.clone()?,
154                provider_source: resolution.provider_instance.clone(),
155                runtime_slot: resolution.value_slot.clone()?,
156                provenance: resolution.provenance.clone(),
157            })
158        })
159        .collect();
160    let initialization_batches = model
161        .component_initialization
162        .instance_batches
163        .iter()
164        .map(|batch| RuntimeComponentInitializationBatch {
165            index: batch.index,
166            instances: batch.instances.clone(),
167            context_sources: batch.context_sources.clone(),
168            slot_bindings: model
169                .component_initialization
170                .slot_binding_batches
171                .iter()
172                .filter(|bindings| bindings.index == batch.index)
173                .flat_map(|bindings| bindings.bindings.clone())
174                .collect(),
175        })
176        .collect();
177    RuntimeComponentRegistry {
178        schema_contract_version: RUNTIME_COMPONENT_REGISTRY_SCHEMA_CONTRACT_VERSION,
179        component_definitions,
180        instances,
181        slot_bindings,
182        instance_context_bindings,
183        initialization_batches,
184    }
185}
186
187fn instance_record(
188    model: &ApplicationSemanticModel,
189    optimized: &OptimizedComponentIrReport,
190    id: &ComponentInstanceId,
191    initialization_batch: usize,
192) -> Option<RuntimeComponentInstanceRecord> {
193    let instance = model.component_instance_plan.instances.get(id)?;
194    (instance.status == ComponentInstanceStatus::Planned).then_some(RuntimeComponentInstanceRecord {
195        instance: instance.id.clone(),
196        component: instance.component.clone(),
197        invocation: instance.invocation.clone(),
198        parent: instance.parent_instance.clone(),
199        depth: instance.depth,
200        initialization_batch,
201        instance_cache_prefix: format!("component-cache:{}", instance.id),
202        instance_context_prefix: format!("component-context:{}", instance.id),
203        optimized_instruction_indices: optimized
204            .optimized_report
205            .instructions
206            .iter()
207            .filter(|instruction| matches!(
208                &instruction.operation,
209                crate::ComponentIrOperation::CreateComponentInstance { instance: operation_instance, .. }
210                    | crate::ComponentIrOperation::InitializeComponentInstance { instance: operation_instance, .. }
211                    | crate::ComponentIrOperation::MaterializeComponentTemplate { instance: operation_instance, .. }
212                if operation_instance == &instance.id
213            ))
214            .map(|instruction| instruction.index)
215            .collect(),
216        structural_region: instance.structural_region.clone(),
217        provenance: instance.provenance.clone(),
218    })
219}
220
221#[cfg(test)]
222mod tests {
223    use crate::{
224        build_application_semantic_model, build_runtime_component_registry,
225        RUNTIME_COMPONENT_REGISTRY_SCHEMA_CONTRACT_VERSION,
226    };
227
228    #[test]
229    fn projects_only_planned_instances_in_deterministic_id_order() {
230        let model = build_application_semantic_model(&presolve_parser::parse_file(
231            "src/Registry.tsx",
232            r#"
233@component("x-leaf") class Leaf extends Component { render() { return <i />; } }
234@component("x-page") class Page extends Component { render() { return <><Leaf /><Leaf /></>; } }
235"#,
236        ));
237        let registry = build_runtime_component_registry(&model, &model.component_ir_optimization);
238        assert_eq!(
239            registry.schema_contract_version,
240            RUNTIME_COMPONENT_REGISTRY_SCHEMA_CONTRACT_VERSION
241        );
242        assert_eq!(registry.instances.len(), 3);
243        assert_ne!(
244            registry.instances[1].instance,
245            registry.instances[2].instance
246        );
247        assert!(registry
248            .instances
249            .windows(2)
250            .all(|pair| pair[0].instance < pair[1].instance));
251        assert!(registry
252            .instances
253            .iter()
254            .all(|record| !record.optimized_instruction_indices.is_empty()));
255    }
256}