Skip to main content

type_bridge_schema/
project.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use type_bridge_contract::diagnostic::{Diagnostic, DiagnosticCategory};
4use type_bridge_contract::id::{AttributeId, RoleId, TypeId, TypeKind};
5use type_bridge_contract::projection::{
6    BindingTarget, CodeResourceDigest, CompleteReadProjection, CreateFieldProjection,
7    CreateProjection, CreateRoleProjection, DeclarationProjection, DeclaredRoleProjection,
8    DirectSubProjection, EmissionPlan, FieldTokenProjection, FunctionParameterProjection,
9    FunctionProjection, FunctionReturnElementProjection, FunctionReturnProjection, ModelProjection,
10    PlayingProjection, ProjectedAnnotation, ProjectedModelForm, ProjectedModelUse,
11    ProjectedMultiplicity, ProjectedTypeRef, ProjectionConfig, ProjectionHandler,
12    QueryTokenProjection, ReadFieldProjection, ReadRoleProjection, ReferenceReadProjection,
13    RoleTokenProjection, RuntimeProjection, RustCreatePolicy, StructFieldProjection,
14    StructProjection, TargetIdentifier,
15};
16use type_bridge_contract::schema::{
17    AnnotationFactId, AnnotationKindId, AnnotationSubjectId, FunctionReturnMode, OwnsFactId,
18    RelatesFactId, SchemaAnnotationValue, SchemaDiagnostic, SchemaDiagnostics, TypeReference,
19    ValueFactId,
20};
21
22use crate::resolve::{EffectiveRelates, ResolvedSchema, ResolvedType};
23
24fn no_source(error: Diagnostic) -> SchemaDiagnostics {
25    SchemaDiagnostics::one(SchemaDiagnostic::new(error, None))
26}
27
28fn projection_error(code: &'static str, message: impl Into<String>) -> SchemaDiagnostics {
29    crate::diagnostic::diagnostic(DiagnosticCategory::InvalidContract, code, message, None)
30}
31
32fn python_class_name(label: &str) -> String {
33    label
34        .replace('_', "-")
35        .split('-')
36        .map(|part| {
37            let mut chars = part.chars();
38            match chars.next() {
39                Some(first) => {
40                    let mut output = first.to_uppercase().collect::<String>();
41                    let rest = chars.as_str();
42                    let uniform = part.chars().all(char::is_uppercase)
43                        || part.chars().all(char::is_lowercase);
44                    if uniform {
45                        output.push_str(&rest.to_lowercase());
46                    } else {
47                        output.push_str(rest);
48                    }
49                    output
50                }
51                None => String::new(),
52            }
53        })
54        .collect()
55}
56
57fn python_member_name(label: &str) -> String {
58    label.replace('-', "_")
59}
60
61fn typescript_member_name(label: &str) -> String {
62    let class_name = python_class_name(label);
63    let mut chars = class_name.chars();
64    chars.next().map_or_else(String::new, |first| {
65        first.to_lowercase().chain(chars).collect()
66    })
67}
68
69fn rust_member_name(label: &str) -> String {
70    label.replace('-', "_").to_ascii_lowercase()
71}
72
73fn python_identifier(value: String) -> Result<TargetIdentifier, SchemaDiagnostics> {
74    if matches!(value.as_str(), "iid" | "model_config" | "model_fields")
75        || value.starts_with("__") && value.ends_with("__")
76    {
77        return Err(projection_error(
78            "reserved_python_projection_identifier",
79            "projected Python name collides with generated runtime state",
80        ));
81    }
82    TargetIdentifier::python(value).map_err(no_source)
83}
84
85fn typescript_identifier(value: String) -> Result<TargetIdentifier, SchemaDiagnostics> {
86    if matches!(
87        value.as_str(),
88        "iid"
89            | "typeToken"
90            | "fields"
91            | "roles"
92            | "plays"
93            | "create"
94            | "reference"
95            | "prototype"
96            | "__proto__"
97    ) {
98        return Err(projection_error(
99            "reserved_typescript_projection_identifier",
100            "projected TypeScript name collides with generated runtime state",
101        ));
102    }
103    TargetIdentifier::typescript(value).map_err(no_source)
104}
105
106fn rust_identifier(value: String) -> Result<TargetIdentifier, SchemaDiagnostics> {
107    TargetIdentifier::rust(value).map_err(no_source)
108}
109
110fn class_identifier(
111    target: BindingTarget,
112    label: &str,
113) -> Result<TargetIdentifier, SchemaDiagnostics> {
114    let value = python_class_name(label);
115    match target {
116        BindingTarget::Python => python_identifier(value),
117        BindingTarget::TypeScript => typescript_identifier(value),
118        BindingTarget::Rust => rust_identifier(value),
119    }
120}
121
122fn member_identifier(
123    target: BindingTarget,
124    label: &str,
125) -> Result<TargetIdentifier, SchemaDiagnostics> {
126    match target {
127        BindingTarget::Python => python_identifier(python_member_name(label)),
128        BindingTarget::TypeScript => typescript_identifier(typescript_member_name(label)),
129        BindingTarget::Rust => rust_identifier(rust_member_name(label)),
130    }
131}
132
133fn reference_identifier(
134    target: BindingTarget,
135    name: &TargetIdentifier,
136) -> Result<TargetIdentifier, SchemaDiagnostics> {
137    match target {
138        BindingTarget::Python => python_identifier(format!("{}Ref", name.as_str())),
139        BindingTarget::TypeScript => typescript_identifier(format!("{}Ref", name.as_str())),
140        BindingTarget::Rust => rust_identifier(format!("{}Ref", name.as_str())),
141    }
142}
143
144#[derive(Default)]
145struct NameRegistry {
146    names: BTreeMap<(String, String), String>,
147}
148
149impl NameRegistry {
150    fn insert(
151        &mut self,
152        namespace: impl Into<String>,
153        name: &TargetIdentifier,
154        identity: impl Into<String>,
155    ) -> Result<(), SchemaDiagnostics> {
156        let key = (namespace.into(), name.as_str().to_owned());
157        let identity = identity.into();
158        if let Some(previous) = self.names.get(&key) {
159            if previous != &identity {
160                return Err(projection_error(
161                    "projection_name_collision",
162                    format!(
163                        "projected name `{}` in namespace `{}` collides between `{}` and `{}`",
164                        key.1, key.0, previous, identity
165                    ),
166                ));
167            }
168        } else {
169            self.names.insert(key, identity);
170        }
171        Ok(())
172    }
173}
174
175fn annotations(
176    subject: AnnotationSubjectId,
177    values: &BTreeMap<AnnotationKindId, SchemaAnnotationValue>,
178) -> Result<BTreeMap<AnnotationFactId, ProjectedAnnotation>, SchemaDiagnostics> {
179    values
180        .iter()
181        .map(|(kind, value)| {
182            let id = AnnotationFactId::new(subject.clone(), kind.clone());
183            ProjectedAnnotation::new(id.clone(), value.clone())
184                .map(|annotation| (id, annotation))
185                .map_err(no_source)
186        })
187        .collect()
188}
189
190fn effective_relates_annotation_subject(
191    relates: &EffectiveRelates,
192) -> Result<AnnotationSubjectId, SchemaDiagnostics> {
193    let role = RoleId::new(
194        relates.id().relation().label().as_str(),
195        relates.id().role().label().as_str(),
196    )
197    .map_err(no_source)?;
198    RelatesFactId::new(relates.id().relation().clone(), role)
199        .map(AnnotationSubjectId::Relates)
200        .map_err(no_source)
201}
202
203fn ordered_owns(resolved: &ResolvedType) -> Vec<OwnsFactId> {
204    let mut ordered = Vec::new();
205    let mut seen = BTreeSet::new();
206    for attribute in resolved.owned_attribute_order() {
207        if let Some(owns) = resolved.owns().get(attribute) {
208            seen.insert(attribute.clone());
209            ordered.push(owns.id().clone());
210        }
211    }
212    ordered.extend(
213        resolved
214            .owns()
215            .iter()
216            .filter(|(attribute, _)| !seen.contains(*attribute))
217            .map(|(_, owns)| owns.id().clone()),
218    );
219    ordered
220}
221
222fn read_model_use(id: &TypeId) -> Result<ProjectedModelUse, SchemaDiagnostics> {
223    let form = match id.kind() {
224        TypeKind::Entity => ProjectedModelForm::Complete,
225        TypeKind::Relation => ProjectedModelForm::Reference,
226        TypeKind::Attribute | TypeKind::Struct => {
227            return Err(projection_error(
228                "invalid_projection_reference",
229                "only entity and relation types can be projected as role players",
230            ));
231        }
232    };
233    Ok(ProjectedModelUse::new(id.clone(), form))
234}
235
236fn create_model_uses(id: &TypeId) -> Result<BTreeSet<ProjectedModelUse>, SchemaDiagnostics> {
237    match id.kind() {
238        TypeKind::Entity => Ok(BTreeSet::from([ProjectedModelUse::new(
239            id.clone(),
240            ProjectedModelForm::Complete,
241        )])),
242        TypeKind::Relation => Ok(BTreeSet::from([
243            ProjectedModelUse::new(id.clone(), ProjectedModelForm::Complete),
244            ProjectedModelUse::new(id.clone(), ProjectedModelForm::Reference),
245        ])),
246        TypeKind::Attribute | TypeKind::Struct => Err(projection_error(
247            "invalid_projection_reference",
248            "only entity and relation types can be projected as role players",
249        )),
250    }
251}
252
253fn immediate_specialization(
254    resolved: &ResolvedType,
255    relates: &EffectiveRelates,
256) -> Result<Option<RoleId>, SchemaDiagnostics> {
257    if relates.replaced_roles().is_empty() {
258        return Ok(None);
259    }
260    let mut ranked = relates
261        .replaced_roles()
262        .iter()
263        .filter_map(|role| {
264            resolved
265                .supertypes()
266                .iter()
267                .position(|parent| parent.label() == role.declaring_relation())
268                .map(|distance| (distance, role.clone()))
269        })
270        .collect::<Vec<_>>();
271    ranked.sort();
272    let Some((distance, role)) = ranked.first().cloned() else {
273        return Err(projection_error(
274            "invalid_projection_reference",
275            "specialized role has no declaring relation in the resolved ancestor chain",
276        ));
277    };
278    if ranked.get(1).is_some_and(|next| next.0 == distance) {
279        return Err(projection_error(
280            "ambiguous_role_specialization_projection",
281            "more than one replaced role is nearest to the specializing relation",
282        ));
283    }
284    Ok(Some(role))
285}
286
287fn resolve_type_reference(
288    reference: &TypeReference,
289    resolved: &ResolvedSchema,
290) -> Result<ProjectedTypeRef, SchemaDiagnostics> {
291    match reference {
292        TypeReference::Value(value) => Ok(ProjectedTypeRef::Scalar(*value)),
293        TypeReference::Schema(label) => {
294            let models = resolved
295                .types()
296                .keys()
297                .filter(|id| id.label() == label)
298                .cloned()
299                .collect::<Vec<_>>();
300            let structures = resolved
301                .structs()
302                .keys()
303                .filter(|id| id.label() == label)
304                .cloned()
305                .collect::<Vec<_>>();
306            match (models.as_slice(), structures.as_slice()) {
307                ([model], []) => Ok(ProjectedTypeRef::Model(ProjectedModelUse::new(
308                    model.clone(),
309                    ProjectedModelForm::Complete,
310                ))),
311                ([], [structure]) => Ok(ProjectedTypeRef::Struct(structure.clone())),
312                ([], []) => Err(projection_error(
313                    "unknown_projection_type_reference",
314                    "function signature references an unknown projected type",
315                )),
316                _ => Err(projection_error(
317                    "ambiguous_projection_type_reference",
318                    "function signature label resolves to more than one projected type",
319                )),
320            }
321        }
322    }
323}
324
325fn function_return(
326    returns: &FunctionReturnMode,
327    resolved: &ResolvedSchema,
328) -> Result<FunctionReturnProjection, SchemaDiagnostics> {
329    let project = |element: &type_bridge_contract::schema::FunctionReturnElement| {
330        Ok(FunctionReturnElementProjection::new(
331            resolve_type_reference(element.type_ref(), resolved)?,
332            element.optional(),
333        ))
334    };
335    match returns {
336        FunctionReturnMode::Scalar(element) => {
337            Ok(FunctionReturnProjection::Scalar(project(element)?))
338        }
339        FunctionReturnMode::Tuple(elements) => elements
340            .iter()
341            .map(project)
342            .collect::<Result<Vec<_>, _>>()
343            .map(FunctionReturnProjection::Tuple),
344        FunctionReturnMode::Stream(elements) => elements
345            .iter()
346            .map(project)
347            .collect::<Result<Vec<_>, _>>()
348            .map(FunctionReturnProjection::Stream),
349    }
350}
351
352fn link_components(resolved: &ResolvedSchema) -> Result<Vec<BTreeSet<TypeId>>, SchemaDiagnostics> {
353    let components = resolved.dependency_graph().strongly_connected_components();
354    let mut membership = BTreeMap::new();
355    for (index, component) in components.iter().enumerate() {
356        for member in component {
357            if membership.insert(member.clone(), index).is_some() {
358                return Err(projection_error(
359                    "invalid_projection_emission_plan",
360                    "resolver SCC partition repeats a model identity",
361                ));
362            }
363        }
364    }
365    if membership.len() != resolved.types().len()
366        || resolved
367            .types()
368            .keys()
369            .any(|id| !membership.contains_key(id))
370    {
371        return Err(projection_error(
372            "invalid_projection_emission_plan",
373            "resolver SCC partition does not cover every model",
374        ));
375    }
376    let mut dependents = vec![BTreeSet::<usize>::new(); components.len()];
377    let mut indegree = vec![0usize; components.len()];
378    for id in resolved.types().keys() {
379        let source = membership[id];
380        for dependency in resolved
381            .dependency_graph()
382            .dependencies(id)
383            .into_iter()
384            .flatten()
385        {
386            let target = membership[dependency];
387            if source != target && dependents[target].insert(source) {
388                indegree[source] += 1;
389            }
390        }
391    }
392    let minimum = components
393        .iter()
394        .map(|component| {
395            component
396                .first()
397                .cloned()
398                .expect("resolver SCCs are non-empty")
399        })
400        .collect::<Vec<_>>();
401    let mut ready = indegree
402        .iter()
403        .enumerate()
404        .filter(|(_, degree)| **degree == 0)
405        .map(|(index, _)| (minimum[index].clone(), index))
406        .collect::<BTreeSet<_>>();
407    let mut ordered = Vec::with_capacity(components.len());
408    while let Some((_, index)) = ready.pop_first() {
409        ordered.push(components[index].clone());
410        for dependent in &dependents[index] {
411            indegree[*dependent] -= 1;
412            if indegree[*dependent] == 0 {
413                ready.insert((minimum[*dependent].clone(), *dependent));
414            }
415        }
416    }
417    if ordered.len() != components.len() {
418        return Err(projection_error(
419            "invalid_projection_emission_plan",
420            "resolver SCC condensation graph is cyclic",
421        ));
422    }
423    Ok(ordered)
424}
425
426/// Derive a target-specific runtime projection without provider I/O or mutation.
427pub fn project(
428    resolved: &ResolvedSchema,
429    target: BindingTarget,
430    config: &ProjectionConfig,
431    handlers: &[ProjectionHandler],
432    resources: &[CodeResourceDigest],
433) -> Result<RuntimeProjection, SchemaDiagnostics> {
434    if config.target() != target {
435        return Err(projection_error(
436            "projection_config_target_mismatch",
437            "projection configuration belongs to a different target",
438        ));
439    }
440    let mut names = NameRegistry::default();
441    let mut models = BTreeMap::new();
442    let mut playing_facts = BTreeMap::new();
443
444    for (id, resolved_type) in resolved.types() {
445        let target_name = class_identifier(target, id.label().as_str())?;
446        names.insert("root", &target_name, format!("model:{id:?}"))?;
447        let reference_name = if matches!(id.kind(), TypeKind::Entity | TypeKind::Relation) {
448            let reference = reference_identifier(target, &target_name)?;
449            names.insert("root", &reference, format!("reference:{id:?}"))?;
450            Some(reference)
451        } else {
452            None
453        };
454        let query_token_name = if target == BindingTarget::Rust {
455            let name = rust_identifier(format!("{}Type", target_name.as_str()))?;
456            names.insert("root", &name, format!("query-token:{id:?}"))?;
457            Some(name)
458        } else {
459            None
460        };
461        let type_annotations = annotations(
462            AnnotationSubjectId::Type(id.clone()),
463            resolved_type.annotations(),
464        )?;
465        let ordered_field_ids = ordered_owns(resolved_type);
466        let mut field_tokens = BTreeMap::new();
467        let mut direct_fields = Vec::new();
468        let mut create_fields = Vec::new();
469        let mut read_fields = Vec::new();
470        for owns_id in &ordered_field_ids {
471            let owns = resolved_type
472                .owns()
473                .get(owns_id.attribute())
474                .ok_or_else(|| {
475                    projection_error(
476                        "invalid_projection_reference",
477                        "ordered ownership is absent from the effective ownership map",
478                    )
479                })?;
480            let name = member_identifier(target, owns.id().attribute().label().as_str())?;
481            names.insert(
482                format!("model:{id:?}"),
483                &name,
484                format!("field:{:?}", owns.id()),
485            )?;
486            let declaring_id = match owns.origin().declared() {
487                type_bridge_contract::schema::SchemaFactId::Owns(fact_id) => fact_id.clone(),
488                _ => {
489                    return Err(projection_error(
490                        "invalid_projection_reference",
491                        "declared resolution origin for owns fact is not an Owns fact ID",
492                    ));
493                }
494            };
495            let multiplicity = ProjectedMultiplicity::from_cardinality(owns.cardinality());
496            let token = FieldTokenProjection::new(
497                owns.id().clone(),
498                declaring_id,
499                name,
500                multiplicity,
501                owns.is_key(),
502                owns.is_unique(),
503                annotations(
504                    AnnotationSubjectId::Owns(owns.id().clone()),
505                    owns.annotations(),
506                )?,
507            )
508            .map_err(no_source)?;
509            let attribute_model =
510                TypeId::new(TypeKind::Attribute, owns.id().attribute().label().as_str())
511                    .map_err(no_source)?;
512            let value = ProjectedTypeRef::Model(ProjectedModelUse::new(
513                attribute_model,
514                ProjectedModelForm::Complete,
515            ));
516            if owns.origin().is_direct() {
517                direct_fields.push(owns.id().clone());
518            }
519            create_fields.push(CreateFieldProjection::new(
520                owns.id().clone(),
521                value.clone(),
522                multiplicity,
523            ));
524            read_fields.push(ReadFieldProjection::new(
525                owns.id().clone(),
526                value,
527                multiplicity,
528            ));
529            field_tokens.insert(owns.id().clone(), token);
530        }
531
532        let replaced = resolved_type
533            .relates()
534            .values()
535            .flat_map(|relates| relates.replaced_roles().iter().cloned())
536            .collect::<BTreeSet<_>>();
537        let mut role_tokens = BTreeMap::new();
538        let mut direct_roles = BTreeMap::new();
539        let mut create_roles = BTreeMap::new();
540        let mut read_roles = BTreeMap::new();
541        let mut create_enabled = resolved_type.is_constructible() && !resolved_type.is_abstract();
542        for (role_id, relates) in resolved_type.relates() {
543            if replaced.contains(role_id) {
544                continue;
545            }
546            let name = member_identifier(target, role_id.label().as_str())?;
547            names.insert(format!("model:{id:?}"), &name, format!("role:{role_id:?}"))?;
548            let resolved_role = resolved.roles().get(role_id).ok_or_else(|| {
549                projection_error(
550                    "invalid_projection_reference",
551                    "effective relates role is absent from the resolved role index",
552                )
553            })?;
554            let specializes = immediate_specialization(resolved_type, relates)?;
555            let multiplicity = ProjectedMultiplicity::from_cardinality(relates.cardinality());
556            let player_union_name = if target == BindingTarget::Rust {
557                let name = rust_identifier(format!(
558                    "{}{}Player",
559                    target_name.as_str(),
560                    python_class_name(role_id.label().as_str()),
561                ))?;
562                names.insert("root", &name, format!("player-union:{id:?}:{role_id:?}"))?;
563                Some(name)
564            } else {
565                None
566            };
567            let mut token = RoleTokenProjection::new(
568                id.clone(),
569                role_id.clone(),
570                name,
571                resolved_role.accepted_players().clone(),
572                specializes.clone(),
573                multiplicity,
574                relates.is_abstract(),
575                annotations(
576                    effective_relates_annotation_subject(relates)?,
577                    relates.annotations(),
578                )?,
579            )
580            .map_err(no_source)?;
581            if let Some(name) = player_union_name {
582                token = token.with_player_union_target_name(name);
583            }
584            let read_players = resolved_role
585                .accepted_players()
586                .iter()
587                .map(read_model_use)
588                .collect::<Result<BTreeSet<_>, _>>()?;
589            let mut create_players = BTreeSet::new();
590            for player in resolved_role.accepted_players() {
591                create_players.extend(create_model_uses(player)?);
592            }
593            if relates.origin().is_direct() {
594                direct_roles.insert(
595                    role_id.clone(),
596                    DeclaredRoleProjection::new(role_id.clone(), specializes),
597                );
598            }
599            if relates.accepts_players_at_effective_scope() {
600                if create_players.is_empty() && multiplicity.required() {
601                    create_enabled = false;
602                } else if !create_players.is_empty() {
603                    create_roles.insert(
604                        role_id.clone(),
605                        CreateRoleProjection::new(role_id.clone(), create_players, multiplicity)
606                            .map_err(no_source)?,
607                    );
608                }
609            }
610            read_roles.insert(
611                role_id.clone(),
612                ReadRoleProjection::new(role_id.clone(), read_players, multiplicity)
613                    .map_err(no_source)?,
614            );
615            role_tokens.insert(role_id.clone(), token);
616        }
617
618        let mut direct_plays = BTreeSet::new();
619        for plays in resolved_type.plays().values() {
620            if plays.origin().is_direct() {
621                direct_plays.insert(plays.id().clone());
622            }
623            let plays_name = member_identifier(
624                target,
625                &format!(
626                    "plays-{}-{}-{}",
627                    id.label(),
628                    plays.id().role().declaring_relation(),
629                    plays.id().role().label()
630                ),
631            )?;
632            names.insert("root", &plays_name, format!("plays:{:?}", plays.id()))?;
633            let projected = PlayingProjection::new(
634                plays.id().clone(),
635                plays.id().role().clone(),
636                ProjectedMultiplicity::from_cardinality(plays.cardinality()),
637                annotations(
638                    AnnotationSubjectId::Plays(plays.id().clone()),
639                    plays.annotations(),
640                )?,
641            )
642            .map_err(no_source)?
643            .with_target_name(plays_name);
644            if playing_facts
645                .insert(plays.id().clone(), projected)
646                .is_some()
647            {
648                return Err(projection_error(
649                    "invalid_runtime_projection_map",
650                    "an effective playing identity appears more than once",
651                ));
652            }
653        }
654
655        let key_fields = ordered_field_ids
656            .iter()
657            .filter(|owns| resolved_type.key_attributes().contains(owns.attribute()))
658            .cloned()
659            .collect();
660        let value_annotations = if let Some(value) = resolved_type.value_type() {
661            let attribute = AttributeId::new(id.label().as_str()).map_err(no_source)?;
662            annotations(
663                AnnotationSubjectId::Value(ValueFactId::new(attribute)),
664                value.annotations(),
665            )?
666        } else {
667            BTreeMap::new()
668        };
669        let direct_sub = resolved_type
670            .direct_sub()
671            .map(|sub| {
672                let annotations = annotations(
673                    AnnotationSubjectId::Sub(sub.id().clone()),
674                    sub.annotations(),
675                )?;
676                DirectSubProjection::new(
677                    sub.id().clone(),
678                    sub.origin().declared().clone(),
679                    annotations,
680                )
681                .map_err(no_source)
682            })
683            .transpose()?;
684        let declaration = DeclarationProjection::new(
685            resolved_type.supertypes().first().cloned(),
686            resolved_type.value_type().map(|value| value.value_type()),
687            resolved_type.is_abstract(),
688            resolved_type.is_constructible(),
689            type_annotations,
690            direct_fields,
691            direct_roles,
692            direct_plays,
693        )
694        .map_err(no_source)?
695        .with_direct_sub(direct_sub)
696        .map_err(no_source)?
697        .with_value_annotations(value_annotations)
698        .map_err(no_source)?;
699        let create_target_name = if target == BindingTarget::Rust && create_enabled {
700            if config.rust_create_policy() != Some(RustCreatePolicy::ValidatedInputV1) {
701                return Err(projection_error(
702                    "unsupported_rust_create_policy",
703                    "Rust projection does not recognize the configured create policy",
704                ));
705            }
706            let name = rust_identifier(format!("{}Create", target_name.as_str()))?;
707            names.insert("root", &name, format!("create:{id:?}"))?;
708            Some(name)
709        } else {
710            None
711        };
712        let mut create = CreateProjection::new(create_enabled, create_fields, create_roles)
713            .map_err(no_source)?;
714        if let Some(name) = create_target_name {
715            create = create.with_target_name(name);
716        }
717        let role_upcasts = resolved_type
718            .relates()
719            .iter()
720            .filter_map(|(role, relates)| {
721                if relates.replaced_roles().is_empty() {
722                    return None;
723                }
724                let mut ancestors = relates.replaced_roles().iter().cloned().collect::<Vec<_>>();
725                ancestors.sort_by_key(|ancestor| {
726                    resolved_type
727                        .supertypes()
728                        .iter()
729                        .position(|parent| parent.label() == ancestor.declaring_relation())
730                        .unwrap_or(usize::MAX)
731                });
732                Some((role.clone(), ancestors))
733            })
734            .collect();
735        let complete_read = CompleteReadProjection::new(
736            read_fields,
737            read_roles,
738            resolved_type.supertypes().to_vec(),
739        )
740        .map_err(no_source)?
741        .with_role_upcasts(role_upcasts)
742        .map_err(no_source)?;
743        let reference_read =
744            ReferenceReadProjection::new(reference_name, key_fields).map_err(no_source)?;
745        let mut query_tokens =
746            QueryTokenProjection::new(id.clone(), field_tokens, role_tokens).map_err(no_source)?;
747        if let Some(name) = query_token_name {
748            query_tokens = query_tokens.with_target_name(name);
749        }
750        let model = ModelProjection::new(
751            id.clone(),
752            target_name,
753            declaration,
754            create,
755            complete_read,
756            reference_read,
757            query_tokens,
758        )
759        .map_err(no_source)?;
760        models.insert(id.clone(), model);
761    }
762
763    let mut structs = BTreeMap::new();
764    for (id, structure) in resolved.structs() {
765        let target_name = class_identifier(target, id.label().as_str())?;
766        names.insert("root", &target_name, format!("struct:{id:?}"))?;
767        let mut fields = Vec::new();
768        for field in structure.fields() {
769            let target_field = member_identifier(target, field.name().as_str())?;
770            names.insert(
771                format!("struct:{id:?}"),
772                &target_field,
773                format!("field:{:?}", field.name()),
774            )?;
775            fields.push(StructFieldProjection::new(
776                field.name().clone(),
777                target_field,
778                field.value_type(),
779                field.optional(),
780            ));
781        }
782        structs.insert(
783            id.clone(),
784            StructProjection::new(id.clone(), target_name, fields).map_err(no_source)?,
785        );
786    }
787
788    let mut functions = BTreeMap::new();
789    for (id, function) in resolved.functions() {
790        let target_name = member_identifier(target, id.label().as_str())?;
791        names.insert("root", &target_name, format!("function:{id:?}"))?;
792        let mut parameters = Vec::new();
793        for parameter in function.declaration().signature().parameters() {
794            let target_parameter = member_identifier(target, parameter.name().as_str())?;
795            names.insert(
796                format!("function:{id:?}"),
797                &target_parameter,
798                format!("parameter:{:?}", parameter.name()),
799            )?;
800            parameters.push(FunctionParameterProjection::new(
801                parameter.name().clone(),
802                target_parameter,
803                resolve_type_reference(parameter.type_ref(), resolved)?,
804            ));
805        }
806        functions.insert(
807            id.clone(),
808            FunctionProjection::new(
809                id.clone(),
810                target_name,
811                parameters,
812                function_return(function.declaration().signature().returns(), resolved)?,
813            )
814            .map_err(no_source)?
815            .with_annotations(annotations(
816                AnnotationSubjectId::Function(id.clone()),
817                function.annotations(),
818            )?)
819            .map_err(no_source)?,
820        );
821    }
822
823    let mut shells = resolved.types().values().collect::<Vec<_>>();
824    shells.sort_by_key(|model| (model.supertypes().len(), model.id().clone()));
825    let emission = EmissionPlan::new(
826        shells.into_iter().map(|model| model.id().clone()).collect(),
827        link_components(resolved)?,
828        structs.keys().cloned().collect(),
829        functions.keys().cloned().collect(),
830    )
831    .map_err(no_source)?;
832    RuntimeProjection::try_new(
833        target,
834        config.clone(),
835        resolved.semantic_fingerprint().clone(),
836        handlers,
837        resources,
838        models,
839        structs,
840        functions,
841        playing_facts,
842        emission,
843    )
844    .map_err(no_source)
845}