1use std::collections::BTreeMap;
2
3use serde::Serialize;
4
5use crate::runtime_computed_artifact::{
6 runtime_instruction, RuntimeComputedArtifactInstruction, RuntimeComputedArtifactOperand,
7};
8use crate::{
9 build_context_update_plan, build_runtime_context_registry, ContextEvaluationBatchId,
10 ExecutionBoundary, OptimizedContextIrReport, RuntimeContextConsumerRecord,
11 RuntimeContextEvaluationBatch, RuntimeContextSourceKind, RuntimeContextSourceRecord,
12};
13
14pub const RUNTIME_CONTEXT_ARTIFACT_SCHEMA_VERSION: u32 = 2;
15
16#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
18pub struct RuntimeContextArtifact {
19 pub schema_version: u32,
20 pub sources: Vec<SerializedContextSource>,
21 pub consumers: Vec<SerializedContextConsumerBinding>,
22 pub initial_batches: Vec<SerializedContextEvaluationBatch>,
23 pub action_updates: Vec<SerializedContextActionUpdatePlan>,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
27pub struct SerializedContextSource {
28 pub source: String,
29 pub context: String,
30 pub slot: String,
31 pub source_function: String,
32 pub source_kind: SerializedContextSourceKind,
33 pub program: SerializedContextProgram,
34 pub required_state: Vec<String>,
35 pub required_computed: Vec<String>,
36 pub prerequisite_computed_batches: Vec<u32>,
37 pub evaluation_batch: SerializedContextBatchId,
38 pub semantic_type: String,
39 pub execution_boundary: SerializedContextExecutionBoundary,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
43pub struct SerializedContextProgram {
44 pub result: String,
45 pub instructions: Vec<SerializedContextInstruction>,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
49#[serde(untagged)]
50pub enum SerializedContextInstruction {
51 Evaluate(RuntimeComputedArtifactInstruction),
52 InitializeContextSlot {
53 #[serde(rename = "kind")]
54 kind: SerializedContextInstructionKind,
55 slot: String,
56 value: RuntimeComputedArtifactOperand,
57 },
58 LoadContextSlot {
59 #[serde(rename = "kind")]
60 kind: SerializedContextInstructionKind,
61 result: String,
62 slot: String,
63 },
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
67#[serde(rename_all = "snake_case")]
68pub enum SerializedContextInstructionKind {
69 InitializeContextSlot,
70 LoadContextSlot,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
74#[serde(rename_all = "kebab-case")]
75pub enum SerializedContextSourceKind {
76 Provider,
77 ContextDefault,
78}
79
80#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
81pub struct SerializedContextConsumerBinding {
82 pub consumer: String,
83 pub context: String,
84 pub selected_source: String,
85 pub slot: String,
86 pub load_identity: String,
87 pub semantic_type: String,
88 pub source_batch: SerializedContextBatchId,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
92pub struct SerializedContextEvaluationBatch {
93 pub id: SerializedContextBatchId,
94 pub sources: Vec<String>,
95}
96
97#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
98pub struct SerializedContextActionUpdatePlan {
99 pub action_batch: String,
100 pub invalidated_sources: Vec<String>,
101 pub prerequisite_computed_batches: Vec<u32>,
102 pub source_evaluation_batches: Vec<SerializedContextBatchId>,
103 pub affected_consumers: Vec<String>,
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
107pub struct SerializedContextBatchId {
108 pub plan: &'static str,
109 pub index: u32,
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
113#[serde(rename_all = "kebab-case")]
114pub enum SerializedContextExecutionBoundary {
115 Client,
116 Server,
117}
118
119#[must_use]
122pub fn build_runtime_context_artifact(
123 model: &crate::ApplicationSemanticModel,
124 optimized: &OptimizedContextIrReport,
125) -> RuntimeContextArtifact {
126 let registry = build_runtime_context_registry(model, optimized);
127 let updates = build_context_update_plan(model, optimized);
128 let programs = context_programs(optimized);
129 RuntimeContextArtifact {
130 schema_version: RUNTIME_CONTEXT_ARTIFACT_SCHEMA_VERSION,
131 sources: registry
132 .sources
133 .iter()
134 .filter_map(|record| {
135 programs
136 .get(&record.source)
137 .cloned()
138 .map(|program| serialized_source(record, program))
139 })
140 .collect(),
141 consumers: registry.consumers.iter().map(serialized_consumer).collect(),
142 initial_batches: registry
143 .initial_batches
144 .iter()
145 .map(serialized_batch)
146 .collect(),
147 action_updates: updates.actions.iter().map(serialized_update).collect(),
148 }
149}
150
151#[must_use]
157pub fn runtime_context_artifact_json(artifact: &RuntimeContextArtifact) -> String {
158 serde_json::to_string_pretty(artifact).expect("Context runtime artifact should serialize")
159 + "\n"
160}
161
162fn context_programs(
163 optimized: &OptimizedContextIrReport,
164) -> BTreeMap<crate::ContextValueSourceId, SerializedContextProgram> {
165 optimized
166 .source_evaluations
167 .iter()
168 .filter_map(|evaluation| {
169 let function = optimized
170 .optimized_module
171 .modules
172 .iter()
173 .flat_map(|module| &module.functions)
174 .find(|function| function.id == *evaluation.function.as_semantic_id())?;
175 let instructions = function
176 .blocks
177 .iter()
178 .flat_map(|block| &block.instructions)
179 .map(serialized_instruction)
180 .collect::<Option<Vec<_>>>()?;
181 Some((
182 evaluation.source.clone(),
183 SerializedContextProgram {
184 result: evaluation.result.as_str().to_string(),
185 instructions,
186 },
187 ))
188 })
189 .collect()
190}
191
192fn serialized_instruction(
193 instruction: &crate::IrInstruction,
194) -> Option<SerializedContextInstruction> {
195 match &instruction.kind {
196 crate::IrInstructionKind::InitializeContextSlot { slot, value } => {
197 Some(SerializedContextInstruction::InitializeContextSlot {
198 kind: SerializedContextInstructionKind::InitializeContextSlot,
199 slot: slot.as_str().to_string(),
200 value: RuntimeComputedArtifactOperand::Value {
201 value: value.as_str().to_string(),
202 },
203 })
204 }
205 crate::IrInstructionKind::LoadContextSlot { slot } => {
206 Some(SerializedContextInstruction::LoadContextSlot {
207 kind: SerializedContextInstructionKind::LoadContextSlot,
208 result: instruction.result.as_ref()?.as_str().to_string(),
209 slot: slot.as_str().to_string(),
210 })
211 }
212 _ => runtime_instruction(instruction).map(SerializedContextInstruction::Evaluate),
213 }
214}
215
216fn serialized_source(
217 record: &RuntimeContextSourceRecord,
218 program: SerializedContextProgram,
219) -> SerializedContextSource {
220 SerializedContextSource {
221 source: source_id(&record.source),
222 context: record.context.as_str().to_string(),
223 slot: record.slot.as_str().to_string(),
224 source_function: record.function.as_semantic_id().as_str().to_string(),
225 source_kind: source_kind(record.source_kind),
226 program,
227 required_state: semantic_ids(&record.required_state),
228 required_computed: semantic_ids(&record.required_computed),
229 prerequisite_computed_batches: record.prerequisite_computed_batches.clone(),
230 evaluation_batch: batch_id(&record.evaluation_batch),
231 semantic_type: record.semantic_type.to_string(),
232 execution_boundary: boundary(record.boundary),
233 }
234}
235
236fn serialized_consumer(record: &RuntimeContextConsumerRecord) -> SerializedContextConsumerBinding {
237 SerializedContextConsumerBinding {
238 consumer: record.consumer.as_str().to_string(),
239 context: record.context.as_str().to_string(),
240 selected_source: source_id(&record.selected_source),
241 slot: record.slot.as_str().to_string(),
242 load_identity: record.load_identity.as_semantic_id().as_str().to_string(),
243 semantic_type: record.semantic_type.to_string(),
244 source_batch: batch_id(&record.source_batch),
245 }
246}
247
248fn serialized_batch(batch: &RuntimeContextEvaluationBatch) -> SerializedContextEvaluationBatch {
249 SerializedContextEvaluationBatch {
250 id: batch_id(&batch.id),
251 sources: batch.sources.iter().map(source_id).collect(),
252 }
253}
254
255fn serialized_update(update: &crate::ContextActionUpdatePlan) -> SerializedContextActionUpdatePlan {
256 SerializedContextActionUpdatePlan {
257 action_batch: update.action_batch.as_str().to_string(),
258 invalidated_sources: update.invalidated_sources.iter().map(source_id).collect(),
259 prerequisite_computed_batches: update.prerequisite_computed_batches.clone(),
260 source_evaluation_batches: update
261 .source_evaluation_batches
262 .iter()
263 .map(batch_id)
264 .collect(),
265 affected_consumers: update
266 .affected_consumers
267 .iter()
268 .map(|consumer| consumer.as_str().to_string())
269 .collect(),
270 }
271}
272
273fn batch_id(batch: &ContextEvaluationBatchId) -> SerializedContextBatchId {
274 SerializedContextBatchId {
275 plan: "initial",
276 index: batch.index,
277 }
278}
279
280fn semantic_ids(values: &[crate::SemanticId]) -> Vec<String> {
281 values
282 .iter()
283 .map(|value| value.as_str().to_string())
284 .collect()
285}
286
287fn source_id(source: &crate::ContextValueSourceId) -> String {
288 match source {
289 crate::ContextValueSourceId::Provider(provider) => provider.as_str().to_string(),
290 crate::ContextValueSourceId::ContextDefault(context) => {
291 format!("{}/default", context.as_str())
292 }
293 }
294}
295
296const fn source_kind(kind: RuntimeContextSourceKind) -> SerializedContextSourceKind {
297 match kind {
298 RuntimeContextSourceKind::Provider => SerializedContextSourceKind::Provider,
299 RuntimeContextSourceKind::ContextDefault => SerializedContextSourceKind::ContextDefault,
300 }
301}
302
303const fn boundary(boundary: ExecutionBoundary) -> SerializedContextExecutionBoundary {
304 match boundary {
305 ExecutionBoundary::Client => SerializedContextExecutionBoundary::Client,
306 ExecutionBoundary::Server => SerializedContextExecutionBoundary::Server,
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use crate::{
313 build_application_semantic_model, build_runtime_context_artifact, lower_components_to_ir,
314 optimize_context_ir, runtime_context_artifact_json,
315 RUNTIME_CONTEXT_ARTIFACT_SCHEMA_VERSION,
316 };
317
318 #[test]
319 fn emits_deterministic_context_slot_programs_without_runtime_lookup_operations() {
320 let model = build_application_semantic_model(&presolve_parser::parse_file(
321 "src/App.tsx",
322 r#"
323@component("x-app")
324class App extends Component {
325 count = state(1);
326 @context()
327 total!: number;
328 @provide(App.total)
329 providedTotal: number = this.count + 2;
330 @consume(App.total)
331 total!: number;
332 render() { return <main />; }
333}
334"#,
335 ));
336 let optimized = optimize_context_ir(&lower_components_to_ir(&model));
337 let artifact = build_runtime_context_artifact(&model, &optimized);
338 let json = runtime_context_artifact_json(&artifact);
339
340 assert_eq!(
341 artifact.schema_version,
342 RUNTIME_CONTEXT_ARTIFACT_SCHEMA_VERSION
343 );
344 assert_eq!(artifact.sources.len(), 1);
345 assert_eq!(artifact.consumers.len(), 1);
346 assert_eq!(artifact.sources[0].program.instructions.len(), 4);
347 assert!(json.contains("initialize_context_slot"));
348 assert!(json.contains("load-state"));
349 assert!(!json.contains("find_provider"));
350 assert!(!json.contains("resolve_context"));
351 assert!(!json.contains("walk_parent"));
352 assert!(!json.contains("lookup_by_name"));
353 assert_eq!(
354 json,
355 runtime_context_artifact_json(&build_runtime_context_artifact(&model, &optimized))
356 );
357 }
358}