Feature Interactions

This chapter explains the feature interactions of different features working together.

configurationFileMode - Feature Switch for Users

The mode looks like a statement about file count, and it is also the switch that decides which configuration features exist at all:

Mode Several files Entities merge Priority $fileType Fragments $path Mixins $mixins

SingleFile

no

no

no

no

no

MultipleFiles

yes

no

no

no

no

MultipleFilesEntitiesMergeable

yes

yes

no

yes

yes

MultipleFilesEntitiesMergeableWithPriority

yes

yes

yes

yes

yes

Consequences:

  • Choosing SingleFile denies your users every reuse mechanism Confµgami has on the configuration side.

    • It is the right call for a small, single-owner model and the wrong one for anything a platform team and a project team fill in together.

  • Choosing a mergeable mode hands your users mixins and fragments

    • Expect entities of the same type to be configured through a class-wide default rather than one by one.

  • Only the mergeable modes allow a top level entity with multiplicity 1..1, because presence is then checked on the merged result. In plain MultipleFiles mode every file would have to satisfy it alone, which is why that combination is rejected.

  • The .default.def.json5 and .preconfig.def.json5 files you ship depend on the same switch, see below.

Defaults You Ship Are Mixins

A .default.def.json5 or .preconfig.def.json5 file is written in configuration syntax and usually holds a $mixins entry without a $mixin name,to each every type:

{
    $definition: "SensorLoop@1.0",
    $fileType: "DefaultValues",
    $mixins: [
        {
            $type: "SensorLoop::Sensor",
            sampleRate: 100
        }
    ]
}
  • The content activates per CfgSpace only once one real .cfg.json(5) file declares the definition. A workspace where nobody configures your definition sees none of your defaults.

  • Its $fileType decides the layer, DefaultValues* below the user and PreConfiguration* above, and the file suffix has to match the type.

  • A parameter default in the definition and a default file are different mechanisms. Prefer the definition default for a per-parameter fallback and a default file for a coherent block of values a project is expected to override.

Reuse, Visibility and Generated Names

typeRef, inherits and scope solve different problems and constrain each other:

  • typeRef embeds a shape, inherits extends an entity type. Structs cannot inherit, so shared struct fields move into a named struct type embedded by typeRef.

  • Both need the target to be visible: declared in the same definition, or scope: "public". Making a type public is an API decision, not a convenience: another definition may then inherit from it, reference it, or use it as a union member, and renaming it breaks those definitions.

  • A list property must declare an explicit typeName with the singular name, and that name is what every type path, union member and refTargetTypes entry spells. Renaming it is a breaking change for the configurations that write $type and for the generated code alike.

  • Simple type names have to stay unique across what is generated, so a definition Indicator with a root relation indicator produces the clashing pair Indicator and Indicator::Indicator and aborts code generation. Name the definition and its root relation differently, or set typeName.

Unions Reach Into Deserialization

The union options look like validation settings and are really code generation settings:

  • A discriminated union (allowTypeAmbiguity: false, the default) requires $type in the configuration, generates it as a constant per member, and deserializes unambiguously in every consumer. It accepts entity and struct members only, since a scalar or a ref cannot carry the discriminator.

  • allowTypeAmbiguity: true is what allows scalar and ref members, and the price untagged deserialization.

  • Use an enum instead when the variants carry no payload of their own. It configures more simply and generates a plain enum type.

Constraints Are Enforced, Hints Are Not

A definition mixes keys that change validation with keys that are documentation only:

Key Effect

min, max, minLength, maxLength, pattern, literalsExhaustive, multiplicity

Enforced by the validator and, for the numeric ones.

literalsNonExhaustive

Completion only, other values stay valid.

unit, display

Hover, inlay hints and doc comments. display: "hex" does not restrict what an author may write, and a hex string is accepted for every integer parameter regardless.

embeddedTypeHint

Documentation only, and rejected on an entity and a struct.

pattern deserves its own warning because it is unanchored unless you write ^…​$. See the caution in the type reference before using anything beyond character classes and quantifiers.

Annotations Cross Every Boundary

An annotation type is data about the model, and its declaration decides how far that data travels:

  • targets restricts the element kind and the side, the Def suffix for definition elements and Cfg for configuration elements. An annotation meant for your users needs a …​Cfg target, one meant for your own definition a …​Def target, and All covers both.

  • retention: "Source" keeps the annotation out of every merged JSON output, so downstream generators never see it. Output, the default, is what a tool consuming cfu cfg export-merged relies on.

  • typeSymbolRef only exists inside an annotation type declaration, and it makes an annotation field a checked type path instead of a free string, which is how _deprecated validates its replacedBy.

  • _deprecated annotation has semantics: it produces a diagnostic of the declared level at every configuration element using the deprecated definition element, and it reaches generated code as #[deprecated] or @deprecated and the schema as "deprecated": true. A deprecationLevel: "error" therefore breaks every existing configuration on the spot, which is the point.

  • Custom annotations deliberately cannot change the validation of the element they sit on.

Generation Follows the Configuration File Mode

cfu def generate emits a different set of schemas depending on the file mode: for a mergeable definition the strict <Name>.merged.schema.json validates the fully merged configuration, while <Name>.schema.json treats every field as optional so that a single partial file still validates. An editor pointed at the wrong one of the two reports missing required properties on every contributing file. See Code Generation for the rest, including the cases where generated code is deliberately less strict than the validator.

Additional Resources