presolve_compiler/
context_resume.rs1use serde::{Deserialize, Serialize};
2
3use crate::{
4 ContextValueSlotId, ContextValueSourceId, ExecutionBoundary, RuntimeContextRegistry,
5 RuntimeContextSourceKind, SourceProvenance,
6};
7
8#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
10#[serde(transparent)]
11pub struct ContextResumeSlotId(String);
12
13impl ContextResumeSlotId {
14 #[must_use]
15 pub fn for_source(source: &ContextValueSourceId) -> Self {
16 let source = match source {
17 ContextValueSourceId::Provider(provider) => provider.as_str().to_string(),
18 ContextValueSourceId::ContextDefault(context) => {
19 format!("{}/default", context.as_str())
20 }
21 };
22 Self(format!("{source}/resume:context-slot"))
23 }
24
25 #[must_use]
26 pub fn as_str(&self) -> &str {
27 &self.0
28 }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(rename_all = "snake_case")]
33pub enum ContextSlotResumeStatus {
34 Uninitialized,
35 Initialized,
36 Failed,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct ContextResumeRecord {
41 pub source: ContextValueSourceId,
42 pub context: crate::ContextId,
43 pub runtime_slot: ContextValueSlotId,
44 pub resume_slot: ContextResumeSlotId,
45 pub semantic_type: crate::SemanticTypeId,
46 pub source_kind: RuntimeContextSourceKind,
47 pub initial_status: ContextSlotResumeStatus,
48 pub action_batches: Vec<crate::SemanticId>,
49 pub consumers: Vec<crate::ConsumerId>,
50 pub boundary: ExecutionBoundary,
51 pub provenance: SourceProvenance,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Default)]
55pub struct ContextResumePlan {
56 pub records: Vec<ContextResumeRecord>,
57}
58
59#[must_use]
60pub fn build_context_resume_plan(
61 registry: &RuntimeContextRegistry,
62 updates: &crate::ContextUpdatePlan,
63) -> ContextResumePlan {
64 ContextResumePlan {
65 records: registry
66 .sources
67 .iter()
68 .map(|source| ContextResumeRecord {
69 source: source.source.clone(),
70 context: source.context.clone(),
71 runtime_slot: source.slot.clone(),
72 resume_slot: ContextResumeSlotId::for_source(&source.source),
73 semantic_type: source.semantic_type.clone(),
74 source_kind: source.source_kind,
75 initial_status: ContextSlotResumeStatus::Uninitialized,
76 action_batches: updates
77 .actions
78 .iter()
79 .filter(|action| action.invalidated_sources.contains(&source.source))
80 .map(|action| action.action_batch.clone())
81 .collect(),
82 consumers: registry
83 .consumers
84 .iter()
85 .filter(|consumer| consumer.selected_source == source.source)
86 .map(|consumer| consumer.consumer.clone())
87 .collect(),
88 boundary: source.boundary,
89 provenance: source.provenance.clone(),
90 })
91 .collect(),
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use crate::{
98 build_application_semantic_model, build_resume_manifest, build_resume_plan,
99 ContextSlotResumeStatus,
100 };
101
102 #[test]
103 fn plans_distinct_uninitialized_resume_slots_for_runtime_context_sources() {
104 let model = build_application_semantic_model(&presolve_parser::parse_file(
105 "src/App.tsx",
106 r#"
107@component("x-app")
108class App extends Component {
109 @context() locale: string = "en";
110 @consume(App.locale) locale!: string;
111 render() { return <main />; }
112}
113"#,
114 ));
115 let plan = build_resume_plan(&model);
116 let record = plan
117 .contexts
118 .records
119 .first()
120 .expect("Context resume record");
121 assert_eq!(
122 record.initial_status,
123 ContextSlotResumeStatus::Uninitialized
124 );
125 assert_ne!(record.resume_slot.as_str(), record.runtime_slot.as_str());
126 let manifest = build_resume_manifest(&model);
127 assert_eq!(manifest.schema_version, 7);
128 assert_eq!(
129 manifest
130 .phase_i_component_resume_records
131 .iter()
132 .filter(|record| matches!(
133 record,
134 crate::ResumeManifestPhaseIComponentResumeRecord::ContextSlot { .. }
135 ))
136 .count(),
137 1
138 );
139 }
140}